jump to navigation

Windows forms and WPF Interoperability January 9, 2012

Posted by fofo in Visual Studio 2010, WPF, XAML.
Tags:
trackback

In this post I will not be investigating at what some people call the “Windows single technology applications” which basically means that we build WinForms apps for the windows operating systems using only Windows Forms controls or alternatively build a windows WPF application using only the built-in or 3rd party WPF controls.

I will be looking into the “Mixed technology applications”. In other words how to build Windows Forms applications using both Windows Forms and WPF controls.Moreover how to build WPF applications using both WPF and Windows Forms controls.I will be using C# to build those applications.

I know that many people since the introduction of  WPF have designed and implemented using this awesome new technology introduced with .NET 3.0.

But what about our existing WinForms applications? Are we going to rewrite everything?What about the controls we built for a WinForms application? Are we going to rewrite those too? What about 3rd party controls we have purchased? We do not want to spend time and money to write functionality that we already have got.

In a nutshell we want to have interoperability and that it is possible with the current technologies.It is possible to reuse than recreate.

Let’s start with our first example. In this example I will show how to add an existing Windows Form to a WPF application.

1) Launch Visual Studio. I will be using Visual Studio 2010 Ultimate edition.

2) Create a new WPF application and give it a name of your choice. Choose C# as the development language.

3) Add a new item to your application, a new Window(WPF). Name it Footballers.xaml

4) Add the following xaml code to the MainWindow.xaml file

<StackPanel>
<Button Content="Log in" Margin="5,25,5,5"
MaxWidth="200" MinHeight="35"
Name="loginButton"
Click="loginButton_Click"/>
<Button Content="Footballers window" Margin="5,25,5,5"
MaxWidth="200" MinHeight="35"
Name="footballersWindowButton"
Click="footballersWindowButton_Click"/>
<Button Content="Footballers form" Margin="5,25,5,5"
MaxWidth="200" MinHeight="35"
Name="footballersFormButton"
Click="footballersFormButton_Click"/>
<Button Content="About" Margin="5,25,5,5"
MaxWidth="200" MinHeight="35"
Name="aboutButton"
Click="aboutButton_Click"/>
</StackPanel>

5) Run your application so you can have a feeling on what we are going to build.

6) As you see we have a button that when clicked must launch the Footballers window.This is the code to achieve that.

 private void footballersWindowButton_Click(object sender, RoutedEventArgs e)
{
var fooballers = new Footballers();
fooballers.Show();

}

7) We have also added an “About” button. That will launch an About form. We know that Windows Forms have such functionality and we will utilise it. Add a new item to your application, an About Box (Add –> New Item –>Windows Forms –>About Box).Name it AboutForm.References are added automatically to the application and the Windows Forms designer pops up showing the “AboutForm” form.

8) When you select the AboutForm (Windows Form) you will see in the Toolbox area the Windows Forms controls.When you select the .xaml file in the Toolbox the WPF controls will appear.

9) I need to add login functionality to my application. I will use an existing Windows Forms (Login Form) that I have already created. I will add an existing item (a LoginForm.cs)  file to my application.

10) In the MainWindow.xaml we will add the the code required to launch the Login form and the About form.The code follows.

private void loginButton_Click(object sender, RoutedEventArgs e)
{
var login = new LoginForm();
login.Show();
}

private void aboutButton_Click(object sender, RoutedEventArgs e)
{
var about = new AboutForm();
about.Show();
}

11) Run your application and click the relevant buttons to launch the windows.

12) As you see it is pretty easy to add Window Forms to a WPF application. Maybe they use a different graphical subsystem to render but it is very easy to launch window forms from a WPF application. Now I would like to show you how easy it is to share information between the various forms of the application. I want to store the login name in a variable and show it in the MainWindow. Add a class file to your application. Name it General.cs. This is the code for the static class. Basically we define a string (LoginName).

public static class General
{
public static string LoginName = string.Empty;

}

13) Now we will add some code in the LoginForm.cs that will store the username the user enters in the form.This is the code

