• WPF进阶之接口(3):INotifyPropertyChanged,ICommand


    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

  • 相关阅读:
    iOS开发UI篇—使用UItableview完成一个简单的QQ好友列表(二)
    iOS开发UI篇—使用UItableview完成一个简单的QQ好友列表(一)
    iOS开发UI篇—简单介绍静态单元格的使用
    iOS开发UI篇—UITableview控件使用小结
    ios开发UI篇—使用纯代码自定义UItableviewcell实现一个简单的微博界面布局
    iOS开发UI篇—使用xib自定义UItableviewcell实现一个简单的团购应用界面布局
    iOS开发UI篇—使用嵌套模型完成的一个简单汽车图标展示程序
    iOS开发UI篇—实现UItableview控件数据刷新
    iOS开发UI篇—推荐两个好用的Xcode插件(提供下载链接)
    人人都应学习的公链知识——比原总体架构
  • 原文地址:https://www.cnblogs.com/anbylau2130/p/3435472.html
Copyright © 2020-2023  润新知