如何学好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交给设计师自己去搞,我们各行其事,通过工具组合就可以了。可实际开发中,这里又出现了问题,UIXAML部分可以完全丢给设计人员么?

  话题展开会有点长,微软提供了Expression Studio套装来支持用工具生成XAML。那么它是否好用呢?经过了一些配合,经常听到设计人员以及开发人员的抱怨:这个没有Photoshop好用,会限制我的灵感他们生成的XAML太糟糕了...”。确实,XAML沟通这个项目太理想化了,在同一项目中,设计人员使用Blend进行设计,开发人员用VS来开发代码逻辑,这个落地是有很大困难的: 
  · 有些UI效果是很难或者不可以用XAML来描述的,需要手动编写效果。 
  · 大多数设计人员很难接受面向对象思维,包括对资源(Resource)的复用也不甚理想。 
  · Blend生成的XAML代码并不高效,一种很简单的布局也可能被翻译成非常冗长难读的XAML

  经历了很多不愉快之后,很多公司引入了一个integrator的概念。专门抽出一个较有经验的开发,负责把设计人员提供的XAML代码整理成符合要求的XAML(没有什么是在中间增加一个层次不能解决的)。并且在设计人员无法实现XAML的情况下,根据设计人员的需要来编写XAML或者手动编写代码。关于这方面较好的建议是:设计人员放弃Blend,使用Expression DesignExpression Design工具还是较复合设计惹怒眼的,除了要注意些像素对齐的问题外,这样的输出结果,开发人员是比较容易转化的。可使用Blend打开工程,再从Expression里复制粘贴,其粘贴是可以格式化到剪切板的——可在design文件中选中某一个图形,复制,并切到blend对应的父节点下点粘贴,适当进行后续地修改。

  作为矢量化的图形工具,Expression Studio确实提供了很多便利,也部分达到了设计人员同开发人员协同合作,只是不像微软描述的那样自然。总体来说,还可以落地。

  后面,要步入本篇文章的重点,也是很多时候听起来无奈的事情——微软在宣传WPF的时候,过分宣传XAML和配套工具的简易性,这样偏向造成了很多刚接触WPF的人的一种误解:WPF=XAML? 哦,又是类似HTML的玩意...

  这是不太对的,作为一款新的图形引擎,微软以Foundation作后缀,代表了它的野心。寄希望于托管平台的支持,WPF期望打破长久以来桌面开发与Web开发的壁垒。当然,由于需要.net3.0+版本支持,配套的XBAP已经逐渐被Silverlight所取替。确实在WPF的设计之中,XAMLMarkup)算是它的亮点,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, WinFormWPF 量比:

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) ,这里不再赘述了。

 

  随着依赖属性的引入,又带来了设计上的一个新模式:MVVMMode-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(
thisnew 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(
thisnew 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搞出了一系列名词:逻辑树、视觉树,路由事件,StyleTemplate等等。

  

  那么如何学好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  
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

 

posted @ 2009-07-31 15:20  周永恒  阅读(34826)  评论(101编辑  收藏  举报