Tabbed Interface

This content is no longer actively maintained. It is provided as is, for anyone who may still be using these technologies, with no warranties or claims of accuracy with regard to the most recent product version or service release.

Aa155741.offvba(en-us,office.10).gif

WORDfixer

LEVEL: Experienced Programmer Word XP | DocBar | Tabs

Stealing Excel's Tabs Makes Document Windows Easier to Arrange in Word

By Romke Soldaat

In Windows of Opportunity I moaned that Word lacked options to arrange document windows the way you can in Access and Excel. Thanks to Word's near-unlimited customizability, however, it proved to be fairly easy to steal these features and arrange documents in any way. In this article I'll continue my theft and take Excel's tabbed interface.

Tabs and Taskbars

During the past few years, I've heard many Office users rave about the worksheet tabs in Excel (see FIGURE 1). And, at the same time, those users complain that Word doesn't have a similar feature.

Aa155741.vba200112rs_f_image002(en-us,office.10).jpg
FIGURE 1: Tabs are perfect navigation tools.

Let's get two things straight before we continue. First, it wasn't Excel that premiered the tabbed interface. Quite a few other software packages used it long before Microsoft jumped on the bandwagon. Second, Word does give you the option to flip between open documents if you check the Windows in Taskbar option in the Options dialog box. Word then goes into Single Document Interface (SDI) mode and displays an icon for each document on the taskbar. With one click, your document jumps into the foreground.

The trouble with this solution is that the text on the taskbar icon (the name of the document) is usually unreadable, especially if you have many documents open and a few applications running at the same time. Also, the fact that the icons are outside the application window forces you to make long mouse movements to switch between documents. Aside from all that, if you have Word XP, you're probably glad enough that you can turn the Windows in Taskbar option off and go back to Word's pre-2000 Multiple Documents Interface (MDI).

Introducing the DocBar

So, what's the answer? As far as I'm concerned, what Word really needs is a navigation tool with the click-and-activate features of the Windows taskbar and with the convenience of having it inside the application window, just like Excel's worksheet tabs. Is that possible? The accompanying download file proves it. Once you install the add-in template for Word XP (sorry, but it won't work with earlier versions), you'll get a toolbar named DocBar that sits at the bottom of Word's application window. DocBar contains a button for each open document window (see FIGURE 2).

Aa155741.vba200112rs_f_image004(en-us,office.10).jpg
FIGURE 2: The DocBar in Word. Switching documents has never been easier.

The immediate advantages are clear: You can always see which documents are open, and you can switch between them without ever going to the Window menu. The drawback is that you lose a bit of workspace.

But it doesn't stop there. Let's go through the features of the DocBar:

  • General - The DocBar contains an All button, plus one button for each open document window. The button that represents the active window is highlighted. Move the mouse pointer over any button to see the full path name of the associated document. If you have more windows open than fit on the DocBar, you can access the invisible buttons by clicking on the Toolbar Options icon at the end of the DocBar.
  • Switching between document windows - To activate a document window, click on the appropriate button or hold down the [Alt] key, and press the button's underlined character.
  • Running document actions -Click the button associated with the active document window to open a menu that contains most options of the File menu, and the New Window option of the Window menu (see FIGURE 3).
  • Running actions for all windows -Click the All button to display a menu that lets you arrange, save, or close all open document windows and exit Word (see FIGURE 4).
  • Closing a single window without first activating it - Hold down the [Shift] key and click a button to close the associated document window. The application will prompt you to save the document if it contains unsaved edits.
  • Closing multiple windows without first activating them - Hold down the [Ctrl] key and click any button to select or deselect it. Then click the All button, and choose the Close selected windows option.
  • Updating the DocBar - Under certain circumstances, the DocBar may not show all document windows or contain buttons for windows that are not open anymore. In that case, hold down the [Ctrl] key and click the All button to update the DocBar.

Aa155741.vba200112rs_f_image005(en-us,office.10).gif
FIGURE 3: The document commands you use most frequently are just a button-click away.

