[转]如何学好WPF

2021-03-11 11:27

阅读:315

标签:expr   padding   之间   bsp   个数   learn   处理   元素   引擎   

 

  用了三年多的WPF,开发了很多个WPF的项目,就我自己的经验,谈一谈如何学好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太糟糕了...”。确实,在同一项目中,设计人员使用Blend进行设计,开发人员用VS来开发代码逻辑,这个想法稍有理想化: 
  · 有些UI效果是很难或者不可以用XAML来描述的,需要手动编写效果。 
  · 大多数设计人员很难接受面向对象思维,包括对资源(Resource)的复用也不理想 
  · Blend生成的XAML代码并不高效,一种很简单的布局也可能被翻译成很冗长的XAML

  在经历过这样不愉快的配合后,很多公司引入了一个integrator的概念。专门抽出一个比较有经验的开发人员,负责把设计人员提供的XAML代码整理成比较符合要求的XAML,并且在设计人员无法实现XAML的情况下,根据设计人员的需要来编写XAML或者手动编写代码。关于这方面,我的经验是,设计人员放弃Blend,使用Expression DesignDesign工具还是比较符合设计人员的思维,当然,需要特别注意一些像素对齐方面的小问题。开发人员再通过设计人员提供的design文件转化到项目中。这里一般是用Blend打开工程,Expression系列复制粘贴是格式化到剪切板的,所以可以在design文件中选中某一个图形,点复制,再切到blend对应的父节点下点粘贴,适当修改一下转化过来的效果。

  作为一个矢量化图形工具,Expression Studio确实给我们提供了很多帮助,也可以达到设计人员同开发人员进行合作,不过,不像微软描述的那样自然。总的来说,还好,但是不够好。

  这里,要步入本篇文章的重点了,也是我很多时候听起来很无奈的事情。微软在宣传WPF时过于宣传XAML和工具的简易性了,造成很多刚接触WPF的朋友们会产生这样一副想法。WPF=XAML? 哦,类似HTML的玩意...

  这个是不对的,或者是不能这么说的。作为一款新的图形引擎,以Foundation作为后缀,代表了微软的野心。借助于托管平台的支持,微软寄希望WPF打破长久以来桌面开发和Web开发的壁垒。当然,由于需要.net3.0+版本的支持,XBAP已经逐渐被Silverlight所取替。在整个WPF的设计里,XAMLMarkup)确实是他的亮点,也吸取了Web开发的精华。XAML对于帮助UI和实现的分离,有如如虎添翼。但XAML并不是WPF独有的,包括WF等其他技术也在使用它,如果你愿意,所有的UI你也可以完成用后台代码来实现。正是为了说明这个概念,PetzoldApplication =codes + markup 一书中一分为二,前半本书完全使用Code来实现的,后面才讲解了XAML以及在XAML中声明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

  当然,这个数字未必准确,也不能由此说明WPF相比Asp.netWinForm,有多复杂。但是面对如此庞大的类库,想要做到一览众山小也是很困难的。想要搞定一个大家伙,我们就要把握它的脉络,所谓庖丁解牛,也需要知道在哪下刀。在正式谈如何学好WPF之前,我想和朋友们谈一下如何学好一门新技术。

  学习新技术有很多种途经,自学,培训等等。相对于我们来说,听说一门新技术,引起我们的兴趣,查询相关讲解的书籍(资料),边看书边动手写写Sample这种方式应该算最常见的。那么怎么样才算学好了,怎么样才算是学会了呢?在这里,解释下知识树的概念:

 技术图片

  这不是什么创造性的概念,也不想就此谈大。我感觉学习主要是两方面的事情,一方面是向内,一方面是向外。这棵所谓树的底层就是一些基础,当然,只是个举例,具体图中是不是这样的逻辑就不要见怪了。学习,就是一个不断丰富自己知识树的过程,我们一方面在努力的学习新东西,为它添枝加叶;另一方面,也会不停的思考,理清脉络。这里谈一下向内的概念,并不是没有学会底层一些的东西,上面的东西就全是空中楼阁了。很少有一门技术是仅仅从一方面发展来的,就是说它肯定不是只有一个根的。比方说没有学过IL,我并不认为.NET就无法学好,你可以从另外一个根,从相对高一些的抽象上来理解它。但是对底层,对这种关键的根,学一学它还是有助于我们理解的。这里我的感觉是,向内的探索是无止境的,向外的扩展是无限可能的。

  介绍了这个,接下来细谈一下如何学好一门新技术,也就是如何添砖加瓦。学习一门技术,就像新new了一个对象,你对它有了个大致了解,但它是游离在你的知识树之外的,你要做的很重要的一步就是把它连好。当然这层向内的连接不是一夕之功,可能会连错,可能会少连。我对学好的理解是要从外到内,再从内到外,就读书的例子谈一下这个过程:

  市面关于技术的书很多,名字也五花八门的,简单的整理一下,分为三类,就叫V1V2V3吧。 
