Excerpt taken from Developing Visual Basic Addins by Steven Roman, published by O'Reilly and Associates. ISBN 1-56592-527-0.

Copyright © 1999 by The Roman Press, Inc. All Rights Reserved. You may view and print this document for your own personal use only. No portion of this document may be sold or incorporated into any other document for any reason.

User Interface Objects

In this chapter, we take a look at the objects that relate to the VB IDE's user interface. As shown in Figure 7-1, the principal objects are


· CodePane and CodePanes
· CommandBar and CommandBars
· Window and Windows
We discussed CommandBars at length in an earlier chapter, so we will look at the other objects here.


Figure 7-1: Objects related to the user interface

The Window Object

Simply put, a Window object represents a window in the VB IDE. The VBIDE object model lets us do the following to a VB IDE window:


· resize it
· move it
· make it visible or invisible
· read (but not write) its caption
· dock it to a linked window frame or link it to another window.
The ActiveWindow property of the VBE object returns a reference to the currently active window (the window that has the focus when VB has the focus), as in


Dim w as Window

Set w = oVBE.ActiveWindow

The MainWindow property returns a reference to the main VB IDE window:


Dim w as Window

Set w = oVBE.MainWindow

A Window object has 12 properties and 2 methods (Close and SetFocus), all of which are shown in Table 7-1.


Table 7-1. Window object members

Caption

LinkedWindowFrame

VBE

Close

LinkedWindows

Visible

Collection

SetFocus

Width

Height

Top

WindowState

Left

Type

 

Let us take a look at some of these members.


The Type Property
The read-only Type property of a Window object describes the type of the window. Its value can be any one of the constants in the following vbext_WindowType enum:


Enum vbext_WindowType

End Enum

In a more general sense, Window objects fall into two categories, which Microsoft refers to as:


· code windows and designers (windows of type 0 or 1)
· development environment windows (the rest)
We will refer to code windows and designers as temporary windows (for reasons that will become clear in a moment) and the other types of windows as permanent windows.


The Windows Collection
The Windows collection contains all of the IDE's Window objects, both permanent and temporary. Permanent windows cannot be removed from the Windows collection. Applying the Close method just makes the window invisible; that is, it has the same effect as setting the Visible property to True.


On the other hand, temporary windows come and go from the Windows collection. The Close method can be used to remove a temporary window from the Windows collection. (Setting the Visible property of a temporary window to False simply makes the window invisible.)


Note that we do not create new windows directly; the Windows collection has no Add method. Instead, we activate a particular VB component (form or module), which will cause VB to create a Window object for that component. We will discuss the Activate method of the VBComponent object later in the book.


Nonetheless, if a window is in the Windows collection, we refer to it as an open window. This has significance only for temporary windows, since permanent windows are always open.


Access to the Windows collection is available through the Windows property of the VBE object, as in


Dim ws as Windows

Set ws = oVBE.Windows

MsgBox ws.Count

For example, to display a list of the currently open windows, that is, the windows in the Windows collection, along with their types, just place the code in Example 7-1 in the Click event of the Feature1 control in your add-in code shell.


Example 7-1. Listing currently open windows

