深入理解WPF中MVVM的設計思想

来源:https://www.cnblogs.com/hsiang/archive/2023/09/18/17709739.html
-Advertisement-
Play Games

近些年來,隨著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 和鏈接的數據保持同步,同時可以將用戶輸入路由到相應的命令。具體如下圖所示:

如上圖所示:

  1. View(用戶頁面),主要用於向使用者展示信息,並接收用戶輸入的信息(數據綁定),及響應用戶的操作(Command)。
  2. ViewModel(用戶視圖業務邏輯),主要處理客戶請求,以及數據呈現。
  3. Model數據模型,作為存儲數據的載體,是一個個的具體的模型類,通過ViewModel進行調用。但是在小型項目中,Model並不是必須的
  4. IService(數據介面),數據訪問服務,用於獲取各種類型數據的服務。數據的形式有很多種,如網路數據,本地數據,資料庫數據,但是在ViewModel調用時,都統一封裝成了Service。在小型項目中,IService數據介面也並不是必須的,不屬於MVVM的範疇
  5. 在上圖中,DataBase,Network,Local等表示不同的數據源形式,並不屬於MVVM的範疇。

 

前提條件

 

要實現MVVM,首先需要滿足兩個條件:

  1. 屬性變更通知,在MVVM思想中,由WinForm的事件驅動,轉變成了數據驅動。在C#中,普通的屬性,並不具備變更通知功能,要實現變更通知功能,必須要實現INotifyPropertyChanged介面。
  2. 綁定命令,在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/
本文版權歸作者和博客園共有,寫文不易,支持原創,歡迎轉載【點贊】,轉載請保留此段聲明,且在文章頁面明顯位置給出原文連接,謝謝。
關註個人公眾號,定時同步更新技術及職場文章


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 對重寫代碼說不。 以下為譯文: 1、重寫代碼消耗了12個月! 我們從頭開始重寫代碼浪費的時間。 你能想象在軟體行業,12個月的時間沒有任何新產品推出,沒有任何新版本更新嗎? 真的,我不由自主地問自己這個問題: 在這個快速發展的世界里,12月的時間能讓我們做多少事情? “2015年1月20日,星期二, ...
  • Nacos 2.x版本增加了GRPC服務介面和客戶端,極大的提升了Nacos的性能,本文將簡單介紹grpc-java的使用方式以及Nacos中集成GRPC的方式。 grpc-java GRPC是google開源的、以protobuf作為序列化方式、以http2作為通信協議的高性能rpc框架。 grp ...
  • 1.什麼是權重比例 權重比例計算即將各數值乘以相應的權數,然後加總求和得到總體值,再除以總的單位數。 如何計算 有一個對象集合為[A,B,C,D,E,F,G,H,I,J],其對象的全紅 總權重為10 每一個對象的權重為1/10=0.1 2.什麼是權重覆蓋區域 權重覆蓋區域是對象在整體權重範圍中的鎖分 ...
  • JDK21 計劃23年9月19日正式發佈,雖然一直以來都是“版本隨便出,換 8 算我輸”,但這麼多年這麼多版本的折騰,如果說之前的 LTS版本JDK17你還覺得不香,那 JDK21還是有必要關註一下,因為會有一批重要更新發佈到生產環境中,特別是千呼萬喚的虛擬線程,雖然說這東西我感覺不需要的用不到,需 ...
  • 前言 MongoDB是一個基於分散式文件存儲的開源資料庫系統,使用C++語言編寫。它是一個介於關係資料庫和非關係資料庫之間的產品,具有類似關係資料庫的功能,但又有一些非關係資料庫的特點。MongoDB的數據模型比較鬆散,採用類似json的bson格式,可以靈活地存儲各種類型的數據 MongoDB的優 ...
  • gRPC 是開發中常用的開源高性能遠程過程調用(RPC)框架,tonic 是基於 HTTP/2 的 gRPC 實現,專註於高性能、互操作性和靈活性。該庫的創建是為了對 async/await 提供一流的支持,並充當用 Rust 編寫的生產系統的核心構建塊。今天我們聊聊通過使用tonic 調用grpc... ...
  • 來源:虛無境的博客 地址:www.cnblogs.com/xuwujing/p/11953697.html 在介紹Nginx的負載均衡實現之前,先簡單的說下負載均衡的分類,主要分為硬體負載均衡和軟體負載均衡,硬體負載均衡是使用專門的軟體和硬體相結合的設備,設備商會提供完整成熟的解決方案,比如F5,在 ...
  • CRC校驗技術是用於檢測數據傳輸或存儲過程中是否出現了錯誤的一種方法,校驗演算法可以通過計算應用與數據的迴圈冗餘校驗(CRC)檢驗值來檢測任何數據損壞。通過運用本校驗技術我們可以實現對特定記憶體區域以及磁碟文件進行完整性檢測,並以此來判定特定程式記憶體是否發生了變化,如果發生變化則拒絕執行,通過此種方法來... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...