· V1
类,名字一般比较好认,类似30天学通XXX,一步一步XXX…没错,入门类书。这种书大致上都是以展示为主的,一个一个Sample,一步一步的带你过一下整个技术。大多数我们学习也都是从这开始的,倒杯茶水,打开电子书,再打开VS,敲敲代码,只要注意力集中些,基本不会跟不上。学完这一步,你应该对这门技术有了一定的了解,当然,你脑海中肯定不自觉的为这个向内连了很多线,当然不一定正确,不过这个新东东的创建已经有轮廓了,我们说,已经达到了从外的目的。 
· V2
类,名字就比较乱了,其实意思差不多,只是用的词语不一样。这类有深入解析XXXXXX本质论这种书良莠不齐,有些明明是入门类书非要换个马甲。这类书主要是详细的讲一下书中的各个Feature, 来龙去脉,帮你更好的认识这门技术。如果你是带着问题去的,大多数也会帮你理清,书中也会顺带提一下这个技术的来源,帮你更好的把请脉络。这种书是可以看出作者的功力的,是不是真正达到了深入浅出。这个过程结束,我们说,已经达到了从外到内的目的。 
· V3
类,如果你认真,踏实的走过了前两个阶段,我觉得在简历上写个精通也不为过。这里提到V3,其实名字上和V2也差不多。往内走的越深,越有种冲动想把这东西搞透,就像被强行注入了内力,虽然和体内脉络已经和谐了,不过总该自己试试怎么流转吧。这里谈到的就是由内向外的过程,第一本给我留下深刻印象的书就是侯捷老师的深入浅出MFC,在这本书中,侯捷老师从零开始,一步一步的构建起了整个类MFC的框架结构。书读两遍,如醍醐灌顶,痛快淋漓。如果朋友们有这种有思想,讲思想,有匠心的书也希望多多推荐,共同进步。

  回过头,就这个说一下WPFWPF现在的书也有不少,入门的书我首推MSDN。其实我觉得作为入门类的书,微软的介绍就已经很好了,面面俱到,用词准确,Sample附带的也不错。再往下走,比如Sams.Windows.Presentation.Foundation.Unleashed或者Apress_Pro_WPF_Windows_Presentation_Foundation_in_NET_3_0也都非常不错。这里没有看到太深入的文章,偶有深入的也都是一笔带过,或者是直接用Reflector展示一下Code

  接下来,谈一下WPF的一些Feature。因为工作关系,经常要给同事们培训讲解WPF,越来越发现,学好学懂未必能讲懂讲透,慢慢才体会到,这是一个插入点的问题。大家的水平参差不齐,也就是所谓的总口难调,那么讲解的插入点就决定了这个讲解能否有一个好的效果,这个插入点一定要尽可能多的插入到大家的知识树上去。最开始的插入点是大家比较熟悉的部分,那么往后的讲解就能一气通贯,反之就是一个接一个的概念,也就是最讨厌的用概念讲概念,搞得人一头雾水。

  首先说一下Dependency Property(DP)。这个也有很多人讲过了,包括我也经常和人讲起。讲它的储存,属性的继承,验证和强制值,反射和值储存的优先级等。那么为什么要有DP,它能带来什么好处呢?

  抛开DP,先说一下Property,属性,用来封装类的数据的。那么DP,翻译过来是依赖属性,也就是说类的属性要存在依赖,那么这层依赖是怎么来的呢。任意的一个DPMSDN上的一个实践是这样的:

 

 
public static readonly DependencyProperty IsSpinningProperty = DependencyProperty.Register("IsSpinning"typeof(bool)); 

public bool IsSpinning 

    
get { return (bool)GetValue(IsSpinningProperty); } 
    
