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.
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
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
vbext_wt_CodeWindow = 0
vbext_wt_Designer = 1
vbext_wt_Browser = 2
vbext_wt_Watch = 3
vbext_wt_Locals = 4
vbext_wt_Immediate = 5
vbext_wt_ProjectWindow = 6
vbext_wt_PropertyWindow = 7
vbext_wt_Find = 8
vbext_wt_FindReplace = 9
vbext_wt_Toolbox = 10
vbext_wt_LinkedWindowFrame = 11
vbext_wt_MainWindow = 12
vbext_wt_Preview = 13
vbext_wt_ColorPalette = 14
vbext_wt_ToolWindow = 15
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( _
ByVal CommandBarControl As Object, _
handled As Boolean, _
CancelDefault As Boolean)
' 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
vbext_ws_Normal = 0
vbext_ws_Minimize = 1
vbext_ws_Maximize = 2
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
s = IIf(w.Caption <> "", w.Caption, _
"(none)")
s = s & " // " & WindowTypeName(w.Type) & " // "
If w.LinkedWindowFrame Is Nothing Then
s = s & "(not linked)"
Else
s = s & w.LinkedWindowFrame.Caption
End If
aiMsg.AddItem s
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.
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
vbext_cv_ProcedureView = 0
vbext_cv_FullModuleView = 1
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
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.
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()
' Set form dimensions to be out of the way
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:
Public bStopScrolling As Boolean
Public rDelayRate As Single
Also, add the Delay procedure shown in Example 7- 5.
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.