[WPF] MVVM 軟體架構模式 - 再論 ViewModelBase 與簡化使用方式

再論 ViewModelBase 與簡化使用方式

太久沒更新,回頭看之前文章真是心有感悟,那就不要再回頭修改它了~就重新寫過~XD

基本概念從這一篇 [WPF] MVVM 軟體架構模式 - 從基礎元件 ViewModelBase 到完成一個基本的範例 開始,再加上後來實作的心得與修正一些當時的誤解 囧rz


一開始先釐清 MVVM 裡面 View 與 ViewModel 如何溝通合作;當 UI 介面異動時,與之 Binding 的 Property 可以正常變更,當 Property 變更時需要透過 INotifyPropertyChanged 介面通知 UI 屬性已經變更

這個 ViewModelBase 就是用來實作 INotifyPropertyChanged 用作專案上各個 ViewModel(可以當作 DataContext 的一種) 的基礎類別

以下我修改一下上一篇文章的 ViewModelBase

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace MVVM_Sample.ViewModel
{
    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public void SetField<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
        {
            if (property != null)
            {
                if (property.Equals(value))
                {
                    return;
                }
            }

            property = value;
            OnPropertyChanged(propertyName);
        }

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
            // 也可以寫成 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

接著是繼承這 ViewModelBase 的 ViewModel 裡面實作與 UI 連動的 Property

這個 ViewModelBase 提供的方法有兩種 - 利用 SetField 與 OnPropertyChanged;以下兩個範例是相同的唷,夾雜使用 Lambda 語法的私貨~

方法一(SetField):

namespace MVVM_Sample.ViewModel
{
    public class SampleViewModel : ViewModelBase
    {
        private string msg = "Welcome";
        public string Msg
        {
            get => msg;
            set => SetField(ref msg, value, "Msg");
        }
        
        private int cnt = 0;
        public int Cnt
        {
            get { return cnt; }
            set { SetField(ref cnt, value, "Cnt"); }
        }
    }
}

方法二(OnPropertyChanged):

namespace MVVM_Sample.ViewModel
{
    public class SampleViewModel : ViewModelBase
    {
        private string msg = "Welcome";
        public string Msg
        {
            get => msg;
            set =>
            {
                msg = value;
                OnPropertyChanged();
            };
        }
        
        private int cnt = 0;
        public int Cnt
        {
            get { return cnt; }
            set 
            {
                cnt = value;
                OnPropertyChanged();
            }
        }
    }
}

至於使用哪個比較合手就看各人選擇,我自己比較偏好第二種方法的寫法


這裡再放上一個更簡化的 ViewModelBase 版本,再簡化 Set 部分要寫的程式碼成簡單一行!

再改寫一下 ViewModelBase 基礎類別部分,主要差異是加入一個 Dictionary 字典查詢,將對應的類別成員變數放到此字典檔內用索引查詢,這樣可以再省掉方法二寫 OnPropertyChanged 那行

但是 Get 部分就得使用這個基礎類別提供的 GetF 來處理,不然 SetF 可會搜尋不到的…. 囧rz

話不多說,直接上碼!

using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace MVVM_Sample.ViewModel
{
    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        private readonly Dictionary<string, object> CDicField = new Dictionary<string, object>();

        public event PropertyChangedEventHandler PropertyChanged;

        public T GetF<T>([CallerMemberName] string propertyName = null)
        {
            object propertyValue;
            if (!CDicField.TryGetValue(propertyName, out propertyValue))
            {
                propertyValue = default(T);
                CDicField.Add(propertyName, propertyValue);
            }

            return (T)propertyValue;
        }

        public void SetF(object value, [CallerMemberName] string propertyName = null)
        {
            if (!CDicField.ContainsKey(propertyName) || CDicField[propertyName] != (object)value)
            {
                CDicField[propertyName] = value;
                OnPropertyChanged(propertyName);
            }
        }
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
            // 也可以寫成 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

以下就是使用方法囉~效果如同原本 ViewModelBase 的範例~

namespace MVVM_Sample.ViewModel
{
    public class SampleViewModel : ViewModelBase
    {
        private string msg = "Welcome";
        public string Msg
        {
            get => GetF<string>();
            set => SetF(value);
        }
        
        private int cnt = 0;
        public int Cnt
        {
            get => GetF<int>();
            set => SetF(value);
        }
    }
}

若想再簡化也可以把 GetF/SetF 改成 G/S 也是可以打幾個字 囧

有更好想法或是勘誤也歡迎大家一起來交流囉~謝謝唷~