WPF to Windows Forms Interop is one of the development strategies that will take existing applications and bring them to the future. WPF currently provides the ability to fairly easily create new experiences for visualizing applications. Windows Forms has the tools, the frameworks, and years of development effort invested into its existence. If you have a legacy application in Windows Forms and wish to add new experiences to it or better ways to visualize information; then Interop is the way to go.
Infragistics has released its first WPF controls and included in this suite of controls are new metaphors for visualizing information or for that matter creating navigation items. It makes perfect sense to include these controls inside of existing Windows Forms Applications.
To begin we'll focus on the first challenge in integration, which is to get the WPF control (in this case the XamDataPresenter) into the Windows Forms Application. First you need to add references to the Microsoft WPF assemblies that handle WPF, the assemblies are as follows:
After adding references to the Microsoft assemblies, next add references to the Infragistics assemblies which include:
Next, you'll need to add code to host the WPF control inside the Windows Forms application. In the case of this sample, I'll perform the core logic inside of the Form_Load event. The main piece of code revolves around the ElementHost object; which is a container that is used to hold an element [e.g. WPF Control].
For the purpose of this sample, I'll also create an instance of the XamDataPresenter and assign its DataSource property. Then, I'll need to associate the XamDataPresenter as a child of the ElementHost object. The code will look something like this:
… using System.Windows.Forms.Integration; … namespace WindowsForms_WPF_InteropDemo { public partial class Main: Form { private ElementHost XamHost; private Infragistics.Windows.DataPresenter.XamDataPresenter XamDP; … private void Form1_Load(object sender, EventArgs e) { XamHost = new ElementHost(); XamHost.Dock = DockStyle.Fill; this.Controls.Add(XamHost); XamDP = new Infragistics.Windows.DataPresenter.XamDataPresenter(); XamDP.DataSource = this.vSalesPersonSalesByFiscalYearsBindingSource; XamHost.Child = XamDP; } …
This implementation is fairly easy to achieve, gives us access to the full XamDataPresenter object model and it was only a few lines of code. This will enable developers to fully target the control inside of Visual Studio.
One of the core concepts for WPF development was the separation of Design from Development. This means you might want to open up the XamDataPresenter inside of Blend and style it in order to fully achieve the experience that you wish to deliver. Now, you could do all of the work on a 'dummy' control inside of Blend and cut-copy-paste the XAML code into your Windows Forms project, but that isn't really a good scenario and breaks down the communication between the two tooling environments and if I want to fully utilize the power of WPF styling capabilities then I should look for another approach.
One way to get around the problem that I've mentioned is to create a WPF Control Library that hosts the XamDataPresenter. This will enable me to fully design the control inside of Blend and I will be able to host the ControlLibrary as the control inside of ElementHost within Visual Studio. (Note: Control Libraries can be built in Blend, but unless you add them to a dummy project they cannot be tested/run.)
The first challenge I will face is that there is no assigned data source; to get around this I can create a simple XML file that mimics my datasource and I can use that as an intermediate step to style my XamDataPresenter or I can set the BindToSampleData property which will fill my XamDataPresenter with a generic set of data and enable me to focus purely on the styling aspects of the control. That being said, I now have all of the abilities as a designer like the ability to visually style the control and add custom presenters.
After making all of the design changes, I can simply build my project and either a.) add the project to my Windows Forms Solution or b.) add the DLL for my custom control to the references in the Windows Forms Project.
Inside of Visual Studio, I associate the UserControl to the element host by following the same process as previously noted. I will notice that there is no DataSource property, and in fact, none of the familiar properties that I had in my first sample are there. As a matter of fact, I am now seeing all of the properties of the UserControl and not that of the XamDataPresenter. The ideal solution is to have both the ability to design in Blend and code inside of Visual Studio. To achieve this, I'll have to add some code to my UserControl that exposes the underlying properties of the XamDataPresenter.
To begin, I'll start by adding a public property to my UserControl that lets me access the datasource property of the XamDataPresenter. The code might look something like this:
public System.Collections.IEnumerable DataSource { get { return this.XamDataPresenterInternal.DataSource; } set { this.XamDataPresenterInternal.DataSource = value; } }
Theoretically, I can expose all of the properties I need on the underlying XamDataPresenter by associating them as public properties of the usercontrol. Aside from properties, I may also need to expose some events to my base project. In this sample, I'll add some events that validate cell values before exiting edit mode on a grid cell. Since, the EditModeEndingEvent will not be available in my Windows Forms Application, I'll create a public event off of the user control that will handle the event off of the underlying XamDataPresenter and let me act on it inside of my Windows Forms Application. The following code will be placed in my .xaml.cs file:
public static RoutedEvent EditModeEndingEvent = EventManager.RegisterRoutedEvent ("EditModeEnding", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(XamDataPresenterWrapper)); public event RoutedEventHandler EditModeEnding { add { AddHandler(EditModeEndingEvent, value); } remove { RemoveHandler(EditModeEndingEvent, value); } } void XamDataPresenterInternal_EditModeEnding(object sender, Infragistics.Windows.DataPresenter.Events.EditModeEndingEventArgs e) { RaiseEvent(new RoutedEventArgs(XamDataPresenterWrapper.EditModeEndingEvent, e)); }
Also, make sure that the .xaml file knows of the event:
<igDP:XamDataPresenter Name="XamDataPresenterInternal" EditModeEnding="XamDataPresenterInternal_EditModeEnding"/>
Now that I've handled these changes to my ControlLibrary, I'm going to build it and then jump back into my Windows Forms Project and start hooking everything up. To begin, I'll start by hooking up the DataSource Property:
… using System.Windows.Forms.Integration; … namespace WindowsForms_WPF_InteropDemo { public partial class Main: Form { private ElementHost XamHost; private Infragistics.XamDataPresenterWrapper XamDataPresenter; … private void Main_Load(object sender, EventArgs e) { … XamHost = new ElementHost(); XamHost.Dock = DockStyle.Fill; this.Controls.Add(XamHost); XamDataPresenter = new Infragistics.XamDataPresenterWrapper(); XamDataPresenter.DataSource = this.vSalesPersonSalesByFiscalYearsBindingSource; XamHost.Child = XamDataPresenter; } …
After completing this work item, I can hook up the event and perform any logic in that event that I need to handle. For the purpose of this sample, I'll add the event handler to the (Main_Load) Form_Load event and then I'll add the code to perform whatever logic is needed.
… using Infragistics.Windows.DataPresenter.Events; … XamDataPresenter.EditModeEnding += new System.Windows.RoutedEventHandler(XamDataPresenter_EditModeEnding); … void XamDataPresenter_EditModeEnding(object sender, System.Windows.RoutedEventArgs e) { // Arguments for the Edit Mode Ending Event EditModeEndingEventArgs editEndingArgs = e.OriginalSource as EditModeEndingEventArgs; if (editEndingArgs == null) return; // Column name of the field being edited (Infragistics.Windows.DataPresenter.Events) string columnName = editEndingArgs.Cell.Field.Name; // Value of the edited Cell (after edits were made) string newCellValue = editEndingArgs.Editor.Text; if (columnName == "Title" ) { // Check the New Cell Value to see if it changed to anything other // than Sales Representative if (newCellValue != "Sales Representative") { //Do not persist the changes editEndingArgs.AcceptChanges = false; MessageBox.Show("Title Must Be Sales Representative"); } } } …
In this case, making sure that all of the "Sales Representatives" remain "Sales Representatives" was my task.
After completing these steps, here is the much anticipated screenshot showing the Grid View of the XamDataPresenter inside of a Windows Forms application (minus some cool transition/animation effects).
Next, witness this screenshot of the Carousel View of the XamDataPresenter also hosted within a Windows Forms application.
Note: You'll notice that I'm utilizing the underlying Infragistics EditModeEndingEventArgs in my Windows Forms application. This means that I need to have a reference to the Infragistics WPF assemblies in order to achieve this. If for some reason, you do not want to include a dependency on the Infragistics WPF assemblies then you can use reflection, and do something like this (although, I would recommend just using the NetAdvantage WPF Assemblies):
void XamDataPresenter_EditModeEnding(object sender, System.Windows.RoutedEventArgs e) { //Find the Cell Object that was edited object cell = e.OriginalSource.GetType().GetProperty("Cell").GetValue( e.OriginalSource, null); //Get the field that the cell belongs to object field = cell.GetType().GetProperty("Field").GetValue(cell, null); //Get the name of the field that the cell belongs to string name = field.GetType().GetProperty("Name").GetValue(field, null) as string; //Check and see if column name is Title if (name == "Title") { //Get to the text typed by the user object editor = e.OriginalSource.GetType().GetProperty("Editor").GetValue( e.OriginalSource, null); string text = editor.GetType().GetProperty("Text").GetValue(editor, null) as string; //Check to see if the text has changed from Sales Representative if (text != "Sales Representative") { e.OriginalSource.GetType().GetProperty("AcceptChanges").SetValue( e.OriginalSource, false, null); MessageBox.Show("Title Must Be Sales Representative"); } } }
If you're a developer, do everything in Visual Studio and rely on the styling that we provide. If you have a graphic designer or need Blend interaction, then perhaps wrapping the control inside of a control library is a smarter choice, as it helps keep the separation of roles in place.
Lastly, there are some limitations to the Interop story that are important to note. For instance, you can only host one child per element host control, WPF and Windows Forms have different scaling models, or it’s not possible to nest an ElementHost inside of a WindowsFormsHost or vice versa. To read a complete list of limitations you can visit http://blogs.msdn.com/scoberry/archive/2006/09/01/735844.aspx.
Enjoy expanding your Rich Client Experiences!