Thursday, January 24, 2008

WPF Data Binding in code

Recently, I was working on a WPF prototype for our application. Unfortunately, we need some features in a grid control and some other controls that won't allow us to use the WPF grids available on the market. For this reason, we need to use the Infragistics grid (the same one we've used in the past), the WinForms version.

Unfortunately, we also need to have docking (VS 2005 style) and tabbed MDI. Since docking has flyout controls, and WPF z-order doesn't work right with WinForms, we need to host our application in Windows Forms framework.

When you have a windows forms control in a WPF layout, the windows forms control (in the WindowsFormsHost control of WPF) doesn't participate in the z-order the way you might expect. Unfortunately, windows HWNDs don't support layering like WPF (DirectX) does, instead each HWND owns its part of the screen (except for areas overlapped by overlapping windows). Transparency in HWNDs is done by 'hacks', not builtin support.

Due to this structure, winforms controls must be rendered 'on top' of WPF content, when you host winforms in WPF windows. This means my flyout windows will show up "under" the winforms window, rather than on top of it like you'd expect. Anyway, the only way to get around this is to host WPF controls in WinForms rather than the other way around.

One of the real benefits of using WPF is the XAML markup and the ability to do data binding so easily in your layouts. Unfortunately, at first glance, this seems to be lost when you switch to WPF controls on top of a WinForms layout. Don't fear, all is not lost.

Fortunately, even though you can't use XAML markup to define your data binding, you can programmatically add bindings to your winforms layout. For instance, we have a infragistics grid (a winforms control) that lists the 'items' in our browser that people can choose to look at. On the other hand, we have WPF controls that react to which item is selected in the browser grid. It'd be nice if we could bind the WPF controls to a property on the other control so that they are automatically updated when necessary.

The way I did this was to create a DependencyObject that contains my properties for the grid that I'd like to bind to - in particular one of them was CurrentItem. The properties are DependencyProperty-based. Now, I needed one-way binding to update my WPF controls when the current item changed in the grid, but just in case, I set up my code to support two way binding, so that if I chose to set the value of the property from other parts of my code, I could do so safely and have everything update properly.

So, now I have a DO with a DP. When I register the DP, I created a update method so that I could trigger code when the property was changed - this is done by adding a delegate to the UIPropertyMetadata constructor passed in the DependencyProperty.Register call. To expose this DO to my clients (I've called it BindingInterfaceObject), I make a property on my UserControl (that contains the Infragistics Grid) called BindingInterface and create an instance of the BindingInterfaceObject in the constructor of the UserControl. I give it access to the UserControl's private members by making it a nested class and passing the 'this' pointer to its constructor.

In order to bind to it, I need to set bindings on my WPF controls. I do this by creating a Binding object (System.Windows.Data.Binding) with the name of the property in BindingInterfaceObject that I care about (in my case CurrentItem) and pass this binding object to the SetBinding method on the WPF controls.

Voila! I now have WPF controls binding to changes in a non-WPF control. The only thing I have to do now is set the CurrentItem property whenever the appropriate event occurs on the grid. I do this by handling those events in my UserControl and setting the BindingInterface.CurrentItem as appropriate.

I can provide sample code if you're interested. Just ask.

No comments: