The article Scripting How-To: Adding a Custom 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 Custom 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.
To start, paste the following code into a new code module:
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.
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.
DlgEnable statement handles this action. So, let's add the following statement to
Case 1 of the
Run the script and look at the text label for item 2. It should be disabled, as shown below:
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.
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:
When you run this modification, you will see that the text box that corresponds to the disabled label is now invisible (not just disabled):
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:
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:
...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
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 main
Select Caseblock in
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, then
SuppValue&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 to
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:
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
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
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
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
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:
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
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: