Messenger
Every message box the R package can walk into is shown from here. While the messenger is armed it shows nothing: it writes the text down and answers the caller with the answer that caller named. While it is disarmed it calls MsgBox with exactly what it was given and answers the click. A person opening the workbook and pressing buttons sees the boxes they have always seen.
THE RECORD LIVES ON THE DEFAULT INSTANCE
Callers write Messenger.Show and this class has no Create. One Create per call site would give each caller its own instance and its own record, and Messages would come back empty at the end of the run. One instance, one record.
ARM AND DISARM COME IN PAIRS
A wrapper arms the messenger as its first act and disarms it in its exit path, in the same Excel session. Nothing is written to the file, so a run that dies part way cannot leave a delivered workbook mute.
Arm starts a run: it empties the record. Disarm ends the silence and keeps the record, because the summary a wrapper answers is read after the run is over.
THE FORCE STATE AND CarryOn
Some questions ask whether to push past a warning. A call site writes Messenger.CarryOn() as its silent answer to one of those: vbYes when the run was armed with force, vbNo otherwise. Force is the only thing CarryOn reads, and Disarm drops it.
THE STORED SWITCH COVERS THE OPEN PATH
A box that fires from Workbook_Open fires before any Application.Run can reach a wrapper, so nothing in process can arm the messenger in time. OBT__SILENT_OPERATIONS is read off the workbook itself, and the open path arms on what it answers:
If Messenger.ReadStoredSwitch(ThisWorkbook) Then Messenger.Arm ThisWorkbook
The read goes through HiddenNames.QuickValue, three COM crossings with no instance behind them. HiddenNames.Create walks the whole Names collection of the host, which is the largest cost that class has, and the open path reads one value once.
A missing name reads as No, so a workbook built before the switch existed behaves exactly as it did.
WHY A CLASS
Six classes show a box today, and a class calling a standard module is out of scope (conventions.md section 9). A class serves the modules and the classes both.
Depends on: ProjectError, BetterArray, HiddenNames, The switch is stored at workbook level as a string holding Yes or No. The, name is settled on both sides of the wire and this is where the VBA side, keeps it: the three workbooks seed the flag through Messenger.SwitchName., What separates a title from its message on a recorded line.
Version: 1.0 (2026-08-31)
Lifecycle
Reset #
reset
Put the messenger back to its opening state
Signature:
Public Sub Reset()
Empties the record, ends the silence, drops the force state and forgets the host. Arm calls this, so a new run never reads the messages of the run before.
Arming
Arm #
arm
Turn the boxes off for the length of this call
Signature:
Public Sub Arm(ByVal hostBook As Workbook, Optional ByVal force As Boolean = False)
Reads nothing from the file. The wrapper decides, and the wrapper disarms in its exit path. The record of any run before is emptied here.
Parameters:
hostBook: Workbook. The workbook this run is about.force: Boolean. Optional, default False. True makes CarryOn answer vbYes, which is what pushes a run past a warning that would otherwise stop it.
Disarm #
disarm
Turn the boxes back on
Signature:
Public Sub Disarm()
The record is kept. A wrapper disarms and then reads Messages for the summary it answers.
Armed #
armed
Whether the boxes are off right now
Signature:
Public Property Get Armed() As Boolean
Returns: Boolean. True while the messenger is armed.
HostBook #
host-book
The workbook the current run is about
Signature:
Public Property Get HostBook() As Workbook
Returns: Workbook. What Arm was given, or Nothing.
CarryOn #
carry-on
The silent answer to a question that asks whether to push on
Signature:
Public Function CarryOn() As VbMsgBoxResult
Written at a call site as the silent answer of a warning the run may pass through: Messenger.Show(text, Messenger.CarryOn(), vbYesNo). A run armed with force carries on. Every other run stops, which is the safe reading of a warning nobody is there to read.
Returns: VbMsgBoxResult. vbYes when the run was forced, vbNo otherwise.
The stored switch
SwitchName #
switch-name
The name the switch is stored under
Signature:
Public Property Get SwitchName() As String
The three workbooks seed their flag through this, so the string lives in one place on the VBA side.
Returns: String. OBT__SILENT_OPERATIONS
SwitchOnValue #
switch-on-value
The stored value that means silent
Signature:
Public Property Get SwitchOnValue() As String
Returns: String. Yes
SwitchOffValue #
switch-off-value
The stored value that means the workbook behaves as it always has
Signature:
Public Property Get SwitchOffValue() As String
Returns: String. No
ReadStoredSwitch #
read-stored-switch
Read the silence switch off a workbook
Signature:
Public Function ReadStoredSwitch(ByVal hostBook As Workbook) As Boolean
The read costs three COM crossings through HiddenNames.QuickValue. A workbook that holds no such name answers False, so every workbook built before the switch existed behaves as it did.
This answers the value and changes nothing. The open path arms on it:
If Messenger.ReadStoredSwitch(ThisWorkbook) Then Messenger.Arm ThisWorkbook
Parameters:
hostBook: Workbook. The workbook holding the name.
Returns: Boolean. True when the stored value reads Yes.
Showing
Show #
show
Show one box, or write it down and answer for it
Signature:
Public Function Show(ByVal message As String, _
ByVal silentAnswer As Long, _
Optional ByVal boxStyle As Long = vbOKOnly, _
Optional ByVal title As String = vbNullString) As VbMsgBoxResult
Armed: nothing is shown, the text goes into the record and silentAnswer comes back. Disarmed: MsgBox is called with what this was given and the click comes back unchanged.
silentAnswer is the second parameter and it carries no default. A box that guards a destructive step and one that merely asks "carry on" want different answers, and only the call site knows which. VBA needs every optional parameter last, so boxStyle and title follow it, and a call site that forgets the answer fails to compile.
Parameters:
message: String. The text of the box.silentAnswer: Long. What this answers while armed. vbOK for a box with one button, and for a question the answer that call site wants with nobody there. Messenger.CarryOn() for a warning a forced run may pass through.boxStyle: Long. Optional, default vbOKOnly. The MsgBox style.title: String. Optional. The box title.
Returns: VbMsgBoxResult. The click while disarmed, silentAnswer while armed.
The record
Messages #
messages
Everything the run swallowed, one line each
Signature:
Public Function Messages() As String
Returns: String. The recorded lines joined by a line break. Empty when nothing was swallowed.
HasMessages #
has-messages
Whether the run swallowed anything
Signature:
Public Function HasMessages() As Boolean
Returns: Boolean. True when the record holds at least one line.
Internal members (not exported)
Lifecycle
Class_Initialize #
class-initialize
Start with an empty record and the boxes on
Signature:
Private Sub Class_Initialize()
Private helpers
RecordMessage #
record-message
Write one swallowed box into the record
Signature:
Private Sub RecordMessage(ByVal message As String, ByVal title As String)
Parameters:
message: String. The text of the box.title: String. The box title. May be empty.
SingleLine #
single-line
Flatten a box text onto one line
Signature:
Private Function SingleLine(ByVal boxText As String) As String
A box text often carries line breaks. The record answers one line per message, so a reader can count what a run swallowed, and every break becomes a space.
Parameters:
boxText: String. The text to flatten.
Returns: String. The same text with no line break in it.
ThrowError #
throw-error
Raise a ProjectError-based exception
Signature:
Private Sub ThrowError(ByVal errNumber As Long, ByVal message As String)
Parameters:
errNumber: Long. A ProjectError value.message: String. What went wrong.
Used in (19 file(s))
- DesignerPreparation.cls
- EventLinelist.cls
- Linelist.cls
- MasterSetupPreparation.cls
- SetupPreparation.cls
- SetupTranslationsTable.cls
- EventsDesignerAdvanced.bas
- LinelistRun.bas
- FormLogicExportMig.bas
- SetupRibbon.bas
- TestLLImportTiming.bas
- TestDesignerPreparation.bas
- TestMessenger.bas
- TestEventLinelist.bas
- TestLinelistRun.bas
- TestMasterSetupPreparation.bas
- TestSetupPreparation.bas
- TestSetupRibbon.bas
- TestSetupTranslationsTable.bas