1. 程式人生 > >WPF進階之介面:INotifyPropertyChanged,ICommand

WPF進階之介面: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
                    {