If you're an Office developer, you may find some interesting tips and tricks in the following section. If not, just download the DocBar add-in and enjoy Word even more than before!

The Inside Story

It doesn't take a rocket scientist to write VBA code that displays the names of open document windows on a command bar. The Windows collection represents all document windows and contains all the window names that appear at the bottom of the Window menu. The UpdateTabs routine in Listing One demonstrates how the number of buttons on the DocBar stays in sync with the number of document windows. The routine also demonstrates how the Caption property of each window is used to become the Caption property of each button. That's all fairly simple. However, there are a few caveats.

Getting your priority right. The space on the DocBar is limited to the width of the application window. Any button that doesn't fit will disappear in the void at the right end of the command bar. So how do you keep the button for the active window visible, even if its index value is high enough to push it off the toolbar? The trick is to use the Priority property of the CommandBarControl object. This property determines whether the control can be dropped from a docked command bar if its controls can't fit in a single row. Strangely enough, Microsoft claims you can use priority numbers from 0 through 7. But, at the same time, Microsoft officials say values other than 1 are ignored. Don't ask me why. Just remember that a priority value of 1 means you cannot delete the control from a toolbar.

In the UpdateTabs routine, the Priority property is linked with the button's State property. If the button represents the active window, its State property is set to msoButtonDown, and the Priority property is set to Abs(State), which returns a value of 1. In all other cases, the State property is msoButtonUp, which also sets the Priority to 0.

Keeping track of window events. During a typical Word session, the list of document windows changes continuously. The user opens and closes documents and also creates new documents from scratch and then saves them. And, for each document, you can open as many new windows as you like. It's obvious the DocBar must keep track of these changes and update the row of buttons accordingly. The mechanism that makes this possible is an event procedure.

Word exposes a large number of events to VBA programmers (XP adds more than 10 new ones), but in the DocBar application, only one is used: WindowActivate. This event occurs when any document window is activated, whether it's a new document, one that has just been opened, or a window that becomes active because another window has been closed. If you're not familiar with application events, look under Application object in VBA Help and select any item in the Events list. The following code shows how simple the WindowActivate procedure is. If the SuspendUpdate property equals False, the UpdateTabs routine runs (see the listing at the end of this article for more information):

  Private Sub App_WindowActivate(ByVal Doc As Document, _
  ByVal Win As Window) 
   If Not SuspendUpdate Then
    UpdateTabs
   End If
End Sub

Keeping track of changing document names. What happens if a user creates a new document and then saves it for the first time? What if he or she saves an existing document under a new name? The WindowActivate event does not cover this sort of activity.

That's why the DocBar application also tracks a different kind of event: the Click event of the CommandBarButton object, which occurs when the user clicks a button or chooses a menu item. The buttons' DocBar tracks are the ones that represent the Save, Save As, and Save All commands. Each time the user selects these commands, the application intercepts the event and executes its own instructions, including one that updates the buttons on the DocBar.

As an example, here's the code that runs when the user clicks the Save button:

  Private Sub CmdSave_Click(ByVal Ctrl As _
 Office.CommandBarButton, CancelDefault As Boolean)
   On Error Resume Next
   ' Run this only if the document hasn't been saved yet. 
   If Len(ActiveDocument.Path) = 0 Then
    CancelDefault = True
    ActiveDocument.Save
    UpdateTabs
   End If
End Sub

Unfortunately, this method isn't foolproof. If a user presses [Ctrl]s to save a document that hasn't been saved before, its name changes, but the Click event of the Save button does not take place. You can't have it all.

Because of space restrictions, the listing of the class module that keeps track of these events is not printed here, but you can study the code in the download file.

Closing multiple documents in reverse order. The option to close selected windows runs the CloseSelectedWindows macro. In the listing, you can see these windows are closed in the reverse order of their index values. This is the only way to make sure you're closing the correct windows.

Aa155741.vba200112rs_f_image006(en-us,office.10).gif
FIGURE 4: Everything you want to do with multiple windows is now in a single menu.

