Overview
The article Scripting How-To: Adding a Dynamic Dialog, talks about how to perform some simple dialog initialization, such as populating a list box, or setting a default value for a text box control. Much more capability is included in Sax VB in terms of manipulating a dialog after it is presented to the user, and this page will talk about these capabilities. This discussion will assume you are already familiar with general dialog development, including the content here: Scripting How-To: Adding a Dynamic Dialog
Put simply, dynamic dialog control is all about the DialogFunc. A DialogFunc is a special type of function that is associated with the dialog GUI by entering a name in the field, like this:
NOTE: To get to this dialog, you can do one of two things:
- After Insert->UserForm, just start typing a name. The editor will assume that you are specifying a "Caption" for the dialog and open this form, putting the name you typed in the "Caption" field (and, therefore, making this the caption on your dialog when it is opened at run-time).
- After Insert->UserForm, double-click anywhere on the form that is not occupied by another control and this form will open.
By setting the "Dialog Function" field to "DialogFunc", we are making the required link between the GUI and the event handlers. When you apply the changes from the dialog editor to the code, you will get a prompt:
We recommend you always say "Yes". What happens when you say "Yes" is that a VB function is added at the end of your code, and it is populated in a very specific way. VERY IMPORTANT: A Dialog Function MUST take the form that is generated automatically as skeleton code. The individual cases in the Select Case
block are predefined and can not be changed. We can, however, add event handlers within this framework and create many types of actions by our dialogs.
Our example below will illustrate how to use all the dynamic events available to a dialog. The general functionality will be that the user will select a number from a drop-down list and the selected number of dialog controls will be shown in the GUI. If the user chooses 1, then one text label and one text box will be displayed. If the user chooses 2, then two of each control will be displayed. We will dynamically control what the text labels say, we will enable/disable controls, and we will turn controls' visibility on and off.
Getting Started
To start, paste the following code into a new code module:
Option Explicit Dim i As Integer Dim nums(4) As String ' Code Module Sub Main For i = 1 To 4 nums(i) = CStr(i) Next i Begin Dialog UserDialog 510,231,"My Dialog",.DialogFunc ' %GRID:10,7,1,1 Text 20,14,170,14,"Pick a number from 1 to 4:",.Text5 DropListBox 90,35,90,70,nums(),.num Text 280,14,210,14,"MsgOut",.msgOut Text 290,70,50,14,"Box 1",.Text1 Text 290,98,50,14,"Box 2",.Text2 Text 290,126,50,14,"Box 3",.Text3 Text 290,154,50,14,"Box 4",.Text4 TextBox 350,70,90,21,.TextBox1 TextBox 350,98,90,21,.TextBox2 TextBox 350,126,90,21,.TextBox3 TextBox 350,154,90,21,.TextBox4 OKButton 200,203,90,21 End Dialog Dim dlg As UserDialog Dialog dlg End Sub Rem See DialogFunc help topic for more information. Private Function DialogFunc(DlgItem$, Action%, SuppValue&) As Boolean Select Case Action% Case 1 ' Dialog box initialization Case 2 ' Value changing or button pressed Rem DialogFunc = True ' Prevent button press from closing the dialog box Case 3 ' TextBox or ComboBox text changed Case 4 ' Focus changed Case 5 ' Idle Rem Wait .1 : DialogFunc = True ' Continue getting idle actions Case 6 ' Function key End Select End Function
If you run this code, you will see the following dialog:
All we have done so far is to place the controls, but we have not yet specified any behavior for the dialog. To do that, we will edit the DialogFunc to change how the dialog looks based on certain events.
The DialogFunc
As mentioned above, a skeleton version of the DialogFunc
was created when we first set up the dialog GUI. If you look at the code, you will see that DialogFunc
is simply a Select Case
block of code. Each Case
statement corresponds to a specific action taken in the dialog GUI. The skeleton code includes comments for each Case
statement, so let's look at them one at a time...
Case 1: Dialog Initialization
Any code added in Case 1
is executed when the dialog is first created. Since the default selection in the drop-down box is "1", let's look at how we can turn off the controls for selections "2", "3", and "4".
There are VB keywords to handle specific actions. In our case, we'll start by examining enable/disable and visibility on/off for these controls.
Enable/Disable
The DlgEnable
statement handles this action. So, let's add the following statement to Case 1
of the DialogFunc
:
DlgEnable "Text2", False
Run the script and look at the text label for item 2. It should be disabled, as shown below:
The DlgEnable
statement was set to operate on the Text control whose Field
parameter is "Text2", and the enable status is set to False
, or not enabled.
Note that this, and all other commands like it, are only valid within a dialog function. If you try this in the main code block, an error will be generated. In short, dynamic dialog functions must exist in a DialogFunc
. Also, note that there is regular VB help available for all the dynamic dialog commands. Search for "dlg" and the commands should appear for help information.
Visible/Invisible
In the same manner, we can set whether a control is visible or invisible. Add the following line of code, also in the Case 1
code block:
DlgVisible "TextBox2", False
When you run this modification, you will see that the text box that corresponds to the disabled label is now invisible (not just disabled):
Text Labels
Finally, lets look at how we can change the string displayed by a text label. In the upper right corner of the dialog, up to now you have seen "MsgOut", which is the default label specified in the dialog definition (Text 280,14,210,14,"MsgOut",.msgOut
). Again, there is a specific command to perform this task dynamically. Add the following line of code, again in the Case 1
code block:
DlgText "MsgOut", "The [default] selection is 1"
Run the code and you will see that the message in the upper right now matches the default selection of "1":
Wrapping Up Initialization
With these three commands, we already have a powerful set of tools with which to work. Since the default selection is "1", let's complete the initialization code by turning off all the controls for boxes 2, 3, and 4. Just for argument's sake, let's make the labels invisible and the text boxes disabled. The DialogFunc
should now look like this:
Rem See DialogFunc help topic for more information. Private Function DialogFunc(DlgItem$, Action%, SuppValue&) As Boolean Select Case Action% Case 1 ' Dialog box initialization DlgVisible "Text2", False DlgVisible "Text3", False DlgVisible "Text4", False DlgEnable "TextBox2", False DlgEnable "TextBox3", False DlgEnable "TextBox4", False DlgText "MsgOut", "The [default] selection is 1" Case 2 ' Value changing or button pressed Rem DialogFunc = True ' Prevent button press from closing the dialog box Case 3 ' TextBox or ComboBox text changed Case 4 ' Focus changed Case 5 ' Idle Rem Wait .1 : DialogFunc = True ' Continue getting idle actions Case 6 ' Function key End Select End Function
...and the dialog should appear like this:
Case 2: Value Changing or Button Pressed
This section of code handles events that are caused by pushing a command button or changing the value of a control, such as a drop-down list, selecting a different radio button option, changing the state of a checkbox, etc. In general, this section handles all controls that return a value, as opposed to a string. We will use this section to update the dialog when the user selects a different value in the drop-down box.
Since a dialog can have many controls, and there is only one section to handle events from these controls, there must be a mechanism to let the DialogFunc
know which control is sending the event. This is handled in the arguments defined with DialogFunc
itself (DlgItem$, Action%, SuppValue&
). This will be explained in detail below.
Updating the Dialog GUI
Let's start with the case where the user changes the drop-down selection to "2". The desired behavior is to activate the labels and text boxes for the first two items, leaving the third and fourth items disabled and invisible. Also, we'll want to update the "MsgOut" tect control. From a coding standpoint, we want to react to a change in drop-down state, and we need to know which control changed and what its new value is. Fortunately, this framework is automatically built into DialogFunc
. Here's how it works:
When we run the code and change the selection in the drop-down box (say from "1" to "2"), DialogFunc
is automatically called. Let's look at the definition for DialogFunc
:
Private Function DialogFunc(DlgItem$, Action%, SuppValue&) As Boolean
Once we understand how the arguments work, we can then understand how to process the events generated by the dialog.
DlgItem$
is the name of the control that has changed. In our case,DlgItem$
= "num", since "num" is the name of the drop-down control.Action%
is the argument used for the mainSelect Case
block inDialogFunc
. So,Action%
= 1 for initialization,Action%
= 2 for our current area of interest, and so on.SuppValue&
is the value, where appropriate, of the control. If we were concerned about a command button, thenSuppValue&
is irrelevant because all we can do with a command button is to push it. But, in our case with a drop-down control, we need to know which value was selected. This information is passed toDialogFunc
throughSuppValue&
.
With this information, we can now write the code to process a change in the drop-down control's value. Let's start very simple: we will add code to simply tell us what drop-down selection was made, so we can verify program flow and operation. Add the following block of code to DialogFunc
, but this time put it in the Case 2
section:
Select Case DlgItem$ Case "num" If SuppValue& = 0 Then MsgBox("Item 1",vbOkOnly) ElseIf SuppValue& = 1 Then MsgBox("Item 2",vbOkOnly) ElseIf SuppValue& = 2 Then MsgBox("Item 3",vbOkOnly) ElseIf SuppValue& = 3 Then MsgBox("Item 4",vbOkOnly) End If End Select
All we are doing here is making sure we can read the appropriate values properly. We do this by testing SuppValue&
in a Select Case
block. So, a VB message box is displayed, indicating what the selection was in the drop-down control. Note that the drop-down returns a value based on 0, so the drop-down reports states 0/1/2/3, corresponding to a choice in the list of 1/2/3/4.
Since we are getting proper reports, we can now make the real code changes needed for our dialog. The following code block shows the correct settings for the complete Case 2
section in DialogFunc
:
Case 2 ' Value changing or button pressed Rem DialogFunc = True ' Prevent button press from closing the dialog box Select Case DlgItem$ Case "num" If SuppValue& = 0 Then DlgVisible "Text2", False DlgVisible "Text3", False DlgVisible "Text4", False DlgEnable "TextBox2", False DlgEnable "TextBox3", False DlgEnable "TextBox4", False DlgText "MsgOut", "The selection is 1" ElseIf SuppValue& = 1 Then DlgVisible "Text2", True DlgVisible "Text3", False DlgVisible "Text4", False DlgEnable "TextBox2", True DlgEnable "TextBox3", False DlgEnable "TextBox4", False DlgText "MsgOut", "The selection is 2" ElseIf SuppValue& = 2 Then DlgVisible "Text2", True DlgVisible "Text3", True DlgVisible "Text4", False DlgEnable "TextBox2", True DlgEnable "TextBox3", True DlgEnable "TextBox4", False DlgText "MsgOut", "The selection is 3" ElseIf SuppValue& = 3 Then DlgVisible "Text2", True DlgVisible "Text3", True DlgVisible "Text4", True DlgEnable "TextBox2", True DlgEnable "TextBox3", True DlgEnable "TextBox4", True DlgText "MsgOut", "The selection is 4" End If End Select
All we are doing here is checking the value of SuppValue&
, which contains the selection in the drop-down control, and setting the various dialog properties to match our choice. One interesting note here... The initialization code set the MsgOut
text to "The [default] selection is 1". If we choose "1" in the drop-down, however, the MsgOut
tect is "The selection is 1". This is correct. The only time DialogFunc
executes Case 1
is when it is initialized, so we only see "[default]" in the message before it is changed for the first time.
These code changes should produce the following 4 states (betond the initialization state, which we have already discussed) if your dialog is operating properly:
Case 3: TextBox or ComboBox Changing
This section handles changes in a control that returns a string, rather than a value. In practice, you will likely find limited use for this section because the values in TextBox and ComboBox controls are available after the dialog is closed under normal circumstances. The only time you would want to use this section in DialogFunc
is if you want to respond to a change in a TextBox or a ComboBox and update the dialog while it is still displayed.
To illustrate this functionality, let's say the following sequence of events occurs:
- The dialog is initialized and displayed
- The user selects "2" in the drop-down box
- The user puts the cursor in TextBox1 and enters a value
- The user either tabs down to TextBox2 or simply clicks on TextBox2
As soon as one of these last actions occurs, the dialog will detect that TextBox1 has changed. To see this, add the following code to Case 3
of DialogFunc
:
Select Case DlgItem$ Case "TextBox1" MsgBox("TextBox1 has changed! " & SuppValue& & " characters", vbOkOnly) Case "TextBox2" MsgBox("TextBox2 has changed! " & SuppValue& & " characters", vbOkOnly) End Select
When you run this, select either "1" or "2" for the drop-down control, then enter an alphanumeric string in TextBox1. If, after making these changes, you shift the focus away from TextBox1 (but don't click the OK button yet), you will get a VB message box telling you which TextBox has changed and the number of characters in the new string. Note that the number of characters is reported by SuppValue&
in this case.
Since a ComboBox also lets the user enter text, it works in exactly the same way. It triggers the event when it loses focus after a text change, and the number of characters in the new string is reported by SuppValue&
.
Case 4: Focus Changing
In any Windows application, "focus" is owned by the active control at any given time. In our case, the active control when the dialog is first opened is the "pick a number" listbox, so it has focus to start with. There may be times when we want to keep track of which control has focus and, perhaps as importantly, which control has lost focus. An example would be to validate the contents of a data box before proceeding. This capability is provided in DialogFunc
by Case 4 of the Select Case
block.
This portion of DialogFunc
uses both DlgItem$
(contains the ID of the control gaining focus) and SuppValue&
(contains the ID of the control losing focus). To capture both events, our code will use two nested Select Case
blocks:
Case 4 ' Focus changed Select Case DlgItem$ Case "TextBox1" Select Case SuppValue& Case 0 Case 1 MsgBox("TextBox1 has focus. " & vbTab & "DropListBox.num lost focus.", vbOkOnly) Case 2 Case 3 Case 4 Case 5 Case 6 Case 7 Case 8 End Select Case "TextBox2" Select Case SuppValue& Case 0 Case 1 MsgBox("TextBox2 has focus. " & vbTab & "DropListBox.num lost focus.", vbOkOnly) Case 2 Case 3 Case 4 Case 5 Case 6 Case 7 MsgBox("TextBox2 has focus. " & vbTab & "TextBox1 lost focus.", vbOkOnly) Case 8 End Select End Select
The outer Select Case
loop (with DlgItem$
argument) determines which control is getting focus, while the inner loop (with SuppValue&
argument) determines which control is losing focus. The few possibilities that are covered in the example include:
- TextBox1 gains focus from the listbox
- TextBox2 gains focus from the listbox
- TextBox2 gains focus from TextBox1
Note that there are two ways shown to reference individual controls. One way is through the control's ID parameter, shown in the outer loop. The other way is through the control's "Z-order", shown in the inner loop. The best way to explain Z-order is that it's an integer that defines the order of controls gaining focus when the TAB key is used to move between them. In more full-featured editors, Z-order can be set manually for each control. In our editor, I believe Z-order is defined only by the order that the controls are defined in the Begin Dialog
block in Sub Main
.
When this code is executed and one of the above actions takes place, a MessageBox is displayed indicating which controls gained and lost focus. An example MessageBox is shown:
Complete Code Listing
Option Explicit Dim i As Integer Dim nums(4) As String ' Code Module Sub Main For i = 1 To 4 nums(i) = CStr(i) Next i Begin Dialog UserDialog 510,231,"My Dialog",.DialogFunc ' %GRID:10,7,1,1 Text 20,14,170,14,"Pick a number from 1 to 4:",.Text5 DropListBox 90,35,90,70,nums(),.num Text 280,14,210,14,"MsgOut",.msgOut Text 290,70,50,14,"Box 1",.Text1 Text 290,98,50,14,"Box 2",.Text2 Text 290,126,50,14,"Box 3",.Text3 Text 290,154,50,14,"Box 4",.Text4 TextBox 350,70,90,21,.TextBox1 TextBox 350,98,90,21,.TextBox2 TextBox 350,126,90,21,.TextBox3 TextBox 350,154,90,21,.TextBox4 OKButton 200,203,90,21 End Dialog Dim dlg As UserDialog Dialog dlg End Sub Rem See DialogFunc help topic for more information. Private Function DialogFunc(DlgItem$, Action%, SuppValue&) As Boolean Select Case Action% Case 1 ' Dialog box initialization DlgVisible "Text2", False DlgVisible "Text3", False DlgVisible "Text4", False DlgEnable "TextBox2", False DlgEnable "TextBox3", False DlgEnable "TextBox4", False DlgText "MsgOut", "The [default] selection is 1" Case 2 ' Value changing or button pressed Rem DialogFunc = True ' Prevent button press from closing the dialog box Select Case DlgItem$ Case "num" If SuppValue& = 0 Then DlgVisible "Text2", False DlgVisible "Text3", False DlgVisible "Text4", False DlgEnable "TextBox2", False DlgEnable "TextBox3", False DlgEnable "TextBox4", False DlgText "MsgOut", "The selection is 1" ElseIf SuppValue& = 1 Then DlgVisible "Text2", True DlgVisible "Text3", False DlgVisible "Text4", False DlgEnable "TextBox2", True DlgEnable "TextBox3", False DlgEnable "TextBox4", False DlgText "MsgOut", "The selection is 2" ElseIf SuppValue& = 2 Then DlgVisible "Text2", True DlgVisible "Text3", True DlgVisible "Text4", False DlgEnable "TextBox2", True DlgEnable "TextBox3", True DlgEnable "TextBox4", False DlgText "MsgOut", "The selection is 3" ElseIf SuppValue& = 3 Then DlgVisible "Text2", True DlgVisible "Text3", True DlgVisible "Text4", True DlgEnable "TextBox2", True DlgEnable "TextBox3", True DlgEnable "TextBox4", True DlgText "MsgOut", "The selection is 4" End If End Select Case 3 ' TextBox or ComboBox text changed Select Case DlgItem$ Case "TextBox1" MsgBox("TextBox1 has changed! " & SuppValue& & " characters", vbOkOnly) Case "TextBox2" MsgBox("TextBox2 has changed! " & SuppValue& & " characters", vbOkOnly) End Select Case 4 ' Focus changed Select Case DlgItem$ Case "TextBox1" Select Case SuppValue& Case 0 Case 1 MsgBox("TextBox1 has focus. " & vbTab & "DropListBox.num lost focus.", vbOkOnly) Case 2 Case 3 Case 4 Case 5 Case 6 Case 7 Case 8 End Select Case "TextBox2" Select Case SuppValue& Case 0 Case 1 MsgBox("TextBox2 has focus. " & vbTab & "DropListBox.num lost focus.", vbOkOnly) Case 2 Case 3 Case 4 Case 5 Case 6 Case 7 MsgBox("TextBox2 has focus. " & vbTab & "TextBox1 lost focus.", vbOkOnly) Case 8 End Select End Select Case 5 ' Idle Rem Wait .1 : DialogFunc = True ' Continue getting idle actions Case 6 ' Function key End Select End Function