Table-Driven Command BarsFor small-scale Excel applications with a few toolbar buttons and/or menu items, it is perfectly acceptable to hard-code the creation of the command bars and controls your application requires using a custom VBA procedure. (We do recommend that you isolate command bar building code in a separate procedure for ease of maintenance.) When you begin building large-scale applications with multiple dedicated toolbars and menus, hard-coded command bars become very time-consuming to create and difficult to maintain.Chapter 4 Worksheet Design, table-driven is a generic term referring to any process that performs some operation guided by information stored in a dedicated table on a worksheet.Table-driven command bar building is one of the more complex table-driven methodologies. Implemented correctly, however, it is easy to use and far superior to anything that can be accomplished using VBA alone. Even when we resort to using Visual Basic ActiveX DLLs, a technique we cover extensively in Chapter 20 Combining Excel and Visual Basic 6, we use an Excel add-in workbook with a three-line stub procedure assigned to each of the command bar controls that calls the ActiveX DLL. This add-in workbook allows us to use worksheets, which would otherwise be unavailable, to implement table-driven command bars and other table-driven methodologies.In this section we describe the most sophisticated table-driven command bar builder available. As with all other applications described in the book, this command bar builder is included on the CD that accompanies this book and can be integrated into any of your applications. Introducing the Table-Driven Command Bar BuilderThe table-driven command bar builder consists of three parts that form a self-contained unit you can plug directly into any application: a worksheet table that defines the command bars and controls to be built and two code modules that read this table and build the command bars and controls it specifies.Putting It All Together section later in the chapter we demonstrate the workbook containing the version of the command bar builder that you should use in your projects.After you have added definitions of the command bars and controls you want to build to the wksCommandBars worksheet, your application just needs to call one procedure on startup and all of those command bars and controls will be built to your specifications. A second procedure can be called on shutdown to dismantle the command bars and controls specified in the table. We cover this in more detail in the Practical Example section at the end of the chapter.We spend most of this section explaining how to write command bar definitions in the command bar definition table. It's best to think of the code that reads and implements the command bars and controls defined by the table as a black box. This code is too lengthy and complex to cover in any detail in this chapter, but you are strongly encouraged to examine the code if you want to understand how it works. The code is open and heavily commented, so it should be reasonably approachable to the seasoned programmer. The Command Bar Definition TableThe reason we are spending so much time describing the command bar definition table is because you will spend 99 percent of your command bar building time working with it. The only thing you need to do with the code, after adding it to your project, is call the build command bars procedure on startup and the reset command bars procedure on shutdown.The command bar definition table is too wide to display entirely in a screen shot on the printed page, but we will give you the flavor for what it looks like with the partial example shown in Figure 8-1. Figure 8-1. A Partial Command Bar Definition Table[View full size image] ![]() Figure 8-2. A Custom Menu Bar Created by the Command Bar Builder![]() Figure 8-3. Defined Constants in the Command Bar Definition Table![]()
Command Bar NameThe command bar builder has the flexibility to create new command bars as well as add controls to existing command bars. Regardless of whether you are creating a new command bar or adding controls to an existing command bar, you enter the name of the command bar in this column. The command bar builder checks each command bar name specified in this column to see whether it already exists. If the specified command bar already exists, the command bar builder assumes you are adding controls to that command bar. If no command bar with the specified name exists, the command bar builder creates a new command bar with the specified name using the settings specified in later columns.There must be two entries in the Command Bar Name column for every command bar you build or modify. The first entry must be the name or index number of the command bar being created or modified. The second entry is simply the word Stop. The Stop entry must be placed in the row directly below the last row of the command bar definition specified by the first entry. These two entries in the first column work together to bracket the command bar definition so the command bar building code knows where the definition starts and ends.In Figure 8-1, notice how the command bar name Custom Menu Bar is placed at the top of the Command Bar Name column in cell A2 and the word Stop is placed at the bottom in cell A27. As shown in this example, there cannot be any entries between the command bar name value and the Stop keyword. You can stack as many command bar definitions in the table as you like. The only rule is that each subsequent definition must be separated from the previous definition by at least one blank row. Control CaptionThere are three Control Caption columns by default. This is because good user interface design suggests you should not use more than three cascading menu levels. If you really must have additional levels, you can simply insert additional Control Caption columns to the right of the existing three.Similar to the Command Bar Name column, the Control Caption columns can be used to create new controls or add subcontrols to existing controls. If the command bar builder code detects that the caption in the Control Caption column refers to an existing control on the current command bar, it will assume you want to add subcontrols to it. Otherwise it will create a new control with the specified caption using the settings specified in later columns.Regardless of whether you are creating a single control or a cascading series of menus, each control must occupy its own row. The position of a control's caption within the series of Control Caption columns determines the level at which the control will be added. Look again at Figure 8-1. Notice that even though all three Control Caption columns have entries, no row has a Control Caption entry in more than one of the three columns. This is an absolute requirement.You can provide an accelerator key for your control by placing an ampersand directly to the left of the character that you want to use as the accelerator key character. The control can then be activated from the keyboard by pressing the Alt key and the specified character simultaneously. This feature only applies to controls that display their caption and are currently visible.NOTEWhen a control displays a dialog, standard user interface conventions dictate that the caption of the control, if it displays one, should be followed by an ellipsis. See the Excel File > Print… menu for an example of this convention. PositionThe Position setting applies only to CommandBar objects. It specifies the position on the screen where the CommandBar will appear when it is displayed. This setting must be one of the following msoBarPosition enumeration members:msoBarBottom The command bar will be docked at the bottom of the screen.msoBarFloating The command bar will not be docked but instead will float over the screen.msoBarLeft The command bar will be docked on the left side of the screen.msoBarPopup This setting is used to specify command bars that will be displayed when the user right-clicks with the mouse. The command bar will be displayed at the position where the user right-clicked. Command bars with this position setting must be displayed in response to one of the BeforeRightClick event procedures using the syntax: msoBarRight The command bar will be docked on the right side of the screen.msoBarTop The command bar will be docked at the top of the screen. This is the default value if no position is specified. IsMenubarThe IsMenubar setting applies only to CommandBar objects. If set to True, the specified command bar will be the menu bar when it is visible. You can define multiple command bars as menu bars for different purposes, but only one menu bar can be visible at a time. If the IsMenubar setting is False, the command bar will be a toolbar or popup depending on the Position setting. The IsMenubar property must be False for command bars with the Position property value msoBarPopup or a runtime error will occur. Therefore, the command bar builder code will enforce this value for msoBarPopup command bars regardless of the value actually entered in the table. The default value for the IsMenubar setting is False. VisibleThe Visible setting applies only to CommandBar objects. If set to True, the specified CommandBar will be visible, subject to the following limitations:If more than one command bar has both the IsMenubar and Visible settings set to True, the last such command bar in the table will be the menu bar that is actually displayed. All other menu bars will be hidden.The Visible property does not apply to and has no effect on command bars with the Position value msoBarPopup. The default value of the Visible setting is False. This enables you to create a large number of command bars when your application starts up and then display them on demand as needed. WidthThe Width setting applies to CommandBar and CommandBarControl objects. The Width setting must be a positive whole number greater than zero. This setting is not required and there is no default value. If the Width setting is not specified, the width of the command bar or control will be determined automatically by VBA. If the Width setting is not specified for a command bar, VBA will make the command bar wide enough to display all of the controls it contains on a single row. If the Width setting is not specified for a control, VBA will make the control wide enough to display its icon and/or caption.For CommandBar objects, the Width setting applies only when the Position setting is msoBarFloating. The Width setting is ignored for all other command bar Position settings. You cannot make a command bar wider than its automatically calculated width. Setting the width of a floating command bar to a value narrower than its automatically calculated width enables you to stack controls in multiple rows rather than displaying a long, single-row command bar.For CommandBarControl objects, the Width setting always applies and you can set it to any positive whole number greater than zero. If the specified width is too narrow to display the caption and/or icon of the control, however, it will be ignored. Note that all controls on the same popup menu list will have the width of the widest control in the list regardless of their individual Width settings.There are no hard-and-fast rules for deciding exactly what the Width setting should be. The best approach is to first build your command bars and controls without specifying the Width setting. Then use the Immediate window to examine the width property that has been automatically assigned by VBA. You can use that as a starting point from which to increase or decrease the width of your command bars and/or controls. ProtectionThe Protection setting applies only to CommandBar objects. This setting specifies what type of modifications the user will be allowed to make to the command bar. This setting must be one or more of the following msoBarProtection enumeration members. To apply multiple Protection values, just add the values together in the Protection cell for the command bar in question.msoBarNoChangeDock The user cannot change the position at which the command bar is docked.msoBarNoChangeVisible The user cannot change the visibility status of the command bar. If the command bar is visible, the user cannot hide it; if the command bar is hidden, the user cannot display it.msoBarNoCustomize The user cannot add or remove controls on the command bar.msoBarNoHorizontalDock The user cannot dock the command bar in any horizontal position, either top or bottom. Without any additional protection values, the command bar can still be docked vertically. To prevent a command bar from being docked anywhere, just add the msoBarNoHorizontalDock and the msoBarNoVerticalDock enumeration member values together in the Protection cell.msoBarNoMove The command bar cannot be moved. Be careful with this option because it will prevent the user from moving the command bar under any circumstances. For example, if you create a floating command bar whose width causes it to appear partially off-screen, the user will not be able to move the command bar into a position where they can access all of its controls.msoBarNoProtection The user can make any changes to the command bar that he wants.msoBarNoResize The user cannot modify the width or height of the command bar.msoBarNoVerticalDock The user cannot dock the command bar in any vertical position, either left or right. There are two ways a user can delete your command bar regardless of its Protection setting, even if you have disabled the View > Toolbars > Customize menu. Both of these methods provide "back doors" to display the Customize dialog. It is particularly important to disable these options in dictator applications where deleting a custom command bar may leave the user with no way to properly exit the application.First, if the Toolbar List command bar is enabled, the user will be able to delete your custom command bar by right-clicking anywhere over the command bar area and selecting Customize from the shortcut menu. To disable the Toolbar List command bar, execute the following line of code: Second, if any empty toolbar docking surface is exposed on screen (typically beyond the right side of a toolbar), the user can double-click anywhere within this area and the Customize dialog will display. There is no way to directly disable this feature, so you must indirectly disable it by ensuring that no uncovered toolbar docking area is left exposed by your application. The easiest way to do this is to add a nonfunctional CommandBarButton control (one with no Caption or OnAction assignment) at the end of each of your toolbars and set it to be wide enough so it will cover the entire toolbar docking area regardless of the user's screen resolution. IsTemporaryThe IsTemporary setting applies to CommandBar and Command BarControl objects. If set to False, the specified command bar or control will be persisted between Excel sessions. Setting this property to True causes the command bar or control to be discarded when the current session of Excel exits. The default value for this setting is True.The command bar builder will rebuild all command bars and controls defined in the table each time your application runs, so the occasions when you want your custom command bars or controls to be persisted between Excel sessions are very rare. Leave this setting blank so the default value is used unless you have a very good reason to do otherwise. IsEnabledThe IsEnabled setting applies to CommandBar and CommandBarControl objects. This setting determines whether the command bar or control is enabled on startup. A value of True causes the command bar or control to be enabled. A value of False causes the command bar or control to be disabled. Disabled command bars will not be visible to the user. The IsEnabled property overrides the Visible property for command bars in this respect. Disabled controls will be visible but will appear grayed out. The default value for this property is True. OnActionThis setting applies to CommandBarControl objects. It holds the name of the procedure that will be run by the control. This procedure must be a public procedure located in a standard code module. If you want to trap the Click or Change event rather than assigning a procedure to the OnAction property, you can leave this setting blank. We cover control event trapping in the Hooking Command Bar Control Events section later in this chapter. If the Control ID setting is anything other than 1, the OnAction setting is ignored. You will understand why this is the case when we describe the Control ID setting next. Control IDThis setting applies to CommandBarControl objects. Giving this setting a value of 1 means that you are creating a custom control whose properties are specified by the rest of the columns in the table. Any value other than 1 is interpreted as the ID of a built-in Excel control. In that case, the built-in control specified by the Control ID value will be added to your command bar, along with its function and appearance. If you specify a built-in control using the Control ID setting, the following command bar definition table settings will be ignored:OnActionControl TypeControl StyleShortcut TextStateListRangeLists You can determine the ID you need to use in order to add a built-in control to your command bar in the following manner. Assume you want to add the Print menu item from the File menu on the Worksheet Menu Bar to your custom command bar. Enter the following into the VBE Immediate window: The Immediate window is covered in more detail in Chapter 16 VBA Debugging, but for now note that the ? character tells the Immediate window to print the result of the expression that follows it. In this case, the result is the number 4, shown directly below the expression. This is the ID of the Print control. To add this control to your custom command bar, you just place 4 in the Control ID column of the appropriate row in the command bar definition table.NOTEA quirk in the Office CommandBars object model enables you to access the top-level menus of the Excel menu bar as CommandBar objects in their own right. If you loop the contents of the CommandBars collection, you won't find these menus contained in it, but you can access them using the syntax shown above just the same. Control TypeThis setting applies to CommandBarControl objects. It is used to specify what type of control you want. This setting must be one of the following msoControlType enumeration members:msoControlButton This is a menu or toolbar button that simply executes the specified OnAction procedure when it is clicked. The majority of CommandBarControls that you see on Excel's menus and toolbars are this type of control.msoControlComboBox This is a combo box control that enables users to either select an entry from a predefined list or enter a new value of their choosing. An example of this type of control is the Zoom combo box on the Standard toolbar. You can select from a predefined list of zoom values or supply your own.msoControlDropdown This control looks exactly like the msoControlComboBox control but the only option allowed is to select an item from the predefined list.msoControlEdit This is an edit box control that allows the user to enter an arbitrary text value.msoControlPopup This type of control is used to create a submenu containing a list of one or more menu items. All of the top-level menus on the Excel menu bar are of type msoControlPopup. Rather than doing anything directly, they just display their associated submenu. This is the only control type that can display a submenu. For custom controls, the default value for this setting is msoControlButton. We examine how you use each of these control types in more detail in the Putting It All Together section later in this chapter.NOTEIf you look in the VBE Object Browser, you will discover that there are anywhere from 21 to 27 different msoControlType enumeration members depending on the version of Excel you are using. Unfortunately, you are limited to one of the five members listed above when building custom CommandBarControls.Controls with some of the other enumeration member types can be added to a custom command bar by adding a built-in control of those types (by adding the Borders button from the Formatting toolbar, for example, whose type is msoControlSplitButtonPopup). Some of the msoControlType enumeration members simply haven't been implemented. msoControlOCXDropdown is one example. Control StyleThis setting applies to CommandBarControl objects. It specifies the visual layout of the control. This setting does not apply to the Control Types msoControlEdit or msoControlPopup. It applies to the other control types in the following manner:msoControlButton Must be one of the following msoButtonStyle enumeration members:msoButtonAutomatic This is the default value for controls of type msoControlButton. For a menu item, this is equivalent to msoButtonIconAndCaption. For a toolbar button, this is equivalent to msoButtonIcon.msoButtonCaption This style displays only the caption assigned to the control. Any icon assigned to the control is ignored.msoButtonIcon This style is a bit confusing. It displays only the icon for toolbar buttons and only the caption for menu items. If no icon is specified for a toolbar button with this style, a blank button will be created.msoButtonIconAndCaption This style displays the icon and places the caption to the right of the icon for both menu items and toolbar buttons.msoButtonIconAndCaptionBelow This style has exactly the same effect as msoButtonIconAndCaption for menu items. For toolbar buttons it displays the caption centered below the icon.msoButtonIconAndWrapCaption This style is similar to the msoButtonIconAndCaption style, but it wraps long captions to the right of the icon instead of displaying them on a single line. This style gives very poor visual results when used with menu items, so we recommend against using it for that type of control.msoButtonIconAndWrapCaptionBelow For toolbar buttons this style is similar to the msoButtonIconAndCaptionBelow style except that long captions are wrapped beneath the button icon. For menu items this style gives exactly the same poor results as the msoButtonIconAndWrapCaption style, so we recommend against using it for that type of control.msoButtonWrapCaption This style is similar to the msoButtonCaption style in that it ignores any icon assigned to the control. The difference is that it wraps long captions rather than displaying them on a single line.msoControlComboBox and msoControlDropdown Must be one of the following msoComboStyle enumeration members:msoComboNormal This is the default value for controls of type msoControlComboBox and msoControlDropdown. It simply displays the control with no caption.msoComboLabel This style displays the caption directly to the left of the combo box or drop-down control. Because the Control Style setting does not apply to controls of type msoControlPopup, you cannot modify the default appearance of this type of control. There is no workaround for this limitation. Because the Control Style setting does not apply to controls of type msoControlEdit, you cannot provide a caption for these controls. If your edit box control is located on a toolbar, you can work around this limitation by adding a nonfunctional msoControlButton with the msoButtonCaption style that displays the caption you desire directly to the left of the edit box control. We demonstrate this workaround in the Putting It All Together section later in this chapter. Face IDThis setting applies to CommandBarControl objects. It specifies the icon that will be associated with the control. The Face ID setting can be specified in one of three ways:Putting It All Together section later in the chapter.When operating under Excel 2002 or higher, you have the option of specifying an icon and a mask. This method provides significantly superior visual results for these versions of Excel. Both the icon and the mask must be 16x16 pixel graphics that are located on the wksCommandBars worksheet. The icon and mask picture names must be entered together into the Face ID cell separated by a forward slash (/) character. We discuss this method in more detail in the Loading Custom Icons from Files and Hooking Command Bar Control Events sections later in the chapter.Note that the icon picture from the icon/mask pair will automatically be used in versions of Excel earlier than Excel 2002, making method three equivalent to method two when running on down-level versions of Excel.The most important characteristic of a custom icon is that its background appear transparent when applied to a control. To use custom icons under Excel 97/2000, you must use the Set Transparent Color control from the Picture toolbar to specify a transparent background color for the single picture that will become the custom icon for your control. This method is illustrated by the before and after pictures shown in Figure 8-4 and Figure 8-5. Figure 8-4. An Icon Picture Before Setting the Transparent Background![]() Figure 8-5. An Icon Picture After Setting the Transparent Background![]()
Excel 97/2000, Excel 2002 and Excel 2003 all use different UI drawing techniques, so picture backgrounds set to be transparent in one version of Excel will not appear transparent in other versions. As you will see in the section on Loading Custom Icons from Files later in the chapter, two new methods were added to the CommandBarButton object in Excel 2002 that enable you to load pictures directly into the control in a manner that is independent of their appearance on the worksheet.For this reason we recommend that you use the icon/mask method described in point 3 above for creating custom icons in Excel 2002 and higher, while setting the transparent background color of the icon picture using Excel 97/2000. This gives the command bar builder an appropriately formatted icon for use in Excel 97/2000. It will automatically use method two under these versions of Excel. You do not have to do anything special to the mask picture, because the mask is not used under Excel 97/2000. Begin GroupThis setting applies to CommandBarControl objects. It is a True/False setting that specifies whether a separator bar will be placed above or to the left of the specified control depending on context. A value of True adds a separator bar. False is the default value for this setting; so if False is specified or this setting is left blank, no separator bar will be created. BeforeThis setting applies to CommandBarControl objects. It is used to position a new control before an existing control. If the controls are arranged horizontally, the Before setting will place your control to the left of the control specified. If the controls are arranged vertically, the Before setting will place your control above the control specified.The existing control can be specified either by its name or ID number. This setting is not required and there is no default value. If this setting is left blank or if the control specified by this setting cannot be located, this setting will be ignored. In this case, the control will be added at the system default position, which is at the end of the current set of controls on the same level. The Before setting is typically used to position controls that are being added to built-in Excel command bars. TooltipThis setting applies to CommandBarControl objects. It specifies the text that will be contained in the yellow tooltip message that displays when the user hovers the mouse pointer over the control. This setting does not apply to controls of type msoControlPopup or to any child control of an msoControlPopup control. If the Tooltip setting is not specified, the system default value displayed in the tooltip is the caption of the control. Shortcut TextThis setting applies to CommandBarControl objects. It displays the keyboard shortcut that will be assigned to the control directly to the right of the caption for the control. This setting applies only to controls of type msoControlButton that are child controls of an msoControlPopup control (that is, menu items on a submenu). Specifying this setting does not actually assign the specified keyboard shortcut to the macro assigned to the OnAction setting of the control. You must do this separately in your own code. The Shortcut Text setting is not required and there is no default value. TagThis setting applies to CommandBarControl objects. It enables you to store String data for your own use. The Tag setting does not modify the appearance or function of the control in any way. One common use of this setting is to differentiate among controls that have been assigned the same OnAction procedure.Assume you have assigned the OnAction settings of three controls to the same procedure. You then assign the values 1, 2 and 3 to the Tag settings of the first, second and third controls, respectively. When the specified OnAction procedure is called by one of these controls, you can identify the control that called it in the manner shown in Listing 8-1 and conditionally redirect program execution based on the result. Listing 8-1. Distinguishing Controls Using the Tag SettingAs you will see in the Hooking Command Bar Control Events section later in the chapter, the Tag setting is also used to specify the custom controls whose events you want to hook as a group. ParameterThis setting applies to CommandBarControl objects. It is functionally identical to the Tag setting. It is a place for the programmer to store String data that will not have any effect on the appearance or function of the control. StateThis setting applies to CommandBarControl objects. It enables you to create checked menu items or depressed toolbar buttons. The value for this setting must be one of the following msoButtonState enumeration members:msoButtonDown For toolbar buttons this creates the visual effect of the button being depressed. For menu items, the effect depends on whether there is an icon displayed with the menu item. For menu items with icons, the icon will appear depressed in a fashion very similar to the effect on toolbar buttons. For menu items without an icon, a depressed check mark will be added to the left of the menu caption.msoButtonMixed For all current versions of Excel, this value is indistinguishable from msoButtonDown. It is included in the command bar builder in case it becomes supported for some different purpose in a future version of Excel.msoButtonUp This is the default value for this setting. A State value of msoButtonUp has no effect on the appearance of newly created controls. This value only comes into play as a way to remove the effect of the msoButtonDown or msoButtonMixed values. The State setting applies only to custom controls of type msoControlButton. Keep in mind that the State property is a dynamic property of the control. The command bar builder will create the control with whatever initial State value you specify, but after the control has been created you will need to write custom code to modify the State property appropriately in response to user actions. We demonstrate this in the Putting It All Together section later in the chapter. ListRange and ListsThese settings apply to CommandBarControl objects of type msoControlComboBox or msoControlDropdown. We discuss these settings together because they are, in effect, a single setting that specifies the list to be loaded into a combo box or drop-down control. The purpose of these settings is as follows:ListRange This value specifies the address of the range on the wksCommandBars worksheet that holds the list to be loaded into the control. The specified range must be located in the Lists column. Like all other settings described so far, the ListRange setting must be located on the same row as the control to which it applies.Lists This setting is a list of values that will be loaded into the control. This is the only setting that does not have to be located on the same row as the control it applies to, and as we discuss below, it should not be located in rows that are part of any command bar or control definition. You should always place your lists below the last command bar definition in the table. By doing this, if you need to insert or delete rows in a command bar definition, you will not inadvertently alter one of your lists. For similar reasons, use a dynamic formula to create the list address value for the ListRange setting. If you hard-code the list address and then insert or delete rows in the command bar definition table, the list address will no longer be valid. Use of a dynamically adjusting formula to specify the list location solves this problem. An example of a dynamically adjusting List address formula is shown here: This formula indicates that the Lists setting is located in column Y of the command bar definition worksheet. The list currently starts in row 48 of that column and ends in row 53 of that column. Notice, however, that this formula uses relative addresses. If you insert or delete rows above the list, the addresses specified in the formula will automatically adjust to the new location of the list. The result of this formula, as displayed in the ListRange cell and as read by the command bar builder code, is shown here: The command bar builder will use the range specified by this address as the list to be loaded into the control defined in the row in which the ListRange setting is located. Note that the last command bar definition in this example ends at row 47. Therefore, placing the list below row 47 ensures that any subsequent insertions or deletions within the command bar definition table will not have any effect on the contents of the list. We demonstrate this technique in the Putting It All Together section later in the chapter. Post MortemAlthough it seems as if every command bar and control property under the sun has been covered here, we're not even close. Only the most frequently used properties have been included in the command bar builder. Other properties that you may find an occasional need for can always be manually coded into your application.Examples of properties that are not included in the command bar builder are the Height and RowIndex properties of the CommandBar object, because in our experience these properties are rarely used when building command bars. Properties of the CommandBarControl object that are not supported include the DescriptionText property, because it simply duplicates the purpose of the Tag and/or Parameter properties and the HyperLinkType property, because it is so rarely used.There are also dynamic properties such as IsEnabled and State whose initial values are set by the command bar builder, but whose subsequent values must be managed by custom code in your application as the need to change them arises. |