这个例子主要展示同一个需求用WinForm和WPF分别进行实现,通过这个例子,我们可以看到两者之间的区别和联系,同时也可以对我们的项目选型 带来一定的参考作用(原型来自于Josh Smith的一篇文章,个人觉得讲得非常不错,所以对原有例子进行了改造,进而有了这个案例)。
当然作为一项新技术,WPF带来了很多功能,但在使用这些功能的同时也会带来很多缺点,这是不可避免的,正所谓”有利必有弊“吧!所以我们这个例子 并不是讲WPF有如何如何的好,怎样用WPF代替WinForm,而是从两者实现同一个需求进行简单的对比。
这个例子是用Visual Studio 2008编写的,所以大家可以下载下来进行查看.
这个程序并不是要展现声明优秀的架构也不是为了宣扬WPF的种种好处,所以没有采用当前比较热门的MVP、MVVM模式进行开发,同时项目当中你可 以看到很随意的代码,没有对IOC、AOP以及设计模式进行应用,这也是考虑到具体需求和例子简单的原因,况且这里也没有必要,我们在做项目的时候也要时 刻注意什么时候用什么开发框架、开发模式以及项目整体架构。
这个例子非常简单,需求就是展示三大社区的基本信息,同时你可以在输入框对其进行修改,当焦点切换的时候,你就会看到它会自动进行修改,你把鼠标放 在图片上面会提示社区的ID等等。我在这里没有用复杂的逻辑和高深的架构,只是想通过这个例子展示WinForm的WPF的差异和联系,所以在程序处理上 可能会有很多漏洞,比如没有对输入进行验证,你可以输入空格和任意字符等。
下面是WinForms版本的截图:
下面是WPF版本的截图:
如果你编辑了某个社区的中文名称或者英文名称,然后把焦点移到另外一个地方,这些更改就会通过右上角的全名体现出来,因为他们都是通过绑定到公用字 段来实现这些操作的。
整个项目结构如下图所示:
整个项目一共就三个工程,第一个工程BusinessObjects 是WpfApp和WinFormsApp公用的业务类库,WinFormsApp是用WinForm实现的版本,WpfApp是用WPF实现的版本。那么 我们下面就简单分别进行一些介绍:
这两个应用程序都是使用的BusinessObjects作为逻辑类库,BusinessObjects中的Company对UI所使用的数据进行 了Mock。所以他们在需求方面都是一样的,由于比较简单,所以请看下面代码:
Collapse
using System;
using System.ComponentModel;
using System.IO;
using System.Reflection;
namespace BusinessObjects
{
public class Company : INotifyPropertyChanged
{
#region Creation
public static Company[] GetCompanys()
{
// In a real app this would probably call into a data access layer to get records from a database.
return new Company[]
{
new Company(1, "博客园", "CNBlogs", GetPictureFile(1), new DateTime(2004, 1, 12)),
new Company(2, "51CTO", "51CTO", GetPictureFile(2), new DateTime(2005, 3, 1)),
new Company(3, "CSDN", "CSDN", GetPictureFile(3), new DateTime(2000, 1, 20)),
};
}
private static string GetPictureFile(int CompanyID)
{
string fileName = String.Format("emp{0}.jpg", CompanyID);
string folder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
folder = Path.Combine(folder, "Images");
return Path.Combine(folder, fileName);
}
private Company(int id, string chineseName, string EnglishName, string pictureFile, DateTime startDate)
{
this.ID = id;
this.chineseName = chineseName;
this.EnglishName = EnglishName;
this.PictureFile = pictureFile;
this.StartDate = startDate;
}
#endregion // Creation
#region Properties
public int ID { get; private set; }
string _chineseName;
public string chineseName
{
get { return _chineseName; }
set
{
if (value == _chineseName)
return;
_chineseName = value;
this.OnPropertyChanged("chineseName");
this.OnPropertyChanged("FullName");
}
}
string _EnglishName;
public string EnglishName
{
get { return _EnglishName; }
set
{
if (value == _EnglishName)
return;
_EnglishName = value;
this.OnPropertyChanged("EnglishName");
this.OnPropertyChanged("FullName");
}
}
public string FullName
{
get { return String.Format("{0}, {1}", this.EnglishName, this.chineseName); }
}
public string PictureFile { get; private set; }
public DateTime StartDate { get; private set; }
#endregion // Properties
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
上面这段代码没有什么不寻常的地方,大家写WinForm和Asp.Net也会写这样的逻辑类,只是要注意
Company 实现了
INotifyPropertyChanged 接口,大家看到这个接口只有一个OnPropertyChanged的方 法,这个方法就是我们要说的属性变更通知方法,就是说当一个属性改变了,我们需要做些什么来响应这些改变。
WinForms版本就包含一个
Form
和一个展示社区信息的custom
UserControl
, 这个
Form
包含了一个
FlowLayoutPanel控件
, 它主要的作用就是用来承载每个社区的实例. 那么代码就如下所示:
Collapse
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
// Create and initialize a usercontrol for each Company.
foreach(Company com in Company.GetCompanys())
{
CompanyControl comCtrl = new CompanyControl();
comCtrl.Company = com;
this.flowLayoutPanel.Controls.Add(comCtrl);
}
}
}
CompanyControl是我们创建的一个 UserControl,由于每个
CompanyControl都要显 示一个
Company对象的属性值,我在这里使用了 BindingSource控件来进行绑定,这样做也是为了和WPF更接近考虑(增强对比性,呵呵)。具体如下截图:
如上图所示,我们用了BindingSource来获取数据,但有一个属性除外,那就是
Company ID,请看下面代码:
Collapse
namespace WinFormsApp
{
/// <summary>
/// A WinForms control that displays an Company object.
/// </summary>
public partial class CompanyControl : UserControl
{
public CompanyControl()
{
InitializeComponent();
// Convert the picture file path to a Bitmap.
Binding binding = this.CompanyPicture.DataBindings[0];
binding.Format += this.ConvertFilePathToBitmap;
}
void ConvertFilePathToBitmap(object sender, ConvertEventArgs e)
{
e.Value = Bitmap.FromFile(e.Value as string);
}
public Company Company
{
get { return this.CompanyBindingSource.DataSource as Company; }
set
{
this.CompanyBindingSource.DataSource = value;
// The Company's picture shows a tooltip of their ID.
if (value != null)
{
string msg = "Company ID: " + value.ID;
this.toolTip.SetToolTip(this.CompanyPicture, msg);
}
}
}
}
}
这里有几点需要注意.在绑定的时候,我们对PictureFile 字段进行了转换,这个是必须做的. 如果不那样做, 这个图片会绑定失败,因为在绑定的时候它不能自动把
string类型
直接转化为
Image类型
.
现在我们已经把
Company绑定到了我们的控件上, 这里我需要给
PictureBox一个
tooltip的效果. 这个tooltip将显示
Company
ID
, 前缀显示为 "
Company ID:". 现在这个是在代码里面写的,没有在窗体中发现有WPF ToolTip等类似的工具,不知道大家用到过没有?
总的来说, 这是一个很简单的例子,我们的大部分功能也是用代码没有写代码,是通过visual designer进行实现的.然后通过一部分代码把它衔接起来, 我们看到Windows Forms是一个非常快速和实用的开发平台.
WPF版本我这里就做得很简单了,由于开发WPF程序提供了很多模板和工具,所以我这里基本没写什么代码,全部的代码都是通过XAML实现,并且大 部分都是自动生成的,只是我们要根据项目具体情况做一些修改就行。
这个WPF项目同样有一个
Window
和一个custom
UserControl
, 和 WinForms 版本基本一样. 只是WinForms中用
FlowLayoutPanel
来承载
EmployeeControl
s 控件, 而WPF 用的是
ItemsControl
来承载这个用户控件.更加可喜的是,WPF通过模板来进行定制,所以我们就不需要像WinForms那样写循环加载控件的代码,下面就是WPF用XAML 实现的窗体代码:
Collapse
<Window
x:Class="WpfApp.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp"
xmlns:model="clr-namespace:BusinessObjects;assembly=BusinessObjects"
Title="WPF App" Height="558" Width="581"
WindowStartupLocation="CenterScreen"
>
<Window.DataContext>
<ObjectDataProvider
ObjectType="{x:Type model:Company}"
MethodName="GetCompanys"
/>
</Window.DataContext>
<Grid Width="555">
<Label
Name="label1"
HorizontalContentAlignment="Center" VerticalAlignment="Top"
FontSize="20" FontWeight="Bold"
Height="36.6" Margin="0,16,0,0"
>
.NET 中文社区大比拼</Label>
<ItemsControl
ItemsSource="{Binding}"
HorizontalContentAlignment="Center"
Margin="46,59,25,0"
Focusable="False"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:CompanyControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
在如下的XAML代码中,这里有几点需要注意,。Window的DataContext赋予了一个ObjectDataProvider的对象,而 ObjectDataProvider又会调用GetEmployees这个方法。所以一旦把DataContext设置到
Company 对象,并且把ItemsControl的 ItemsSource设置为“
{Binding}” 就意味着该控件里面会自动显示
Company 对象的所有数据。
这里我们并不需要像WinForm一样用循环的方式创建CompanyControl的实例。这是因为ItemsControl中的 ItemTemplate属性设置为了一个DataTemplate,同时ItemsControl中的ItemsSource绑定到了
Company 的对象数组,那么ItemTemplate就会知 道如何创建一个CompanyControl,所以大家看到这里写的代码就相对变少了,这也是XAML的一个优点之一。
该CompanyControl的后台CS文件也是空的(除了必须的InitializeComponent),所以它不像的WinForms应用 程序那么累赘,界面和逻辑紧密的耦合在了一起。下面就是CompanyControl的XAML代码, 这个代码相对来说就比较简单了。
Collapse
<UserControl x:Class="WpfApp.CompanyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="137" Width="481">
<Border
BorderBrush="Black"
BorderThickness="1"
Margin="2"
SnapsToDevicePixels="True" Width="469">
<Grid Height="129" Width="451">
<Image Source="{Binding PictureFile}"
Margin="10" Name="image1" Stretch="Fill"
Width="150" Height="80" HorizontalAlignment="Left" >
<Image.ToolTip>
<TextBlock>
<Run TextBlock.FontWeight="Bold">Company ID:</Run>
<TextBlock Margin="4,0,0,0" Text="{Binding ID}" />
</TextBlock>
</Image.ToolTip>
</Image>
<Label
Content="{Binding FullName}"
Height="34" Margin="99,2,0,0"
Name="中英文名称"
VerticalAlignment="Top"
HorizontalContentAlignment="Right"
FontSize="16" FontWeight="Bold" />
<Label Margin="190,34,0,0" Name="chineseNameLabel"
FontWeight="Bold" Height="28"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Width="73">中文名称:</Label>
<TextBox
Text="{Binding chineseName}"
HorizontalAlignment="Right" Margin="0,39,10,0"
Name="textBox1" Width="172" Height="23"
VerticalAlignment="Top" TextDecorations="None" />
<Label FontWeight="Bold" Height="28" Margin="190,0,0,34"
Name="EnglishNameLabel" VerticalAlignment="Bottom"
HorizontalAlignment="Left"
Width="73">英文名称:</Label>
<TextBox
Text="{Binding EnglishName}"
Height="23" Margin="0,0,10,34" Name="textBox2"
VerticalAlignment="Bottom" HorizontalAlignment="Right"
Width="172" />
<Label Height="28" Margin="190,0,185,2"
Name="startDateLabel" VerticalAlignment="Bottom"
FontWeight="Bold">创建日期:</Label>
<Label
Content="{Binding StartDate}"
Height="28" HorizontalAlignment="Right" Margin="0,0,10,2"
Name="startDateValueLabel" VerticalAlignment="Bottom"
Width="172" />
</Grid>
</Border>
</UserControl>
如上面的代码所示,UI上的很多元素我们都可以通过拖控件进行实现,有个功能需要自己简单的写一写代码,UI上面有一个功能就是你把鼠标放在图片上 的时候会提示
Company ID,这个功能通过
ToolTip 属性进行实现的。ToolTip
属性是WPF所有元素的基类FrameworkElement的一个属性,所以我们可以在这些 子元素当中直接使用。
那么大家注意到,这个小功能在WinForm中我们要写一些代码,而在WPF就可以直接通过属性定制,所以在很多方面WPF对这些方面都做了封装和 简化,也提高了我们的开发效率。
通过上面的案例,我们主要认识到:如果不需要强大的图形和显示效果,WinForms和WPF 都能完成同一个需求,只是WinForms在设计的时候比较痛苦一些,并且没有单独把UI分立出来,所以很多时候都会和逻辑进行耦合;而WPF就不一样 了,它用XAML来进行UI的设计,然后用后台C#或VB等语言来进行操作,这样就使职责进行了分立,使每个部分都发挥到了最好,同时也提高了开发效率。
对于长期从事WinForms或者其他没有从事过ASP.NET等开发人员, 可能不太习惯XAML的这种开发习惯. 但对于ASP.NET 的开发者来说上手就比较容易一些了,因为在很多方面它和HTML有很多相似之处. 不过也没有关系,只要做了一段时间以后就会发现XAML代码是那么的有趣,以至于看到它就有一种亲切感!
这个案例并不是介绍我们如何放弃WinForm和如何转向于WPF,只是想通过他们的异同进行一下简单的对比,大家都知道WPF的特长在于UI和逻辑的分 离、强大的动画和图形效果,但是性能却是一个摆脱不去的瓶颈。而WinForm正好相反,它在性能上得到了比较好的体现,但在显示强大动画和图形效果以及 一些高交互的效果方面就显得不能为力了,所以我们在做项目的时候应该有一个权衡,尤其是在现在的硬件和软件基础上。