set { SetValue(IsSpinningProperty, value); } 


 

  单看IsSpinning,和传统的属性没什么区别,类型是bool型,有getset方法。只不过内部的实现分别调用了GetValueSetValue,这两个方法是DependecyObject(简称DO,是WPF中所有可视Visual的基类)暴露出来的,传入的参数是IsSpinningProperty。再看IsSpinningProperty,类型就是DependencyProperty,前面用了static readonly,一个单例模式,有DependencyProperty.Register,看起来像是往容器里注册。

  粗略一看,也就是如此。那么,它真正的创新、威力在哪里呢。抛开它精巧的设计不说,先看储存。DP中的数据也是存储在对象中的,每个DependencyObject内部维护了一个EffectiveValueEntry的数组,这个EffectiveValueEntry是一个结构,封装了一个DependencyProerty的各个状态值animatedValue(作动画)baseValue(原始值)coercedValue(强制值)expressionValue(表达式值)。我们使用DenpendencyObject.GetValue(IsSpinningProperty)时,就首先取到了该DP对应的EffectiveValueEntry,然后返回当前的Value

  那么,它和传统属性的区别在哪里,为什么要搞出这样一个DP呢?第一,内存使用量。我们设计控件,不可避免的要设计很多控件的属性,高度,宽度等等,这样就会有大量(私有)字段的存在,一个继承树下来,低端的对象会无法避免的膨胀。而外部通过GetValueSetValue暴露属性,内部维护这样一个EffectiveValueEntry的数组,顾名思义,只是维护了一个有效的、设置过值的列表,可以减少内存的使用量。第二,传统属性的局限性,这个有很多,包括一个属性只能设置一个值,不能得到变化的通知,无法为现有的类添加新的属性等等。

  这里谈谈DP的动态性,表现有二:可以为类A设置类B的属性;可以给类A添加新的属性。这都是传统属性所不具备的,那么是什么让DependencyObject具有这样的能力呢,就是这个DenpencyProperty的设计。在DP的设计中,对于单个的DP来说,是单例模式,也就是构造函数私有,我们调用DependencyProperty.Register或者DependencyProperty.RegisterAttached这些静态函数的时候,内部就会调用到私有的DP 的构造函数,构建出新的DP,并把这个DP加入到全局静态的一个HashTable中,键值就是根据传入时的名字和对象类型的hashcode取异或生成的。

  既然DependencyProperty是维护在一个全局的HashTable中的,那么具体到每个对象的属性又是怎么通过GetValueSetValue来和DependencyProperty关联上的,并获得PropertyChangeCallback等等的能力呢。在一个DP的注册方法中,最多传递五个参数 :

public static DependencyProperty Register(string name, Type propertyType, TypeownerType, PropertyMetadata typeMetadata,

                                                            ValidateValueCallbackvalidateValueCallback);

其中第一和第三个参数就是用来确定HashTable中的键值,第二个参数确定了属性的类型,第四个参数是DP中的重点,定义了DP的属性元数据。在元数据中,定义了属性变化和强制值的Callback等。那么在一个SetValue的过程中,会出现哪些步骤呢:

  1. 取得该DP下对应这个DependencyObjectPropertyMetadata,这句可能听起来有些拗口。Metadata,按微软一般的命名规则,一般是用来描述对象自身数据的,那么一个DP是否只含有一个propertyMetadata呢?答案是不是,一个DP内部维护了一个比较高效的map,里面存储了多个propertyMetadata,也就是说DPpropertyMetadata是一对多的关系。这里是为什么呢,因为同一个DP可能会被用到不同的DependencyObject中去,对于每类DependencyObject,对这个DP的处理都有所不同,这个不同可以表现在默认值不同,properyMetadata里面的功能不同等等,所以在设计DP的时候设计了这样一个DPpropertyMetadata一对多的关系。
  2. 取得了该DP下对应真正干活的PropertyMetadata,下一步要真正的”SetValue”了。这个“value”就是要设置的值,设置之后要保存到我们前面提到的EffectiveValueEntry上,所以这里还要先取得这个DP对应的EffectiveValueEntry。在DependencyObject内部的EffectiveValueEntry的数组里面查找这个EffectiveValueEntry,有,取得;没有,新建,加入到数组中。
  3. 那么这个EffectiveValueEntry到底是用来干什么的,为什么需要这样一个结构体?如果你对WPF有一定了解,可能会听说WPF值储存的优先级,local value>style trigger>template trigger>…。在一个EffectiveValueEntry中,定义了一个BaseValueSourceInternal,用来表示设置当前Value的优先级,当你用新的EffectiveValueEntry更新原有的EffectiveValueEntry时,如果新的EffectiveValueEntryBaseValueSourceInternal高于老的,设置成功,否则,不予设置。
  4. 剩下的就是proertyMetadata了,当你使用类似如下的参数注册DP

 
