INotifiPropertyChanged 1. 做用:向客户端发出某一属性值已更改的通知。该接口包含一个PropertyChanged事件成员(MSDN的解释) INotifyPropertyChanged 接口用于向客户端(一般是执行绑定的客户端)发出某一属性值已更改的通知。 例如,考虑一个带有名为 FirstName 属性的 Person 对象。若要提供通常性属性更改通知,则 Person 类型实现NotifyPropertyChanged 接口并在 FirstName 更改时引起 PropertyChanged 事件。若要在将客户端与数据源进行绑定时发出更改通知,则绑定类型应具备下列任一功能: A. 实现 INotifyPropertyChanged 接口(首选)。 B. 为绑定类型的每一个属性提供更改事件。 2. 使用: public abstract class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } } } 定义一个抽象基类,该类实现了INotifyPropertyChanged接口,全部继承自ViewModelBase 的类,都将具 有:通知绑定端,后台属性发生变化的能力。 public class MainViewModel : ViewModelBase { //............. private LeagueList dataToShow; public LeagueList DataToShow { get { if (dataToShow == null) dataToShow = new LeagueList(); return dataToShow; } set { dataToShow= value; OnPropertyChanged("DataToShow"); } } } MainViewModel 继承BaseViewModel,当属性值发生变化的时候,即在属性的set段中调用OnPropertyChanged函数,那么就能通知UI,绑定数据源发生了变化,UI也会自动更新数据显示。那么如何实现绑定呢,看看下面代码: <Window x:Class="Views.Window" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ViewModel="clr-namespace:ViewModels" xmlns:View="clr-namespace:Views" xmlns:c="clr-namespace:Commands"> <Window.Resources> <DataTemplate DataType="{x:Type ViewModel:MainViewModel}"> <View:MainView/> </DataTemplate> </Window.Resources> 上面代码的意思是,MainViewModel中定义各类数据源和代码逻辑,如后这些数据按照MainView中所定义的布局进行显示,这也就是DataTemplate的做用,这里不展开(后续将在数据模板中介绍)。 好了,如今把两个类:MainViewMode(l数据,普通cs文件),MainView(UI,即一个xaml文件,一般该类为一个Usrcontrol)进行了绑定,那么具体的数据怎么实现绑定呢。简单,看看MainView中的代码: <ListBox Grid.Column="0" Name="topiclist" ItemsSource="{Binding Path=DataToShow}"> 这样就实现了,具体数据的绑定。 3. 进一步分析 (1)绑定分析: 首先定义一个抽象基类BaseViewModel实现INotifyProperChanged接口;定义MainViewModel继承自BaseVIewModel,这样就能使用PropertyChange函数,当属性值发生变化的时候,在set段调用PropertyChange函数。 其次,定义好MainView文件,该文件定义界面布局,实现UI,经过绑定MainViewModel中数据。 最后在Window.xaml中使用DataTemplate将MainViewModel和MainView进行绑定 注意在WPF中,xaml 和xam.cs文件是自动绑定的,可是MainViewModel是普通的cs文件,不是xaml.cs文件,所以,仅仅在MainView中使用绑定,系统不会再MainViewModel中去寻找数据源,,而是在MainView.xaml.cs中去寻找数据。所以须要最后一步。 (2)为何不在xaml.cs中定义数据源和逻辑代码? 缘由1:xaml.cs是控件的逻辑文件,而MainViewModel须要继承INotifyPropertyChange接口,这样就必须让控件继承INotifyPropertyChanged,至关因而控件重写了,这样的编程模式,xaml.cs将愈来愈大,这个类的测试也将愈加复杂。所以,从下降类复杂度的角度,不该在xaml.cs中定义数据源和逻辑代码。 缘由2:若是在xaml.cs中实现逻辑,,不利于逻辑和UI的分离,不利于UI和逻辑的分开编写,下降的程序编写的效率,同时增长了代码的耦合度。采用MainViewModel和 MainView的框架,其实就是MVVM模式(Model-View-ViewModel),该模式能够说和WPF是珠联璧合,等我陆续阐述完,各类基础后,,我将在WPF进阶之MVVM中详细说明,如今请你们,耐心掌握基础。 (3)看刚才的例子,ListBox的ItemsSource一般须要一个集合类型数据,好了,咱们知道ObservableCollection是一个数据集合类型,而且实现了INotifyPropertyChanged接口,那么为何不绑定到一个ObservableCollection数据类型呢? 缘由:说的很对一般绑定到ObservableCollection是可行的,绑定到普通数据,并实现INotifyPropertyChanged也是可行的,。他们的区别在于ObservableCollection继承INotifyCollectionChanged, INotifyPropertyChanged那么当Collection添加项,删除项,刷新时,都将发送PropertyChanged通知,有时这部分功能是咱们不须要的,所以,采用本身实现INotifyPropertyChanged的类将更具灵活性。 ICommand 定义: 你们知道在xaml中定义的事件,响应函数只能在xaml.cs中,如上所述,若是咱们采用MVVM框架,那么咱们不能经过事件响应的模式,实现代码逻辑。那么如何监听事件呢。咱们使用命令。且看下面实现 //不带参数的命令类型 public class DelegateCommand : ICommand { public DelegateCommand(Action executeMethod) : this(executeMethod, null, false) { } public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod) : this(executeMethod, canExecuteMethod, false) { } public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod, bool isAutomaticRequeryDisabled) { if (executeMethod == null) { throw new ArgumentNullException("executeMethod"); } _executeMethod = executeMethod; _canExecuteMethod = canExecuteMethod; _isAutomaticRequeryDisabled = isAutomaticRequeryDisabled; } public bool CanExecute() { if (_canExecuteMethod != null) { return _canExecuteMethod(); } return true; } public void Execute() { if (_executeMethod != null) { _executeMethod(); } } public bool IsAutomaticRequeryDisabled { get { return _isAutomaticRequeryDisabled; } set { if (_isAutomaticRequeryDisabled != value) { if (value) { CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers); } else { CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers); } _isAutomaticRequeryDisabled = value; } } } public void RaiseCanExecuteChanged() { OnCanExecuteChanged(); } protected virtual void OnCanExecuteChanged() { CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers); } public event EventHandler CanExecuteChanged { add { if (!_isAutomaticRequeryDisabled) { CommandManager.RequerySuggested += value; } CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2); } remove { if (!_isAutomaticRequeryDisabled) { CommandManager.RequerySuggested -= value; } CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value); } } bool ICommand.CanExecute(object parameter) { return CanExecute(); } void ICommand.Execute(object parameter) { Execute(); } private readonly Action _executeMethod = null; private readonly Func<bool> _canExecuteMethod = null; private bool _isAutomaticRequeryDisabled = false; private List<WeakReference> _canExecuteChangedHandlers; } public class DelegateCommand<T> : ICommand { //篇幅限制,不作展开,这里和前一个DelegateCommand相似,不过是定义一个带参数的命令 } //采用弱引用,避免内存泄漏 internal class CommandManagerHelper { internal static void CallWeakReferenceHandlers(List<WeakReference> handlers) { if (handlers != null) { // Take a snapshot of the handlers before we call out to them since the handlers // could cause the array to me modified while we are reading it. EventHandler[] callees = new EventHandler[handlers.Count]; int count = 0; for (int i = handlers.Count - 1; i >= 0; i--) { WeakReference reference = handlers[i]; EventHandler handler = reference.Target as EventHandler; if (handler == null) { // Clean up old handlers that have been collected handlers.RemoveAt(i); } else { callees[count] = handler; count++; } } // Call the handlers that we snapshotted for (int i = 0; i < count; i++) { EventHandler handler = callees[i]; handler(null, EventArgs.Empty); } } } internal static void AddHandlersToRequerySuggested(List<WeakReference> handlers) { if (handlers != null) { foreach (WeakReference handlerRef in handlers) { EventHandler handler = handlerRef.Target as EventHandler; if (handler != null) { CommandManager.RequerySuggested += handler; } } } } internal static void RemoveHandlersFromRequerySuggested(List<WeakReference> handlers) { if (handlers != null) { foreach (WeakReference handlerRef in handlers) { EventHandler handler = handlerRef.Target as EventHandler; if (handler != null) { CommandManager.RequerySuggested -= handler; } } } } internal static void AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler) { AddWeakReferenceHandler(ref handlers, handler, -1); } internal static void AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler, int defaultListSize) { if (handlers == null) { handlers = (defaultListSize > 0 ? new List<WeakReference>(defaultListSize) : new List<WeakReference>()); } handlers.Add(new WeakReference(handler)); } internal static void RemoveWeakReferenceHandler(List<WeakReference> handlers, EventHandler handler) { if (handlers != null) { for (int i = handlers.Count - 1; i >= 0; i--) { WeakReference reference = handlers[i]; EventHandler existingHandler = reference.Target as EventHandler; if ((existingHandler == null) || (existingHandler == handler)) { // Clean up old handlers that have been collected // in addition to the handler that is to be removed. handlers.RemoveAt(i); } } } } } } 上面的代码没必要深究,之后使用多了天然明白,这里不作展开。 定义一个带参数命令 DelegateCommand<object> newsItemLoopCommand; public ICommand NewsItemLoopCommand { get { if (newsItemLoopCommand == null) { Action<object> exe = new Action<object>(NextOrPreviousNews); newsItemLoopCommand = new DelegateCommand<object>(exe); } return newsItemLoopCommand; } } 咱们将Button的Command绑定到此命令,而且传递一个参数: <Button x:Name="BackButton" Command="{Binding Path=ItemLoopCommand}" CommandParameter="Previous"/> 这样当click Button时,就会执行NextOrPreviousNews函数。 问题: 1. 基本上只有少数控件(如Button)在带有Command,多数控件没有Command属性,怎么使用命令呢。 咱们知道有些时间发生后,属性值也会变化,例如ListBox 的SelectionChanged事件发生时,SelectedItem也将产生变化,那么咱们能够将SelectedItem绑定一个后台属性PropertyItem,当SelectionChanged发生时变化,SelectedItem也变,因为绑定,PropertyItem也变,所以咱们能够在PropertyItem的set段中加入逻辑代码,达到咱们的目的。 2.这里咱们遗留了几个问题。MVVM的讲解,ICommand实现的讲解,弱引用的讲解。 ICommand实现的讲解看看MSDN便知道,主要是实现CanExcute和Excute两个函数。 弱引用,我将在下一篇中介绍。 关于MVVM请等慢慢讲完全部基础。包括:控件重写,数据绑定等内容。
转自:http://hi.baidu.com/leo_han/item/fe0eec6f54217e0da0cf0f0a编程