2011年12月31日

WPF – MVVM (三)

做到這邊,我們已經做了很大部分的分離了,但是實際上,我們還有一個地方還沒處理,那就是放在View那邊的邏輯,也就是按下Button時的事件,所以我們這邊再進一步的去處理。

移除View裡面的邏輯

首先我們先把View裡面的邏輯移除,並把它放到ViewModel裡面去,所以我們先將View裡面的東西移除掉,現在裡面真的是空空沒有東西了。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WPFMVVM3
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

接下來我們來看看移到ViewModel產生了甚麼變化。

邏輯移到ViewModel

到這邊,我相信大家就會產生一個疑問,那View裡面的按鈕按下後,要怎樣去呼叫ViewModel裡面的東西呢??其實我們還是會使用一個Binding的方式,只是這次Binding的是一個ICommand的型別;接下來我們繼續處理ViewModel,這次在ViewModel加了一個ICommand型別的參數UpdateTitleNmae,他會回傳一個實作ICommand的RelayCommand實體,並且傳入兩個方法UpdateTitleExecute,CanUpdateTitleExecute ( 沒錯,不要懷疑,傳入進去的是方法)。等下我們會建立RelayCommand類別,到時候就會看到,另外UpdateTitleExecute這個方法裡面定義著要處理的邏輯,也就是從View搬過來的邏輯;而CanUpdateTitleExecute代表著是否可以執行此方法,因為我們這邊一定都會讓此方法( UpdateTitleExecute )可以動作,所以CanUpdateTitleExecute就直接回傳True。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Windows.Input;

namespace WPFMVVM3
{
    //實作InotifyPropertyChanged
    public class PostsViewModel : INotifyPropertyChanged
    {
        public Posts posts{ get; set;}
      
        public event PropertyChangedEventHandler PropertyChanged;

        //定義一個ICommand型別的參數,他會回傳實作ICommand介面的RelayCommand類別。
        public ICommand UpdateTitleName { get { return new RelayCommand(UpdateTitleExecute, CanUpdateTitleExecute); } }

        public PostsViewModel()
        {
            posts = new Posts { postsText = "", postsTitle = "Unknown" };
        }

        public string PostsTitle
        {
            get { return posts.postsTitle; }
            set 
            {
                if (posts.postsTitle != value)
                {
                    posts.postsTitle = value;
                    RaisePropertyChanged("postsTitle");
                }
            }
        } 

        //產生事件的方法
        private void RaisePropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        //更新Title,原本放在View那邊的邏輯,藉由繫結的方式來處理按下Button的事件。
        void UpdateTitleExecute()
        {
            PostsTitle = "SkyMVVM";
        }

        //定義是否可以更新Title
        bool CanUpdateTitleExecute()
        {
            return true;
        }
    }
}

這樣,ViewModel就完成了,接下來是處理RelayCommand。

撰寫RelayCommand

RelayCommand是實作ICommand的類別,一開始我們就可以看到定義了Func和Action,這兩個就是用來參考到剛剛傳入進來的兩個方法,並進行了一些的處理。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using System.Diagnostics;

namespace WPFMVVM3
{
    public class RelayCommand :ICommand
    {
        
        readonly Func _canExecute;
        readonly Action _execute;

        public RelayCommand(Action execute)
            : this(execute, null)
        {
        }

        public RelayCommand(Action execute, Func canExecute)
        {
            if (execute == null)
                throw new ArgumentNullException("execute");
            _execute = execute;
            _canExecute = canExecute;
        }

        public event EventHandler CanExecuteChanged
        {
            add
            {

                if (_canExecute != null)
                    CommandManager.RequerySuggested += value;
            }
            remove
            {

                if (_canExecute != null)
                    CommandManager.RequerySuggested -= value;
            }
        }

        [DebuggerStepThrough]
        public Boolean CanExecute(Object parameter)
        {
            return _canExecute == null ? true : _canExecute();
        }

        public void Execute(Object parameter)
        {
            _execute();
        }
    }
}

最後,我們必須重新調整XMAL。

調整XMAL

最後的一個動作,我們將Button的Click屬性拿掉了,改成Command屬性,並將之Binding到ViewModel的UpdateTitleName屬性,就完成了。

<Window x:Class="WPFMVVM3.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WPFMVVM3"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <!-- 實例化PostViewModel -->
        <local:PostsViewModel />
    </Window.DataContext>
    <Grid>
        <Label  Content="{Binding PostsTitle}"    Height="28" HorizontalAlignment="Left" Margin="12,12,0,0" Name="label1" VerticalAlignment="Top" />
        <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="145,13,0,0" Name="button1" VerticalAlignment="Top" Width="75" Command="{Binding UpdateTitleName}" />
    </Grid>
</Window>

以上就完成了!

結尾

到這邊就算是手工打造MVVM的一個段落了,其實現在有許多的Framework可以幫我們處理一些繁雜的事物,但如果能了解原理,我相信會更有幫助,所以這次用了循序漸進的方式,來記錄這些內容,也希望能給大家一點幫助!

參考資料

3 則留言:

  1. bool CanUpdateTitleExecute()
    這個方法的作用是什麼? 是不是在非同步才會用到呢?
    假設考慮到每個按鈕有非同步的特性每個按鈕是不是都需要單獨的方法控制

    回覆刪除
  2. 好文~~ 解說得很精采~

    回覆刪除
  3. 微軟的架構初看不是很了解透徹,感覺像是reflection之類的東西,解說的很詳細,有空再來多研讀幾次!

    回覆刪除