Excerpt from Professional VB 2005 with .NET 3.0
Windows Presentation Foundation (WPF) Controls in Visual Basic 2005 Windows Forms
by Bill Sheldon
Yes, again you will need to transition existing application source code to a new technology paradigm. Perhaps not this year or next, but at some point the WPF paradigm will be used to update the look and feel of existing applications. So how will this transition compare to the last major .NET-related transition — the one from COM? Whereas Visual Studio includes a tool to attempt to migrate code from the COM-based world to .NET, there will not be a migration tool provided to transition existing user interfaces to WPF. You might consider this to be a good thing, considering the history of the current migration tools. However, Microsoft is providing libraries to allow user-interface developers to integrate these two interface models. In the long run this integration will probably go the way of COM-Interop. Which is to say it'll be available but will carry such a stigma that people will only use it when absolutely necessary.
In our new book, Professional VB 2005 with .NET 3.0 (Wrox, 2007, ISBN: 978-0-470-12470-3), Chapter 18, "Integrating WPF with Windows Forms," takes you through several key areas of WPF integration with Windows Forms including Forms integration — Crossbow, Using Windows Forms controls in WPF, Interop limitations, and the subject of this article - Using WPF controls in Windows Forms.
Hosting WPF Controls in Windows Forms
Allowing you to host WPF Controls within your existing Windows Forms-based applications is key to giving you an opportunity to introduce new functionality that requires the capabilities of WPF without forcing you to entirely rewrite your application. In this way even as you work on upgrading an existing application to WPF you aren't forced to take on a single large project. As for the integration itself, it isn't page- or window-based, although you can introduce new WPF windows to an existing application. The integration is focused on allowing you to incorporate new controls into your existing application.
Accordingly, the model is based around the idea that you can encapsulate the functionality of a new WPF control as a User Control. This has a couple of key advantages, the first being that if you've been working with .NET you are already familiar with user controls and how they function. Once again the paradigms of previous user-interface models appear and are reused within WPF. The second big advantage to having this modeled around user controls is that as more of your application moves to WPF you don't have to rewrite the user controls you create today when later they are used within a pure WPF environment.
At some point you'll move the user control outside of the integration
ElementHost control which will host it in Windows Forms. However, the underlying control can remain unchanged. To demonstrate integration it is assumed that you have downloaded and installed the "Visual Studio 2005 extensions for .NET Framework 3.0 (WCF & WPF), November 2006 CTP" and its prerequisites including .NET Framework 3.0. This package updates Visual Studio 2005 and provides three new templates for creating WPF projects. The one which you will need for Windows Forms Integration is the Windows Control Library.
However, the first step is to open Visual Studio 2005 and go to the New Project dialog. On this dialog select the Windows category of templates and create a new Windows Forms application. For example purposes you can name this
ProVBWinForm_Interop. Visual Studio will use the template to create a new Windows Forms project. At this point, using the File menu, choose to add a second project to your solution.
This time select the .NET Framework 3.0 category and instead of creating a new application create a new Custom Control Library (WPF). For demonstration purposes you can use the name
InteropControlLibrary. When you are done the Visual Studio Solution Explorer will look similar to what is shown in Figure 1. The next step is to add the customization to the newly created WPF library after which the Windows Form application will be updated to reference the integration library and the new WPF Control.
The first step in customization is to add a splitter to the control's surface area. You may find that your newly created control has no actual display area. This is because the control is designed to automatically adapt to display controls placed on it. For the purposes of this example, you can edit the XAML for your control and add
Width attributes to the User Control. For demonstration purposes you can use a height of 281 pixels and a width of 230 characters. Just as an aside note that instead of assigning a size to the user control, you could assign a fixed size to the grid with the same results.
Next the plan is to divide this grid. By selecting the display area for your control, you'll find that if you move your mouse over the left edge small horizontal and vertical windows will appear around your control. As you move your mouse up and down within this display are you'll find that a horizontal line and a positional arrow appear. On either side of the point representing your mouse you'll see numbers in the small vertical window representing the number of pixels in the display above and below your cursor respectively. Finally by clicking in that window you'll create a Grid.RowDefinition.
<Grid.RowDefinitions> <RowDefinition Height="0.106761565836298*" /> <RowDefinition Height="0.893238434163702*" /> </Grid.RowDefinitions>
The preceding lines of XAML demonstrate the type of addition to the Grid control definition you should see. In looking at the generated XAML you'll note that each of the two rows which have been defined represent a percentage of the screen. Thus as the screen resizes these areas will both increase in height. You can change this default behavior by changing the
Height attribute of the row definitions. Instead of using a percentage you could in fact use an integer value representing a number of pixels, and if only one of the two rows is assigned this Height attribute then the remaining row would automatically expand to fill the remaining display area. The
Grid.RowDefinition settings create the boundary between the toolbar and the main display area for the user control shown in Figure 1.
As shown in Figure 1 the goal is to create a control which has the appearance of a tool bar with a button and drop-down list box sitting above a text edit control. Once you have the grid row positioned in the upper quarter of the control's display area, drag a
RichTextBox into the bottom portion of the display area. You can give it a set of 0 or near-0 margins, and delete any
Width properties for this control. This will bind the control to the borders of its display area. For this example the control's definition should include the statement
Grid.Row="1". Since the grid contains two rows and since the rows are viewed as a zero-based array of display panes, this places the control in the second pane, without this definition, as you'll see with the toolbar, items are associated with
Once you have done this, the next step is to add a new
ToolBar control to the display. Similar to the way in which you just added a
RichTextBox control to the lower pane, now add a
ToolBar control to the upper window. Again edit the
Margin settings, making it so the
ToolBar fills the entire display area. Next drag a
Button control from the toolbox into the display area of the
Unfortunately the default behavior of the designer doesn't currently layer this
Button within the
ToolBar. So you'll want to edit the XAML again. In this case you'll need to remove the closing slash "/" from the
ToolBar declaration and then add a separate closing tag for the
ToolBar. Place this closing tag on the line below your newly added
Button control. Then edit the margin settings for the button so that you bind the display within the toolbar and fill the vertical area of the toolbar. To better manage the size of the
Button, add a
Width property set to a value such as 75. Also change the display value of the text for the button to "Edit Note".
Next add a
ComboBox to the display next to the button. If when you drag and drop it on the form it isn't automatically placed on the toolbar move the default definition of the
ComboBox within the definition of the toolbar. Again edit the
Margin settings. However, in this case the first value isn't the distance in pixels from the left edge of the display area; instead it is the number of pixels from the control to the left. Thus a number such as 5 indicates that the left edge of the
ComboBox should be placed 5 pixels from the right edge of the
Edit Note button. In order to finish defining the display of the
ComboBox, set its remaining margin settings to 0 and assign it a default width of 120 pixels.
The preceding steps define the simple control shown in Figure 1 and the complete XAML is shown in the following code block. This also completes the definition of your sample WPF control library, so compile your application to ensure there aren't any pending errors.
<UserControl x:Class="UserControl1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Height="281" Width="230"> <Grid><Grid.RowDefinitions> <RowDefinition Height="0.106761565836298*" /> <RowDefinition Height="0.893238434163702*" /> </Grid.RowDefinitions> <RichTextBox Margin="1,0,0,0" Name="RichTextBox1" Grid.Row="1" /> <ToolBar Margin="0,0,0,0" Name="ToolBar1" > <Button HorizontalAlignment="Left" Margin="2,0,0,0" Name="Button1" Width="75">Edit Note</Button> <ComboBox Margin="5,0,0,0" Width="120" Name="ComboBox1" /> </ToolBar> </Grid> </UserControl>
The next step is to start customizing the Windows Forms application. This starts by adding the three required references for you to be able to embed this control. The first is the
Windows.Forms.Integration library, the second is the .NET 3.0
PresentationCore library and the final is the
InteropControlLibrary. As has been shown, you can go into the project properties for your Windows Forms project and go to the references tab.
Choose to add references, and in the list of available .NET libraries you'll find the
PresentationCore.dll available as a reference. There are other Presentation libraries also available from this screen, and you can choose to add these to your project as well.
Next using the Browse button navigate to your
C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0 and add the
WindowsFormsIntegration.dll assembly to your project. Finally, switch to the project references tab and add a reference to your local project.
Next go to the design view for the
Form1.vb file that was created by the Windows Forms template when you created this project. Extend the default size of the design surface with the size of your control in mind. Make sure that the form has enough height to place a command
Button below the WPF user control. And then drag and drop a button onto your form. This demonstration isn't going to customize this button or its events, but it does simplify the next step.
In looking through the toolbox you will probably find that the
ElementHost control associated with the interface library isn't available from the toolbox. The solution to this is to add it directly into
Forms1.Designer.vb. To access this form, use the Solution Explorer to display all files and then looking below
Form1.vb you'll find the design file. Double click to open the file and you'll see the generated partial class definition associate with your form. The next code block contains the bottom half of this file.
'NOTE: The following procedure is required by the Windows Form Designer 'It can be modified using the Windows Form Designer. 'Do not modify it using the code editor. <System.Diagnostics.DebuggerStepThrough()> _ Private Sub InitializeComponent() Me.Button1 = New System.Windows.Forms.Button Me.ElementHost1 = New System.Windows.Forms.Integration.ElementHost Me.SuspendLayout() ' 'Button1 ' Me.Button1.Location = New System.Drawing.Point(96, 238) Me.Button1.Name = "Button1" Me.Button1.Size = New System.Drawing.Size(75, 23) Me.Button1.TabIndex = 0 Me.Button1.Text = "Button1" Me.Button1.UseVisualStyleBackColor = True ' 'ElementHost1 ' Me.ElementHost1.Location = New System.Drawing.Point(0, 0) Me.ElementHost1.Name = "ElementHost1" Me.ElementHost1.Size = New System.Drawing.Size(300, 230) Me.ElementHost1.TabIndex = 1 Me.ElementHost1.BackColor = System.Drawing.SystemColors.Control ' 'Form1 ' Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font Me.ClientSize = New System.Drawing.Size(292, 273) Me.Controls.Add(Me.Button1) Me.Controls.Add(Me.ElementHost1) Me.Name = "Form1" Me.Text = "Form1" Me.ResumeLayout(False) End Sub Friend WithEvents Button1 As System.Windows.Forms.Button Private WithEvents ElementHost1 As System.Windows.Forms.Integration.ElementHost End Class
At this point you are going to disregard the note which is included in this file and edit this file in order to add the
ElementHost control to your form. Working from top to bottom in this file, the first step is to add a new instance of the
ElementHost control. This is done with the line below.
Me.ElementHost1 = New System.Windows.Forms.Integration.ElementHost
This line assigns an instance of an
ElementHost control to the variable
ElementHost1 which you will define later in this process. However, the next step is to copy the information associated with the
Button which was added in order to define the characteristics of your newly added control. The definition of the button's properties provides a template which you can then customize to define the properties for the host control.
' 'ElementHost1 ' Me.ElementHost1.Location = New System.Drawing.Point(0, 0) Me.ElementHost1.Name = "ElementHost1" Me.ElementHost1.Size = New System.Drawing.Size(300, 230) Me.ElementHost1.TabIndex = 1 Me.ElementHost1.BackColor = System.Drawing.SystemColors.Control
As is illustrated in the preceding code block, the location of the control can be placed in the upper left corner of the display; the name of the control is assigned to match the variable name and the size is set large enough to encapsulate your entire WPF control's display area. The
TabIndex is optional and in this case can be set to 1, which leaves only the back color. Note that instead of the property defined for the button, here the property being referenced is
BackColor. In the sample code above, it is set to match the default background color of the form. However, if you want to better see the bounds of your
ElementHost control you can easily set this to a different color.
The next step is to add your new control instance to the array of controls contained on the form. This is done with the preceding line of code. It adds your instance of the
ElementHost control to the control array that is referenced by the windows form. The final step is to add the declaration of the
ElementHost1 variable. This is placed along with the declaration of your button control at the bottom of the file. This declaration needs to include the
WithEvents modifier, since you will want your application to be able to raise and respond to events associated with the contents of the host control.
Private WithEvents ElementHost1 As System.Windows.Forms.Integration.ElementHost
You may notice that we haven't added an instance of the actual WPF control which you actually want to embed in your application yet. The reason for this has to do with the properties that
ElementHost currently exposes in Visual Studio 2005. If you save your changes to
Form1.Designer.vb and switch to design view for Form1, your display should look similar to the one shown in Figure 2. Note the expanded properties window on the right hand side. You'll notice this currently is set to display/edit the properties for
As you look through these properties, you'll see that this list of properties looks similar to the list which is available for other Windows Forms controls. You can modify the color settings of the host control and generally customize its layout details. Visual Studio will automatically regenerate the
Form1.Designer.vb file. The problem is that the
Child property of the
ElementHost control is not in this list. This is a problem because if you do attempt to place a reference to that property into the designer-generated code, then whenever anyone changes one of these properties in the designer, Visual Studio will regenerate that code block, and since it doesn't recognize that property it will not regenerate it.
The result is that with this version of the WPF extensions for Visual Studio you need to assign the
Child property in the
Load event of the form. Thus you need to double click on the surface of your form and have Visual Studio generate the
Form1_Load event handler. To this event handler add the line of code which instantiates an instance of your
InteropControlLibrary UserControl1 object as the
Child control for your
Element Host control.
Public Class Form1 Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load Me.ElementHost1.Child = New InteropControlLibrary.UserControl1() End Sub End Class
With the code shown above in place, it's time to build and run your application. When you review the design display you'll note that your embedded WPF control does not display in design view since it is not loaded. However after building and running your control you should see a display similar to the one shown in Figure 3.
This illustrates how you can create a new WPF component which can be incorporated into an existing Windows Forms application. Thus you can start the process of migrating your application to WPF while still focusing the majority of your available resources on adding new capabilities to your existing application. Migration means you are not forced to attempt to spend the majority of your cycles rewriting your existing interface and can instead start by integrating these two display methodologies. The preceding example demonstrated one way of working with a WPF control within a Windows Forms application.
This article is excerpted from Chapter 18, "Integrating WPF with Windows Form,s" of the book Professional VB 2005 with .NET 3.0 (Wrox, 2007, ISBN: 978-0-470-12470-3), by Bill Evjen, Billy Hollis, Bill Sheldon, Kent Sharkey, and Tim McCarthy. Bill Sheldon is a software architect and engineer. Holding a degree in computer science from the Illinois Institute of Technology (IIT), Bill has been actively employed as a software engineer since resigning his commission with the United States Navy. He is employed as a principal engineer with InterKnowlogy in Carlsbad, California, and also works as an instructor for Visual Basic and Team System-related courses at the University of California San Diego Extension. In addition to writing books, Bill has published dozens of articles including the Developer Update Newsletter and occasional feature articles with SQL Server magazine and other Penton publications. He is an established online presenter for MSDN and speaks at live events such as Microsoft Developer Days and community events such as user groups and code camp. Bill is an avid cyclist and active in the fight against diabetes.