WPF 并非从头开始重写它。 一种常见情况是希望在 Windows 窗体应用程序内嵌入使用 WPF 实现的一个或多个控件。 有关自定义 WPF 控件的详细信息,请参阅
控件自定义
。
本讲解将分步介绍承载 WPF 复合控件的应用程序,以在 Windows 窗体应用程序中执行数据输入。 复合控件打包在一个 DLL 中。 此常规步骤可扩展到更复杂的应用程序和控件。 本讲解旨在实现与
讲解:在 WPF 中承载 Windows 窗体复合控件
中几乎完全相同的外观和功能。 主要区别在于承载方案是相反的。
本演练分为两个部分。 第一部分简要介绍了 WPF 复合控件的实现。 第二部分详细讨论了如何在 Windows 窗体应用程序中承载复合控件、接收来自控件的事件以及访问控件的某些属性。
本演练涉及以下任务:
实现 WPF 复合控件。
实现 Windows 窗体主机应用程序。
有关本讲解所涉及任务的完整代码列表,请参阅
在 Windows 窗体中承载 WPF 复合控件示例
。
若要完成本演练,必须具有 Visual Studio。
实现 WPF 复合控件
此示例中使用的 WPF 复合控件是一种简单的数据输入窗体,用于获取用户的姓名和地址。 当用户单击两个按钮的其中一个以指示任务已完成时,该控件会引发将该信息返回给主机的自定义事件。 下图显示呈现的控件。
下图显示了 WPF 复合控件:
启动项目:
启动 Visual Studio,然后打开“新建项目”对话框。
在“Visual C# 和 Windows”类别中,选择“WPF 用户控件库”模板。
将新项目命名为
MyControls
。
对于位置,指定可以方便命名的顶层文件夹,如
WindowsFormsHostingWpfControl
。 随后,将主机应用程序放在此文件夹中。
单击“确定”以创建该项目 。 默认项目包含一个名为
UserControl1
的控件。
在解决方案资源管理器中,将
UserControl1
重命名为
MyControl1
。
项目应具有对以下系统 DLL 的引用。 如果默认未包含其中任何 DLL,请将它们添加到项目中。
PresentationCore
PresentationFramework
WindowsBase
创建用户界面
UI 由五个
TextBox
元素组成。 每个
TextBox
元素具有关联的
TextBlock
元素用作标签。 底部有两个
Button
元素,“确定”和“取消”。 当用户单击任一按钮时,该控件会引发将信息返回给主机的自定义事件。
Grid
元素中包含各种 UI 元素。 可以使用
Grid
以排列复合控件的内容,方法与在 HTML 中使用
Table
元素大致相同。 WPF 还具有
Table
元素,但
Grid
更轻量级,更适合于简单的布局任务。
以下 XAML 演示基本布局。 此 XAML 通过指定
Grid
元素中的列号和行号来定义控件的整体结构。
在 MyControl1.xaml 中,将现有 XAML 替换为以下 XAML。
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MyControls.MyControl1"
Background="#DCDCDC"
Width="375"
Height="250"
Name="rootElement"
Loaded="Init">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
</Grid>
向 Grid 添加 TextBlock 和 TextBox 元素
通过将元素的
RowProperty
和
ColumnProperty
特性设置到适当的行号和列号,可以在网格中放置 UI 元素。 请记住行号和列号是从零开始的。 通过设置元素
ColumnSpanProperty
属性,可以让其跨越多个列。 有关
Grid
元素的更多信息,请参阅
创建网格元素
。
以下 XAML 显示复合控件的
TextBox
和
TextBlock
元素及其
RowProperty
和
ColumnProperty
特性,设置这些属性是为了在网格中正确放置元素。
在 MyControl1.xaml 中,在
Grid
元素内添加以下 XAML。
<TextBlock Grid.Column="0"
Grid.Row="0"
Grid.ColumnSpan="4"
Margin="10,5,10,0"
HorizontalAlignment="Center"
Style="{StaticResource titleText}">Simple WPF Control</TextBlock>
<TextBlock Grid.Column="0"
Grid.Row="1"
Style="{StaticResource inlineText}"
Name="nameLabel">Name</TextBlock>
<TextBox Grid.Column="1"
Grid.Row="1"
Grid.ColumnSpan="3"
Name="txtName"/>
<TextBlock Grid.Column="0"
Grid.Row="2"
Style="{StaticResource inlineText}"
Name="addressLabel">Street Address</TextBlock>
<TextBox Grid.Column="1"
Grid.Row="2"
Grid.ColumnSpan="3"
Name="txtAddress"/>
<TextBlock Grid.Column="0"
Grid.Row="3"
Style="{StaticResource inlineText}"
Name="cityLabel">City</TextBlock>
<TextBox Grid.Column="1"
Grid.Row="3"
Width="100"
Name="txtCity"/>
<TextBlock Grid.Column="2"
Grid.Row="3"
Style="{StaticResource inlineText}"
Name="stateLabel">State</TextBlock>
<TextBox Grid.Column="3"
Grid.Row="3"
Width="50"
Name="txtState"/>
<TextBlock Grid.Column="0"
Grid.Row="4"
Style="{StaticResource inlineText}"
Name="zipLabel">Zip</TextBlock>
<TextBox Grid.Column="1"
Grid.Row="4"
Width="100"
Name="txtZip"/>
设置 UI 元素的样式
数据输入窗体上的许多元素外观相似,这意味着它们对于其若干属性具有相同的设置。 前一个 XAML 会使用
Style
元素为元素的类定义标准属性设置,而不是分别设置每个元素的特性。 这种方法可以降低控件的复杂性,并使你能够通过单个样式特性更改多个元素的外观。
Style
元素包含着
Grid
元素的
Resources
属性中,因此可用于控件中的所有元素。 如果命名了某样式,可以将
Style
元素集添加到样式的名称,将其应用到某个元素。 未命名的样式将成为该元素的默认样式。 有关样式的详细信息,请参阅
样式设置和模板化
。
以下 XAML 演示了复合控件的
Style
元素。 若要查看样式如何应用于元素,请参阅前一个 XAML。 例如,最后
TextBlock
元素具有
inlineText
样式,且最后
TextBox
元素使用默认样式。
在 MyControl1.xaml 中,在
Grid
开始元素后添加以下 XAML。
<Grid.Resources>
<Style x:Key="inlineText" TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="10,5,10,0"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="FontSize" Value="12"/>
</Style>
<Style x:Key="titleText" TargetType="{x:Type TextBlock}">
<Setter Property="DockPanel.Dock" Value="Top"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Margin" Value="10,5,10,0"/>
</Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Margin" Value="10,5,10,0"/>
<Setter Property="Width" Value="60"/>
</Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Margin" Value="10,5,10,0"/>
</Style>
</Grid.Resources>
复合控件上的最后两个元素是“确定”和“取消”
Button
元素,它们占用了
Grid
最后一行的前两列。 这些元素使用一个公共事件处理程序,
ButtonClicked
,和在前一个 XAML 中定义的默认
Button
样式。
在 MyControl1.xaml 中,在最后
TextBox
元素后添加以下 XAML。 复合控件的 XAML 部分现已完成。
<Button Grid.Row="5"
Grid.Column="0"
Name="btnOK"
Click="ButtonClicked">OK</Button>
<Button Grid.Row="5"
Grid.Column="1"
Name="btnCancel"
Click="ButtonClicked">Cancel</Button>
实现代码隐藏文件
代码隐藏文件 MyControl1.xaml.cs 实现了三个基本任务:
处理用户单击其中一个按钮时发生的事件。
从
TextBox
元素检索数据,并将其打包在自定义事件参数对象中。
引发自定义
OnButtonClick
事件,该事件会通知主机用户已完成,并将数据传递回主机。
该控件还公开多个可用来更改外观的颜色和字体属性。 与用于托管 Windows 窗体控件的
WindowsFormsHost
类不同,
ElementHost
类只公开控件
Background
的属性。 为了保持此代码示例与
演练:在 WPF 中托管 Windows 窗体复合控件
中所讨论示例之间的相似性,该控件会直接公开其余属性。
代码隐藏文件的基本结构
该代码隐藏文件由单个命名空间
MyControls
组成,它包含以下两个类:
MyControl1
和
MyControlEventArgs
。
namespace MyControls
public partial class MyControl1 : Grid
//...
public class MyControlEventArgs : EventArgs
//...
第一个类 MyControl1 是一个分部类,其中包含实现 MyControl1.xaml 中定义的 UI 功能的代码。 当解析 MyControl1.xaml 时,XAML 会转换为相同的分部类,且这两个分部类会合并形成编译的控件。 出于此原因,代码隐藏文件中的类名必须与分配给 MyControl1.xaml 的类名相匹配,并且它必须继承自控件的根元素。 第二个类 MyControlEventArgs 是一个事件参数类,用于将数据发送回主机。
打开 MyControl1.xaml.cs。 更改现有的类声明,使其具有以下名称并继承自 Grid。
public partial class MyControl1 : Grid
初始化控件
下面的代码实现几个基本任务:
声明一个专用事件 OnButtonClick 及其关联的委托 MyControlEventHandler。
创建几个存储用户数据的私有全局变量。 此数据通过相应的属性公开。
为控件的 Loaded 事件实现处理程序 Init。 此处理程序通过向全局变量分配 MyControl1.xaml 中定义的值来对它们进行初始化。 为此,它使用分配给典型 TextBlock 元素 nameLabel 的 Name,以访问该元素的属性设置。
删除现有的构造函数并将以下代码添加到 MyControl1 类。
public delegate void MyControlEventHandler(object sender, MyControlEventArgs args);
public event MyControlEventHandler OnButtonClick;
private FontWeight _fontWeight;
private double _fontSize;
private FontFamily _fontFamily;
private FontStyle _fontStyle;
private SolidColorBrush _foreground;
private SolidColorBrush _background;
private void Init(object sender, EventArgs e)
//They all have the same style, so use nameLabel to set initial values.
_fontWeight = nameLabel.FontWeight;
_fontSize = nameLabel.FontSize;
_fontFamily = nameLabel.FontFamily;
_fontStyle = nameLabel.FontStyle;
_foreground = (SolidColorBrush)nameLabel.Foreground;
_background = (SolidColorBrush)rootElement.Background;
用户通过单击“确定”按钮或“取消”按钮指示数据输入任务已完成。 这两个按钮使用相同的 Click 事件处理程序 ButtonClicked。 这两个按钮有一个名称:btnOK 或 btnCancel,使处理程序能够通过检查 sender 参数的值来确定单击了哪个按钮。 该处理程序执行以下任务:
创建一个 MyControlEventArgs 对象,用以包含来自 TextBox 元素的数据。
如果用户单击了“取消”按钮,则将 MyControlEventArgs 对象的 IsOK 属性设置为 false。
引发 OnButtonClick 事件,向主机指示用户已完成,并且传回所收集的数据。
在 Init 方法后,将以下代码添加到 MyControl1 类。
private void ButtonClicked(object sender, RoutedEventArgs e)
MyControlEventArgs retvals = new MyControlEventArgs(true,
txtName.Text,
txtAddress.Text,
txtCity.Text,
txtState.Text,
txtZip.Text);
if (sender == btnCancel)
retvals.IsOK = false;
if (OnButtonClick != null)
OnButtonClick(this, retvals);
类的其余部分只是公开对应于前面所述的全局变量的属性。 当属性更改时,set 访问器会通过更改对应的元素属性并更新基础全局变量来修改控件的外观。
将以下代码添加到 MyControl1 类。
public FontWeight MyControl_FontWeight
get { return _fontWeight; }
_fontWeight = value;
nameLabel.FontWeight = value;
addressLabel.FontWeight = value;
cityLabel.FontWeight = value;
stateLabel.FontWeight = value;
zipLabel.FontWeight = value;
public double MyControl_FontSize
get { return _fontSize; }
_fontSize = value;
nameLabel.FontSize = value;
addressLabel.FontSize = value;
cityLabel.FontSize = value;
stateLabel.FontSize = value;
zipLabel.FontSize = value;
public FontStyle MyControl_FontStyle
get { return _fontStyle; }
_fontStyle = value;
nameLabel.FontStyle = value;
addressLabel.FontStyle = value;
cityLabel.FontStyle = value;
stateLabel.FontStyle = value;
zipLabel.FontStyle = value;
public FontFamily MyControl_FontFamily
get { return _fontFamily; }
_fontFamily = value;
nameLabel.FontFamily = value;
addressLabel.FontFamily = value;
cityLabel.FontFamily = value;
stateLabel.FontFamily = value;
zipLabel.FontFamily = value;
public SolidColorBrush MyControl_Background
get { return _background; }
_background = value;
rootElement.Background = value;
public SolidColorBrush MyControl_Foreground
get { return _foreground; }
_foreground = value;
nameLabel.Foreground = value;
addressLabel.Foreground = value;
cityLabel.Foreground = value;
stateLabel.Foreground = value;
zipLabel.Foreground = value;
将数据发送回主机
文件中的最后一个组件是 MyControlEventArgs 类,该类用于将收集的数据发送回主机。
将以下代码添加到 MyControls 命名空间。 该实现非常简单明了,因而不再进一步讨论。
public class MyControlEventArgs : EventArgs
private string _Name;
private string _StreetAddress;
private string _City;
private string _State;
private string _Zip;
private bool _IsOK;
public MyControlEventArgs(bool result,
string name,
string address,
string city,
string state,
string zip)
_IsOK = result;
_Name = name;
_StreetAddress = address;
_City = city;
_State = state;
_Zip = zip;
public string MyName
get { return _Name; }
set { _Name = value; }
public string MyStreetAddress
get { return _StreetAddress; }
set { _StreetAddress = value; }
public string MyCity
get { return _City; }
set { _City = value; }
public string MyState
get { return _State; }
set { _State = value; }
public string MyZip
get { return _Zip; }
set { _Zip = value; }
public bool IsOK
get { return _IsOK; }
set { _IsOK = value; }
生成解决方案。 生成将产生一个名为 MyControls.dll 的 DLL。
Windows 窗体主机应用程序使用 ElementHost 对象来托管 WPF 复合控件。 该应用程序处理 OnButtonClick 事件以接收来自复合控件的数据。 该应用程序还具有一组选项按钮,可用于修改控件外观。 下图显示应用程序。
下图显示托管在 Windows 窗体应用程序中的 WPF 复合控件
启动项目:
启动 Visual Studio,然后打开“新建项目”对话框。
在“Visual C#”和“Windows”类别中,选择“Windows 窗体应用程序”模板。
将新项目命名为 WFHost。
对于位置,指定包含 MyControls 项目的同一顶层文件夹。
单击“确定”以创建该项目 。
还需要添加对 DLL 的引用,包含 MyControl1 和其他程序集。
在解决方案资源管理器中右键单击项目名称,然后选择“添加引用”。
单击“浏览”选项卡,然后浏览到包含 MyControls.dll 的文件夹。 在本演练中,此文件夹位于 MyControls\bin\Debug。
选择 MyControls.dll,然后单击“确定”。
添加对下列程序集的引用。
PresentationCore
PresentationFramework
System.Xaml
WindowsBase
WindowsFormsIntegration
通常在窗体的 Load 事件处理程序中实现托管代码。 以下代码显示 Load 事件处理程序,这是 WPF 复合控件的 Loaded 事件的处理程序,以及稍后将使用的几个全局变量的声明。
在 Windows 窗体设计器中,双击窗体以创建 Load 事件处理程序。 在 Form1.cs 顶部,添加以下 using 语句。
using System.Windows;
using System.Windows.Forms.Integration;
using System.Windows.Media;
将现有 Form1 类的内容替换为以下代码。
private ElementHost ctrlHost;
private MyControls.MyControl1 wpfAddressCtrl;
System.Windows.FontWeight initFontWeight;
double initFontSize;
System.Windows.FontStyle initFontStyle;
System.Windows.Media.SolidColorBrush initBackBrush;
System.Windows.Media.SolidColorBrush initForeBrush;
System.Windows.Media.FontFamily initFontFamily;
public Form1()
InitializeComponent();
private void Form1_Load(object sender, EventArgs e)
ctrlHost = new ElementHost();
ctrlHost.Dock = DockStyle.Fill;
panel1.Controls.Add(ctrlHost);
wpfAddressCtrl = new MyControls.MyControl1();
wpfAddressCtrl.InitializeComponent();
ctrlHost.Child = wpfAddressCtrl;
wpfAddressCtrl.OnButtonClick +=
new MyControls.MyControl1.MyControlEventHandler(
avAddressCtrl_OnButtonClick);
wpfAddressCtrl.Loaded += new RoutedEventHandler(
avAddressCtrl_Loaded);
void avAddressCtrl_Loaded(object sender, EventArgs e)
initBackBrush = (SolidColorBrush)wpfAddressCtrl.MyControl_Background;
initForeBrush = wpfAddressCtrl.MyControl_Foreground;
initFontFamily = wpfAddressCtrl.MyControl_FontFamily;
initFontSize = wpfAddressCtrl.MyControl_FontSize;
initFontWeight = wpfAddressCtrl.MyControl_FontWeight;
initFontStyle = wpfAddressCtrl.MyControl_FontStyle;
上述代码中的 Form1_Load 方法演示了托管 WPF 控件的常规步骤:
创建新 ElementHost 对象。
将控件的 Dock 属性设置为 DockStyle.Fill。
将 ElementHost 控件添加到 Panel 控件的 Controls 集合。
创建 WPF 控件的实例。
通过将复合控件分配给 ElementHost 控件的 Child 属性在窗体上托管该复合控件。
Form1_Load 方法中的剩余两行将处理程序附加到两个控件事件:
当用户单击“确定”或“取消”按钮时,OnButtonClick 是该复合控件触发的自定义事件。 处理该事件可获取用户的响应并收集用户指定的任何数据。
Loaded 是在完全加载 WPF 控件时引发的标准事件。 此处使用该事件是因为本示例需要使用控件中的属性初始化几个全局变量。 在窗体的 Load 事件时期,该控件不会完全加载,并且这些值仍设置为 null。 需要等到该控件的 Loaded 事件发生之后,才能访问这些属性。
Loaded 事件处理程序显示在前面的代码中。 下一部分将讨论 OnButtonClick 处理程序。
OnButtonClick 事件会在用户单击“确定”或“取消”按钮时发生。
事件处理程序将检查事件参数的 IsOK 字段,以确定单击了哪个按钮。 lbldata 变量对应于前面讨论过的 Label 控件。 如果用户单击“确定”按钮,则来自该控件的 TextBox 控件会分配给对应的 Label 控件。 如果用户单击“取消”按钮,则将 Text 值设置为默认字符串。
将以下按钮单击事件处理程序代码添加到 Form1 类。
void avAddressCtrl_OnButtonClick(
object sender,
MyControls.MyControl1.MyControlEventArgs args)
if (args.IsOK)
lblAddress.Text = "Street Address: " + args.MyStreetAddress;
lblCity.Text = "City: " + args.MyCity;
lblName.Text = "Name: " + args.MyName;
lblState.Text = "State: " + args.MyState;
lblZip.Text = "Zip: " + args.MyZip;
lblAddress.Text = "Street Address: ";
lblCity.Text = "City: ";
lblName.Text = "Name: ";
lblState.Text = "State: ";
lblZip.Text = "Zip: ";
生成并运行应用程序。 在 WPF 复合控件中添加一些文本,然后单击“确定”。 文本将显示在标签中。 此时,尚未添加代码来处理单选按钮。
修改控件的外观
窗体上的 RadioButton 控件使用户能够更改 WPF 复合控件的前景色和背景色以及一些字体属性。 背景色由 ElementHost 对象公开。 其余属性作为控件的自定义属性公开。
双击窗体上的每个 RadioButton 控件以创建 CheckedChanged 事件处理程序。 将 CheckedChanged 事件处理程序替换为以下代码。
private void radioBackgroundOriginal_CheckedChanged(object sender, EventArgs e)
wpfAddressCtrl.MyControl_Background = initBackBrush;
private void radioBackgroundLightGreen_CheckedChanged(object sender, EventArgs e)
wpfAddressCtrl.MyControl_Background = new SolidColorBrush(Colors.LightGreen);
private void radioBackgroundLightSalmon_CheckedChanged(object sender, EventArgs e)
wpfAddressCtrl.MyControl_Background = new SolidColorBrush(Colors.LightSalmon);
private void radioForegroundOriginal_CheckedChanged(object sender, EventArgs e)
wpfAddressCtrl.MyControl_Foreground = initForeBrush;
private void radioForegroundRed_CheckedChanged(object sender, EventArgs e)
wpfAddressCtrl.MyControl_Foreground = new System.Windows.Media.SolidColorBrush(Colors.Red);
private void radioForegroundYellow_CheckedChanged(object sender, EventArgs e)
wpfAddressCtrl.MyControl_Foreground = new System.Windows.Media.SolidColorBrush(Colors.Yellow);
private void radioFamilyOriginal_CheckedChanged(object sender, EventArgs e)
wpfAddressCtrl.MyControl_FontFamily = initFontFamily;
private void radioFamilyTimes_CheckedChanged(object sender, EventArgs e)
wpfAddressCtrl.MyControl_FontFamily = new System.Windows.Media.FontFamily("Times New Roman");
private void radioFamilyWingDings_CheckedChanged(object sender, EventArgs e)
wpfAddressCtrl.MyControl_FontFamily = new System.Windows.Media.FontFamily("WingDings");
private void radioSizeOriginal_CheckedChanged(object sender, EventArgs e)
wpfAddressCtrl.MyControl_FontSize = initFontSize;
private void radioSizeTen_CheckedChanged(object sender, EventArgs e)
wpfAddressCtrl.MyControl_FontSize = 10;
private void radioSizeTwelve_CheckedChanged(object sender, EventArgs e)
wpfAddressCtrl.MyControl_FontSize = 12;
private void radioStyleOriginal_CheckedChanged(object sender, EventArgs e)
wpfAddressCtrl.MyControl_FontStyle = initFontStyle;
private void radioStyleItalic_CheckedChanged(object sender, EventArgs e)
wpfAddressCtrl.MyControl_FontStyle = System.Windows.FontStyles.Italic;
private void radioWeightOriginal_CheckedChanged(object sender, EventArgs e)
wpfAddressCtrl.MyControl_FontWeight = initFontWeight;
private void radioWeightBold_CheckedChanged(object sender, EventArgs e)
wpfAddressCtrl.MyControl_FontWeight = FontWeights.Bold;
生成并运行应用程序。 单击不同的单选按钮来查看在 WPF 复合控件上的效果。
ElementHost
WindowsFormsHost
在 Visual Studio 中设计 XAML
演练:在 WPF 中托管 Windows 窗体复合控件
演练:在 Windows 窗体中承载三维 WPF 复合控件