WPF Vista Gadgets - Part 2: Using ActiveX

In my earlier post, I talked about WPF in the Sidebar by using the IFRAME to host XBAPs.  In this post, I'll talk about another technique: using ActiveX. For an in depth discussion of using ActiveX in gadgets, see Jaime Rodriguez's blog post.  He's got a bunch of great posts on gadgets in general.

Before jumping into the code, please be aware that the same issues around WPF in the Sidebar discussed in my earlier post apply to this technique as well. 

There are two major upsides to using ActiveX to host WPF vs. using IFRAMEs. 

The first is that you can use ActiveX as a bridge between gadget Javascript code and your underlying WPF code.  This means that you can do things like bubble mouse events from your WPF code to Javascript, so that you can cause things like initiate flyouts.  Pretty much any data you can marshal between managed and unmanaged code is fair game.  You can also call into your WPF code from Javascript as well, so that you could grab settings from the Sidebar and pass them to your WPF code.  

The second upside is that your gadget isn't dependent on the IE cache and XBAP installer.  This means that the user experience of installing the gadget is much improved. 

These upsides come with a price: the ActiveX control must be registered in the registry, which requires some kind of installation such as an .msi.  The default gadget installation process cannot install ActiveX.  The ActiveX control can't be access via reg-free COM. 

With this in mind, try out my WPF ActiveX control (source included) which has a 3D carousel (based on the sample from this article) that launches videos in the flyout when clicked.  So, you will get a different video depending on the billboard in the carousel you click.  Here's a screenshot:

The way this works is that there are two ActiveX controls.  One is instantiated in the gadget HTML, which displays the carousel.  The second ActiveX control is hosted in the flyout HTML page and it plays the media.  To get the flyout to pop, I bubble up the event from WPF to the first ActiveX control. I then write the video that should be played to a settings file and read it from the second HTML page control, passing it down to the ActiveX control. Maybe there's a more elegant way, but this method works!

To build this, I created a WinForms User Control, attributed it to make it visible as an ActiveX control and then used the ElementHost to get the WPF content into the WinForm.  (The other alternative would be to create an ActiveX control directly in C++ and then use HWNDHost for the WPF content.)  Your development will be much easier if you check the box "Register For COM" on the Build Project Properties.  This will keep your control up to data in the registry and calls regasm *.dll /codebase on your behalf.

I had to do some refactoring of my WPF code to support ElementHost, as both the Window control and the Page control don't like having parents.   So, you'll see that even though I have a page1.xaml, the root of the tree is a Grid.   Also, because I no longer had an appliction object, I had to do some refactoring to get at my ResourceDictionaries.  I used a technique lifted from the Logon Screen SDK sample where I instantiate my resources and then set them programmatically, which you can see in the ListBoxItem3D.cs file.

Another problem I had to overcome was figuring out the path to my content files.  This wasn't a problem with XBAPs because when the XBAP is instantiated, the executing code is the XBAP .exe, thus it is able to find things like media files using a relative path.  However, for an ActiveX control, it is Internet Explorer which is the parent and so relative paths when loading media doesn't work.  To get around this, I used the System.Reflection.Assembly.GetExecutingAssembly to figure out where the ActiveX control actually was being instantiated from.   I knew that my media files were relative to the assembly and so I was good to go.  Note that there is nothing stopping one from loading media files from the web as opposed to loading them from the file system.

By far, the crux of the code was marshaling between the ActiveX and the Javascript, so that I could bubble up the 3D hit testing, etc.  I couldn't have done this without Jaime's help; he explains all the gorey interop details on this topic.  Suffice to say that it works!   I had trouble using the settings feature built into the gadgets -- for some reason the flyout could read the settings file -- so I used Bruce Williams persistent settings code.

To install the gadget, I started trying to write an .MSI, but ran into problems getting my .dll regsitered, so I just cheated and wrote a .cmd that runs regasm.  You'll need to run this install.cmd file from a command prompt with administrative privileges. But putting this in an .MSI should easy for those that know how to write installers.