Private Sub cbeFeature1_Click( _

' Display window captions and types

aiMsg.FirstItem "CAPTION -- TYPE"

For Each w In oVBE.Windows

s = IIf(w.Caption <> "", w.Caption, _

"(none)") & " // " & WindowTypeName(w.Type)

aiMsg.AddItem s

Next

End Sub

The WindowTypeName procedure used above simply converts a number (the value of a Window object's Type property) into a symbolic constant. Its source code is shown in Example 7-2. You can place this procedure in the standard basMain module. Incidentally, if you don't feel like typing this in, my object browser (which is on the CD that comes with this book) will do it for you!


Example 7-2. The WindowTypeName procedure

Function WindowTypeName(iValue As Integer) _

As String

Dim sName As String

Select Case iValue

Case 0: sName = "vbext_wt_CodeWindow"

Case 1: sName = "vbext_wt_Designer"

Case 2: sName = "vbext_wt_Browser"

Case 3: sName = "vbext_wt_Watch"

Case 4: sName = "vbext_wt_Locals"

Case 5: sName = "vbext_wt_Immediate"

Case 6: sName = "vbext_wt_ProjectWindow"

Case 7: sName = "vbext_wt_PropertyWindow"

Case 8: sName = "vbext_wt_Find"

Case 9: sName = "vbext_wt_FindReplace"

Case 10: sName = "vbext_wt_Toolbox"

Case 11: sName = "vbext_wt_LinkedWindowFrame"

Case 12: sName = "vbext_wt_MainWindow"

Case 13: sName = "vbext_wt_Preview"

Case 14: sName = "vbext_wt_ColorPalette"

Case 15: sName = "vbext_wt_ToolWindow"

Case Else: sName = "<invalid>"

End Select

WindowTypeName = sName

End Function

To refer to an existing window, we can use the window's caption as the index for the Item method. Thus, for instance, the window state for the Object Browser window is accessed as follows


oVBE.Windows("Object Browser").WindowState

Window Size, Position, and State
The WindowState property can be used to set or return the current state of the window. Its value comes from the following enum:


Enum vbext_WindowState

End Enum

The Window object has the usual Left, Top, Height and Width properties, which can be set in order to resize and reposition the window. Note that, contrary to what the help documentation states, these dimensions are measured in pixels, not twips.


The values of of the Left, Top, Height, and Width properties of hidden windows are set to 0.


Linked and Docked Windows
As you know, some VB IDE windows can be docked, that is attached to the edge of the main VB window. Some windows can also be linked to another window, that is attached to the side of another window.


All docked or linked windows lie inside a linked window frame, which is just a special type of window. To get the linked window frame of a window, we use the LinkedWindowFrame property. If a window is not currently linked or docked, this property will return Nothing. To illustrate, consider the following modification of the code in Example 7-1:


aiMsg.FirstItem "CAPTION // TYPE // LINKED"

For Each w In oVBE.Windows

"(none)")

s = s & "(not linked)"

s = s & w.LinkedWindowFrame.Caption

Next

Running this code when the window configuration is as shown in Figure 7-2 gives the output shown in Figure 7-3.


Figure 7-2:The VBE IDE with open windows

Figure 7-3: Linked windows in a WindowLinkFrame

From these figures we can see, for instance, that the Properties window is docked to the main VB window, the form window is not docked (or linked) and the Project and Form Layout windows are docked, but to a linked frame window that has no caption, nor can we set the caption, since it is read-only.


A window can be docked to a linked window frame window by adding the window to the linked window frame window's LinkedWindows collection, using the Add method. For example, the following code (which could be placed in the usual Click event) will cause the Form Layout window to be docked against the main VB IDE window:


For Each w In oVBE.Windows

If w.Caption = "Form Layout" Then

oVBE.MainWindow.LinkedWindows.Add w

End If

Next

Similarly, the Remove method of the LinkedWindows collection will undock the window, but not destroy it.


The problem with docking windows programmatically is that there does not seem to be any way to control the location of newly docked windows within the linked window frame. Thus, it may simply be better to let the user do his or her own docking with the mouse.


The CodePane Object

As you may know, a code window can be split into two separate code panes, using the split bar that lies above the vertical scroll bar in a code window. Thus, a code window may contain one or two code panes.


Exactly one of the code panes in a code window is active. It is the code pane that contains the cursor when the corresponding window has the input focus. The data in the Objects and Procedures list boxes located above the code window reflect the information in the active code pane.


The VBE object has a read-only ActiveCodePane property that returns the active CodePane object. Since the property is read-only, it cannot be used to set the active code pane, however.


Note that the F6 function key will switch between code panes in a two-pane window; so the active code pane can be changed in this way using the SendKeys statement. As we will see, the Show method can be used to cycle the active code pane through the entire CodePanes collection.


The CodePanes Collection
The VBE object also has a CodePanes property that returns a CodePanes collection. This collection contains all of the open CodePane objects, that is, all of the code panes that are associated with open code windows (whether visible or not). These are the code panes associated with the code windows that are currently in the Windows collection.


The CodePanes collection is rather boring, in the sense that we cannot add or delete items from it. All we can do is access individual items by index number using the Item method and get a count of the total number of items in the collection (using the Count property).


Evidently, the CodePane objects that are associated with a code Window object are not directly accessible from that Window object. (We might have expected a CodePanes collection for each Window object, containing one or two code panes.)


To find the CodePane object(s) that are associated with a given Window object, we must work backwards. That is, we can iterate through the CodePanes collection, checking the Window property (which returns the associated Window object) as we go.


CodePane Properties and Methods
The properties and methods of the CodePane object are given in Table 7-2.


Table 7-2. CodePane Object Members

CodeModule

GetSelection

VBE

CodePaneView

SetSelection

Window

Collection

Show

 

CountOfVisibleLines

TopLine

 

The CodeModule Property
We will discuss CodeModule objects in a later chapter. Suffice it to say now that these important objects represent the actual code within a code pane.


The CodePaneView Property
This property returns a value that indicates whether the code pane is in Procedure view or Full Module view. In particular, it takes on one of the values in the following enum:


Enum vbext_CodePaneview

End Enum

Unfortunately, this property is read-only, so we cannot change the view state by setting this value. (Drat!)


The CountOfVisibleLines Property
This read-only property returns a Long that gives the maximum number of visible lines possible in the code pane. This is the number of lines that would be visible if the code pane was completely full. (Thus, this property has nothing to do with the current code in the code pane.)


The TopLine Property
This property returns or sets (as a Long) the line number of the line currently at the top of the code pane.


To experiment with this property, you can place the following line in the usual Click event of your AddInShell add-in:


oVBE.ActiveCodePane.TopLine = _

InputBox("Enter top line number.")

There are a few points that should be made with respect to this property. First, the "lowest" view possible is when the last line in the code pane is at the bottom of the window not the top (assuming there is enough code to fill the window). This view is obtained, for example, if we set the TopLine property to a number that is greater than the actual number of lines in the code pane. Because of this, the actual value of the TopLine property may be less than the value you assign to it. If it's important to know which line is at the top of a pane, you should retrieve the value of the TopLine property immediately after you assign a value to it.


Second, the TopLine setting is made relative to all of the code in the code pane, not just the visible code in the current procedure. Thus, line 1 is the first line in the Declarations section, even when the code pane is in Procedure view.


Finally, setting the TopLine property does not move the cursor. Thus, for instance, if the cursor is on line 100, we set the TopLine property to 1 and then hit the down arrow key, VB will jump to line 101! To avoid this, we must use the SetSelection method (discussed below) to move the cursor after setting the TopLine property.


The Show Method
The Show method makes the code pane active, that is, it gives the code pane the focus. This makes it easy to cycle through all of the currently open code panes:


Static cCP As Integer

On Error Resume Next

' Validation

If cCP = 0 Then cCP = 1

If cCP > oVBE.CodePanes.Count Then cCP = 1

oVBE.CodePanes(cCP).Show

cCP = cCP + 1

The GetSelection and SetSelection Methods
The GetSelection and SetSelection methods get and set the currently selected code within a code pane. Syntax for the SetSelection method is


CodePaneObject.SetSelection(startline, startcol,_

endline, endcol)

where each of the parameters is a Long that specifies the starting or ending line or column of the selection.


Note that startcol pertains only to the first line of the selection and endcol pertains only to the last line of the selection. Put another way, all lines in the selection except possibly the first and last are selected in their entirety.


Note also that the character at column endcol of line endline is not included in the selection.


Thus, for instance, the code


oVBE.ActiveCodePane.SetSelection 1, 4, 6, 3

selects line 1 starting in column 4, all of lines 2 through 5 and the first 2 character positions in line 6.


The GetSelection method is similar in syntax


CodePaneObject.GetSelection(startline, startcol, _

endline, endcol)

The parameters have the same meaning as in the SetSelection method but are so-called out parameters (or arguments passed by reference to the method) because VB fills them with the correct values. However, we must declare the variables for VB, as in


Dim x As Long

Dim y As Long

Dim z As Long

Dim w As Long

oVBE.ActiveCodePane.GetSelection x, y, z, w

Example: Clearing the Debug Window

One of the most glaring (and trivial) omissions to the VB object model is a Clear method for the Debug object. How nice it would be to be able to clear the Immediate window of old text before issuing a Debug.Print statement.


Although there is nothing we can do to supply a Clear method for the Debug object, we can at least create a simple add-in that makes it easier to clear the immediate window from design mode. The code in Example 7-3, which can be placed in a menu item's Click event, will do the trick.


Example 7-3. Code to clear the Immediate window

Dim winActive As VBIDE.Window

Dim winImm As VBIDE.Window

Set winImm = gVBInst.Windows("Immediate")

If winImm Is Nothing Then Exit Sub

' Save the currently active window

Set winActive = gVBInst.ActiveWindow

'Do not clear if Window Not Visible

If winImm.Visible = True Then

winImm.SetFocus

SendKeys "^({Home})", True

SendKeys "^(+({End}))", True

SendKeys "{Del}", True

End If

' Return to active window

winActive.SetFocus

Set winImm = Nothing

Note that this code uses SendKeys statements to clear the Immediate window. Therefore, it must be tested as a compiled DLL so it will run in-process.


Example: Scrolling a Code Pane

The TopLine property is all we need to build a simple scrolling feature that scrolls a code pane. The steps are as follows:


1_ Add a new form to your add-in, say frmScroll. The code for this form is shown in Example 7-4.

Example 7-4. Code for the frmScroll form

Private Sub Form_Activate()

Me.Top = 0

Me.Left = 0

Me.Width = 5000

Me.Height = 10

End Sub

Private Sub Form_KeyDown(KeyCode As Integer, _

Shift As Integer)

Select Case KeyCode

Case vbKeyEscape

bStopScrolling = True

Case vbKeyUp

' Subtract 0.02 to delay rate

rDelayRate = rDelayRate - 0.02

' Validate

If rDelayRate <= 0.02 Then

rDelayRate = 0.02

Beep

End If

Case vbKeyDown

' Add 0.02 to delay rate

rDelayRate = rDelayRate + 0.02

End Select

Me.Caption = "Scrolling ... " & rDelayRate

End Sub

Private Sub Form_Load()

Me.KeyPreview = True

Me.Caption = "Scrolling ... " & rDelayRate

End Sub

1_ In the basMain standard module, add the following two public declarations:

Example 7-5. The Delay procedure in the basMain code module

Sub Delay(rTime As Single)

'Delay rTime seconds (min=.01, max=300)

Dim OldTime As Variant

'Safty net

If rTime < 0.01 Or rTime > 300 Then rTime = 1

OldTime = Timer

Do

DoEvents

Loop Until Timer - OldTime >= rTime

End Sub

1_ In the Click event for cbcFeature1, add a call to ScrollCodePane, and then the ScrollCodePane procedure shown in Example 7-6 to the Connect class module.

Example 7-6. The ScrollCodePane procedure

Sub ScrollCodePane()

Dim lPrevLine As Long

Dim lSafe As Long

rDelayRate = 0.1

frmScroll.Show

oVBE.ActiveCodePane.TopLine = 1

lSafe = 0

lPrevLine = -1

Do

lSafe = lSafe + 1

' Check for Escape key

If bStopScrolling Then

bStopScrolling = False

Exit Do

End If

' Save this to check for end of code pane

lPrevLine = oVBE.ActiveCodePane.TopLine

' Scroll one line

oVBE.ActiveCodePane.TopLine = _

oVBE.ActiveCodePane.TopLine + 1

' If no more lines, get out

If oVBE.ActiveCodePane.TopLine = lPrevLine _

Then Exit Do

' Wait

Delay rDelayRate

Loop Until lSafe > 10000

Unload frmScroll

End Sub

Note that we can increase or decrease scrolling speed using the up and down arrow keys and stop scrolling by hitting the Escape key.