再論 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 也是可以打幾個字 囧
有更好想法或是勘誤也歡迎大家一起來交流囉~謝謝唷~