Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 41 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
41
Dung lượng
274,82 KB
Nội dung
After calls to LineDown and StartOfLine, the EditPoint changed, but the original highlighted text in the document did not. By calling CreateEditPoint, you are effectively creating an object that is disconnected from the original selection. As the macro proceeds, it compares the start EditPoint object to the end Edit- Point object, using the LessThan method. This method compares the positions within the document, and if the object is earlier in the document than the parameter (that is, if the start point is less than the end point), the function returns True. (You also have at your disposal GreaterThan and EqualTo functions, which are members of EditPoint, TextPoint, and VirtualPoint.) There is really little difference between a TextPoint and a VirtualPoint, except that a VirtualPoint can appear at the very end of a line, representing the whitespace to the right of a line. Working with Multiple Windows and Panes In Visual Studio .NET, the IDE user can have multiple windows open, each containing a copy of the same source code file. Additionally, the user can split each window verti- cally into two panes, allowing two separate views of the same file. Changes to a docu- ment affect not only the current pane or window, but all panes and windows containing the same document. Such changes include undo and redo actions. How- ever, each pane and window maintains its own insertion point and highlighted text. You can highlight text in one pane, while highlighting separate text in another pane or window for the same document. Similarly, you can have the insertion point at one place in one pane or window and in another place within the same document but in another pane or window. When you have multiple windows showing the same document, the Document.Selection object corresponds to the window that is active. Further, if the windows have multiple panes, the Document.Selection object will correspond to the pane that is active. The following macro creates a new window for the currently active document. (Note, however, that the NewWindow function is not available for VB.NET code files; thus, this macro will function only for C++ and C# code files. Running it for a VB.NET code file will generate a “Not implemented” error message.) In this macro I included a Try/Catch block, because you cannot call NewWindow for every type of window. Sub CreateNewWindow() Dim doc As Document Try doc = DTE.ActiveDocument doc.NewWindow() Catch e As System.Exception Programming the Document and User Interface Objects 221 MsgBox(e.Message) End Try End Sub The Document object includes a Windows property that contains a collection of Window objects. Normally this collection consists of only one Window object, repre- senting the single window containing the document. But if you create a second win- dow using the preceding macro, then you will find two Window objects in the Windows collection. When you create a second window for a single document, you will see either two tabs, if you’re running in tabbed mode, or two windows, if you’re running in MDI mode. (The default is tabbed mode.) However, even though you have two Window objects, you still have only a single Document object. You can see this by running the following macro: Sub ListDocumentsWithCount() VBMacroUtilities.Setup(DTE) VBMacroUtilities.Clear() Dim d As Document For Each d In DTE.Documents VBMacroUtilities.Print(d.FullName & “ “ & d.Windows.Count) Next End Sub If you run the previous macro, CreateNewWindow, and then run this macro, List- DocumentsWithCount, you will see that you still have only a single Document object for the two windows. The ListDocumentsWithCount macro prints out the name of each document, along with the Windows.Count property, which tells how many Win- dow objects are in the Windows collection. You can also split a single window into two panes. To do this, choose Window➪Split (and then Window➪Remove Split to remerge the window). To find out the panes, start with a Window object. From there, you obtain a Tex- tWindow object, which includes a Panes collection. The following macro shows you how to do this: Sub ListPanes() VBMacroUtilities.Setup(DTE) VBMacroUtilities.Clear() Dim tw As TextWindow Dim panes As TextPanes Dim pane As TextPane tw = ActiveDocument.Windows.Item(1).Object panes = tw.Panes For Each pane In panes VBMacroUtilities.Print(pane.Height) Next End Sub 222 Chapter 10 This macro operates on the first window of the active document. Make sure your active document is a text window (such as a source code file); otherwise, you’ll get an exception. You also might try splitting the window into two panes to see the results. The macro also prints out the height of each pane. The Pane object has both a width and a height, and, interestingly, the values of the width and height are in terms of char- acters, not pixels. Thus, the width is the number of columns the editor window’s pane can show, and the height is the number of rows it can show. Navigating the User Interface Hierarchy Many of the window objects in the IDE contain a hierarchy of some sort. For example, the Solution Explorer contains a hierarchy that lists, first, the projects, and under each project the project items. Inside the EnvDTE namespace is a class called UIHierarchy, which contains the elements in a hierarchy. To obtain the UIHierarchy instance for a particular window (such as for the Solution Explorer window), first access the Window object for the window, and from the Window object access the Object property. The Object property in such windows is an instance of UIHierarchy. If the window is not one that shows a sort of hierarchy (for example, the Output window does not), then the Object property is not an instance of UIHierarchy. Therefore, before you can use the Object property, you must check its type. When I was doing research for this chapter, I first checked the GUID of the Object property, expecting it to be the same each time a UIHierarchy was present. That would have made sense, since the UIHierarchy is a class. But I was wrong. The Object property does, in fact, refer to a different type of COM object for each window, thus the Solution Explorer window and the Server Explorer window, both of which have a UIHierar- chy available, have different GUIDs for their Object properties. So instead of check- ing the GUID, the correct way is to call the VB.NET function TypeName. Interestingly, even though TypeName returns the correct string (such as “UIHierar- chy”), you cannot call typeof, as typeof will return a COM type, but not the name UIHierarchy. Therefore, in my code I am using TypeName. Before using the UIHierarchy object, make sure the Object property for the window is not set to Nothing. If the window has no hierarchy (or if the window can support a hierarchy but does not currently have a hierarchy showing), then the Object property will be set to Nothing, rather than to an instance of UIHierarchy. The following macro gets the active window, then the Object property, and makes sure the Object property is not null (or, in VB.NET terminology, Nothing); next the code gets the type name of the Object property. If the type name is UIHierarchy, the code traverses through the hierarchy by calling a recursive function. Here’s the macro: Sub Traverse(ByVal indent As Integer, ByVal items As UIHierarchyItems) Dim subitem As UIHierarchyItem For Each subitem In items VBMacroUtilities.Print(“ “.PadLeft(indent * 4) & _ Programming the Document and User Interface Objects 223 subitem.Name & “ “ & subitem.IsSelected) If Not subitem.UIHierarchyItems Is Nothing Then Traverse(indent + 1, subitem.UIHierarchyItems) End If Next End Sub Sub UIHierTest() Dim window As Window VBMacroUtilities.Setup(DTE) window = DTE.ActiveWindow If Not window.Object Is Nothing Then If TypeName(window.Object) = “UIHierarchy” Then Dim UIH As UIHierarchy = window.Object VBMacroUtilities.Print(window.Caption & “ “ & _ window.ObjectKind) Traverse(0, UIH.UIHierarchyItems) Else VBMacroUtilities.Print(“ Not a UIHierarchy type: “ _ & TypeName(DTE.ActiveWindow.Object)) End If Else VBMacroUtilities.Print(“ No hierarchy is available.”) End If End Sub This macro is primarily for informational purposes, as it does not modify anything in the IDE. To try out the macro, make sure you have a solution open. Then, open the Solution Explorer window, making sure it’s active (that is, its title bar is showing as the active window). Run the macro by double-clicking the macro name in the Macro Explorer. When you click on a window and then double-click a macro in the Macro Explorer, the DTE.CurrentWindow object will still contain a reference to the window you clicked prior to running a macro. This is an advantage, as it allows you to select a window and then run a macro without causing the DTE object to treat the Macro Explorer as the active window. If you want to traverse the Macro Explorer’s hierarchy, instead of using the CurrentWindow property, you can use DTE.Windows.Item(Constants. vsWindowKindMacroExplorer). The macro will traverse through the hierarchy, starting at the top, listing the names in the tree. Here’s a sample output: Solution Explorer - Solution Items {3AE79031-E1BC-11D0-8F78-00A0C9110057} MyProjects False 224 Chapter 10 TEAMFLY Team-Fly ® CSharpWinApp1 False References False System False System.Data False System.Drawing False System.Windows.Forms False System.XML False App.ico False AssemblyInfo.cs False Form1.cs True test.xml False XMLSchema1.xsd False DatabaseProject1 False Change Scripts False Queries False Database References False FOXPRO.C:\dev\Projects\BOOKS.DBC False The first line gives the name of the window (just to make sure I clicked the right one) and then the GUID of the Object (stored in the ObjectKind property). I printed out this GUID so you can see that the GUID is different for different windows. Next is the name of the solution, which corresponds to the first item in the tree. This solution has two projects in it, a C# program and a database application for a FoxPro database. I listed the names of each project, along with the items inside the project. Now you’ll notice that the hierarchy contains an item for every item in the Solution Explorer, including folders. In addition to the name of each item in the tree, the macro prints out the value of the item’s IsSelected property. This is where your macro can determine whether the user has clicked on the item. You can see in my list that the C# form file, Form1.cs, is the selected item. If you select multiple items (using the Shift or Ctrl keys), all the selected items will have a True by them. A quick aside on the recursive subroutine Traverse: Let me begin by explaining how the UIHierarchy object works. It contains a property called UIHierar- chyItems, which is a list of UIHierarchyItem objects. Each UIHierarchyItem object, in turn, contains a UIHierarchyItems list. That means that the top item in the hierarchy is a UIHierarchy object, whereas all the other items in the hierarchy are UIHierarchyItem objects. Since I wanted to write my Traverse routine to handle the general case, I made its parameter a UIHierarchyItems collection. That way, whether I’m dealing with a UIHierarchy object or a UIHierarchyItem object, I can always pass in the object’s UIHierarchyItems property. Also, the Traverse routine takes an integer, which represents an indentation level. You can think of this as how deep the current item is in the recursion. But the way I use the item in the macro is to create a string of spaces whose length is four times the indentation level. (I do that with the PadLeft function; there are probably other ways to do the same thing.) Thus, when I print out the items, they have an indentation that matches the indentation in the window’s tree. You can traverse a window’s hierarchy even if the window is not currently showing. For example, if you close the Solution Explorer window, the IDE is still aware of the Solution Explorer, so you can still traverse its hierarchy. Programming the Document and User Interface Objects 225 Finding a Hierarchy Item If you want to find a particular hierarchy item, you can start by locating the window for the hierarchy. Do so via the usual method, such as this: Dim win As Window = DTE.Windows.Item( _ Constants.vsWindowKindSolutionExplorer) The Constants class contains a list of window types, all of which start with the vsWindowKind. The easiest way to locate the constant name is to know the name of the window (such as Solution Explorer or Server Explorer), then type the word Con- stants into the code editor, next the dot, and then slowly type the first few letters of vsWindowKind. As you do, a popup window will appear showing a list of all the members of the Constants class; the list will be centered on the names that start with vsWindowKind. You can then scroll through the list, looking for the name of the win- dow you’re trying to locate. The name will typically be the same as the window’s cap- tion, but without the spaces. Thus, the Solution Explorer’s constant is vsWindowKindSolutionExplorer, and the Server Explorer’s is vsWindowKind- ServerExplorer. After you have the Window object, grab the object’s UIHierarchy object through the Object property: Dim uih As UIHierarchy = win.Object After you have the UIHierarchy item, you have some choices, depending on your situation. If you know the name of the item (such as Form1.cs in the CSharpWinApp1 project), you can quickly find the item using the UIHierarchy object’s GetItem function. For this function, however, you need the full path to the item inside the hier- archy. So in the case of the Form1.cs item in the CSharpWinApp1 project, you would use a couple of lines such as these: Dim item As UIHierarchyItem item = uih.GetItem(“MySolution\CSharpWinApp1\Form1.cs”) You can see that I had to give the full path to the Form1.cs item, including the root node in the hierarchy, which is the name of the solution. For all the items except the root, the name is the same as it appears in the window’s tree. But the name is not as it appears in the window for the root node; instead, the name is simply that of the solu- tion. Interestingly, if you put an invalid name in the string, you will receive an error message that reads, “The parameter is incorrect.” Finding an Item Using Regular Expressions If you’ve never worked with regular expressions, I strongly encourage you to learn about them. Regular expressions make any string or text processing amazingly simple, once you understand the somewhat cumbersome syntax. For years, regular expressions were something that primarily Unix gurus understood; the rest of us simply didn’t like to admit that we knew nothing about them. 226 Chapter 10 In this book, I can give you only a bit of introductory material on regular expressions so I recommend you get a copy of Mastering Regular Expressions, by Jeffrey E.F. Friedl (O’Reilly & Associates, 2nd ed., July 2002). This is, by far, the best book on the topic, and one that every programmer should own (in addition to the book in your hands, of course). The beauty of regular expressions is that you can write a complex pattern and then determine if a string matches it. This is a bit like the wildcard patterns you can use in a DOS window, such as *.txt, which refers to every filename ending in “.txt.” The .NET framework includes a set of full-featured regular-expression classes that makes regular expression handling remarkably easy. To use the classes, you first define your search string, passing it into the constructor of the Regex class: reg = New Regex(“.*\.cs”) This line of code defines a pattern that matches any filename ending in the string “.cs”. The beginning of the pattern is .*, which means any string of characters. (It’s equivalent to the single asterisk (*) in DOS filenaming; however, in this case, the dot character refers to any character, and the asterisk means any number of the previous special character. Thus .* means any number of any character.) Since the dot character is special, you use a backslash followed by a dot if you really mean a dot. Thus, \.cs means the literal string “.cs”. And so this particular Regex object is specifying a pat- tern that will match any string ending in “.cs”. The regular expression classes live in the System.Text.Regular Expressions namespace. Thus, to access the classes, you either must fully qualify them as System.Text.RegularExpressions.Regex; or, in VB.NET, you need an imports statement at the beginning of your code, as in Imports System.Text.RegularExpressions. Then you can test a string to determine whether it matches the regular expression using the Match class. Here’s an example: reg = New Regex(“.*\.cs”) VBMacroUtilities.Print(reg.Match(“hello”).Success) VBMacroUtilities.Print(reg.Match(“MyFile.cs”).Success) The first line results in the string False being written to the output window, since the first string, “hello”, does not match the pattern. The second string, “MyFile.cs”, however, does match the pattern, and so the second line writes the string True to the output window. Next is a macro that uses the previous regular expression pattern to search for all items in the Solution Explorer that end in “.cs”—that is, the macro finds all C# source files. ‘ Need ‘ Imports System.Text.RegularExpressions Private reg As Regex Programming the Document and User Interface Objects 227 Sub RegTraverse(ByVal items As UIHierarchyItems) Dim subitem As UIHierarchyItem Dim m As Match For Each subitem In items m = reg.Match(subitem.Name) If m.Success Then VBMacroUtilities.Print(subitem.Name) End If If Not subitem.UIHierarchyItems Is Nothing Then RegTraverse(subitem.UIHierarchyItems) End If Next End Sub Sub DemoRegExItems() VBMacroUtilities.Setup(DTE) VBMacroUtilities.Clear() Dim win As Window = DTE.Windows.Item( _ Constants.vsWindowKindSolutionExplorer) Dim uih As UIHierarchy = win.Object reg = New Regex(“.*\.cs”) RegTraverse(uih.UIHierarchyItems) End Sub You can see how I perform the match in this code: I call the Match function of the Regex object; this function returns an object of class Match. This Match object has a member called Success, which is either True or False, corresponding to whether the match succeeded. If the match worked, then I print out the name of the item. Other- wise, I just move on. Selecting a Hierarchy Item The UIHierarchy object has a property called SelectedItems, which is an array of, as you can imagine, UIHierarchyItem objects that are currently selected. Now, remember, you have a UIHierarchy object for each window that contains a hierarchy, so if you have both the Solution Explorer and the Server Explorer windows open, each window can have a set of selected items. When you switch to one window, the other window’s selected items change color to indicate they’re still selected but not active. Therefore, if you are using the UIHierarchy object for two different windows, you might well find that both objects have a list of items in the SelectedItems array. Each UIHierarchyItem object has a Selected property, meaning you have two ways of finding which items are selected: If you are traversing the hierarchy, you can simply look at an item’s Selected property; or, if you are not traversing the hierarchy, you can obtain the list of selected items using the root UIHierarchy object’s SelectedItems property. Here’s a simple macro that lists all the items selected in both the Solution Explorer and the Server Explorer windows: 228 Chapter 10 Sub ListSelectedItems() VBMacroUtilities.Setup(DTE) VBMacroUtilities.Clear() Dim win As Window = DTE.Windows.Item( _ Constants.vsWindowKindSolutionExplorer) Dim uih As UIHierarchy = win.Object Dim uihitem As UIHierarchyItem VBMacroUtilities.Print(“Solution Explorer”) For Each uihitem In uih.SelectedItems VBMacroUtilities.Print(“ “ & uihitem.Name) Next win = DTE.Windows.Item( _ Constants.vsWindowKindServerExplorer) uih = win.Object VBMacroUtilities.Print(“Server Explorer”) For Each uihitem In uih.SelectedItems VBMacroUtilities.Print(“ “ & uihitem.Name) Next End Sub And here’s a sample output listing from this macro: Solution Explorer AssemblyInfo.cs Form1.cs Server Explorer book_id bookauth Note: For the Server Explorer window, I had a books database open, and I had clicked on two fields in the list, resulting in the book_id and bookauth items in this output. Just as you can traverse a window’s hierarchy even if the window is closed, you can also obtain a list of selected items. For example, if you select two items in the Solution Explorer window and then close the window, you would still see the selected items listed in the Solution Explorer’s UIHierarchy.SelectedItems property. IDE users might not understand this point and might get confused if you run an add-in or other program based on a selected item when the window containing the selected item is closed. I recommend, therefore, that you first check the Window object’s Visible property, in addition to checking for selected items in the hierarchy. When you are traversing a hierarchy, you can cause the IDE to behave as if the user double-clicked an item in the hierarchy. For example, if you have the UIHierar- chyItem for a folder in the Solution Explorer, you can call the item’s Select method. If the folder is currently expanded, then the folder will collapse. But if the folder is col- lapsed, then it will expand. Programming the Document and User Interface Objects 229 When you use the UIHierarchyItem.Select method, the focus does not move to the window containing the UIHierarchyItem. Instead, the focus will either remain on whichever window previously had the focus or, in the case of double-clicking a macro’s name in the Macro Explorer, the focus will return to whichever window previously had the focus. If you want to set the focus to the window containing the item, call the Window object’s Activate method. Collapsing Nodes Using a combination of the Document object, the Project object, and the various hierarchy objects, you can write a macro that will collapse any project nodes in the Solution Explorer that have no open documents. For example, if you have a solution with 10 projects, with only one source file open, but the tree in the Solution Explorer has several projects expanded (making for a somewhat messy view), then the follow- ing macro will collapse all the nodes in the tree except for the project containing the single file that is open. Sub CollapseUnused() ‘ Gather all projects Dim openprojects As New Collection() Dim myproject As Project For Each myproject In DTE.Solution.Projects openprojects.Add(myproject, myproject.FullName) Next ‘ Remove projects from list that ‘ have an open document Dim doc As Document Dim proj As Project For Each doc In DTE.Documents Try proj = doc.ProjectItem.ContainingProject openprojects.Remove(proj.FullName) Catch End Try Next ‘ Close the projects in the list Dim win As Window = DTE.Windows.Item( _ Constants.vsWindowKindSolutionExplorer) Dim uih As UIHierarchy = win.Object Dim item As UIHierarchyItem Dim projitem As ProjectItem Try For Each item In uih.UIHierarchyItems.Item(1).UIHierarchyItems Dim name As String = TypeName(item.Object) If name = “Project” Then 230 Chapter 10 [...]... Dim commands As Commands = DTE.Commands Dim command1 As Command = commands.AddNamedCommand( _ addInInstance, _ “Show”, “Class Manager”, “Shows the Tool Window”, True, _ 59, Nothing, _ vsCommandStatus.vsCommandStatusSupported + _ vsCommandStatus.vsCommandStatusEnabled) Dim viewMenu As CommandBarPopup = _ DTE.CommandBars(“MenuBar”) _ Controls(“&View”) Dim viewMenuBar As CommandBar = viewMenu.CommandBar... called Visual Studio NET Tools that includes a Visual Studio NET Command Prompt item, which I suggest you use for opening your DOS window It has all the path and other environment variables set up properly for you.) As I mentioned in Chapter 7, “Creating Add-ins for the IDE,” you can invoke the Visual Studio NET program from the command prompt Here’s the DOS command for invoking devenv in command-line... you can use Visual Basic’s built-in Shell command Like MsgBox and InputQuery, Shell is not a part of the NET framework; instead, it is a keyword in the Visual Basic language Alternatively, you can tell Visual Studio NET to spawn a command by issuing the Tools.Shell command Remember, the IDE maintains a list of commands (of which your macros are a part, as are the commands you add to your add-ins) One... neededText As vsCommandStatusTextWanted, _ ByRef statusOption As vsCommandStatus, _ ByRef commandText As Object) _ Implements IDTCommandTarget.QueryStatus If neededText = EnvDTE.vsCommandStatusTextWanted _ vsCommandStatusTextWantedNone Then If cmdName = “ClassManager.Connect.Show” Then statusOption = CType(vsCommandStatus _ vsCommandStatusEnabled + vsCommandStatus _ vsCommandStatusSupported, vsCommandStatus)... command-line version of the Visual Studio NET product ■ ■ Invoke a macro that builds the solution for you The first of these, starting a second instance of the IDE, is pretty self-explanatory: You just run Visual Studio NET again The second is quite simple, too, provided you open a DOS window that has the paths and other environment variables set properly (The Start menu item in Microsoft Visual Studio. .. othersMenu As CommandBarPopup = _ viewMenu.Controls(“Oth&er Windows”) Dim othersBar As CommandBar = othersMenu.CommandBar command1.AddControl(othersBar, 1) Catch End Try End Sub The Exec and QueryStatus functions follow The only command that this add-in supports is the Show command, which works in conjunction with the menu item for displaying the tool window This is the same as described in Chapter 7 as well... check the box to create a toolbar menu When Visual Studio NET creates the project for you, add references to Microsoft VisualStudio.VCCodeModel.dll and the VSUserControlHostLib library The VSUserControlHostLib is a library that you can download from Microsoft’s Web site I provided instructions for obtaining and building the library in Chapter 7, “Creating Add-ins for the IDE,” in the section “Using... both IDTExtensibility2 and IDTCommandTarget, meaning that not only is this an add-in, but it supports commands as well You can also see I added code to the OnDisconnection handler that hides the tool window Further, I removed the applicationInstance variable and replaced it with a global variable that I called 2 37 238 Chapter 11 DTE (the DTE variable is defined later in the ClassCommands.vb module) That... the code in the third module, ClassCommands.vb, will be accessing the ClassManagerForm object; the ManagerForm variable is in the ClassCommands.vb module The CodeModel and Build Objects Figure 11.1 The ClassManagerForm ‘ Remember to add a reference to ‘ Microsoft.VisualStudio.VCCodeModel! ‘ Also, added these two imports lines Imports EnvDTE Imports Microsoft.VisualStudio.VCCodeModel Public Class ClassManagerForm... processors, one for the VB.NET and C# languages and one for the C+ +.NET language However, for each class in one set, there’s a corresponding class in the other set, and the corresponding classes work similarly If you read Chapter 10, you are familiar with the EditPoint object Each CodeElement class includes a StartPoint and an EndPoint object, which specify where the element begins and ends, respectively, . commands As Commands = DTE.Commands Dim command1 As Command = commands.AddNamedCommand( _ addInInstance, _ “Show”, “Class Manager”, “Shows the Tool Window”, True, _ 59, Nothing, _ vsCommandStatus.vsCommandStatusSupported. check the box to create a toolbar menu. When Visual Studio .NET creates the project for you, add references to Microsoft .VisualStudio.VCCodeModel.dll and the VSUserControlHostLib library. The VSUserControlHostLib. objects in .NET, the framework has two sets of classes: one for C++ .NET and one for both VB .NET and C#. Finally, be aware that you cannot use the code model classes to modify VB .NET source code; such