This is the fourth part in the series of posts on Visual Studio 2010’s use of WPF. This week, we’ll take a look at how Visual Studio 2010 detects and hosts WPF content ‘natively’ while at the same time allowing for non-WPF content.
Frames and Panes
In the Visual Studio extensibility model, the client area of a window is called a “pane” and it is enclosed and separated from other panes by a “frame”. If you’re writing an extension for Visual Studio and creating a new window, the “pane” is the piece that you are responsible for, and the “frame” is handled by the shell, Visual Studio itself. In the extensibility model, extensions access frames via the IVsWindowFrame interface. The pane, implemented by the extension, is accessed by the shell through the IVsWindowPane interface. Let’s take a look at IVsWindowPane.
interface IVsWindowPane : IUnknown
HRESULT SetSite([in] IServiceProvider *pSP);
HRESULT CreatePaneWindow([in] HWND hwndParent, [in] int x, [in] int y, [in] int cx, [in] int cy, [out] HWND *hwnd);
HRESULT GetDefaultSize([out] SIZE *psize);
HRESULT LoadViewState([in] IStream *pstream);
HRESULT SaveViewState([in] IStream *pstream);
HRESULT TranslateAccelerator(LPMSG lpmsg);
Observe the CreatePaneWindow method. A couple of things you should notice from that method are that it supplies the HWND of its parent (the frame) and that it returns the contents of the pane as an HWND. For an HWND-based user interface like Visual Studio 2008 (and older), that’s ideal but, in the WPF-based Visual Studio 2010, the frame isn’t an HWND at all. It’s just a WPF ContentPresenter which hosts arbitrary WPF content.
For Visual Studio 2010, we didn’t want to force extension authors to move to WPF if they didn’t want or need to. At the same time, we didn’t want to insert HWNDs artificially, particularly if the pane itself was a WPF control. An obvious example of the latter is the new Text Editor. We had to come up with a mechanism that allowed for both the HWND-style hosting for existing HWND-based panes and direct hosting of WPF content without any intervening HWNDs.
Pane Presentation Technology Negotiation
To allow for HWND-less hosting, when a window is first being shown, the frame begins a negotiation with the pane. The currency for this negotiation is the “IVsUIElementPane” interface, new in Visual Studio 2010. Take a look at it below and notice the similarity to IVsWindowPane.
interface IVsUIElementPane : IUnknown
HRESULT SetUIElementSite([in] IServiceProvider *pSP);
HRESULT CreateUIElementPane([out] IUnknown** ppunkUIElement);
HRESULT GetDefaultUIElementSize([out] SIZE *psize);
HRESULT LoadUIElementState([in] IStream *pstream);
HRESULT SaveUIElementState([in] IStream *pstream);
HRESULT TranslateUIElementAccelerator([in] LPMSG lpmsg);
In particular, when comparing with IVsWindowPane’s methods, notice the equivalent of the CreatePaneWindow method. The CreateUIElementPane method creates the content, but notice that it doesn’t specify anything about the container (hwndParent). That’s because WPF FrameworkElements may be created without connecting them to the visual tree. It’s the act of setting the Child or Content property (or adding it to the Children collection) of another element which connects the two together and that happens after the call to CreateUIElementPane. That’s also why there’s no need to specify the size of the child window. It’s expected that the pane will always fill the frame. (If you don’t want that, for example if you have fixed-sized content, you can always wrap your content in a Canvas control and use that as the pane.) Notice also that the out parameter of CreateUIElementPane is declared as an IUnknown. In managed code, this appears as an ‘object’ – the most universal type we can use. That’s because IVsUIElementPane was designed to work with any presentation framework, not just WPF. By using IUnknown or object, we can extend support to other technologies in the future.
The negotiation between the frame and pane begins with the shell asking the component whether it supports IVsUIElementPane. If it does, then the shell will call CreateUIElementPane. If that fails, or if the component does not support IVsUIElementPane, then the shell falls back to using IVsWindowPane. We’ll come back to that in a moment. For now, if CreateUIElementPane succeeds, the shell now has an object (IUnknown) which may or not be a WPF element. Again, the shell probes for different possibilities. The first, and easiest test, is whether the object is in fact a WPF FrameworkElement. A simple try-cast (C# as operator) achieves that. If that fails, then a couple of other casts are attempted: IVsUIWpfElement and IVsUIWin32Element. These two COM interfaces are included to round out some missing capabilities, although neither is heavily used in Visual Studio 2010. IVsUIWpfElement may be used for working with WPF content, but implementing the pane logic in native code. IVsUIWin32Element may be used to create HWND-based panes via the IVsUIElementPane mechanism (instead of the traditional IVsWindowPane mechanism). It’s here that we may, in the future, plug in support for additional presentation technologies. For example, if we were one day to support DirectX content hosting, we would invent an IVsUIDirectXElement interface with methods specific to DirectX content.
By having the frame code drive the negotiation process, it’s the frame that decides which technologies it supports and in which preferential order. Since the Visual Studio 2010 frames are WPF, they naturally prefer to host WPF content directly, so they always ask first for FrameworkElement or IVsUIWpfElement before falling back to alternative mechanisms.
Returning to what happens if the probe for IVsUIElementPane fails, or if the element returned from CreateUIElementPane is IVsUIWin32Element. In this case the shell now knows that it needs to host HWND-based content and in order to do that it needs an hwndParent. It needs to create a new HWND where previously one didn’t exist. WPF’s HwndHost class is used to “host HWNDs” and Visual Studio 2010 will create one on demand for just that purpose. This HwndHost becomes the parent for the yet-to-be created pane window.
Behind the scenes note: When we first implemented this HWND-hosting, we tried to set the owner (hwndParent) to the IDE’s main window without using an HwndHost. In theory that would work just fine, but in practice we discovered many instances where pane implementations and, in particular, test code used the parent window (hwndParent) for their implementations. For example, they would set the caption on the parent window in order to change the title of a tool window. So, as a concession to compatibility, we added the extra HwndHost as described above.
If all these COM interfaces seem a bit complicated, never fear! The Managed Package Framework (MPF) version 10.0 has been extended with support for these new interfaces and easy direct hosting of WPF content. The WindowPane class and its derived class, ToolWindowPane, now have a Content property. This overridable property is typed as ‘object’, to make it as flexible as possible. When you implement your own window by deriving from WindowPane or ToolWindowPane, you may implement the Content property and return a new FrameworkElement. The rest of the IVsUIElementPane plumbing is hooked up for you automatically. If you don’t implement the Content property (or return null), then the shell will fall back to the original Window property which is used for Windows Forms hosting.
The new project templates supplied in the Visual Studio SDK have been updated for Visual Studio 2010 to use these new classes. If you create a new managed package with a tool window, the wizard-generated code will include an override of the Content property to display a WPF UserControl in your tool window.
Tip: Avoid ElementHost in Visual Studio 2010
In Visual Studio 2008, the only way to host WPF content in your panes was to make it look like an HWND. The Windows Forms integration class ElementHost was used to do that. In Visual Studio 2010, unfortunately, that means that you’d end up using HWND-based hosting for what is ultimately WPF content. This creates redundant HWNDs and incurs large performance penalties – main memory, GPU memory (an extra DirectX surface) and elapsed time.
If you are currently using an ElementHost for your pane, and don’t need to support Visual Studio 2008, then please consider enabling direct WPF hosting by upgrading to the 10.0 MPF (Microsoft.VisualStudio.Shell.10.0.dll) and using the new Content property described above. Removing the intermediate HWNDs makes rendering smoother and uses much less memory. We removed these intermediate HWNDs and ElementHosts from a number of our own tool windows between Beta 2 and the Release Candidate for some healthy performance wins.
Previous posts in the series:
Paul Harrington – Principal Developer, Visual Studio Platform Team
Biography: Paul has worked on every version of Visual Studio .Net to date. Prior to joining the Visual Studio team in 2000, Paul spent six years working on mapping and trip planning software for what is today known as Bing Maps. For Visual Studio 2010, Paul designed and helped write the code that enabled the Visual Studio Shell team to move from a native, Windows 32-based implementation to a modern, fully managed presentation layer based on the Windows Presentation Foundation (WPF). Paul holds a master’s degree from the University of Cambridge, England and lives with his wife and two cats in Seattle, Washington.