It’s been a long time since I published the latest article in this mini-series. In the first part I gave an overview about the hierarchy basics and in the second part I treated the basic structure of hierarchies. The first time I introduced code samples was the third part dealing with property access and hierarchy traversal.
After that article I was thinking about creating a few small samples to build basic custom hierarchies. However, it was harder than I had ever guessed before! I did not find appropriate samples and documentations so I made many experiments to discover how hierarchies and hierarchy windows work in order to create my own solutions. After a few weeks of trying several ways and many implementation patterns I decided to create a basic framework that makes programming hierarchies a relatively straightforward task. This article is the first fruit of my efforts.
I can say that the framework I established is quite far away from a perfect one, but I carry on extending it with hierarchy support types. You may not be surprised if I tell you the hierarchy support types are built into the VSXtra framework. In this post I introduce you hierarchy windows and their co-operation with hierarchies. In the next posts we are going to create custom hierarchies.
Visual Studio uses Hierarchy Windows to display hierarchies and allow the user to interact with them. The most known of them are the Solution Explorer and the Server Explorer tool windows. Hierarchies hosted in hierarchy windows live in a strong synergy with Visual Studio. Just a few characterizing points about this synergy:
Hierarchy items (zero, one or more) can be selected in the hierarchy windows and Visual Studio is able to track this selection so that other packages and objects can access this selection context.
Nodes can be assigned to documents (just like as Solution Explorer nodes can be assigned to source files)
Hierarchy windows and so hierarchy nodes can be targeted by commands. The hierarchy and its hosting window can cooperate to execute (or even refuse) commands received.
Hierarchy items can notify the window about changes in their states and even other objects in Visual Studio can subscribe to these hierarchy events.
A hierarchy window is an object that implements the IVsUIHierarchyWindow interface. This interface defines all responsibilities of a window that can display a hierarchy and allows the user interacting with it. The interface has only a few methods:
public interface IVsUIHierarchyWindow
int Init(IVsUIHierarchy pUIH, uint grfUIHWF, out object ppunkOut);
int ExpandItem(IVsUIHierarchy pUIH, uint itemid, EXPANDFLAGS expf);
int AddUIHierarchy(IVsUIHierarchy pUIH, uint grfAddOptions);
int RemoveUIHierarchy(IVsUIHierarchy pUIH);
int SetWindowHelpTopic(string lpszHelpFile, uint dwContext);
int GetItemState(IVsUIHierarchy pHier, uint itemid, uint dwStateMask,
out uint pdwState);
int FindCommonSelectedHierarchy(uint grfOpt, out IVsUIHierarchy lppCommonUIH);
int SetCursor(IntPtr hNewCursor, out IntPtr phOldCursor);
int GetCurrentSelection(out IntPtr ppHier, out uint pitemid,
out IVsMultiItemSelect ppMIS);
The interface defines nine operations that do not really seem enough to define all what can be done with a hierarchy. The secret is behind the method called ExpandItem that has a bit misleading name. Despite of its name that suggests we can use it to visually expand an item of the hierarchy, in reality this method allows about a dozen of operations like collapsing a hierarchy item, extending the current selection, highlighting the item with bold typeface or graying it imitating the “to be cut” state, etc.
I do not want to bore you with going along on the methods above one-by-one; we’ll see the most relevant ones later.
IVsUIHierarchyWindow is just an interface. To use it we need a service object implementing this behavior. You can imagine that it is not easy to create a window to carry out all the operations we expect from a hierarchy window. Fortunately, you do not have to write your own hierarchy window implementation from scratch, you can use the one implemented by Visual Studio. That implementation is a COM object that represents the hierarchy in a tree view control as you recognize it in Solution Explorer.
The beauty of IVsUIHierarchyWindow is that theoretically you can use it to produce your own visual representation of a hierarchy window, for example using WPF to make a twist on the “ordinal” user experience. Before starting thinking about it, please let time for yourself to understand how hierarchy windows act as a bridge among hierarchies, Visual Studio IDE and user interactions.
If you take a closer look at the method definitions above almost all of them has a parameter with a type of IVsUIHierarchy. This interface is similar to IVsHierarchy not only in its name but also in its functionality. IVsUIHierarchy implements IVsHierarchy and adds two new methods:
public interface IVsUIHierarchy : IVsHierarchy
int QueryStatusCommand(uint itemid, ref Guid pguidCmdGroup, uint cCmds,
OLECMD prgCmds, IntPtr pCmdText);
int ExecCommand(uint itemid, ref Guid pguidCmdGroup, uint nCmdID,
uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut);
IVsUIHierarchy actually allows hierarchy nodes being OLE command targets. It will be obvious if you compare the definition above with the definition of the IOleCommandTarget interface:
public interface IOleCommandTarget
int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD prgCmds,
int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn,
The IVsUIHierarchy methods have a first uint parameter that addresses the hierarchy item to send the command or command status notification for. So, what is role of IVsUIHierarchy in conjunction with a hierarchy window? To understand it we must take a short overview (or repeat) about commands and command routing.
A command represents a logical action (user interaction or other action raised by the code) that can result in execution of some code. The command itself is not necessarily owns the code to be invoked when the user (or code) initiates the action. A command target is an entity that knows how to execute a command received from its environment. It can route, execute or even refuse a command. The command target also provides information about the state of the command. So, IOleCommandTarget and the new methods of IVsUIHierarchy actually define the responsibilities of command targets.
Routing Hierarchy Commands
When a command is raised there might be zero, one or more command targets that can understand what the command means and how to carry out the related action. Visual Studio provides a routing algorithm to forward the command to the fitting target.
In the way the command “bubbles up” from the current level called the active command context. If the active command context is not a command target the command bubbles up to the upper level. If this context is a command target it has the chance to handle the command or to say “I do not know what to do with this command” and the bubble goes on accordingly.
The routing algorithm defines the following levels from the bottom to the top level:
Present Add-in. Commands first are offered to the registered and loaded Add-ins.
Context (shortcut) menus. If the user initiated the command from a context menu, the command target object belonging to this menu has the first chance to handle the command then the normal route (starting from Add-Ins) is applied.
Focus window. The window having the focus is the next entity that could undertake command handling. This can be either a tool window or a document window (e.g. a window related to an editor). The management of the command is different depending on what kind of window we have.
Current project. The current project gets the opportunity to process the command. If it does not handle, the command goes up in the hierarchy of projects till the level of solution. All nodes on this route can manage the command just like as other command target objects. (VS SDK allows creating subprojects called “flavors”. When we use subprojects, the upper level of a project is not necessary the solution.)
Environment. Each package should be able to handle commands owned (defined) by itself. Theoretically it is not mandatory but why to define custom commands that are not handled by the only entity that would know how to deal with them...
Global level. If a command is not handled during the previous levels, the environment attempts to forward it to the appropriate package (the package defining the command).
A custom hierarchy window generally gets commands when it has the focus. I said generally, because a hierarchy window can be set up to get commands even if that does not have the focus. After getting the command the hierarchy window examines it and if it cannot process directly it sends the command to the hierarchy (or hierarchies) hosted in the window.
There are commands resulted by a user interaction that are directly sent to the hierarchy through the IVsUIHierarchy interface. In this case the hierarchy window sends the identifier of the item as the subject of the command along with the command and so the hierarchy can address the appropriate item. Hierarchy windows have special commands with the command GUID defined by the GUID_VsUIHierarchyWindowCmds value of the VSConstants type. The command IDs within this command group are defined by VSConstants.VsUIHierarchyWindowCmdIds enumeration and can have the following values:
The command is sent when the user double-clicks anywhere within the hierarchy window. If double-click is right on one of the items the command is directly sent to that item. If the double-click hits on any other window area, the command is sent to the root hierarchy item (actually to the hierarchy itself).
The command is sent when the user selects one or more hierarchy item and presses the Enter key. If a single item is selected, the command is directly sent to that item. If multiple items are selected the command is sent with the special command ID VSITEMID_SELECTION.
The command is sent when the user selects one or more hierarchy item and right-clicks on the selection. If a single item is selected, the command is directly sent to that item. If multiple items are selected the command is sent with the special command ID VSITEMID_SELECTION.
When a hierarchy node turns to an editable entry field this command is sent to the appropriate hierarchy item. This command always addresses one item.
This command is sent to a hierarchy item when its editable entry field turns back to a static text item.
When the user presses the Esc key during the editing of a hierarchy item label, this command is sent to the appropriate node.
Beside these commands, other commands also can be sent to the hierarchy by the environment. Of course, any other commands can be sent by program code to the hierarchy nodes.
Setting up Hierarchy Windows
A hierarchy window is only a blank window if we do not put a hierarchy (an IVsUIHierarchy instance) in it. So, first we have to create an object implementing the IVsUIHierarchy to have at least a simple root node in order it can be visualized. When we have it, we have to call the Init method of the IVsUIHierarchyWindow interface of the corresponding window. The Init method sets up the style of the hierarchy window and optionally adds an initial hierarchy to the window. The method has the following signature:
int Init(IVsUIHierarchy pUIH, uint grfUIHWF, out object ppunkOut);
The pUIH parameter represents the hierarchy to show in the window. It can have a null value meaning there is no hierarchy shown at the moment of the call. The ppunkOut output parameter retrieves a pointer to an IUnknow interface to the IVsWindowFrame object holding information about the frame hosting the hierarchy window. The grfUIHWF parameter is a set of flags determining the style (visual style and basic behavior) of the hierarchy window. It gets its value from the __UIHWINFLAGS enumeration.
A hierarchy window can host one or more hierarchies at the same time. For example, the Solution Explorer displays only one hierarchy, but the Server Explorer can show up more hierarchies. You can add hierarchies to the window by calling the AddUIHierarchy method and drop a hierarchy from the window with the RemoveUIHierarchy method.
Cooperation between the Hierarchy Window and the Hierarchy
In the first part and second part I treated the IVsHierarchy interface and the structural properties. I described how hierarchy is formed from a set of items and told that the IVsHierarchy abstraction is very different from the one approached by a tree view control.
Just to recall the most important statements:
Each node in the hierarchy is identified by a 32-bit unsigned integer called hierarchy item ID. The VSITEMID_ROOT represent the root node of the hierarchy. The value VSITEMID_NIL has the same role as the null value: it represents a reference to a non-existing (or missing) hierarchy item. IVsHierarchy uses four methods to query and set the properties of items in a hierarchy. These methods are GetProperty, GetGuidProperty, SetProperty and SetGuidProperty. The relationships among the items of the hierarchy (parent and child nodes) are defined with so-called structural properties. The most important of them are the followings: Parent, FirstChild, FirstVisibleChild, NextSibling and NextVisibleSibling. There are other properties that describe the relations between hierarchy nodes and nested (shortcut) hierarchies: ParentHierarchy and ParentHierarchyItemId.
IVsHierarchy does not provide you any operations like GetChildren, AddNode, RemoveNode, AddChild or RemoveChild! Clients of IVsHierarchy simply use the GetProperty method to obtain the structural properties (and of course other hierarchy related values) when displaying the hierarchy. It is the task of the object implementing the operations changing the hierarchy and to administer the structural properties accordingly. A hierarchy window is a client of the IVsUIHierarchy objects hosted within the window. But what kind of cooperation should be built between the hierarchy window and a hosted hierarchy in order the changes in the hierarchy can be displayed?
The key behind this mechanism is based on hierarchy change notification events. IVsHierarchy defines two methods to subscribe for notification events and to unsubscribe from them:
int AdviseHierarchyEvents(IVsHierarchyEvents pEventSink, out uint pdwCookie);
int UnadviseHierarchyEvents(uint dwCookie);
The IVsHierarchyEvents defines the notifications to be received when there are any changes in the hierarchy. The hierarchy window subscribes to these notifications through calling the AdviseHierarchyEvents method when a hierarchy is hosted in the window. Of course, it unsubscribes from the events when the hierarchy is removed. IVsHierarchyEvents is declared with the following notification methods:
public interface IVsHierarchyEvents
int OnItemAdded(uint itemidParent, uint itemidSiblingPrev, uint itemidAdded);
int OnItemsAppended(uint itemidParent);
int OnItemDeleted(uint itemid);
int OnPropertyChanged(uint itemid, int propid, uint flags);
int OnInvalidateItems(uint itemidParent);
int OnInvalidateIcon(IntPtr hicon);
So, when the object implementing the IVsUIHierarchy interface changes the hierarchy behind, it should raise the appropriate event (call the appropriate method of IVsHierarchyEvents). For example, when adding a child item to a node, it can either call OnItemAdd (inserting the new child somewhere in between the existing children) or OnItemsAppended (appending one or more child to the end of the list of children already parented by the node). If there are many changes in the children of a parent node, the OnInvalidateItems event should be raised. When only a single property has been altered, the OnPropertyChanged event is the one to fire.
The hierarchy window gets these notifications and decides which part of the hierarchy should be repainted. Then turns to the hierarchy object and asks for item properties required for the repaint operation using the GetProperty and GetGuidProperty methods of the IVsHierarchy interface. For example, if the OnInvalidateItems event is called, the hierarchy window receives the ID of the item where children changed and asks for all the visual properties of all children parented by the node with this ID.
The hierarchy window not only queries IVsUIHierarchy properties but sets a few of them! One of the most often used properties is Expanded that indicates if a node is expanded visually in the tree view representing the hierarchy. Why is it useful?
Often hierarchies are built on-demand. If producing the children of a parent is an expensive operation, it is a good strategy to enumerate them at the first time when the parent node is expanded. For example, if a node represents a folder and its children correspond to files in that folder, it is useful to obtain the list of files only when the user expands the folder node.
As the hierarchy window calls the SetProperty method of IVsUIHierarchy, we can recognize that a hierarchy node is about to be expanded (or collapsed) and we can use this information to set up the children nodes (or even in case of collapsing dispose the unused resources).
Implementing Custom Hierarchies
Using an existing hierarchy like the one we can see in the Solution Explorer is not a very difficult task while we handle it only in read-only manner. The IVsUIHierarchy interface itself is not the easiest tool we can use to obtain information about existing items and properties, but you can wrap its functionality into helper classes just as I did in the third part of this mini-series.
If you want to modify properties within the hierarchy, you must have a basic understanding of the specific hierarchy (the meaning and semantics of properties) you are about to manipulate. It is generally not as easy as a simple information query.
If you want to modify the structure of the hierarchy you must know subtle details about the hierarchy and this is a more complex and time-consuming task than the previous two. In this case you must have practice in consuming almost all interfaces mentioned in this post.
The most difficult task is to create your own custom hierarchies. Above the prerequisites of the aforementioned tasks you must implement (and not just simply consume) the relevant interfaces. It is a really complex task coming from the IVsHierarchy type of abstraction and the co-operation between the hierarchies and windows displaying them.
I think the best way to teach how to create custom hierarchies is to use samples. I will follow this approach and in the few next parts I will show you samples. Beside the samples I have created some core classes providing help for you by turning back the IVsUIHierarchy-like approach into the one similar to tree view controls.
Where are we?
To extend Visual Studio with functionality using hierarchies, it is not enough to know how hierarchies are built through the IVsHierachy interface. In order the hierarchies can be displayed and can respond to user interactions, those should be hosted in hierarchy windows represented by the IVsUIHierarchy interface.
You should not write your own hierarchy window (although you can do this), you can use the one provided by Visual Studio. This hierarchy window co-operates with the hosted hierarchy to offer the functionality you perceive. It forwards commands coming from the environment to the hierarchy. For specific user interactions (like pressing the Enter key while an item is selected) the hierarchy window directly addresses the corresponding item or the list of selected items. The hierarchy window paints its area by querying the visual properties of hierarchy nodes. The hierarchy notifies the window about its changes to allow refreshing the UI accordingly.
In the next posts we’ll see how to build a custom hierarchy that work seamlessly with hierarchy windows.
Dec 03 2008, 07:18 AM