Showing posts with label DependencyProperty. Show all posts
Showing posts with label DependencyProperty. Show all posts

Saturday, May 30, 2015

Windows Form MVVM databinding

I inherited a complex Windows Form UserControl that contains a DataGridView showing a matrix data structure like this.

The code behind takes care of the generation of the headers and cell values. It also takes care of the cell color scheme based on the value of the cell.

Because of the complexity and the fact that there's no out-of-the-box DataGridView implementation for WPF application, I decided to reuse this Windows Form UserControl in a WPF application I was working on. The problem is that Windows Form does not support data binding in WPF MVVM context. We will need to do the plumbing ourselves to be able to use the data binding functionality. One way to do this is as follows:

  1. Wrap the Windows Form UserControl in a WPF UserControl by using WindowsFormsHost
  2. Create DependencyProperty in this WPF UserControl, which we can use to bind to a viewmodel
  3. Use PropertyChangedCallback to modify property or state in the DataGridView control
  4. Use events in the DataGridView control to return property or state back to the DependencyProperty in the WPF UserControl

I'm posting a simple version of the original source code to illustrate this. Let's start with the business objects.

The Business Objects

    public class TenorStrikeRate
    {
        public TenorStrikeRate(string tenor, double strike, double rate)
        {
            Tenor = tenor;
            Strike = strike;
            Rate = rate;
        }

        public string Tenor { get; set; }

        public double Strike { get; set; }

        public double Rate { get; set; }
    }
    public class TenorStrikeRates : IEnumerable<TenorStrikeRate>
    {
        private readonly SortedList<Tuple<string, double>, TenorStrikeRate> internalData;

        public TenorStrikeRates()
        {
            internalData = new SortedList<Tuple<string, double>, TenorStrikeRate>();
        }

        public List<string> UniqueSortedTenors
        {
            get { return internalData.Values.Select(x => x.Tenor).Distinct().ToList(); }
        }

        public List<double> UniqueSortedStrikes
        {
            get { return internalData.Values.Select(x => x.Strike).Distinct().ToList(); }
        }

        public void Add(TenorStrikeRate toBeAddedItem)
        {
            Tuple<string, double> key = new Tuple<string, double>(toBeAddedItem.Tenor, toBeAddedItem.Strike);
            if (internalData.ContainsKey(key))
            {
                internalData.Remove(key);
            }

            internalData.Add(key, toBeAddedItem);
        }

        public TenorStrikeRate Find(string tenor, double strike)
        {
            return internalData.Values.FirstOrDefault(x => IsTenorEqual(x.Tenor, tenor) && IsDoubleEqual(x.Strike, strike));
        }

        public IEnumerator<TenorStrikeRate> GetEnumerator()
        {
            IEnumerator<TenorStrikeRate> iterator = internalData.Values.GetEnumerator();
            while (iterator.MoveNext())
            {
                yield return iterator.Current;
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj))
                return false;
            if (ReferenceEquals(this, obj))
                return true;
            if (obj.GetType() != typeof(TenorStrikeRates))
                return false;

            TenorStrikeRates other = (TenorStrikeRates)obj;
            return IsAllItemInListEqual(internalData.Values, other.internalData.Values);
        }
        
        private bool IsAllItemInListEqual(IList<TenorStrikeRate> thisList, IList<TenorStrikeRate> otherList)
        {
            bool isEqual = thisList.Count.Equals(otherList.Count);
            for (int index = 0; index < otherList.Count && isEqual; index++)
            {
                TenorStrikeRate thisItem = thisList[index];
                TenorStrikeRate otherItem = otherList[index];
                isEqual = IsTenorEqual(thisItem.Tenor, otherItem.Tenor) 
                    && IsDoubleEqual(thisItem.Strike, otherItem.Strike)
                    && IsDoubleEqual(thisItem.Rate, otherItem.Rate);
            }

            return isEqual;
        }

        private bool IsDoubleEqual(double one, double two)
        {
            if (double.IsNaN(one) && double.IsNaN(two))
            {
                return true;
            }

            return Math.Abs(one - two) < 1e-15;
        }

        private bool IsTenorEqual(string one, string two)
        {
            return one.Equals(two, StringComparison.InvariantCultureIgnoreCase);
        }
    }

The Windows Form UserControl

The Windows Form UserControl contains a DataGridView named "dgv". Set(TenorStrikeRates source) populates the DataGridView. GetDataGridSource() returns the current source state of the DataGridView. In this class we also need to declare a public event to return the current data grid source to the subscribers. A situation where we want to return the current data grid source is when users perform a data grid cell editing operation. We can then hook up this public event to the DataGridView CellEndEdit event which occurs when edit mode stops for the currently selected cell.

    public partial class DataGridViewUc : System.Windows.Forms.UserControl
    {
        public DataGridViewUc()
        {
            InitializeComponent();
        }

        public delegate void ReturnGridSourceEventHandler(TenorStrikeRates currentGridSource);

        public event ReturnGridSourceEventHandler ReturnDataGridSource;

        public void Set(TenorStrikeRates source)
        {
            dgv.Columns.Clear();
            dgv.Rows.Clear();
            if (source != null)
            {
                PopulateColumnHeader(source.UniqueSortedStrikes);
                PopulateRowHeader(source.UniqueSortedTenors);
                PopulateCells(source);
            }
        }

        public TenorStrikeRates GetDataGridSource()
        {
            TenorStrikeRates quotes = new TenorStrikeRates();
            foreach (DataGridViewColumn column in dgv.Columns)
            {
                string columnHeaderValue = column.HeaderText;
                if (columnHeaderValue != null)
                {
                    double strike = ReadStrike(columnHeaderValue);
                    foreach (DataGridViewRow row in dgv.Rows)
                    {
                        object rowHeaderValue = row.HeaderCell.Value;
                        if (rowHeaderValue != null)
                        {
                            string tenor = rowHeaderValue.ToString();
                            object rateValue = row.Cells[column.Name].Value;
                            double rate = rateValue == null ? double.NaN : Convert.ToDouble(rateValue);
                            quotes.Add(new TenorStrikeRate(tenor, strike, rate));
                        }
                    }
                }
            }

            return quotes;
        }

        private void PopulateColumnHeader(List<double> strikes)
        {
            foreach (double strike in strikes)
            {
                string headerText = strike.ToString("P2");
                dgv.Columns.Add(headerText, headerText);
            }
        }

        private void PopulateRowHeader(List<string> tenors)
        {
            int numberOfRows = tenors.Count;
            dgv.Rows.Add(numberOfRows);
            for (int i = 0; i < numberOfRows; i++)
            {
                dgv.Rows[i].HeaderCell.Value = tenors[i];
            }
        }

        private void PopulateCells(TenorStrikeRates quotes)
        {
            List<string> tenors = quotes.UniqueSortedTenors;
            List<double> strikes = quotes.UniqueSortedStrikes;
            for (int rowIdx = 0; rowIdx < tenors.Count; rowIdx++)
            {
                string optionTenor = tenors[rowIdx];
                for (int colIdx = 0; colIdx < strikes.Count; colIdx++)
                {
                    double strike = strikes[colIdx];
                    TenorStrikeRate quote = quotes.Find(optionTenor, strike);
                    if (quote != null)
                    {
                        double rate = quote.Rate;
                        DataGridViewCell cell = dgv.Rows[rowIdx].Cells[colIdx];
                        cell.Value = rate;
                    }
                }
            }
        }
        
        private double ReadStrike(string input)
        {
            string percentSymbol = Thread.CurrentThread.CurrentCulture.NumberFormat.PercentSymbol;
            input = input.Replace(percentSymbol, string.Empty);
            return double.Parse(input, Thread.CurrentThread.CurrentCulture.NumberFormat) / 100;
        }

        private void dgv_CellEndEdit(object sender, DataGridViewCellEventArgs e)
        {
            if (ReturnDataGridSource != null)
            {
                TenorStrikeRates newSource = GetDataGridSource(); 
                ReturnDataGridSource(newSource);
            }
        }
    }

The WPF Host

To be able to bind the DataGridView UserControl to a viewmodel, we will need to wrap it in a WPF UserControl by using WindowsFormsHost. Then we define DependencyProperty GridSource that we can bind to a property in the viewmodel. Next we define PropertyChangedCallback OnGridSourcePropertyChanged, which is called whenever GridSource property changed. In the callback, we pass in the new source to the DataGridView so it can update its display. The WPF UserControl needs to subscribe to the DataGridView's ReturnDataGridSource event to make sure any changes in the DataGridView is propagated to the WPF host layer.

XAML:


<UserControl x:Class="DataGridViewHostedInWpfControl.WpfHost.DataGridViewUcHost"
             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:DataGridViewHostedInWpfControl.WinFormLayer"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    
    <WindowsFormsHost x:Name="MyWinFormsHost">
        <local:DataGridViewUc />
    </WindowsFormsHost>

</UserControl>

Code behind:

    public partial class DataGridViewUcHost : System.Windows.Controls.UserControl
    {
        public static readonly DependencyProperty GridSourceProperty = DependencyProperty.Register(
             "GridSource",
             typeof(TenorStrikeRates),
             typeof(DataGridViewUcHost),
             new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnGridSourcePropertyChanged));

        public TenorStrikeRates GridSource
        {
            get { return (TenorStrikeRates)GetValue(GridSourceProperty); }
            set { SetValue(GridSourceProperty, value); }
        }

        private static DataGridViewUc dgvUc;

        public DataGridViewUcHost()
        {
            InitializeComponent();

            dgvUc = (DataGridViewUc)MyWinFormsHost.Child;
            dgvUc.ReturnDataGridSource += OnReturnDataGridSource;
        }

        private static void OnGridSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TenorStrikeRates newGridSource = (TenorStrikeRates)e.NewValue;
            TenorStrikeRates currentGridSource = dgvUc.GetDataGridSource();
            if (!currentGridSource.Equals(newGridSource))
            {
                dgvUc.Set(newGridSource);
            }
        }

        private void OnReturnDataGridSource(TenorStrikeRates currentGridSource)
        {
            GridSource = currentGridSource;
        }
    }

The MainWindow

Now let's build a small demo. Create a StackPanel containing the WPF control that hosts our DataGridView UserControl. Add a TextBox to the StackPanel. Everytime we change a value in the data grid, the change will be shown in the TextBox.

XAML:


<Window x:Class="DataGridViewHostedInWpfControl.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DataGridViewHostedInWpfControl.WpfHost"
        Title="MainWindow" Height="350" Width="525">
    
    <StackPanel>
        <Label Content="Input" />
        <local:DataGridViewUcHost GridSource="{Binding Input}" MinWidth="300" MinHeight="100" Margin="10" />
        <Label Content="Output" />
        <TextBox Text="{Binding Output}" />
    </StackPanel>

</Window>

Code behind:

    public partial class MainWindow : System.Windows.Window
    {
        public MainWindow()
        {
            MainViewModel vm = new MainViewModel();
            DataContext = vm;

            InitializeComponent();

            vm.Input = BuildInitialInput();
        }

        private TenorStrikeRates BuildInitialInput()
        {
            var quotes = new TenorStrikeRates();
            quotes.Add(new TenorStrikeRate("1y", -0.01, 0.01));
            quotes.Add(new TenorStrikeRate("1y", 0, 0.02));
            quotes.Add(new TenorStrikeRate("1y", 0.01, 0.03));
            quotes.Add(new TenorStrikeRate("2y", -0.01, 0.04));
            quotes.Add(new TenorStrikeRate("2y", 0, 0.05));
            quotes.Add(new TenorStrikeRate("2y", 0.01, 0.06));
            return quotes;
        }
    }

ViewModel:

    public class MainViewModel : INotifyPropertyChanged
    {
        private TenorStrikeRates input;
        public TenorStrikeRates Input
        {
            get { return input; }
            set
            {
                input = value;
                OnPropertyChanged("Input");
                Output = BuildOutputValue(value);
            }
        }

        private string output;
        public string Output
        {
            get { return output; }
            set
            {
                output = value;
                OnPropertyChanged("Output");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

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

        private string BuildOutputValue(TenorStrikeRates tenorStrikeRates)
        {
            string text = string.Empty;
            foreach (TenorStrikeRate item in tenorStrikeRates)
            {
                text = text + string.Format("Tenor: {0}, Strike: {1}, Rate: {2}\n", item.Tenor, item.Strike.ToString("P2"), item.Rate);
            }

            return text;
        }
    }

Source Code

https://github.com/velianarie/DataGridViewHostedInWpfControl

Wednesday, April 1, 2015

WPF DataGrid Dynamic Numeric Formatting

I have a simple WPF DataGrid containing numbers that I want to format dynamically based on the state of 2 other WPF controls:

  • UserControl with 2 buttons: the precision
  • ComboBox: the format specifier


The User Control: Precision Adjuster

XAML:
<UserControl x:Class="DataGridFormatting.PrecisionAdjuster"
             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"
             mc:Ignorable="d"
             d:DesignHeight="50" d:DesignWidth="100">

    <UserControl.Resources>
        <Style TargetType="{x:Type Button}">
            <Setter Property="Width" Value="30" />
            <Setter Property="Height" Value="30" />
            <Setter Property="Margin" Value="3" />
        </Style>   
    </UserControl.Resources>

    <WrapPanel>
        <Button Command="{Binding ReducePrecision}">
            <Image Source="Resources/DecimalLess.png" />
        </Button>
        <Button Command="{Binding IncreasePrecision}">
            <Image Source="Resources/DecimalMore.png" />
        </Button>
    </WrapPanel>
</UserControl>

Code behind:
namespace DataGridFormatting
{
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    
    public partial class PrecisionAdjuster : UserControl
    {
        public static readonly DependencyProperty PrecisionProperty = DependencyProperty.Register(
            "Precision",
            typeof(int),
            typeof(PrecisionAdjuster),
            new PropertyMetadata(default(int)));

        public int Precision
        {
            get { return (int)GetValue(PrecisionProperty); }
            set { SetValue(PrecisionProperty, value); }
        }

        public PrecisionAdjuster()
        {
            InitializeComponent();

            DataContext = new PrecisionAdjusterViewModel();

            Binding binding = new Binding("Precision") { Mode = BindingMode.TwoWay };
            SetBinding(PrecisionProperty, binding);
        }
    }
}

ViewModel:
namespace DataGridFormatting
{
    using System.ComponentModel;
    using System.Windows.Input;
  
    public class PrecisionAdjusterViewModel : INotifyPropertyChanged
    {
        private int precision;

        public PrecisionAdjusterViewModel()
        {
            Precision = 2;
            ReducePrecision = new RelayCommand(OnReducePrecision);
            IncreasePrecision = new RelayCommand(OnIncreasePrecision);
        }
 
        public int Precision
        {
            get { return precision; }
            set
            {
                precision = value;
                OnPropertyChanged("Precision");
            }
        }

        public ICommand ReducePrecision { get; set; } 

        public ICommand IncreasePrecision { get; set; }

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

        private void OnReducePrecision()
        {
            if (Precision > 0)
            {
                Precision--;
            }
        }

        private void OnIncreasePrecision()
        {
            Precision++;
        }
    }
}

In the code behind, we bind property Precision of the view-model with DependencyProperty Precision of the UserControl. The DependencyProperty Precision can be set outside of this UserControl, for example as the initial precision value. If this is not set, the default value is 2 as defined in the view-model. Everytime we click the button to increase or reduce precision, the precision is adjusted accordingly within the view-model. RelayCommand is a helper class implementing ICommand as described here.

The Main Window

For simplicity, I'm not going to create view-model for the MainWindow. I'll put the test data and the property we're binding to in the code behind:
namespace DataGridFormatting
{
    using System.Collections.ObjectModel;
    using System.Windows;

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            DataGridSource = new ObservableCollection<ItemRate>
            {
                new ItemRate("AAA", 3.0123456789),
                new ItemRate("BBB", 1.8478913937),
                new ItemRate("CCC", 2.3891383276),
                new ItemRate("DDD", 1.2334392431)
            };

            InitializeComponent();
        }

        public ObservableCollection<ItemRate> DataGridSource { get; set; }
    }
}
XAML:
(Note: when you copy and paste the below XAML to Visual Studio, it'll not recognize NumericFormatConverter. But read on, we'll create this in the next section.)
<Window x:Class="DataGridFormatting.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DataGridFormatting"
        Title="MainWindow" Height="250" Width="200"
        Name="ThisWindow">

    <Window.Resources>
        <local:NumericFormatConverter x:Key="MyNumericFormatConverter" />
    </Window.Resources>

    <StackPanel DataContext="{Binding ElementName=ThisWindow}">
        <StackPanel Orientation="Horizontal" Margin="10">
            <TextBlock Text="Choose format:   "/>
            <ComboBox x:Name="FormatSpecifierInput" Width="40">
                <ComboBoxItem Content="N" IsSelected="True" />
                <ComboBoxItem Content="P" />
                <ComboBoxItem Content="C" />
            </ComboBox>
        </StackPanel>

        <local:PrecisionAdjuster x:Name="MyPrecisionAdjuster" Precision="4" HorizontalAlignment="Center" />

        <DataGrid ItemsSource="{Binding DataGridSource}"
                  AutoGenerateColumns="False" Margin="10" HorizontalAlignment="Center">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Items" Binding="{Binding Item}" />
                <DataGridTemplateColumn Header="Rates">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock>
                                <TextBlock.Text>
                                    <MultiBinding Converter="{StaticResource MyNumericFormatConverter}">
                                        <Binding Path="Rate" />
                                        <Binding ElementName="FormatSpecifierInput" Path="Text" />
                                        <Binding ElementName="MyPrecisionAdjuster" Path="Precision" />
                                    </MultiBinding>
                                </TextBlock.Text>
                            </TextBlock>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
    </StackPanel>
</Window>

First we create a ComboBox containing the format we want to display our rates in. In this example, the possible values are N (Number), P (Percent) and C (Currency). I'm using .NET standard numeric format strings as described here. Name the ComboBox FormatSpecifierInput, so that we can bind this to the DataGrid later on.

Next we put the UserControl PrecisionAdjuster we created earlier into the Window. Name this control MyPrecisionAdjuster, set the initial Precision to 4.

The DataGrid has 2 columns with ItemsSource bound to property DataGridSource of type ObservableCollection<ItemRate>. The first column is DataGridTextColumn bound to property Item of class ItemRate.

    public class ItemRate
    {
        public ItemRate(string item, double rate)
        {
            Item = item;
            Rate = rate;
        }

        public string Item { get; set; }

        public double Rate { get; set; }
    }

The second column is a DataGridTemplateColumn TextBlock with Text property bound to:

  1. Property Rate of ObservableCollection<ItemRate> (bound to property DataGridSource)
  2. Property Text of the ComboBox FormatSpecifierInput
  3. DependencyProperty Precision of the UserControl MyPrecisionAdjuster
Use MultiBinding to describe a collection of Binding objects attached to a single binding target property. The Converter property is a IMultiValueConverter. We define the class that implements IMultiValueConverter in Windows.Resources. Note that at this point in time, we have yet to create this multi value converter class. Let's name this class NumericFormatConverter.


The IMultiValueConverter implementation: NumericFormatConverter

namespace DataGridFormatting
{
    using System;
    using System.Globalization;
    using System.Windows.Data;

    public class NumericFormatConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            double number = System.Convert.ToDouble(values[0]);
            string formatSpecifier = System.Convert.ToString(values[1]);
            int precision = System.Convert.ToInt16(values[2]);
            string format = "{0:" + formatSpecifier + precision + "}";  // eg. {0:P4}, {0:N2} 
            return string.Format(format, number);
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

Note that the order of object[] values is important. If we change the MultiBinding order in the XAML to something like this for example:

<MultiBinding Converter="{StaticResource MyNumericFormatConverter}">
    <Binding ElementName="FormatSpecifierInput" Path="Text" />
    <Binding Path="Rate" />
    <Binding ElementName="MyPrecisionAdjuster" Path="Precision" />
</MultiBinding>

Then we'll need to change NumericFormatConverter to:
    string formatSpecifier = System.Convert.ToString(values[0]);
    double number = System.Convert.ToDouble(values[1]);
    int precision = System.Convert.ToInt16(values[2]);

You might want to add some validation in the Convert method to warn your fellow developers to pay attention to the order.

Source Code

https://github.com/velianarie/DataGridFormatting

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