public static readonly DependencyProperty CurrentReadingProperty = DependencyProperty.Register( 
    
"CurrentReading"
    
typeof(double), 
    
typeof(Gauge), 
    
new FrameworkPropertyMetadata( 
        Double.NaN, 
        FrameworkPropertyMetadataOptions.AffectsMeasure, 
        
new PropertyChangedCallback(OnCurrentReadingChanged), 
        
new CoerceValueCallback(CoerceCurrentReading)), new ValidateValueCallback(IsValidReading));

 

  当属性发生变化的时候,就会调用metadata中传入的委托函数。这个过程是这样的, DependencyObject中定义一个虚函数 :

protected virtual voidOnPropertyChanged(DependencyPropertyChangedEventArgs e)

 

  DP发生变化的时候就会先首先调用到这个OnPropertyChanged函数,然后如果metaData中设置了PropertyChangedCallback的委托,再调用委托函数。这里我们设置了FrameworkPropertyMetadataOptions.AffectsMeasure, 意思是这个DP变化的时候需要重新测量控件和子控件的Size。具体WPF的实现就是FrameworkElement这个类重载了父类DependencyObjectOnPropertyChanged方法,在这个方法中判断参数中的metadata是否是FrameworkPropertyMetadata,是否设置了
FrameworkPropertyMetadataOptions.AffectsMeasure
这个标志位,如果有的话调用一下自身的InvalidateMeasure函数。

  简要的谈了一下DependencyProperty,除了微软那种自卖自夸,这个DependencyProperty究竟为我们设计实现带来了哪些好处呢? 
  1. 就是DP本身带有的PropertyChangeCallback等等,方便我们的使用。 
  2. DP的动态性,也可以叫做灵活性。举个例子,传统的属性,需要在设计类的时候设计好,你在汽车里拿飞机翅膀肯定是不可以的。可是DependencyObject,通过GetValueSetValue来模仿属性,相对于每个DependencyObject内部有一个百宝囊,你可以随时往里放置数据,需要的时候又可以取出来。当然,前面的例子都是使用一个传统的CLR属性来封装了DP,看起来和传统属性一样需要声明,下面介绍一下WPF中很强大的Attached Property

  再谈Attached Property之前,我打算和朋友们谈一个设计模式,结合项目实际,会更有助于分析DP,这就是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 PropertyChangedEventHandlerPropertyChanged; 
    } 

 

 

 
    
public class NameObjectViewModel : INotifyPropertyChanged 
    { 

        
private readonly NameObject _model; 

        
public NameObjectViewModel(NameObjectmodel) 
        { 
            _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 Actionobject>((obj) => 
                       { 

                            Name = 
"name2"

                       }), 
                         
new Predicateobject>((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 PropertyChangedEventHandlerPropertyChanged; 


 

 

 
public class RelayCommand : ICommand 
    { 
        
readonly Actionobject> _execute; 
        
readonly Predicateobject> _canExecute; 

        
public RelayCommand(Actionobject> execute, Predicateobject> 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}">RenameButton> 
    
Grid> 
Window

 

类的关系如图所示:

 技术图片

  这里NameObject -> ModelNameObjectViewModel-> ViewModelWindow1 -> View我们知道,在通常的Model-View世界中,无论MVC也好,MVP也好,包括我们现在提到的MVVM,它的ModelView的功能都类似,Model是用来封装核心数据,逻辑与功能计算的模型,View是视图,具体可以对应到窗体(控件)等。那么View的功能主要有,把Model的数据显示出来,响应用户的操作,修改Model,剩下ControllerPresenter的功能就是要组织ModelView之间的关系,整理一下Model-View世界中的需求点,大致有: 
  1. View提供数据,如何把Model中的数据提供给View 
  2. Model中的数据发生变化后,View如何更新视图。 
  3. 根据不同的情况为Model选择不同的View 
  4. 如何响应用户的操作,鼠标点击或者一些其他的事件,来修改Model

  所谓时势造英雄,那么WPFMVVM打造了一个什么时势呢。 
1. FrameworkElement
类中定义了属性DataContext(数据上下文),所有继承于FrameworkElement的类都可以使用这个数据上下文,我们在XAML中的使用类似Text=”{Binding Path=Name}”的时候,隐藏的含义就是从这个控件的DataContext(即NameObjectViewModel)中取它的Name属性。相当于通过DataContext,使ViewModel中存在了一种松耦合的关系。 
2. WPF
强大的Binding(绑定)机制,可以在Model发生变化的时候自动更新UI,前提是Model要实现INotifyPropertyChanged接口,在Model数据发生变化的时候,发出ProperyChaned事件,View接收到这个事件后,会自动更新绑定的UI。当然,使用WPFDenpendencyProperty,发生变化时,View也会更新,而且相对于使用INotifyPropertyChanged,更为高效。 
3. DataTemplate
DataTemplateSelector,即数据模板和数据模板选择器。可以根据Model的类型或者自定义选择逻辑来选择不同的View 
4.
使用WPF内置的Command机制,相对来说,我们对事件更为熟悉。比如一个Button被点击,一个Click事件会被唤起,我们可以注册ButtonClick事件以处理我们的逻辑。在这个例子里,我使用的是Command="{Binding Path=ChangeNameCommand}",这里的ChangeNameCommand就是DataContext(即NameObjectViewModel)中的属性,这个属性返回的类型是ICommand。在构建这个Command的时候,设置了CanExecuteExecute的逻辑,那么这个ICommand什么时候会调用,Button Click的时候会调用么?是的,WPF内置中提供了ICommandSource接口,实现了这个接口的控件就有了触发Command的可能,当然具体的触发逻辑要自己来控制。Button的基类ButtonBase就实现了这个接口,并且在它的虚函数OnClick中触发了这个Command,当然,这个Command已经被我们绑定到ChangeNameCommand上去了,所以Button被点击的时候我们构建ChangeNameCommand传入的委托得以被调用。

  正是借助了WPF强大的支持,MVVM自从提出,就获得了好评。那么总结一下,它真正的亮点在哪里呢
1.
使代码更加干净,我没使用简洁这个词,因为使用这个模式后,代码量无疑是增加了,但ViewModel之间的逻辑更清晰了。MVVM致力打造一种纯净UI的效果,这里的纯净指后台的xaml.cs,如果你编写过WPF的代码,可能会出现过后台xaml.cs代码急剧膨胀的情况。尤其是主window的后台代码,动则上千行的代码,整个window内的控件事件代码以及逻辑代码混在一起,看的让人发恶。 
2.
可测试性。更新UI的时候,只要Model更改后发出了propertyChanged事件,绑定的UI就会更新;对于Command,只要我们点击了ButtonCommand就会调用,其实是借助了WPF内置的绑定和Command机制。如果在这层意思上来说,那么我们就可以直接编写测试代码,在ViewModel上测试。如果修改数据后得到了propertyChanged事件,且值已经更新,说明逻辑正确;手动去触发Command,模拟用户的操作,查看结果等等。就是把UnitTest也看成一个View,这样Model-ViewModel-ViewModel-ViewModel-UnitTest就是等价的。 
3.
使用Attached Behavior解耦事件,对于前面的例子,Button的点击,我们已经尝试了使用Command而不是传统的Event来修改数据。是的,相对与注册事件并使用,无疑使用Command使我们的代码更和谐,如果可以把控件的全部事件都用Command来提供有多好,当然,控件的Command最多一个,Event却很多,MouseMoveMouseLeave等等,指望控件暴露出那么多Command来提供绑定不太现实。这里提供了一个Attached Behavior模式,目的很简单,就是要注册控件的Event,然后在Event触发时时候调用Command。类似的Sample如下:

 

 
      
  public static DependencyPropertyPreviewMouseLeftButtonDownCommandProperty = DependencyProperty.RegisterAttached( 
                
"PreviewMouseLeftButtonDown"
                
typeof(ICommand), 
                
typeof(AttachHelper), 
                
new FrameworkPropertyMetadata(nullnew PropertyChangedCallback(AttachHelper.PreviewMouseLeftButtonDownChanged))); 

        
pu


评论


亲,登录后才可以留言!