如何学好WPF
多年前的文章了,修改一下,以适应现期发展。
WPF,它的全名是Windows Presentation Foundation,是微软在.net3.0 WinFX中提出的。WPF是对Direct3D的托管封装,它的图形表现依赖于显卡。当然,作为一种更高层次的封装,对于硬件本身不支持的一些图形特效的硬实现,WPF提供了利用CPU进行计算的软实现,以用来简化开发人员的工作。
简单的介绍了一下WPF,这方面的资料网上也有很多。作于微软力推的技术,整个推行也符合微软一贯的风格——简单,易用,强大,外加一些创新概念的噱头。
多年前介绍的废话:
噱头一:声明式编程。从理论上讲,声名式变成不算什么创新。Web界面声明式开发已经很多了,这种界面层的声明式开发也是行业上的大势所趋。为了适应声明式编程的需要,微软推出了XAML,是一种扩展的XML语言,并且配套在.NET 3.0中引入了XAML的编译器和运行时解析器。XAML加上IDE强大的智能感知,确实大大提升了对界面的描述能力,这点是值得肯定的。
噱头二:紧接着微软又借着XAML描绘了一副更为美好的画面——界面设计者和代码开发者可以并行的工作,两者通过XAML进行交互,以实现设计和实现的分离。不得不说,这个想法是非常打动人心的。以往设计人员和开发人员的交互是一个痛点,两者多是通过设计人员利用photoshop编辑出来的图片进行交互的,需要开发人员根据生成的图片来进行程序上的转换,以生成实际可运行的程序。多了这么一层转换,出来的效果或多或少总会和设计人员的设计有偏差,所以,很多时候开发人员也不得不忍受设计人员的抱怨。WPF的出现,给了深陷苦恼的开发人员一线曙光——我只负责逻辑代码,UI交给设计师自己去搞,我们各行其事,通过工具组合就可以了。可实际开发中,这里又出现了问题,UI的XAML部分可以完全丢给设计人员么?
话题展开会有点长,微软提供了Expression Studio套装来支持用工具生成XAML。那么它是否好用呢?经过了一些配合,经常听到设计人员以及开发人员的抱怨:“这个没有Photoshop好用,会限制我的灵感”, “他们生成的XAML太糟糕了...”。确实,XAML沟通这个项目太理想化了,在同一项目中,设计人员使用Blend进行设计,开发人员用VS来开发代码逻辑,这个落地是有很大困难的:
· 有些UI效果是很难或者不可以用XAML来描述的,需要手动编写效果。
· 大多数设计人员很难接受面向对象思维,包括对资源(Resource)的复用也不甚理想。
· 用Blend生成的XAML代码并不高效,一种很简单的布局也可能被翻译成非常冗长难读的XAML。经历了很多不愉快之后,很多公司引入了一个integrator的概念。专门抽出一个较有经验的开发,负责把设计人员提供的XAML代码整理成符合要求的XAML(没有什么是在中间增加一个层次不能解决的)。并且在设计人员无法实现XAML的情况下,根据设计人员的需要来编写XAML或者手动编写代码。关于这方面较好的建议是:设计人员放弃Blend,使用Expression Design。Expression Design工具还是较复合设计惹怒眼的,除了要注意些像素对齐的问题外,这样的输出结果,开发人员是比较容易转化的。可使用Blend打开工程,再从Expression里复制粘贴,其粘贴是可以格式化到剪切板的——可在design文件中选中某一个图形,复制,并切到blend对应的父节点下点粘贴,适当进行后续地修改。
作为矢量化的图形工具,Expression Studio确实提供了很多便利,也部分达到了设计人员同开发人员协同合作,只是不像微软描述的那样自然。总体来说,还可以落地。
后面,要步入本篇文章的重点,也是很多时候听起来无奈的事情——微软在宣传WPF的时候,过分宣传XAML和配套工具的简易性,这样偏向造成了很多刚接触WPF的人的一种误解:WPF=XAML? 哦,又是类似HTML的玩意...
这是不太对的,作为一款新的图形引擎,微软以Foundation作后缀,代表了它的野心。寄希望于托管平台的支持,WPF期望打破长久以来桌面开发与Web开发的壁垒。当然,由于需要.net3.0+版本支持,配套的XBAP已经逐渐被Silverlight所取替。确实在WPF的设计之中,XAML(Markup)算是它的亮点,XAML也吸取到了Web开发的精华。对于帮助UI和实现的分离,XAML如虎添翼。但XAML并不是WPF所独有的,WF等其他技术也使用了XAML;XAML只是个语法糖,帮助更舒服地编写UI,如果开发人员愿意的话,在WPF中可以一行XAML都不写,完全使用手撸后台代码的方式来实现所有的UI。正是为了说明这样观念,Petzold在他的书《Application = codes + markup》将内容一分为二,前一半完全使用代码来实现的,后面才讲了XAML在UI上的应用。当然,这样“逆势”的书叫好不叫座,如果有一定WPF开发经验了,回头来看发现其组织书籍的方式非常经典,但如果带着“简单UI”的成见来看,抱着这样的书入门就会一头雾水了。
所以很多朋友抱怨,WPF学习曲线太曲折,它的上手很容易,深入一些就很困难,经常出现一些莫名其妙的问题,不查看源代码都不知道如何解决。这个复杂是由数量级决定的,借一下LearnWPF的数据,来对比一下Asp.net, WinForm和WPF 量比:
ASP.NET 2.0 |
WinForms 2.0 |
WPF |
|
|
|
1098 public types 1551 classes |
777 public types 1500 classes |
1577 public types 3592 classes |
要搞定这么个大家伙,先要撸到它的脉络——庖丁解牛,要知道在哪下刀。先聊一下如何学门新技术。
一门新技术,对大多人来说说,往往是通过看相关书籍入门的,边看书边撸hello world是最常见的方法。一个知识树:
学习是一个不断丰富知识树的过程,一方面添枝加叶(向外)、另一方面不停重构演进(向内)——所谓至大无外,至小无内。
一门新技术,就像一个new出来的对象,在了解其轮廓之前,它是游离在知识树外面的,需要找到新老知识之间连结的关键。
对于WPF来说,入门首选MSDN,微软做的已经够好了,Sample带的也不错。再往下,比如Sams.Windows.Presentation.Foundation.Unleashed或者Apress_Pro_WPF_Windows_Presentation_Foundation_in_NET_3_0也不错。
接下来学些什么?要找一个插入点,期望其“以点破面”、通过它可以凿开WPF的大门,把真东西露出来。这个推荐Dependency Property(DP)。WPF的命门在DP上,从它入手最好不过了。
DP,也叫依赖属性,从名字来看,它首先是一个属性,依赖是一个形容词。就是在传统的属性上加了个“依赖”。
找一个DP,它长这副模样:
public static readonly DependencyProperty IsSpinningProperty = DependencyProperty.Register("IsSpinning", typeof(bool));
public bool IsSpinning
{
get { return (bool)GetValue(IsSpinningProperty); }
set { SetValue(IsSpinningProperty, value); }
}
单看IsSpinning,外表长得和属性一样,除了get/set里面“一大坨”代码。看看这“一坨代码”也知道,就是在“存取”的实现上下文章了。
其实就是个“二房东”的模式,对外面看好像“是我的”,内部就不一定是“咋回事”了。
是咋回事呢?属性的值存在哪了呢?
存在“共享XX上了”,如果属性值不变,就用静态方式存在全局——大家共用一份;如果改变了,就在DependencyObject内部弄个Dictionary<string,object>,将变化的值保存在自己这儿。
细一点说,每个DependencyObject内部搞了个EffectiveValueEntry的数组,EffectiveValueEntry是个结构,封装了很多状态值animatedValue(动画),baseValue(原始值),coercedValue(强制值),expressionValue(表达式值),这些值对应不同的场景。
关于DP的实现,可以参照:一站式WPF--依赖属性(DependencyProperty)一 - 周永恒 - 博客园 (cnblogs.com) ,这里不再赘述了。
随着依赖属性的引入,又带来了设计上的一个新模式:MVVM(Mode-View-ViewModel)。它长这个模样:
public class NameObject : INotifyPropertyChanged
{
private string _name = "name1";
public string Name
{
get
{
return _name;
}
set
{
_name = value;
NotifyPropertyChanged("Name");
}
}
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler
PropertyChanged;
}
public class NameObjectViewModel : INotifyPropertyChanged
{
private readonly NameObject _model;
public NameObjectViewModel(NameObject
model)
{
_model = model;
_model.PropertyChanged += new PropertyChangedEventHandler(_model_PropertyChanged);
}
void _model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyPropertyChanged(e.PropertyName);
}
public ICommand ChangeNameCommand
{
get
{
return new RelayCommand(
new Action<object>((obj) =>
{
Name = "name2";
}),
new Predicate<object>((obj) =>
{
return true;
}));
}
}
public string Name
{
get
{
return _model.Name;
}
set
{
_model.Name = value;
}
}
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler
PropertyChanged;
}
public class RelayCommand : ICommand
{
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add {
CommandManager.RequerySuggested += value; }
remove {
CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new NameObjectViewModel(new NameObject());
}
}
<Window x:Class="WpfApplication7.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<TextBlock Margin="29,45,129,0" Name="textBlock1" Height="21" VerticalAlignment="Top"
Text="{Binding Path=Name}"/>
<Button Height="23" Margin="76,0,128,46" Name="button1" VerticalAlignment="Bottom"
Command="{Binding Path=ChangeNameCommand}">Rename</Button>
</Grid>
</Window
类的关系如图所示:
ViewModel,顾名思义,就是View+Model,是两者的“杂交”,是那片“三不管”的灰色地带。
从名字来看,View和Model都很纯,一个是UI、一个是数据,世上的事往往是没那么单纯的。取得了数据之后,往往要加点“私货”再传给UI。这份脏活原来是Controller干的,现在扔在了ViewModel中。
Controller就像个“销售”一样,原来是直接对应外部需求(View),同时指导内部的工兵干活(Model)。后来发现他太不可控了,为了点销售业绩,啥活都接。
能不能换个模式?
把客户的需求抽象出来,定好个全集,内部对应一份,然后根据不同的需求“适配”给不同客户。这就是ViewModel的由来,将客户(View)的需求抽象成Property、Command等,针对它实现需求,然后用WPF自带的绑定、Command等模式将这个映射“落地”。
说白了,MVVM代表了一种思想,叫做Meta-Control(元控)——通过元数据来控制View,在Model与View之间引入了一层抽象。相对来说,数据结构+算法的思路更像是“数控”。
DP带来了很多东西,依靠它的支撑,WPF搞出了一系列名词:逻辑树、视觉树,路由事件,Style和Template等等。
那么如何学好WPF呢?
1. 熟悉XAML,熟悉布局,熟悉基本控件,能够根据产品端提出的原型画出界面。——(入门)
2. 研究事件、Style、Template,提升自己的项目能力。——(可按Winform风格实现WPF)。
3. 熟悉MVVM,熟悉ICommand,学会使用MVVM框架实现程序。——(进入WPF味道)
4. 研究依赖属性、路由事件,学会写自定义控件。
5. 学会换肤,可以更新整个界面的样式(Light/Dark);学会一个ViewModel对应多个View。(学会写机制)
6. 接触开源框架MVVM Light等,研究其精髓,提升WPF掌控能力。
7. 提升对WPF技术点的取舍能力,向Presentation的本质深入。
祝大家WPF之旅一帆风顺。
作者:周永恒
出处:http://www.cnblogs.com/Zhouyongh
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报