WinForms应用程序在用户界面(UI)方面的能力相较于WPF有所限制。但这是否意味着需要将整个应用程序从WinForms迁移到WPF呢?答案是不一定的。有时候,可以通过使用WPF控件来轻松地给应用程序一个“整容”。
如果想要升级Windows表单应用程序,可以考虑HTML5而不是WPF。尽管个人非常喜欢使用WPF,但是升级到HTML5可以让以更低的成本获得一个多平台的Web应用程序,特别是如果使用转置工具自动进行升级的话。已经做过很多这样的项目,这确实比重写为WPF要容易得多。
然而,与此同时,可以使用“托管”——将WPF控件嵌入到Windows表单应用程序中,以便在Windows表单应用程序中利用WPF的丰富性(以及经过一些练习后的易用性)。此外,在WPF中创建复杂的控件通常比在Windows表单中创建它们要花费更少的时间。
MSDN有一篇关于“在Windows表单中托管WPF复合控件”的文章,但认为它过于复杂了。在这里展示的是一个简单的例子,如何在Windows表单应用程序中托管WPF控件,这种方式简单易学,并且从项目架构的角度来看也更加合适。
让从编写WPF控件开始。目标是一个简单的组合框控件,包含具有“客户”属性的“网格样式”属性:
<UserControl x:Class="WindowsFormsControlLibrary1.ComboBoxWithGrid" 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="250">
<Grid>
<ComboBox x:Name="comboBox" Margin="4" ItemsSource="{Binding Customers}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label BorderThickness="1,1,0,1" BorderBrush="Black" Content="{Binding Path=Name}"/>
<Label BorderThickness="1,1,0,1" BorderBrush="Black" Content="{Binding Path=Address}"/>
<Label BorderThickness="1" BorderBrush="Black" Content="{Binding Path=TelephoneNumber}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</UserControl>
如上XAML代码所示,定义了一个以客户为数据源的组合框。组合框有一个项目模板,用于以“网格样式”展示客户详细信息:
在WPF中,这可以通过使用三个标签并将它们的上下文绑定到“Customer”类属性来轻松完成。
public class Customer
{
public string Name { get; set; }
public string Address { get; set; }
public string TelephoneNumber { get; set; }
}
接下来,需要编写WPF控件的代码后端:
public partial class ComboBoxWithGrid : UserControl
{
public ComboBoxWithGrid()
{
InitializeComponent();
SelectedIndex = 0;
this.DataContext = this;
}
public int SelectedIndex
{
get { return comboBox.SelectedIndex; }
set { comboBox.SelectedIndex = value; }
}
public List<Customer> Customers { get; set; }
}
如所见,代码后端非常简单,将DataContext设置为this,将Customers列表暴露给外部和XAML数据绑定,并且只将选择索引暴露给外部。
这一步实际上更简单。创建了一个简单的类,它继承自System.Windows.Forms.Integration.ElementHost:
[Designer("System.Windows.Forms.Design.ControlDesigner, System.Design")]
[DesignerSerializer("System.ComponentModel.Design.Serialization.TypeCodeDomSerializer , System.Design", "System.ComponentModel.Design.Serialization.CodeDomSerializer, System.Design")]
public class ComboBoxWithGrid_WinformsHost : System.Windows.Forms.Integration.ElementHost
{
protected ComboBoxWithGrid m_WPFComboBoxWithGrid = new ComboBoxWithGrid();
public ComboBoxWithGrid_WinformsHost()
{
base.Child = m_WPFComboBoxWithGrid;
}
public int SelectedIndex
{
get { return m_WPFComboBoxWithGrid.SelectedIndex; }
set { m_WPFComboBoxWithGrid.SelectedIndex = value; }
}
public List<Customer> Customers
{
get { return m_WPFComboBoxWithGrid.Customers; }
set { m_WPFComboBoxWithGrid.Customers = value; }
}
}
此类继承自ElementHost对象,该对象将WPF控件作为子元素持有。请注意,已经覆盖了默认的Designer Smart-Tag和DesignerSerializer属性,以防止设计器覆盖Child元素。
首先,只需要将新控件拖放到Windows表单中:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var customers = new List<Customer>();
for (int i = 0; i < 10; i++)
{
customers.Add(new Customer() { Name = "Name" + i, Address = "Address" + i, TelephoneNumber = "TelephoneNumber" + i });
}
this.comboBoxWithGrid_WinformsHost1.Customers = customers;
this.comboBoxWithGrid_WinformsHost1.SelectedIndex = 6;
}
}