WPF 入門 (二) MVVM 入門


此入門教程是記錄下方參考資料視頻的學習過程
開發工具:Visual Studio 2019

參考資料:https://www.bilibili.com/video/BV1ht411e7Fe

目錄

WPF 入門 (一) XAML 基礎知識

WPF 入門 (二) MVVM 入門

WPF 入門 (三) MVVM 提高

基本常識

開發/學習環境的准備

  • Visual Studio
  • Microsoft Prism:這個框架包含了對 MVVM 開發模式的支持。現在 NuGet 里有
  • Microsoft Blend SDK:不是必須,能解決 MVVM 力不從心的問題。現在 NuGet 里也有

必要知識的准備

  • 熟悉 Data Binding 和 Dependency Property
  • 了解 WPF 中的命令(知道 ICommand 接口即可)
  • 熟悉 Lambda 表達式

創建 Code Snippet (代碼模板)

  • Code Snippet 的使用
    • for
    • class
    • prop
    • ctor
    • ......
  • Code Snippet 的創建
    • Tools --> Code Snippets Manager 找到想修改的文件,從文件管理器復制出來修改,這里復制修改 propfull 的 Snippet
      對應修改代碼模板,修改文件名稱,保存並復制回去就可以使用了
<Title>propn</Title>
<Shortcut>propn</Shortcut>
<Description>Code Snippet for NotificationObject property and backing field</Description>
        <Code Language="csharp"><![CDATA[private $type$ $field$;

public $type$ $property$
{
    get { return $field$;}
    set 
    { 
        $field$ = value;
        this.RaisePropertyChanged("$property$");
    }
}
$end$]]>
        </Code>

這個代碼模板就綁定到 propn 上了
this.RaisePropertyChanged("$property$"); 功能通知屬性改變,這個類以后再寫,"$property$"可以使用 nameof($property$) 更不容易錯

MVVM 設計模式詳解

MVVM = Model - View - ViewModel

為什么要使用 MVVM 模式

  • 團隊層面:統一思維方式和實現方法
  • 架構層面:穩定、解耦、富有禪意
  • 代碼層面:可讀、可測、可替換

解耦是指 UI 與業務邏輯分離

什么是 Model

  • 現實世界中對象的抽象結果,比如 學生 --> Student 之類的

什么是 View 和 ViewModel

  • View = UI
  • ViewModel = Model for View
    • View 需要顯示什么,ViewModel 就要准備什么
    • View 能做什么操作,ViewModel 就要能夠響應這個操作
  • ViewModel 與 View 的溝通
    • 傳遞數據依靠數據屬性
    • 傳遞操作依靠命令屬性

程序的 UI 由 Model 驅動,用戶的數據和操作給 Model
View(User Interface) <-->(雙向的數據屬性) View Model(Model for View)
View(User Interface) -->(單向的命令屬性) View Model(Model for View)
View Model(Model for View) <--> Services <--> Database
View Model(Model for View) <--> Models <--> Services <--> Database

案例講解

初級案例

  • NotificationObject 與 數據屬性
  • DelegateCommand 與 命令屬性
  • View 與 ViewModel 的交互(技術難點)

業務邏輯:

  • 兩個輸入數據
  • 一個按鈕做加法操作
  • 一個顯示兩個輸入相加的結果
  • 一個保存文件操作

新建項目

新建 WPF 項目,項目名稱 SimpleMvvmDemo.Client
先新建文件夾

  • Views
  • ViewModels
  • Models
  • Services
  • Commands

雙向的數據屬性 Binding

NotificationObject 用來做數據傳輸
ViewModels 目錄下新建一個 ViewModel 的基類,命名 NotificationObjector ,具有通知能力的基類
當 ViewModel 里某個屬性的值變化之后,通過某個機制通知 Binding,Binding 把數據送到 View 上
當然,也可以用 Model 去實現這個類/接口,這樣在 ViewModel 中的引用類型的屬性也可以通知 View 上

NotificationObjector 實現 INotifyPropertyChanged 接口,這個接口有一個事件 PropertyChanged
當 ViewModel 的某個屬性借助 數據屬性Binding 關聯到 View 的某個控件上的話,當這個值變化的時候,Binding 就是在監聽着 PropertyChanged 事件,一旦事件發生,就把變化后的值送到界面上去

