近些年來,隨著WPF在生產,製造,工業控制等領域應用越來越廣發,很多企業對WPF開發的需求也逐漸增多,使得很多人看到潛在機會,不斷從Web,WinForm開發轉向了WPF開發,但是WPF開發也有很多新的概念及設計思想,如:數據驅動,數據綁定,依賴屬性,命令,控制項模板,數據模板,MVVM等,與傳統Wi... ...
近些年來,隨著WPF在生產,製造,工業控制等領域應用越來越廣發,很多企業對WPF開發的需求也逐漸增多,使得很多人看到潛在機會,不斷從Web,WinForm開發轉向了WPF開發,但是WPF開發也有很多新的概念及設計思想,如:數據驅動,數據綁定,依賴屬性,命令,控制項模板,數據模板,MVVM等,與傳統WinForm,ASP.NET WebForm開發,有很大的差異,今天就以一個簡單的小例子,簡述WPF開發中MVVM設計思想及應用。
為什麼要用MVVM?
傳統的WinForm開發,一般採用事件驅動,即用戶點擊事件,觸發對應的事件,併在事件中通過唯一標識符獲取頁面上用戶輸入的數據,然後進行業務邏輯處理。這樣做會有一個弊端,就是用戶輸入(User Interface)和業務邏輯(Business)是緊密耦合在一起的,無法做到分離,隨著項目的業務不斷複雜化,這種高度耦合的弊端將會越來越明顯。並且會出現分工不明確(如:後端工程師,前端UI),工作無法拆分的現象。所以分層(如:MVC,MVVM),可測試(Unit Test),前後端分離,就成為必須要面對的問題。而今天要講解的MVVM設計模式,就非常好的解決了我們所面臨的問題。
什麼是MVVM?
MVVM即模型(Model)-視圖(View)-視圖模型(ViewModel) ,是用於解耦 UI 代碼和非 UI 代碼的 設計模式。 藉助 MVVM,可以在 XAML 中以聲明方式定義 UI,將 UI使用數據綁定標到包含數據和命令的其他層。 數據綁定提供數據和結構的鬆散耦合,使 UI 和鏈接的數據保持同步,同時可以將用戶輸入路由到相應的命令。具體如下圖所示:
如上圖所示:
- View(用戶頁面),主要用於向使用者展示信息,並接收用戶輸入的信息(數據綁定),及響應用戶的操作(Command)。
- ViewModel(用戶視圖業務邏輯),主要處理客戶請求,以及數據呈現。
- Model數據模型,作為存儲數據的載體,是一個個的具體的模型類,通過ViewModel進行調用。但是在小型項目中,Model並不是必須的。
- IService(數據介面),數據訪問服務,用於獲取各種類型數據的服務。數據的形式有很多種,如網路數據,本地數據,資料庫數據,但是在ViewModel調用時,都統一封裝成了Service。在小型項目中,IService數據介面也並不是必須的,不屬於MVVM的範疇。
- 在上圖中,DataBase,Network,Local等表示不同的數據源形式,並不屬於MVVM的範疇。
前提條件
要實現MVVM,首先需要滿足兩個條件:
- 屬性變更通知,在MVVM思想中,由WinForm的事件驅動,轉變成了數據驅動。在C#中,普通的屬性,並不具備變更通知功能,要實現變更通知功能,必須要實現INotifyPropertyChanged介面。
- 綁定命令,在WPF中,為瞭解決事件響應功能之間的耦合,提出了綁定命令思想,即命令可以綁定的方式與控制項建立聯繫。綁定命令必須實現ICommand介面。
在上述兩個條件都滿足後,如何將ViewModel中的具備變更通知的屬性和命令,與View中的控制項關聯起來呢?答案就是綁定(Binding)。
當View層的數據控制項和具備通知功能的屬性進行Binding後,Binging就會自動偵聽來自介面的PropertyChanged事件。進而達到數據驅動UI的效果,可謂【一橋飛架南北,天塹變通途】。
MVVM實例
為了進一步感受MVVM的設計思想,驗證上述的理論知識,以實例進行說明。本實例的項目架構如下所示:
MVVM核心代碼
1. 具備通知功能的屬性
首先定義一個抽象類ObservableObject,此介面實現INotifyPropertyChanged介面,如下所示:
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace DemoMVVM.Core
{
/// <summary>
/// 可被觀測的類
/// </summary>
public abstract class ObservableObject : INotifyPropertyChanged
{
/// <summary>
/// 屬性改變事件
/// </summary>
public event PropertyChangedEventHandler? PropertyChanged;
/// <summary>
/// 屬性改變觸發方法
/// </summary>
/// <param name="propertyName">屬性名稱</param>
protected void RaisePropertyChanged([CallerMemberName]string propertyName=null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// 設置屬性值,如果發生改變,則調用通知方法
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="target"></param>
/// <param name="value"></param>
/// <param name="propertyName"></param>
/// <returns></returns>
protected bool SetProperty<T>(ref T target,T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(target, value))
{
return false;
}
else
{
target=value;
RaisePropertyChanged(propertyName);
return true;
}
}
}
}
註意:上述SetProperty主要用於將普通屬性,變為具備通知功能的屬性。
然後定義一個ViewMode基類,繼承自ObservableObject,以備後續擴展,如下所示:
namespace DemoMVVM.Core
{
/// <summary>
/// ViewModel基類,繼承自ObservableObject
/// </summary>
public abstract class ViewModelBase:ObservableObject
{
}
}
2. 具備綁定功能的命令
首先定義一個DelegateCommand,實現ICommand介面,如下所示:
namespace DemoMVVM.Core
{
public class DelegateCommand : ICommand
{
private Action<object> execute;
private Predicate<object> canExecute;
public event EventHandler? CanExecuteChanged;
public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute 不能為空");
}
this.execute = execute;
this.canExecute = canExecute;
}
public DelegateCommand(Action<object> execute):this(execute,null)
{
}
public bool CanExecute(object? parameter)
{
return canExecute?.Invoke(parameter)!=false;
}
public void Execute(object? parameter)
{
execute?.Invoke(parameter);
}
}
}
註意,DelegateCommand的構造函數,接收兩個參數,一個是Execute(幹活的),一個是CanExecute(判斷是否可以幹活的)。
MVVM應用代碼
本實例主要實現兩個數的運算。如加,減,乘,除等功能。
首先定義ViewModel,繼承自ViewModelBase,主要實現具備通知功能的屬性和命令,如下所示:
using DemoMVVM.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime;
using System.Text;
using System.Threading.Tasks;
namespace DemoMVVM
{
public class MainWindowViewModel:ViewModelBase
{
#region 屬性及構造函數
private double leftNumber;
public double LeftNumber
{
get { return leftNumber; }
set { SetProperty(ref leftNumber , value); }
}
private double rightNumber;
public double RightNumber
{
get { return rightNumber; }
set { SetProperty(ref rightNumber , value); }
}
private double resultNumber;
public double ResultNumber
{
get { return resultNumber; }
set { SetProperty(ref resultNumber , value); }
}
public MainWindowViewModel()
{
}
#endregion
#region 命令
private DelegateCommand operationCommand;
public DelegateCommand OperationCommand
{
get {
if (operationCommand == null)
{
operationCommand = new DelegateCommand(Operate);
}
return operationCommand; }
}
private void Operate(object obj)
{
if(obj == null)
{
return;
}
var type=obj.ToString();
switch (type)
{
case "+":
this.ResultNumber = this.LeftNumber + this.RightNumber;
break;
case "-":
this.ResultNumber = this.LeftNumber - this.RightNumber;
break;
case "*":
this.ResultNumber = this.LeftNumber * this.RightNumber;
break;
case "/":
if (this.RightNumber == 0)
{
this.ResultNumber = 0;
}
else
{
this.ResultNumber = this.LeftNumber / this.RightNumber;
}
break;
}
}
#endregion
}
}
創建視圖,併在視圖中進行數據綁定,將ViewModel和UI關聯起來,如下所示:
<Window x:Class="DemoMVVM.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:DemoMVVM"
mc:Ignorable="d"
Title="MVVM示例" Height="350" Width="600">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="0.3*"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Grid.Row="1" Grid.Column="0" Orientation="Horizontal">
<TextBlock Text="A1:" VerticalAlignment="Center" ></TextBlock>
<TextBox Margin="10" Width="120" Height="35" Text="{Binding LeftNumber, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center"></TextBox>
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal">
<TextBlock Text="A2:" VerticalAlignment="Center" ></TextBlock>
<TextBox Margin="10" Width="120" Height="35" Text="{Binding RightNumber, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center"></TextBox>
</StackPanel>
<TextBlock Grid.Row="1" Grid.Column="2" Text="=" VerticalAlignment="Center" HorizontalAlignment="Center"></TextBlock>
<StackPanel Grid.Row="1" Grid.Column="3" Orientation="Horizontal">
<TextBlock Text="A3:" VerticalAlignment="Center" ></TextBlock>
<TextBox Margin="10" Width="120" Height="35" Text="{Binding ResultNumber, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center"></TextBox>
</StackPanel>
<StackPanel Grid.Row="2" Grid.ColumnSpan="4" Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="+" Width="100" Height="35" Margin="10" Command="{Binding OperationCommand}" CommandParameter="+"></Button>
<Button Content="-" Width="100" Height="35" Margin="10" Command="{Binding OperationCommand}" CommandParameter="-"></Button>
<Button Content="*" Width="100" Height="35" Margin="10" Command="{Binding OperationCommand}" CommandParameter="*"></Button>
<Button Content="/" Width="100" Height="35" Margin="10" Command="{Binding OperationCommand}" CommandParameter="/"></Button>
</StackPanel>
</Grid>
</Window>
註意,在xaml前端UI代碼中,分別對TextBox的Text和Button的Command進行了綁定,已達到數據驅動UI,以及UI響應客戶的功能。
在UI的構造函數中,將DataContext數據上下文和ViewModel進行關聯,如下所示:
namespace DemoMVVM
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MainWindowViewModel viewModel;
public MainWindow()
{
InitializeComponent();
viewModel = new MainWindowViewModel();
this.DataContext = viewModel;
}
}
}
MVVM實例演示
通過以上步驟,已經完成了MVVM的簡單應用。實例演示如下:
以上就是深入理解WPF中MVVM的設計思想的全部內容。希望可以拋磚引玉,一起學習,共同進步。
作者:小六公子
出處:http://www.cnblogs.com/hsiang/
本文版權歸作者和博客園共有,寫文不易,支持原創,歡迎轉載【點贊】,轉載請保留此段聲明,且在文章頁面明顯位置給出原文連接,謝謝。
關註個人公眾號,定時同步更新技術及職場文章