Wednesday, March 4, 2015

WPF MVVM binding to Windows Forms control

For a WPF application I’m currently working on, I need to use a legacy Windows Form control. This legacy control has a public function which takes in a complex POCO to do its magic. My WPF view-model handles the creation of this POCO and I need to somehow find a way to bind this POCO created by the view-model to the legacy Windows Form control.

Let’s illustrate this with a very simple example.

In my WPF window I want to reuse a Windows Form control that takes in a Person object and knows how to display it using some other Windows Form built-in controls eg. TextBox Name and Age. The display of the Person object will be triggered by a WPF button.

Steps to solve in a nutshell:
1) Wrap the Windows Form control in a WPF control using WindowsFormsHost
2) Add DependencyProperty of type POCO to the WPF control
3) In WPF main window, add the WPF control and bind the DependencyProperty to property of type POCO in the view-model

The Windows Form Control

The Windows Form control has a public function that takes in a Person object and then display it.

namespace WfControlHostedInWpfControl
{
    using System;
    using System.Windows.Forms;

    public partial class WindowsFormControl : UserControl
    {
        public WindowsFormControl()
        {
            InitializeComponent();
        }

        public void WorkYourMagic(Person person)
        {
            TextBoxName.Text = person.Name;
            TextBoxAge.Text = Convert.ToString(person.Age);
        }
    }
}

The POCO

namespace WfControlHostedInWpfControl
{
    public class Person
    {
        public string Name { get; set; }

        public int Age { get; set; }
    }
}

The WPF Control

The WPF control is a wrapper around the Windows Form control.

The XAML is very simple. It only contains a WindowsFormsHost that hosts our WindowsFormControl. Don’t forget to give the host a name so we can refer to it in the code behind later on.

<UserControl x:Class="WfControlHostedInWpfControl.WpfControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WfControlHostedInWpfControl"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    
    <WindowsFormsHost Name="MyHost">
        <local:WindowsFormControl />
    </WindowsFormsHost>
    
</UserControl>

In the code behind, we retrieve the original Windows Form Control by calling the Child property of WindowsFormsHost. We also define ComplexPoco dependency property of type Person, so that we can bind this property from our WPF main window later on. When this dependency property changes, the public function in the Windows Form control will be called, taking in the updated Person object.

namespace WfControlHostedInWpfControl
{
    using System.Windows;
    using System.Windows.Controls;

    public partial class WpfControl : UserControl
    {
        public static readonly DependencyProperty ComplexPocoProperty = DependencyProperty.Register(
            "ComplexPoco",
            typeof(Person),
            typeof(WpfControl),
            new FrameworkPropertyMetadata(
                default(Person),
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                OnPropertyChanged));

        public Person ComplexPoco
        {
            get { return (Person)GetValue(ComplexPocoProperty); }
            set { SetValue(ComplexPocoProperty, value); }
        }

        private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            WpfControl thisControl = (WpfControl)d;
            WindowsFormsHost wfHost = thisControl.MyHost;
            WindowsFormControl wf = (WindowsFormControl)wfHost.Child;
            wf.WorkYourMagic((Person)e.NewValue);
        }

        public WpfControl()
        {
            InitializeComponent();
        }
    }
}

The WPF Main Window

<Window x:Class="WfControlHostedInWpfControl.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WfControlHostedInWpfControl"
        Title="MainWindow" Height="200" Width="300">

    <StackPanel>
        <local:WpfControl ComplexPoco="{Binding Person}" />
        <Button Content="Show me the magic!" Command="{Binding ShowMagic}" />
    </StackPanel>
    
</Window>

Here we initialize WpfControl we created earlier and bind the dependency property ComplexPoco to Person property in the view-model. The button is bound to ShowMagic command in the view-model. Every time we click on the button, a random Person object will be displayed in the WPF Control.

Code behind:

namespace WfControlHostedInWpfControl
{
    using System.Windows;

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            DataContext = new ViewModel();
        }
    }
}

The view-model:

namespace WfControlHostedInWpfControl
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Windows.Input;

    public class ViewModel : INotifyPropertyChanged
    {
        private Person person;
        private readonly List<Person> repository;

        public ViewModel()
        {
            repository = new List<Person>
            {
                new Person { Name = "John", Age = 20 }, 
                new Person { Name = "Amber", Age = 24 },
                new Person { Name = "Sally", Age = 30 }, 
                new Person { Name = "Abe", Age = 50 }
            };

            ShowMagic = new RelayCommand(PickRandomPerson);
        }

        public Person Person
        {
            get { return person; }
            set
            {
                person = value;
                OnPropertyChanged("Person");
            }
        }

        public ICommand ShowMagic { get; set; }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        private void PickRandomPerson()
        {
            int index = new Random().Next(0, repository.Count);
            Person = repository[index];
        }
    }
}

RelayCommand is a helper class implementing ICommand as described here.

There you have it! A simplified toy example of the actual problem I was dealing with.

Source Code

https://github.com/velianarie/WfControlHostedInWpfControl