private void OK_Click(object sender, EventArgs e)
{

General.LoginName = UsernameTextBox.Text;
this.Close();

}

14) The next step is to change the code in the MainWindow.xaml.cs so that when the


private void loginButton_Click(object sender, RoutedEventArgs e)
{
var login = new LoginForm();

if (login.ShowDialog() == System.Windows.Forms.DialogResult.OK)
MessageBox.Show("Welcome " + General.LoginName);
}

15) Run your application and click the Log in button. Add a new username and a password and click “OK”. You will see the Message box popping up with the name you entered in the login form.With this simple example you see how easy it is to share information among the two forms that are part of different window technologies.

16) Now let’s add some functionality to the button”Footballers Window”.

We will get data from a database and display it in the Footballers.xaml window. But we will need a database first. So in my case I have created a small database with a single table.

This is the T-SQL code you need to execute so you can have the same data.


USE [master]
GO

/****** Object:  Database [football]    Script Date: 01/07/2012 15:22:13 ******/
CREATE DATABASE [football] ON  PRIMARY
( NAME = N'football', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\football.mdf' , SIZE = 3072KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
LOG ON
( NAME = N'football_log', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\football_log.ldf' , SIZE = 1024KB , MAXSIZE = 2048GB , FILEGROWTH = 10%)
GO

ALTER DATABASE [football] SET COMPATIBILITY_LEVEL = 100
GO

IF (1 = FULLTEXTSERVICEPROPERTY('IsFullTextInstalled'))
begin
EXEC [football].[dbo].[sp_fulltext_database] @action = 'enable'
end
GO

ALTER DATABASE [football] SET ANSI_NULL_DEFAULT OFF
GO

ALTER DATABASE [football] SET ANSI_NULLS OFF
GO

ALTER DATABASE [football] SET ANSI_PADDING OFF
GO

ALTER DATABASE [football] SET ANSI_WARNINGS OFF
GO

ALTER DATABASE [football] SET ARITHABORT OFF
GO

ALTER DATABASE [football] SET AUTO_CLOSE OFF
GO

ALTER DATABASE [football] SET AUTO_CREATE_STATISTICS ON
GO

ALTER DATABASE [football] SET AUTO_SHRINK OFF
GO

ALTER DATABASE [football] SET AUTO_UPDATE_STATISTICS ON
GO

ALTER DATABASE [football] SET CURSOR_CLOSE_ON_COMMIT OFF
GO

ALTER DATABASE [football] SET CURSOR_DEFAULT  GLOBAL
GO

ALTER DATABASE [football] SET CONCAT_NULL_YIELDS_NULL OFF
GO

ALTER DATABASE [football] SET NUMERIC_ROUNDABORT OFF
GO

ALTER DATABASE [football] SET QUOTED_IDENTIFIER OFF
GO

ALTER DATABASE [football] SET RECURSIVE_TRIGGERS OFF
GO

ALTER DATABASE [football] SET  DISABLE_BROKER
GO

ALTER DATABASE [football] SET AUTO_UPDATE_STATISTICS_ASYNC OFF
GO

ALTER DATABASE [football] SET DATE_CORRELATION_OPTIMIZATION OFF
GO

ALTER DATABASE [football] SET TRUSTWORTHY OFF
GO

ALTER DATABASE [football] SET ALLOW_SNAPSHOT_ISOLATION OFF
GO

ALTER DATABASE [football] SET PARAMETERIZATION SIMPLE
GO

ALTER DATABASE [football] SET READ_COMMITTED_SNAPSHOT OFF
GO

ALTER DATABASE [football] SET HONOR_BROKER_PRIORITY OFF
GO

ALTER DATABASE [football] SET  READ_WRITE
GO

ALTER DATABASE [football] SET RECOVERY FULL
GO

ALTER DATABASE [football] SET  MULTI_USER
GO

ALTER DATABASE [football] SET PAGE_VERIFY CHECKSUM
GO

ALTER DATABASE [football] SET DB_CHAINING OFF
GO

USE [football]
GO
/****** Object:  Table [dbo].[footballers]    Script Date: 01/07/2012 15:21:51 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[footballers](
[id] [int] IDENTITY(1,1) NOT NULL,
[firstname] [varchar](50) NOT NULL,
[lastname] [varchar](50) NOT NULL,
[isActive] [bit] NULL,
PRIMARY KEY CLUSTERED
(
[id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO

SET IDENTITY_INSERT [dbo].[footballers] ON
INSERT [dbo].[footballers] ([id], [firstname], [lastname], [isActive]) VALUES (1, N'Robbie', N'Fowler', 1)
INSERT [dbo].[footballers] ([id], [firstname], [lastname], [isActive]) VALUES (2, N'Steven', N'Gerrard', 1)
INSERT [dbo].[footballers] ([id], [firstname], [lastname], [isActive]) VALUES (3, N'Kenny', N'Dalglish', 0)
SET IDENTITY_INSERT [dbo].[footballers] OFF

}

Connect to the local instance of SQL Server and in a new query window execute the T-SQL code provided above.

17) In the Visual Studio go to Data–>Add New Data Source…Add a new data source.In the wizard select Database and click Next. Then select Dataset and click Next.In the next step create a new connection that points to the football database.Press OK. In the wizard click Next and Next again.In the database objects window choose footballers table and finally Finish.Drag and drop the Data Sources window on the Footballers window.Please have a look at the generated code in the Footballers.xaml and Footballers.xaml.cs files.

In my case the code in the Footballers.xaml.cs is the following.

private void Window_Loaded(object sender, RoutedEventArgs e)
{

WindowsFormToWPF.footballDataSet footballDataSet = ((WindowsFormToWPF.footballDataSet)(this.FindResource("footballDataSet")));
// Load data into the table footballers. You can modify this code as needed.
WindowsFormToWPF.footballDataSetTableAdapters.footballersTableAdapter
footballDataSetfootballersTableAdapter = new WindowsFormToWPF.footballDataSetTableAdapters.footballersTableAdapter();
footballDataSetfootballersTableAdapter.Fill(footballDataSet.footballers);
System.Windows.Data.CollectionViewSource footballersViewSource =
((System.Windows.Data.CollectionViewSource)(this.FindResource("footballersViewSource")));
footballersViewSource.View.MoveCurrentToFirst();
}

The code for the Footballers.xaml follows

<Window.Resources>
<my:footballDataSet x:Key="footballDataSet" />
<CollectionViewSource x:Key="footballersViewSource"
Source="{Binding Path=footballers, Source={StaticResource footballDataSet}}" />
</Window.Resources>
<Grid DataContext="{StaticResource footballersViewSource}">
<DataGrid AutoGenerateColumns="False" EnableRowVirtualization="True" Height="134"
HorizontalAlignment="Left" ItemsSource="{Binding}" Margin="12,21,0,0" Name="footballersDataGrid"
RowDetailsVisibilityMode="VisibleWhenSelected" VerticalAlignment="Top" Width="222">
<DataGrid.Columns>
<DataGridTextColumn x:Name="idColumn" Binding="{Binding Path=id}" Header="id" IsReadOnly="True" Width="SizeToHeader" />
<DataGridTextColumn x:Name="firstnameColumn" Binding="{Binding Path=firstname}" Header="firstname" Width="SizeToHeader" />
<DataGridTextColumn x:Name="lastnameColumn" Binding="{Binding Path=lastname}" Header="lastname" Width="SizeToHeader" />
<DataGridCheckBoxColumn x:Name="isActiveColumn" Binding="{Binding Path=isActive}" Header="is Active" Width="SizeToHeader" />
</DataGrid.Columns>
</DataGrid>
</Grid>

18) Run your application and click on the “Footballers window” button to see the new window populated with data from the dataset.

19) Now we need to add some functionality to the fourth button “Footballers form”. Add another form (Windows Form) to your application.Name it FootballersForm.cs

Bring the form on the designer.From the Data Sources window, click Details from the drop-down and then drag and drop it on the FootballersForm window.Have a look at the code in the FootballersForm.cs file. Now we need to add some code to launch this Windows Form window from the MainWindow.xaml file.

This is the code for the footballersFormButton_Click event handling routine.

private void footballersFormButton_Click(object sender, RoutedEventArgs e)
{
var FootballersForm = new FootballersForm();

FootballersForm.Show();
}

20) Run your application and click on all buttons to see all the windows popping up. You can see how we can incorporate Window Forms in a WPF project.

21) At this point I would like to mention something that every developer who will try “Interop” should know. Launch Spy++ and move it over a Windows form window. You will see so many handles available for almost any control on the form. You can check that yourself by launching Spy++ and trying to find the handle for the various controls of the Windows Forms windows. You will see that you will get only one handle for the WPF windows.

Have a look at the pictures below to see what I mean.

22) Another thing worth pointing out is how keyboard input is handled in a Windows form window that is part of a WPF application. Launch your application again. Click on the “Footballers form” window and in the window that pops up, try to use the tab key to move between the fields of the record. You will notice that you cannot do that.That is because the WPF application (host application) receives the keyboard input messages but does not know how to pass that keyboard input message to the Windows Form window. Let’s fix that. First we need to add a reference to the WindowsFormIntegration assembly.Now we will add some code to the App.xaml and App.xaml.cs files.

In the App.xaml file we add the Startup application event.Have a look at the code below.

<Application x:Class="WindowsFormToWPF.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml" Startup="Application_Startup"  >

We add the event handling routine for the event in the App.xaml.cs

private void Application_Startup(object sender, StartupEventArgs e)
{
WindowsFormsHost.EnableWindowsFormsInterop();
           //not necessary when running on windows vista or 7. But needed when you run on windows XP
System.Windows.Forms.Application.EnableVisualStyles();
}
}

As you can see I use the WindowsFormsHost class that allows us to host a Windows Forms control on a WPF application.

23)  Launch your application again. Click on the “Footballers form” window and in the window that pops up, try to use the tab key to move between the fields of the record. You will be successful this time.

24) Now we can see another example where you can add an existing WPF window to a Windows Forms application.We need to add some references and Visual studio open the WPF window in the XAML designer.We will use the ElementHost class that can be used to host a Windows Presentation Foundation (WPF) element inside a Windows Forms application.Close this project/application and create a new Windows Forms application.Now we need to add a new WPF window. We are going to use an existing one, the one we created in the previous application, Footballers.xaml. In order to add all the necessary references to the Windows  Forms application, in the Xaml designer drag and drop a label control on the Footballers.xaml.When you do that all the necessary references are added.Some of them are (Accessibility,PresentationCore, PresentationFramework).Now remove the label control.Add a button on the Windows form (main windows form-Form1.cs).In the button1_click event handling routine type

private void button1_Click(object sender, EventArgs e)
{
var footballers = new Footballers();

footballers.Show();
}

25) Now run your application and click on the button. The WPF window will show just fine.Try to move up and down the records using the up and down arrows from the keyboard. That will not work for the same reasons I explained before. The Windows Form (main – host application) receives the input messages from the keyboard but does not know how to pass them on to the WPF window.Let’s fix that.We need to add a reference to our application. We need to add a reference to the WindowsFormsIntegration assembly.In the button1_click event handling routine we need to add another line of code. The complete code follows.

private void button1_Click(object sender, EventArgs e)
{
var footballers = new Footballers();
            System.Windows.Forms.Integration.ElementHost.EnableModelessKeyboardInterop(footballers);
footballers.Show();
}

26) Now let’s run the application again. Click on the button and in the WPF window move up and down the records using the up and down arrows from the keyboard. You will be successful this time.

27) We can also add a Windows Forms built-in or user control to a WPF window and vice versa.I will demonstrate that with additional examples.

28) I will be showing your first how to add a Windows Forms control in a WPF window.In order to do that I will be using the WindowsFormsHost class in the System.Windows.Forms.Integration namespace.

29) Create a new WPF application and choose a suitable name for it. Choose C# as the development language. Add references to the System.Windows.Forms and WindowsFormIntegration assemblies.I will be adding in XAML the assembly reference for Window Forms and mscorlib assemblies.

 xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
xmlns:sys="clr-namespace:System;assembly=mscorlib"

30) Now in the XAML I will be adding a MaskedTextBox control that does not exist in the WPF ecosystem.This is the code in the MainWindow.xaml

<WindowsFormsHost>

   <wf:MaskedTextBox x:Name="maskedTextBox" Mask="(999)-0000000" ValidatingType="{x:Type sys:Int32}" ></wf:MaskedTextBox>

  </WindowsFormsHost>

Run your application and you will see the masked textbox appearing with no problems at all.

31) Finally I am going to provide an example on how to use WPF controls in WinForms Apps.Obviously we need to add references to the needed assemblies.We are going to use the ElementHost control. Add a new folder in your application, a WPF user control.Name it FrameWorkControl.xaml.

32) Αdd a reference to the System.Xaml assembly.

33) The code for the FrameWorkControl.xaml follows. I am basically having a ListBox control with some TextBlock elements inside a stackpanel.

    <Grid>   <ListBox Margin="5" Grid.Row="1" Grid.Column="0"
Name="DotNetListBox"
SelectionChanged="DotNetListBox_SelectionChanged">
<StackPanel Margin="0,0,0,5" Orientation="Horizontal">
<Label Content="ASP.Net" Padding="10" Margin="10" FontFamily="Batang" FontSize="16"></Label>
<TextBlock MaxWidth="500" Margin="10,0,0,0" TextWrapping="Wrap" Tag="ASP.Net"
Text="ASP.NET is the next generation ASP, but it's not an upgraded version of ASP. ASP.NET is an entirely new
technology for server-side scripting" Width="265" Height="66"></TextBlock>
</StackPanel>
<StackPanel Margin="0,0,0,5" Orientation="Horizontal">
<Label Content="WPF" Padding="10" Margin="10" FontFamily="Batang" FontSize="16"></Label>
<TextBlock MaxWidth="500" Margin="10,0,0,0" TextWrapping="Wrap" Tag="WPF"
Text="Windows Presentation Foundation, WPF, provides a unified framework for building applications and
high-fidelity experiences in Windows that blend application UI, documents, and media content" Width="300" Height="94"></TextBlock>
</StackPanel>
</ListBox>
</Grid>

34) For the DotNetListBox_SelectionChanged event handling routine type.

public string SelectedTechnology { get; set; }

        private void DotNetListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
        TextBlock dotnetText = ((StackPanel)DotNetListBox.SelectedValue).Children.OfType<TextBlock>().First();
        this.SelectedTechnology = dotnetText.Tag.ToString();
        }
    }
}

35) Drag and drop an ElementHost control.Click the smarttag icon and in the “Selected Hosted Content:” select the FrameWork control.Now our user control is inside (hosted) inside the Windows Forms application.Resize appropriately.

36) Now add a button in the main form. In the click event handling routine type

private void button1_Click(object sender, EventArgs e)
        {
            MessageBox.Show(string.Format( "You selected {0}",((FrameWorkControl)elementHost1.Child).SelectedTechnology));
        }

Run your application. Select you favourite technology and click the button. Your favourite technology will pop up.

I understand that this is a very long post but hopefully you will find it very interesting.

Leave a comment with your email if you need the source code.

Hope it helps!!!!

Comments»

1. Dot Net Rules : Windows forms and WPF Interoperability - January 9, 2012

[…] alternatively build a windows WPF application using only the built-in or 3rd party WPF controls. (read more) Share Posted: Δευτέρα, 9 Ιανουαρίου 2012 1:56 μμ από το […]

2. hulinning2 - March 13, 2013

would you send me the source code for this mixed app? thanks a lot. my email: hulinning2@yahoo.com


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: