Whatever you hear, Windows Forms are not dead. There are so many floating around that this technology will just never die (much like Cobol!).
But Microsoft has slowed down the development on this mature technology. That doesn’t mean that you are limited forever to the controls in your toolbox. Many companies are still building great controls for Windows Forms.
But sometime you find somebody who built a great control in WPF that you would like use in your Windows Forms based application. All is not lost. You can do it and here is how.
The downloadable code
The code is available in both VB and C#.
What you need?
This month demo code has been built using Visual Studio 2013 but can surely be used with older version (probably starting with VS2010 – but not tested).
For the demo, I found a nice free open source charting component called OxyPlot. If you look at the component web site, you will discover that I could have used the Windows Form version of the control but don’t forget that the purpose of this article is to use a WPF control on a Windows Form.
The VS solution
The solution explained here will be composed of 2 projects: a WPF control library and a Windows Forms test application.
The downloadable solution contains 4 projects (2 VB and 2 CS).
The first thing I have done was to create a Windows Forms application. We won’t do anything with the form yet since we need our WPF control before going on with it.
Creating the WPF control
So I have added a WPF User Control Library project to my solution as shown in figure 1.
Figure 1: adding a WPF User Control Library project
The next thing I have was to a reference to the control I wanted to use. This component is offering a NuGet installation so it is easy for us to integrate it into our application. Right-click the WPF project and select “Manage NuGet packages…”. In the dialog, search (top-right textbox) for “oxyplot” and select “OxyPlot.Wpf” from the list as shown in figure 2 and click the Install button.
Figure 2: Adding a reference to the NuGet package
Modify the XAML code to that it looks like figure 3. There should be only 2 changes. The first one is to declare the oxy namespace (xmlns:oxy) and the second one is the control declaration into the grid.
Figure 3: XAML code of the control
Then we need to write some code into the .xaml.vb file to create the MyModel object on which the control is bound in the XAML file:
Option Strict On Imports OxyPlot Public Class UserControl1 Public Sub New() ' This call is required by the designer. InitializeComponent() ' Add any initialization after the InitializeComponent() call. DataContext = Me End Sub Private _model As PlotModel Public Property MyModel() As PlotModel Get Return _model End Get Set(value As PlotModel) _model = value End Set End Property End Class
There are 2 parts to create. The First one is the MyModel property. I declare it with a backing field because I will revisit this property later in this article. The second part is to add “DataContext = Me” as the last line of the constructor.
So far we haven’t done much but it should be enough to create a reusable WPF control usable from a WindowForms application.
Back to the Windows Forms application
The next thing I have done one was to add a reference to the WPF library we just created as shown in figure 4. The library is available under the Solution tab.
Figure 4: add a reference to the WPF library
If you have not done it yet, you will now need to build your solution so that the control just created shows up in the toolbox as shown in figure 5.
Figure 5: The user control appears in the toolbox (after a build)
At this point, you might be tempted to drag an instance of your new control directly to the form. Depending on the control you built, chances are that it might work. In my case, it won’t just work. Instead you fill get an exception like the one shown in figure 6.
Figure 6: Exception when dropping a control instance
When we created the WPF control, we have added a reference to the OxyPlot NuGet package. Wherever you will drag this control, this package will also need to be available. So go ahead and add a reference to the very same OxyPlot.Wpf NuGet package as we did earlier (check figure 2).
You should now safely be able to drag an instance of the control to your Windows Form.
We now have everything except the data we want to display. I have kept this data to be provided by the main application because this one is probably having the data and want to send it to the WPF control.
OxyPlot offers a great online documentation with a bunch of example (plot and code - http://www.oxyplot.org/ExampleBrowser/). I randomly took 2 examples from that site to include them into the sample.
The first example draws 2 line with different colors:
Private Function Example1() As PlotModel Dim plotModel1 As New PlotModel() plotModel1.LegendSymbolLength = 24 plotModel1.Title = "TwoColorLineSeries" Dim linearAxis1 As New LinearAxis() linearAxis1.ExtraGridlines = New Double() {0, 0} linearAxis1.ExtraGridlines(0) = 0 plotModel1.Axes.Add(linearAxis1) Dim linearAxis2 As New LinearAxis() linearAxis2.Position = AxisPosition.Bottom plotModel1.Axes.Add(linearAxis2) Dim twoColorLineSeries1 As New TwoColorLineSeries() twoColorLineSeries1.Color2 = OxyColors.LightBlue twoColorLineSeries1.Color = OxyColors.Red twoColorLineSeries1.MarkerSize = 4 twoColorLineSeries1.MarkerStroke = OxyColors.Black twoColorLineSeries1.MarkerStrokeThickness = 1.5 twoColorLineSeries1.MarkerType = MarkerType.Circle twoColorLineSeries1.StrokeThickness = 3 twoColorLineSeries1.Smooth = True twoColorLineSeries1.Title = "Temperature at Eidesmoen, December 1986." twoColorLineSeries1.Points.Add(New DataPoint(1, 5)) twoColorLineSeries1.Points.Add(New DataPoint(2, 0)) twoColorLineSeries1.Points.Add(New DataPoint(3, 7)) twoColorLineSeries1.Points.Add(New DataPoint(4, 7)) twoColorLineSeries1.Points.Add(New DataPoint(5, 4)) twoColorLineSeries1.Points.Add(New DataPoint(6, 3)) twoColorLineSeries1.Points.Add(New DataPoint(7, 5)) twoColorLineSeries1.Points.Add(New DataPoint(8, 5)) twoColorLineSeries1.Points.Add(New DataPoint(9, 11)) twoColorLineSeries1.Points.Add(New DataPoint(10, 4)) twoColorLineSeries1.Points.Add(New DataPoint(11, 2)) twoColorLineSeries1.Points.Add(New DataPoint(12, 3)) twoColorLineSeries1.Points.Add(New DataPoint(13, 2)) twoColorLineSeries1.Points.Add(New DataPoint(14, 1)) twoColorLineSeries1.Points.Add(New DataPoint(15, 0)) twoColorLineSeries1.Points.Add(New DataPoint(16, 2)) twoColorLineSeries1.Points.Add(New DataPoint(17, -1)) twoColorLineSeries1.Points.Add(New DataPoint(18, 0)) twoColorLineSeries1.Points.Add(New DataPoint(19, 0)) twoColorLineSeries1.Points.Add(New DataPoint(20, -3)) twoColorLineSeries1.Points.Add(New DataPoint(21, -6)) twoColorLineSeries1.Points.Add(New DataPoint(22, -13)) twoColorLineSeries1.Points.Add(New DataPoint(23, -10)) twoColorLineSeries1.Points.Add(New DataPoint(24, -10)) twoColorLineSeries1.Points.Add(New DataPoint(25, 0)) twoColorLineSeries1.Points.Add(New DataPoint(26, -4)) twoColorLineSeries1.Points.Add(New DataPoint(27, -5)) twoColorLineSeries1.Points.Add(New DataPoint(28, -4)) twoColorLineSeries1.Points.Add(New DataPoint(29, 3)) twoColorLineSeries1.Points.Add(New DataPoint(30, 0)) twoColorLineSeries1.Points.Add(New DataPoint(31, -5)) plotModel1.Series.Add(twoColorLineSeries1) Return plotModel1 End Function
This method can now be called from the form’s constructor:
Sub New() ' This call is required by the designer. InitializeComponent() ' Add any initialization after the InitializeComponent() call. UserControl11.MyModel = Example1() End Sub
If you now hit F5, you should see something like figure 7:
Figure 7: a first example running in our application
Now the next step is to change the content of chart while the application is running.
I thought it would have been as easy as adding 2 buttons to my form and using this code:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click UserControl11.MyModel = Example1() End Sub Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click UserControl11.MyModel = Example2() End Sub
But it is not that easy. The code compiles correctly but nothing happens at run time when you click your buttons. The reason why it isn’t working is related to the DataBinding of WPF. We need to add a couple of lines to notify the control when the bound properties is modified.
We first need to revisit the WPF control to add implement the INotifyPropertyChanged interface. The new code now looks like this:
Option Strict On Imports System.ComponentModel Imports OxyPlot Public Class UserControl1 Implements INotifyPropertyChanged Public Sub New() ' This call is required by the designer. InitializeComponent() ' Add any initialization after the InitializeComponent() call. DataContext = Me End Sub Private _model As PlotModel Public Property MyModel() As PlotModel Get Return _model End Get Set(value As PlotModel) _model = value OnPropertyChanged("MyModel") End Set End Property Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged Protected Overloads Sub OnPropertyChanged(ByVal propertyName As String) RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName)) End Sub End Class
Now you can run the application again and test your 2 buttons. The application will now run as expected.
Conclusion
This article shows you how to create a WPF control that can be used from a Windows Forms application. Using this simple technic, you can now offer WPF controls hosted in your Windows Forms and even interact with them.