Being new to the SDK and studio extensibility I thought that being able to track the active code window would be easy. I was wrong.
Here is what I have and hoping someone would be willing to steer me in the right direction.
I have a ToolWindow which I create inside my package. I want the toolwindows content to be only controlled by the active window. I have been looking at the IVsMonitorSelection interface but I guess I just don't understand the extensibility model of VS very well. I have also looked at the RunningDocumentsTable which I believe maybe could work but have a gut feeling telling me this is not the right way to go. Could be wrong.
Anyways, if someone would be willing to help me out that would be great. Thanks so much in advance.
John
===================================
Apart from the main IDE window (DTE.MainWindow) there are two kind of windows (DTE.Windows):
- Document windows, such as code or designer windows
- Toolwindows, which can be floating, docked (such as the Solution Explorer) or tabbed (like a document window, such as the Object Browser).
And there are two concepts:
- The active window, which can be a toolwindow or a document window
- The active document, which can be active even if its window is not active because a toolwindow is active
A document (EnvDTE.Document) can have one or more windows (Document.Windows), for example a form designer window and a code window, and the active one is Document.ActiveWindow). On the other hand, an EnvDTE.Window can have a document (Window.Document) associated.
You can have the active window with DTE.ActiveWindow, which can return a toolwindow or a document window.
You can have the active document with DTE.ActiveDocument, which can return a document even if its window is not active (because a toolwindow is active).
Some tools want to act on a document only if its window is active (DTE.ActiveDocument.ActiveWindow = DTE.ActiveWindow) while want to act on the active document even if a toolwindow is active. You have to decide your behavior. Notice that it a toolwindow such as the Object Browser (which is tabbed) is active, it is difficult for the user to say which document is active, because its window is not the selected tabbed one. This does not happen if the toolwindow is docked rather than tabbed.
====================================
I recently tackled this problem and blogged about it. For something that seems like it should be very straightforward, it wasn’t. There’s even a page in the SDK help that tries to describe how to do this but misses several of the possible scenarios. My goal was to create a class that would be able to fire off “Created”, “GotFocus” and “LostFocus” and “Closed” events for the lifecycle of any code window. The problem is, you don’t care if an editor loses focus to a docked tool window, you only care if it loses focus to another tabbed document (in which case it’s hidden).
In order to do this, the class needs to implement two interfaces: IVsRunningDocumentTableEvents and IVsSelectionEvents. The first interface will notify our class about changes to the status of all open documents. The second will notify us when Visual Studio detects a selection change of any kind, more on this later. The first step is to have the class implement these interfaces and register for callbacks on them, which is described in the SDK help documentation.
NOTE: When I said that IVsRunningDocumentTableEvents tracks open documents, it’s important to understand what that means because it effects the implementation. Open documents include code editors, property editors, designers, but NOT tabbed tool windows. Because of that, we can’t use IVsRunningDocumentTableEvents to reliably tell us when an editor has lost focus because it doesn’t track tabbed tool windows.
We’re interested in code editors only. When we get a call to our IVsRunningDocumentTableEvents.OnBeforeDocumentWindowShow implementation, the way to detect a valid code editor is to call VsShellUtilities.GetTextView and pass in the value of the pFrame parameter. If you get a valid IVsTextView object back, we have an editor. Inside this function, we are specifically interested in editor creation. If the editor was just created, the fFirstShow parameter will be 1. When this is the case, we can call our “Created” event to notify that a code editor was created. The OnAfterDocumentWindowHide is only called when a window is closed, not when it loses focus. When this is the called, we can call our “Closed” event to notify that a code editor was closed. At this point, you have a good implementation for detecting the opening and closing of editor windows.
We still want to know when the editors get focus or lose focus. To determine this, we need to implement the IVsSelectionEvents interface, specifically the OnElementValueChanged function. This function will be called if the “selection”, or active object, changes to anything.
To determine a change in editor focus:
- Check if the elementID parameter is equal to Constants.SEID_WindowFrame. If not, just return.
- Determine if the varValueNew parameter implements IVsWindowFrame. If so, cast it.
- We are only interested in when we lose focus to another tabbed document, so get the __VSFPROPID.VSFPROPID_FrameMode property of the window frame. If it is 2, then it is a tabbed document (yes, 2 is a magic number, but I don’t know the enum that this value matches up with). If it’s not a tabbed document, then just return.
- Call VsShellUtilities.GetTextView and pass in the value of the varValueNew parameter. If you get a valid IVsTextView object back, then an editor window has gotten focus and you can call the “GotFocus” event.
- Determine if the varValueOld parameter implements IVsWindowFrame. If so, cast it.
- Call VsShellUtilities.GetTextView and pass in the value of the varValueOld parameter. If you get a valid IVsTextView object back, then that editor window has lost focus and you can call the “LostFocus” event.
This can obviously be extended in many ways, but it’s a good basis for code window tracking and something that definitely isn’t immediately intuitive when working with VSIP. Good luck!
=======================================
slush_puppy
I think I follow what you are saying. But right now I a little stuck on "The first step is to have the class implement these interfaces and register for callbacks on them, which is described in the SDK help documentation." I can't seem to find information on how to register for callbacks. Sorry if these questions are bad..but I'm just learning this vs extensibility stuff and it is a bit overwellming at first. I can totally follow you logic in this new class but I can't seem to find information on how to register for callbacks.
Thanks for you help slush_puppy
John
=======================================
Here's some sample code to get you going... you'll need two member uint variables called cookies that are IDs that these services will assign to you. You'll use them to unsubscribe from events later.
private uint m_RDTCookie;
private uint m_shellMonitorCookie;
Now subscribe to the events:
IVsRunningDocumentTable rdt = Package.GetGlobalService(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable;
if (rdt != null)
rdt.AdviseRunningDocTableEvents(this, out m_RDTCookie);
IVsMonitorSelection ms = Package.GetGlobalService(typeof(SVsShellMonitorSelection)) as IVsMonitorSelection;
if (ms != null)
ms.AdviseSelectionEvents(this, out m_shellMonitorCookie);
Once you subscribe for those events, you will get callbacks to the functions you implement on those interfaces.
To unsubscribe, do the following in say, a Dispose method or something:
IVsRunningDocumentTable rdt = MsVsShell.Package.GetGlobalService(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable;
if (rdt != null)
rdt.UnadviseRunningDocTableEvents(m_RDTCookie);
IVsMonitorSelection ms = MsVsShell.Package.GetGlobalService(typeof(SVsShellMonitorSelection)) as IVsMonitorSelection;
if (ms != null)
ms.UnadviseSelectionEvents(m_shellMonitorCookie);
==========================
slush_puppy
Thanks for all your help. I have it all working now responding to window events and everything. Next I just have to get the logic for my package in there. Anyway, the one thing that I wanted to maybe help you out with is the magic number (2). I believe I have found the enum for that. Check out VSFRAMEMODE, it has for values: VSFM_Dock, VSFM_Float, VSFM_FloatOnly and VSFM_MdiChild. Hope this helps you out a bit also.
Thanks for all your help. I may need more as I move along but for now I'm doing pretty good.
John