public class NotificationObjector : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void RasisePropertyChanged(string propertyName)
    {
        if (null != this.PropertyChanged)
        {
            this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
PropertyChangedEventHandler 委托

表示將處理 PropertyChanged 事件的方法,該事件在更改組件上的屬性時引發。

單向的命令屬性 Binding

DelegateCommand 用來做操作傳輸
Commands 目錄下新建一個類,命名 DelegateCommand ,實現 ICommand 接口,里面有三個東西

  • 事件 CanExecuteChanged 當這個命令能不能執行的狀態發生改變的時候,有機會通知命令的調用者
  • 方法 CanExecute(object parameter) 用來幫助命令的呼叫者判斷這個命令能不能執行
  • 方法 Execute(object parameter) 當命令執行的時候,你想做什么,就寫在這個地方

簡陋的 DelegateCommand ,只用於講解本案例

public class DelegateCommand : ICommand
{
    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter)
    {
        if (null == this.CanExecuteFunc)
        {
            return true;
        }
        return this.CanExecuteFunc(parameter);
    }

    public void Execute(object parameter)
    {
        if (null == this.ExecuteAction)
        {
            return;
        }
        this.ExecuteAction(parameter);
    }

    public Action<object> ExecuteAction { get; set; }
    public Func<object, bool> CanExecuteFunc { get; set; }
}

ViewModel

在 ViewModels 目錄新建 MainWindowViewModel ,繼承剛才的 NotificationObject 類,這里面的代碼會用到上面寫的 propn 代碼模板
實現三個數據屬性和兩個命令操作

public class MainWindowViewModel : NotificationObjector
{
    #region 關聯屬性
    private double _input_01;

    public double Input_01
    {
        get { return _input_01; }
        set
        {
            _input_01 = value;
            this.RaisePropertyChanged(nameof(Input_01));
        }
    }

    private double _input_02;

    public double Input_02
    {
        get { return _input_02; }
        set
        {
            _input_02 = value;
            this.RaisePropertyChanged(nameof(Input_02));
        }
    }


    private double _result;

    public double Result
    {
        get { return _result; }
        set
        {
            _result = value;
            this.RaisePropertyChanged(nameof(Result));
        }
    }
    #endregion

    #region 關聯操作
    public DelegateCommand AddCommand { get; set; }
    public DelegateCommand SaveCommand { get; set; }

    private void Add(object parameter)
    {
        this.Result = this.Input_01 + this.Input_02;
    }

    private void Save(object parameter)
    {
        SaveFileDialog dlg = new SaveFileDialog();
        dlg.ShowDialog();
    }

    public MainWindowViewModel()
    {
        this.AddCommand = new DelegateCommand();
        this.SaveCommand = new DelegateCommand();
        //this.AddCommand.ExecuteAction = new Action<object>(this.Add);
        //this.SaveCommand.ExecuteAction = new Action<object>(this.Save);
        this.AddCommand.ExecuteAction = this.Add;
        this.SaveCommand.ExecuteAction = this.Save;
    }

    #endregion

}

View

使用 {Binding} 綁定數據源和操作,要對應屬性,沒有智能提示
MainWindow.xaml

<Window x:Class="SimpleMvvmDdeml.Client.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:SimpleMvvmDdeml.Client"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Menu>
            <MenuItem Header="_File">
                <MenuItem Header="_Save" Command="{Binding SaveCommand}"/>
            </MenuItem>
        </Menu>
        <Grid Grid.Row="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Slider x:Name="Slider_01" Grid.Row="0" Background="LightBlue" Minimum="-100" Maximum="100" Margin="4" Value="{Binding Input_01}"/>
            <Slider x:Name="Slider_02" Grid.Row="1" Background="LightBlue" Minimum="-100" Maximum="100" Margin="4" Value="{Binding Input_02}"/>
            <Slider x:Name="Slider_03" Grid.Row="2" Background="LightBlue" Minimum="-100" Maximum="100" Margin="4" Value="{Binding Result}"/>
            <Button x:Name="Button_Add" Grid.Row="3" Content="Add" Width="120" Height="80" Command="{Binding AddCommand}"/>
        </Grid>
    </Grid>
</Window>

沒有指定 Source 會拿 DataContext 當作 Source ,當 DataContext 也沒有就會往上級找,最后找到 Window
所以,我們需要在構造函數中綁定 ViewModel
MainWindow.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        this.DataContext = new MainWindowViewModel();
    }
}

效果

只修改前台代碼,同樣可以運行

<Window x:Class="SimpleMvvmDdeml.Client.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:SimpleMvvmDdeml.Client"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Menu>
            <MenuItem Header="_File">
                <MenuItem Header="_Save" Command="{Binding SaveCommand}"/>
            </MenuItem>
        </Menu>
        <Grid Grid.Row="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Slider x:Name="Slider_01" Grid.Row="0" Background="LightBlue" Minimum="-100" Maximum="100" Margin="4" Value="{Binding Input_01}"/>
            <Slider x:Name="Slider_02" Grid.Row="1" Background="LightBlue" Minimum="-100" Maximum="100" Margin="4" Value="{Binding Input_02}"/>
            <Slider x:Name="Slider_03" Grid.Row="2" Background="LightBlue" Minimum="-100" Maximum="100" Margin="4" Value="{Binding Result}"/>
            <Button x:Name="Button_Add" Grid.Row="3" Content="Add" Width="120" Height="80" Command="{Binding AddCommand}"/>
        </Grid>
    </Grid>
</Window>

效果

同樣的業務邏輯,只是界面不同,所以只需要修改 View ,ViewModel 和 Model 不需要改變

WPF 入門 (二) MVVM 入門 結束


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM