Windows of Opportunity

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.

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

WORDfixer

Experienced Programmer Word | VBA

Fixing Word's "Arrange All" Feature

By Romke Soldaat

Long before Microsoft introduced the Office suite, there were individual Microsoft applications, created by individual teams that each had individual views of how an application should interface with its user. During the past decade or so, our friends in Redmond have made considerable progress toward consolidating many features and unifying these applications, to give them the same look and feel. But Microsoft hasn't gone far enough. Even in the most recent Office package, Word, Excel, PowerPoint, and Access have many annoying differences.

Take for example the options to arrange document windows on the Window menu. Both Access and Excel offer no less than four ways to display multiple windows inside the application's workspace (see FIGURE 1).

Aa155742.vba200111rs_f_image001(en-us,office.10).gif
FIGURE 1A: Arranging windows in Access.

Aa155742.vba200111rs_f_image002(en-us,office.10).gif
FIGURE 1B: Arranging windows in Excel.

But the makers of Word seem to believe you don't want those choices and are happy enough with just an Arrange All option. Don't ask me to explain the reasons behind these discrepancies. But, if you're asking me if we have to live with them, the answer is simple. Of course not!

The accompanying download file contains a ready-to-use Word XP add-in (sorry, it won't work with Word 2000's Single Document Interface). It gives Word not only the window-arrangement options you've come to love in Access and Excel, but also some features you don't find in any other Office application. If you're an Office programmer, I promise you some interesting discoveries later in the article.

Using the Add-in

Once you've installed the add-in template, you have two new options on the Window menu (see FIGURE 2). Each offers a number of ways to organize your document windows.

Aa155742.vba200111rs_f_image004(en-us,office.10).jpg
FIGURE 2A: The enhanced Window menu, created with the accompanying download template.

Aa155742.vba200111rs_f_image006(en-us,office.10).jpg
FIGURE 2B: The enhanced Window menu, created with the accompanying download template.

In addition to Word's Arrange All option (which behaves as usual), you can choose the following window arrangements:

  • Tile Windows - Windows are arranged in a tiled layout. If the number of windows is odd, the active document window gets twice the space of the other windows.
  • Arrange Horizontally - Windows maintain their full width and are arranged from top to bottom.
  • Arrange Vertically - Windows maintain their full height and are arranged from left to right.
  • Cascade - Windows overlap each other, with the exception of the title bar. The active document is in the foreground.

(In all the above arrangements, any minimized window is not included in the arrangement. One row of minimized windows remains visible at the bottom of the screen.)

  • Arrange Current Document - This option lets you place the active document window next to or above any other open document. If the document you select has the same name as the active document, Word opens a new window for it, and the two windows are arranged together. Any other open window is hidden behind these two, giving you the maximum space to compare them or drag and drop items between them.

All menu options also have a unique Full Screen variation. If you choose any of these additional options, Word switches into Full Screen mode before the windows are arranged, so you have more space for your documents.

If you're a techie, you may find some interesting background in the following section. If not, download the add-in and enjoy Word even more than before.

The Inside Story

At first sight, arranging windows within the available workspace looks like a simple task. All you need to do is find out how much screen width and height is available, and use that information to calculate the maximum space each document window can occupy. Then, you can use the Top and Left properties of the Window object to maneuver each window into its slot.

That's what I thought when I started this project. But things aren't always as straightforward as they seem.

How do you find out how much space is available within the application window? Microsoft tells you to look at the UsableWidth and UsableHeight properties of the Application object. These properties are supposed to return the maximum width and height (in points), to which you can set the width and height of a document window. Forget it! The values you get from these properties include the space that's already occupied by docked command bars, task panes, and anything else that's stuck to the top, left, right, and bottom of the application window. With a bit of bad luck, arranging windows inside this space makes half of them invisible. Call me cynical, but I think these properties should be renamed UnusableWidth and UnusableHeight.

The only reliable way I've found to determine what space is available is by first maximizing a document. Word then blows the document window up as far as it goes, removing its caption and borders and obscuring all other documents. At that point, the active window fills the entire workspace, and you can look at its Height and Width properties to calculate the available space. Or can you? Well, not entirely.

Bogus Results

If you use the Height and Width properties of a maximized document window to calculate the available space for a window arrangement, you get values that are too high, and any window on the right or bottom of the workspace is only partly visible. To find out why, take a look at the Left and Top properties of a maximized window. You'll discover that each of these values comes back as a negative number.

How is that possible? My best guess is that the Word developers are cheating. When a document is maximized, the title bar and borders of the document window are not removed at all. Instead, the window is pushed up to hide the title bar, shifted to the left to hide the left border, and then stretched to the right and bottom to hide the remaining borders.

The result is that the Height property includes the size of an invisible title bar and bottom border, and the Width property includes two vertical borders. (The actual values of the caption and border size depend on the user's Display Properties settings. But, in a default Windows configuration, the Top property of a maximized window comes back as -18, and the Left property as -4.)

To get real UsableHeight and UsableWidth values, use these calculations:

  Dim UsableHeight As Long, UsableWidth As Long With ActiveWindow
  .WindowState = wdWindowStateMaximize
  ' Use additions, because Top and Left are negative. 
  ' Subtract the caption height and border width. 
  UsableHeight = .Height + .Top + .Left
  ' Subtract twice the border width. 
  UsableWidth = .Width + (2 * .Left) 
End With

In Listing One, you'll notice the application uses a slightly different calculation. Word's own Arrange All feature leaves space available at the bottom of the application window if one or more document windows are minimized, so you can still activate them even after the other windows have been arranged. My application follows the same principle, so it reduces the height by a second Top value, if it finds any minimized windows.

Screen Locking the Windows Way

Resizing and moving windows is a fairly quick process, but it still causes some annoying screen flicker, so it would be neater to hide these activities until the shuffling is done. Theoretically, you should be able to use the ScreenUpdating property of the Application object. According to the VBA documentation, this property "controls most display changes on the monitor while a procedure is running."

The bad news is that ScreenUpdating does not hide changes in the size and position of document windows. The good news is that you achieve the desired effect with the Windows LockWindowUpdate API function. This function takes a single parameter: the handle of the window in which you want to disable any drawing. To obtain the handle of Word's application window, you can call the GetActiveWindow API function. The following instruction disables screen updating in the calling application:

  LockWindowUpdate GetActiveWindow

To re-enable screen updating, specify a zero value as the parameter. The following instructions have the same effect:

  LockWindowUpdate 0
LockWindowUpdate False

Note however, that LockWindowUpdate can be a dangerous function. If a VBA macro crashes while the host application is frozen, recovery isn't possible. Closing and restarting the application is your only option.

The cWinArrange Class

The application in the download file uses a class module to take care of everything that calculates, resizes, and moves document windows. Study the source code in cWinArrange.cls, shown in Listing One at the end of this article or in the download file. FIGURE 3 shows the properties and the method exposed by the class.

The cWinArrange class

ArrangeStyle property

Long. Determines the layout of the arranged windows. Can be one of the following constants: wdTiled, wdCascade, wdLeftToRight, wdTopToBottom, wdTwoDocsTopToBottom, wdTwoDocsLeftToRight. If not specified, wdTiled is assumed.

FullScreen property

Boolean. True to arrange document windows in full screen mode, False to keep the application borders and toolbars visible. Default value is False.

Document1, Document2 properties

Strings. The caption text of the two windows to be arranged together. Only valid if ArrangeStyle is set to wdTwoDocsTopToBottom or wdTwoDocsLeftToRight. If both properties contain the same string, or if Document2 is not specified, a new window is opened for Document1, and both windows are arranged.

Execute method

Performs the arrangement.

FIGURE 3: The properties and the method exposed by the cWinArrange** object.**

The usage is very simple. Here are two examples:

  ' Arranging all windows in tiled layout.
With New cWinArrange
  .ArrangeStyle = wdTiled
  .Execute
End With
  ' Arranging the current document with a copy of itself
' in horizontal layout, with Word switched to Full Screen. 
With New cWinArrange
  .ArrangeStyle = wdTwoDocsTopToBottom
  .Document1 = ActiveDocument
  .FullScreen = True
  .Execute
End With

Bonus: Button Icons the XP Way

One interesting novelty in Office XP is the option to assign pictures to command bar buttons without using the FaceId property. Instead, you can use the new Picture and Mask properties and set them to bitmap files. This lets you create button icons outside Office and load them when your application starts. I won't go into more detail in the context of this article, but I'll come back to it in the future. If you can't wait, look for the Picture property of the CommandBarButton object in VBA Help. The download file contains the bitmaps used to create the icons shown in FIGURE 2, and the code that assigns them to the menu items.

Conclusion

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. In this article, you discovered how you can add multiple window arrangements to Word that even beat the ones you find in Access and Excel. In upcoming articles in this series, you'll find many more suggestions to put Word on steroids.

The VBA source referenced in this article is available for download, which contains an unprotected Word XP add-in template. 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, adding menus and mouse support to WordPerfect years before WP itself introduced them. In 1988 he co-founded the Microsoft International Product Group in Dublin, Ireland, and 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. Romke can be contacted at romke@soldaat.com.

Begin Listing One - cWinArrange.cls

(Note: The application in the accompanying file contains two more modules that can't be shown here because of space restrictions.)

  Option Explicit
  
Enum WdArrangeStyle
  wdTiled = 0
   ' wdIcons = 1  ' we don't use this constant
   ' the following constants are not 'official'. 
  wdCascade = 2
  wdLeftToRight = 3   ' aka vertical
  wdTopToBottom = 4   ' aka horizontal
  wdTwoDocsTopToBottom = 5
  wdTwoDocsLeftToRight = 6
End Enum
  
' Public properties. 
Public ArrangeStyle As WdArrangeStyle
Public FullScreen As Boolean
Public Document1 As String
Public Document2 As String
  
Private CurWin As Window
Private Declare Function LockWindowUpdate Lib "user32" _
  (ByVal hwndLock As Long) As Long
Private Declare Function GetActiveWindow Lib "user32" _
  ()As Long
  
Sub Execute()
   If Windows.Count = 0 Then Exit Sub
  
   Dim iNumWindowsToArrange As Long
   Dim fHasMinimizedWindows As Boolean
  
   If ArrangeStyle >= wdTwoDocsTopToBottom Then
    iNumWindowsToArrange = 2
   Else
     Dim Doc As Document, Win As Window
     For Each Doc In Documents
       For Each Win In Doc.Windows
         If Win.WindowState = wdWindowStateMinimize Then
           ' Set a flag if we find a minimized window. 
          fHasMinimizedWindows = True
         Else
           ' Count the windows that are not minimized. 
          iNumWindowsToArrange = iNumWindowsToArrange + 1
         End If
       Next
     Next
   End If
  
   If iNumWindowsToArrange Then
    ActiveWindow.View.FullScreen = FullScreen
  
     Dim iUsableWidth As Long, iUsableHeight As Long
     Dim iCaptionHeight As Long, iBorderWidth As Long
     Dim iCurState As WdWindowState
  
     ' Get metrics based on active window. 
     With ActiveWindow
      iCurState = .WindowState
       ' Blow up to maximum size, so it covers any 
      ' minimized windows. 
      .WindowState = wdWindowStateMaximize
       ' The Top and Left properties are negative numbers. 
      iCaptionHeight = Abs(.Top) 
      iBorderWidth = Abs(.Left) 
       ' Calculate real available space. 
       ' Subtract twice the border width. 
      iUsableWidth = .Width - 2 * iBorderWidth
       ' Subtract the border width, and once or twice the
       ' caption height, and take a row of minimized windows 
      ' into account. 
      iUsableHeight = .Height - iBorderWidth - _
        IIf(fHasMinimizedWindows, 2, 1) * iCaptionHeight
       ' Reset original window. 
      .WindowState = iCurState
     End With
   Else
     Exit Sub
   End If
  
   Select Case ArrangeStyle
     Case wdTwoDocsTopToBottom
       If Len(Document2) = 0 Then Document2 = Document1
       If IsValidWindow(Document1) And _
       IsValidWindow(Document2) Then
        ArrangeTwoWindows _
         Left:=0, _
         Top:=iUsableHeight / 2, _
         Width:=iUsableWidth, _
         Height:=iUsableHeight / 2
       End If
     Case wdTwoDocsLeftToRight
       If Len(Document2) = 0 Then Document2 = Document1
       If IsValidWindow(Document1) And _
       IsValidWindow(Document2) Then
         ArrangeTwoWindows _
          Left:=iUsableWidth / 2, _
           Top:=0, _
          Width:=iUsableWidth / 2, _
          Height:=iUsableHeight
       End If
     Case wdLeftToRight
      ArrangeAllWindows _
       Left:=iUsableWidth / iNumWindowsToArrange, _
       Top:=0, _
       Width:=iUsableWidth / iNumWindowsToArrange, _
       Height:=iUsableHeight
     Case wdTopToBottom
      ArrangeAllWindows _
       Left:=0, _
       Top:=iUsableHeight / iNumWindowsToArrange, _
       Width:=iUsableWidth, _
       Height:=iUsableHeight / iNumWindowsToArrange
     Case wdCascade
     ' The window overlap is based on the following values, 
     ' which are multiplied with the iCaptionHeight value. 
       Const LEFT_OVERLAP As Single = 0.5
       Const TOP_OVERLAP As Single = 0.8
      ArrangeAllWindows _
       Left:=iCaptionHeight * LEFT_OVERLAP, _
       Top:=iCaptionHeight * TOP_OVERLAP, _
       Width:=iUsableWidth - iCaptionHeight * _
       LEFT_OVERLAP * (iNumWindowsToArrange - 1), _
       Height:=iUsableHeight-(iNumWindowsToArrange - 1) * _
        (iCaptionHeight * TOP_OVERLAP) 
     Case wdTiled
       Select Case iNumWindowsToArrange
         Case 2 ' same as wdLeftToRight
          ArrangeAllWindows _
           Left:=iUsableWidth / iNumWindowsToArrange, _
           Top:=0, _
           Width:=iUsableWidth / iNumWindowsToArrange, _
           Height:=iUsableHeight
         Case 3 To 5
          TileWindows _
           NumWindows:=iNumWindowsToArrange, _
           MaxWidth:=iUsableWidth, _
           MaxHeight:=iUsableHeight
         Case Else ' Let Word do the job.
          Windows.Arrange wdTiled
       End Select
     Case Else
   End Select
End Sub
  
Private Sub ArrangeAllWindows( _
  ByVal Left As Long, ByVal Top As Long, _
  ByVal Width As Long, ByVal Height As Long)
  
   Dim iCount As Long
   Dim Doc As Document
   Dim Win As Window
  
   If ArrangeStyle <> wdCascade Then
    MoveWindow CurWin, 0, 0, Width, Height
   Else
    iCount = -1
   End If
  
   For Each Doc In Documents
     For Each Win In Doc.Windows
       If Win <> CurWin Then
         With Win
           If .WindowState <> wdWindowStateMinimize Then
            iCount = iCount + 1
            MoveWindow Win, _
             iCount * Left, iCount * Top, Width, Height
           End If
         End With
       End If
     Next
   Next
  
   If ArrangeStyle = wdCascade Then
    iCount = iCount + 1
    MoveWindow CurWin, _
      iCount * Left, iCount * Top, Width, Height
   End If
End Sub
  
Private Sub ArrangeTwoWindows( _
  ByVal Left As Long, ByVal Top As Long, _
  ByVal Width As Long, ByVal Height As Long)
  
   Dim Win(1) As Window
   Set Win(0) = Windows(Document1) 
   Set Win(1) = Windows(Document2) 
   If Win(0) = Win(1) Then Set
Win(1) = Win(0).NewWindow
   Set CurWin = Win(0) 
  
  MoveWindow Win(0), 0, 0, Width, Height
  MoveWindow Win(1), Left, Top, Width, Height
End Sub
  
Private Sub TileWindows( _
  ByVal NumWindows As Long, _
  ByVal MaxWidth As Long, _
  ByVal MaxHeight As Long)
  
   Dim Doc As Document, Win As Window
   Dim WinWidth As Long, WinHeight As Long
   Dim iCount As Long
   Set CurWin = ActiveWindow
  
  Select Case NumWindows
    Case 3
      WinWidth = MaxWidth / 2
      WinHeight = MaxHeight / 2
      MoveWindow CurWin, 0, 0, MaxWidth / 2, MaxHeight
       For Each Doc In Documents
         For Each Win In Doc.Windows
           If Win <> CurWin Then
             With Win
               If .WindowState <> wdWindowStateMinimize Then
                 Select Case iCount
                   Case 0
                    MoveWindow Win, WinWidth, 0, WinWidth, _
                     WinHeight
                   Case 1
                    MoveWindow Win, WinWidth, WinHeight, _
                     WinWidth, WinHeight
                   Case Else: End Select
                iCount = iCount + 1
               End If
             End With
           End If
         Next
       Next
  Case 4
    WinWidth = MaxWidth / 2
    WinHeight = MaxHeight / 2
    MoveWindow CurWin, 0, 0, WinWidth, WinHeight
     For Each Doc In Documents
       For Each Win In Doc.Windows
         With Win
           If Win <> CurWin Then
             If .WindowState <> wdWindowStateMinimize Then
               Select Case iCount
               Case 0
                MoveWindow Win, WinWidth, 0, WinWidth, _
                 WinHeight
               Case 1
                MoveWindow Win, 0, WinHeight, WinWidth, _
                 WinHeight
               Case 2
                 MoveWindow Win, WinWidth, WinHeight, _
                 WinWidth, WinHeight
               Case Else: End Select
              iCount = iCount + 1
             End If
           End If
         End With
       Next
     Next
  Case 5
    WinWidth = MaxWidth / 3
    WinHeight = MaxHeight / 2
    MoveWindow CurWin, 0, 0, WinWidth, MaxHeight
     For Each Doc In Documents
       For Each Win In Doc.Windows
         If Win <> CurWin Then
           With Win
             If .WindowState <> wdWindowStateMinimize Then
               Select Case iCount
                 Case 0
                  MoveWindow Win, WinWidth, 0, WinWidth, _
                   WinHeight
                 Case 1
                  MoveWindow Win, WinWidth * 2, 0, _
                   WinWidth, WinHeight
                 Case 2
                  MoveWindow Win, WinWidth, WinHeight, _
                   WinWidth, WinHeight
                 Case 3
                  MoveWindow Win, WinWidth * 2, _
                   WinHeight, WinWidth, WinHeight
                 Case Else: End Select
              iCount = iCount + 1
             End If
           End With
         End If
       Next
     Next
    Case Else: End Select
  CurWin.Activate
End Sub
  
Private Function IsValidWindow(ByVal Caption As String) _
  As Boolean
   On Error Resume Next
   Dim Win As Window
   Set Win = Windows(Caption) 
  IsValidWindow = (Err = 0) 
End Function
  
Private Sub Class_Initialize()
  If ActiveWindow.WindowState <> wdWindowStateMinimize Then
     Set CurWinv = ActiveWindow
  Else
     Set CurWin = FirstAvailableWindow
  End If
  If Not (CurWin Is Nothing) Then CurWin.Activate
  LockWindowUpdate GetActiveWindow
End Sub
  
Private Sub Class_Terminate()
   If Not (CurWin Is Nothing) Then CurWin.Activate
   Set CurWin = Nothing
  LockWindowUpdate False
End Sub
  
Private Function FirstAvailableWindow()As Window
   Dim Doc As Document, Win As Window
   For Each Doc In Documents
     For Each Win In Doc.Windows
       If Win.WindowState <> wdWindowStateMinimize Then
         Set FirstAvailableWindow = Win
         Exit Function
       End If
     Next
   Next
End Function
  
Private Sub MoveWindow(Win As Window, _
  ByVal Left As Long, ByVal Top As Long, _
  ByVal Width As Long, ByVal Height As Long)
   With Win
    .Activate
    .WindowState = wdWindowStateNormal
    .Top = Top
    .Left = Left
    .Height = Height
    .Width = Width
   End With
End Sub

End Listing One

Copyright © 2002 Informant Communications Group. All Rights Reserved. • Site Use Agreement • Send feedback to the Webmaster • Important information about privacy