Dynamic UserformsMost userforms that we create are static, which is to say they have a fixed number of controls that are always visible (although may be disabled at certain times). Dynamic userforms display different controls each time the form is shown. Subset UserformsThe easiest way to create a dynamic userform is to start with a form that has more controls of all types than we'll ever need. When the form is shown, we set the position, caption and so on of all the controls we need, hide the extra controls that we don't use and set the userform's size to encompass only the controls we use. This method is ideal to use when the upper limit on the number of controls is known and when each control is a known type. An example would be a survey, where each question might have between two and five responses for the user to pick between. We create the form with five option buttons, set their captions with the applicable responses for each question and hide the unused buttons. Code-Created and Table-Driven UserformsIf we cannot predict a reasonable upper limit on the number of controls, or if there could be lots of different types of control that could be shown, having a pre-prepared set of controls on the form becomes increasingly harder to maintain. Instead, we can add controls to the userform at runtime. It's quite rare to find a situation that requires a userform to be created through code; we can usually either design the form directly or use the "subset" technique to hide the controls we don't need to use.The one situation where code-created userforms make our development life extremely easy is in the use of table-driven dynamic wizards. Imagine a wizard used to generate a batch of reports, with each report using check boxes to set its options. In Step 2 of the wizard, we could display a multiselect list box of the available reports, where the list of reports is read from a table in a worksheet. When the user clicks the Next > button, we populate Step 3 of the wizard with the check boxes appropriate for the selected report(s), where again the check boxes are read from a worksheet table. By implementing a table-driven report wizard, we can add new reports to the wizard just by adding rows to the report and report options lists. An example of this technique can be found on the CD in the ReportWizard.xls workbook and is explained below.Figure 10-9 shows an extract of the wksReportOptions worksheet, containing the lists of the available reports and their options, and Figure 10-10 shows Step 3 of the Report Wizard dialog, where the Client Detail report has been selected to run. Figure 10-9. A List of Reports and Their Options![]() Figure 10-10. The Table-Driven Step 3 of the Report Wizard In this step, the General Options pane is a permanent part of the wizard and contains options common to all the reports. The report-specific panes, such as the Client Detail pane, are created each time this step is initialized. A separate pane is created for each selected report that has some options (note that the two summary reports have no options), using the code in Listing 10-19.Listing 10-19. Code to Create the Report Options PanelsTo keep this example simple, we have only used check boxes for the report's options and forced each check box to be shown on a different row. A real-world version of this technique would have many more columns for the report options, allowing all control types to be used and having more control over their position and style. Scroll RegionsThe observant reader will have noticed that the last line of the procedure shown in Listing 10-19 sets the ScrollHeight of the fraReportOptions frame. This is the frame that contains all the report option panes and was formatted to show a vertical scrollbar in Figure 10-10. Setting the frame's ScrollHeight enables us to add more controls to the frame than can be seen at one time; when the ScrollHeight is bigger than the frame's height, the user can use the scrollbars to see the additional controls. Although this should be considered a last resort in most userform design situations, it can prove very useful when creating dynamic forms that might extend beyond the visible area. Dynamic Control Event Handling and Control ArraysThe biggest downside of adding controls at runtime is that we cannot add procedures to the userform's code module to handle their events. In theory, we could use the VBA Extensibility library to create a userform in a new workbook, add both controls and event procedures to it and then show the form, but we've yet to encounter a situation that requires such a cumbersome solution. We can, however, use a separate pre-prepared class module to handle (most of) the events of the controls we add to the form. The class module shown in Listing 10-20 uses a variable declared WithEvents to handle the events of any TextBox it's hooked up to. The Change event is used to perform nonintrusive validation of the control, checking that it is a number using the CheckNumeric function from earlier. Ideally, we would prefer to use either the BeforeUpdate or AfterUpdate events to perform the validation, so it's done when the user leaves the control instead of every time it's changed. Unfortunately, those events belong to the generic MSForms.Control object and are not exposed to us when we declare a WithEvents object in this way. Listing 10-20. Class to Handle a Text Box's EventsEvery time we add a text box to the form, we create a new instance of the class to handle its events and store all the class instances in a module-level collection, as shown in Listing 10-21. Listing 10-21. Assigning Event Handler Classes to Controls Created at RuntimeWe can use the same technique to handle the events of controls in nondynamic userforms as well. Imagine a form with 50 text boxes, all requiring numeric validation. We could include all 50 Change event procedures in our code and accept the maintenance overhead that brings, or we could use the class module from Listing 10-20 to handle the validation for all our text boxes. The code in Listing 10-22 iterates through all the controls on the form, hooking up new instances of the event handler class for every text box it finds. Listing 10-22. Class to Handle a Text Box's Events
|

In this step, the General Options pane is a permanent part of the wizard and contains options common to all the reports. The report-specific panes, such as the Client Detail pane, are created each time this step is initialized. A separate pane is created for each selected report that has some options (note that the two summary reports have no options), using the code in