As an example, assume five windows are open and that the windows with index values 2, 3, and 5 are to be closed. If you start at the bottom of the list and close window 2 first, Word automatically re-indexes the remaining windows, and each subsequent window gets an index value that is 1 lower than before. If you close window 3 then, you actually close the original window 4. And trying to close window 5 will trigger an error because, by that time, only three windows are left.

You won't have this problem if you close these windows in reverse order (5, 3, and then 2) because the index values are only updated for windows that have a higher value than the one you just closed. And you're not interested in those.

Creating unique button accelerators. One of the features of the DocBar is that it lets you click a button by pressing the [Alt] key along with the underlined letter or number of the button caption. This feature is controlled by the GetInitialValidCharacters function and SetUniqueAccelerators routine (see the listing at the end of the article). Because accelerators must be unique inside the user interface, the GetInitialValidCharacters function looks at the underlined characters of all items on the menu bar. Then, the function removes them from a string that contains the characters 1234567890ABCDEFGHIJKLMNOPQRSTUVWYZ. The remaining part of this string then contains the numbers and letters that are still available. (In English versions of Word, the remaining string is 1234567890BDGJKLMNPQRSUYZ. Note that the list doesn't contain an X because Word has already assigned [Alt]x to a different command.) Each time the DocBar is updated, the SetUniqueAccelerators routine tries to find a matching accelerator character for each button.

In Context

Word XP hasn't solved the problems that have plagued all previous versions. For instance, if you create or modify command bars and command-bar controls while an add-in is running, the add-in template gets "dirty," and the user is prompted to save the changes when the add-in is unloaded. The DocBar application keeps itself "clean" by using the Context class module I described in Word Add-ins As They Should Be. You'll find the full listing in the download file.

No matter how good Word is, there's always room for improvement. Thanks to its great flexibility, you can customize Word to your heart's content.

The VBA source referenced in this article is available for download. The file contains an unprotected add-in template for Word XP. A readme.txt file gives step-by-step installation instructions.

Dutchman Romke Soldaat has been a human word processor since he started as a copywriter in the 60s. After the birth of the PC, he used and customized every text-processing package on the market, and even added menus and mouse support to WordPerfect years before Corel itself introduced them. In 1988, he co-founded the Microsoft International Product Group in Dublin, Ireland, and he wrote his first macros when WinWord was still a prototype. Since his retirement in 1992, he has created a number of successful add-ons for Office. He now lives in Italy. Readers may contact Soldaat at romke@soldaat.com.

Begin Listing One - Partial listing of DocBar.bas

(Note: The application in the download file contains the full code of DocBar.bas as well as two other modules that can't be shown here because of space restrictions.)

  Sub UpdateTabs(Optional KeepSelections As Boolean)
  ' If KeepSelections is True, the state of selected
  ' button is not changed. 
   On Error Resume Next
  
   Dim nWindows As Long   ' Number of open windows. 
  nWindows = Windows.Count
  
   ' The Context class module makes sure that
   ' interface changes are restricted to the
   ' add-in that contains the code. 
   With New Context
    .Activate
     With cBarMain   ' The DocBar commandbar.
      .Visible = CBool(nWindows) 
      .Position = msoBarBottom
      .Protection = MAX_PROTECT
      nButtons = .Controls.Count - 1
       If nWindows Then
         Dim Win As Window
         ' nButtons is the current number of buttons
         ' on the DocBar. 
         If nWindows > nButtons Then
           For i = nButtons To nWindows + 2
             With .Controls.Add
              .Style = msoButtonCaption
             End With
           Next
         End If
         For i = 1 To nWindows
           Set Win = Windows(i) 
           With .Controls(i + 1) 
             .Caption = Win.Caption
            .Parameter = Win.Caption
            .Tag = Win.Index
             If Not KeepSelections Then
              .State = IIf(Win ActiveWindow, 
                 msoButtonDo n, msoButtonUp) 
             End If
             ' Set Priority to 1 if this button represents
             ' the active window, so it's always displayed. 
            .Priority = Abs(.State) 
            .OnAction = "SwitchDoc" 
            .BeginGroup = True
            .Visible = True
           End With
         Next
         ' Hide any unused buttons: 
         For i = nWindows + 1 To nButtons
           With .Controls(i + 1) 
            .Visible = False
            .Caption = vbNullString
           End With
         Next
       End If
     End With
    SetUniqueAccelerators cBarMain
     ' Tell Word that this add-in hasn't changed: 
    .CleanUp
   End With
End Sub
  
Sub SwitchDoc()
  ' This routine runs when a button is clicked. 
   On Error Resume Next
   With CommandBars.ActionControl
     Dim iWin As Long   ' Window index. 
    iWin = Val(.Tag) 
     If iWin
<= Windows.Count And iWin > 0 Then
       ' Shiftstate is a function that returns the state
       ' of the Shift and Control key. 
       Select Case ShiftState
         Case 1    ' shift: close
          Windows(iWin).Close
          UpdateTabs
         Case 2   ' ctrl: multi-select
          .State = Not .State
           If Not (ThisAddIn Is Nothing) Then
             ThisAddIn.Saved = True
           End If
         Case Else
           If iWin = ActiveWindow.Index Then
            UpdateTabs
            ShowFilePopup
           Else
            Windows(iWin).Activate
           End If
       End Select
     End If
   End With
End Sub
  
Sub CloseSelectedWindows()
   On Error Resume Next
  Tracker.SuspendUpdate = True
   With cBarMain
     ' Work backwards. 
     For i = .Controls.Count To 2 Step -1
       With .Controls(i) 
         If .State = True Then
          Windows(Val(.Tag)).Close
         End If
       End With
     Next
   End With
  Tracker.SuspendUpdate = False
  UpdateTabs
End Sub
  
' The following routines are used to create unique 
' accelerator characters for the captions on the DocBar. 
Function GetInitialValidCharacters()As String
  ' This function eliminates accelerator characters that
  ' are already used on the Menu bar, and returns the
  ' unused characters. 
   Dim sValidChars, sChar, n
  sValidChars = "1234567890ABCDEFGHIJKLMNOPQRSTUVWYZ" 
   Dim Ctl As CommandBarControl
   For Each Ctl In CommandBars.ActiveMenuBar.Controls
     With Ctl
       If Len(.Caption) Then
        n = InStr(.Caption, AMPERSAND) 
         If n > 0 And n < Len(.Caption) Then
          sChar = Mid$(.Caption, n + 1, 1) 
          sValidChars = Replace(sValidChars, sChar, _
           vbNullString, , , vbTextCompare) 
         End If
       End If
     End With
   Next
  GetInitialValidCharacters = sValidChars
End Function
  
Sub SetUniqueAccelerators(cBar As CommandBar) 
  ' This function walks through the controls
  ' on a specified commandbar, and attempts to assign
  ' a unique accelerator character to each caption. 
  
  Dim sValidChars, sChar, n, i
  Dim nStart, nStop, nStep
  sValidChars = sInitValidChars
  Dim Ctl As CommandBarControl
  
  For Each Ctl In cBar.Controls
     With Ctl
       If Len(.Caption) Then
         If InStr(.Caption, AMPERSAND) = 0 Then
           If IsNumeric(Right$(.Caption, 1)) Then
            nStart = Len(.Caption): nStop = 1: nStep = -1
           Else
            nStart = 1: nStop = Len(.Caption): nStep = 1
           End If
           For i = nStart To nStop Step nStep
            sChar = Mid$(.Caption, i, 1) 
             If InStr(1, sValidChars, sChar, _
             vbTextCompare) Then
              .Caption = Replace(.Caption, sChar, _
                AMPERSAND & sChar, 1, 1) 
              sValidChars = Replace(sValidChars, sChar, _
               vbNullString, , , vbTextCompare) 
               Exit For
             End If
           Next
           If InStr(.Caption, AMPERSAND) = 0 Then
            .Caption = AMPERSAND & .Caption
           End If
         End If
       End If
     End With
  Next
End Sub

End Listing One