WPF使用MVVM(一)-屬性綁定


WPF使用MVVM(一)-屬性綁定

簡單介紹MVVM

MVVMModel(數據類型),View(界面),ViewModel(數據與界面之間的橋梁)的縮寫,是一種編程模式,優點一勞永逸,初步增加一些邏輯和工作量,但是為后期維護增加了極大的便利性,減少編程的關注點。

如:界面顯示某一數據,在數據有變動的情況下,傳統方式是更新此數據,同時需要手動更新界面中的數據顯示。在MVVM的模式下只需關心數據變更即可,數據可通過綁定的模式進行刷新或不刷新。

關於窗口中的行為(點擊、鼠標移入、移出。。。),也可以通過MVVM的方式進行綁定,后期就算窗體重新設計,依然可以在對應的地方綁定對應的行為,維護方式也非常靈活。

項目搭建

有了大概的層級描述之后, 我們可以嘗試建立一個新的WPF程序,此程序用來顯示某個員工的信息(姓名年齡職業),這里先創建我們的Model(數據模型),字段不多就三個,就叫EmployeeModel

    public class EmployeeModel
    {
        /// <summary>
        /// 姓名
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 年齡
        /// </summary>
        public int Age { get; set; }

        /// <summary>
        /// 職業
        /// </summary>
        public string Profession { get; set; }
    }

然后來布局一下我們的View(界面),這里叫做MainWindow

image-20220105164759019

界面代碼如下(老樣子,注意命名空間與你的項目保持一致):

<Window
    x:Class="Test.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:local="clr-namespace:Test"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800"
    Height="450"
    mc:Ignorable="d">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <TextBlock
            Grid.Row="0"
            Grid.Column="0"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            FontSize="50"
            Text="姓名:" />

        <TextBlock
            Grid.Row="0"
            Grid.Column="1"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            FontSize="50"
            Foreground="#0078d4"
            Text="王虎" />

        <TextBlock
            Grid.Row="1"
            Grid.Column="0"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            FontSize="50"
            Text="年齡:" />

        <TextBlock
            Grid.Row="1"
            Grid.Column="1"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            FontSize="50"
            Foreground="#0078d4"
            Text="35" />
        <TextBlock
            Grid.Row="2"
            Grid.Column="0"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            FontSize="50"
            Text="工種:" />

        <TextBlock
            Grid.Row="2"
            Grid.Column="1"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            FontSize="50"
            Foreground="#0078d4"
            Text="程序員" />

        <Button
            Grid.Row="3"
            Grid.ColumnSpan="2"
            Margin="20"
            Content="更新一下信息"
            FontSize="30"
            FontWeight="Bold" />
    </Grid>
</Window>

可以看到,上面的數據都是寫死的,需要我們將顯示的地方,綁定到我們的數據中,在開始綁定之前呢,這里需要建立一下我們的橋梁,也就是ViewModel,為了和界面能對應上,名字叫做MainWindowVM,在這個ViewModel中,我們使用剛才創建的數據模型EmployeeModel,並在構造函數中,初始化一個員工的信息:

    public class MainWindowVM
    {
        private EmployeeModel _employee;
        /// <summary>
        /// 員工數據
        /// </summary>
        public EmployeeModel EmployeeM
        {
            get { return _employee; }
            set { _employee = value; }
        }

        public MainWindowVM()
        {
            EmployeeM = new EmployeeModel();
            EmployeeM.Name = "小明(綁定)";
            EmployeeM.Age = 44;
            EmployeeM.Profession = "程序員";
        }
    }

現在我們只需要界面綁定一下MainWindowVM中數據EmployeeM的對應屬性就行了,修改界面代碼如下:

<Window
    x:Class="Test.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:local="clr-namespace:Test"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800"
    Height="450"
    mc:Ignorable="d">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <TextBlock
            Grid.Row="0"
            Grid.Column="0"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            FontSize="50"
            Text="姓名:" />

        <TextBlock
            Grid.Row="0"
            Grid.Column="1"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            FontSize="50"
            Foreground="#0078d4"
            Text="{Binding EmployeeM.Name}" />

        <TextBlock
            Grid.Row="1"
            Grid.Column="0"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            FontSize="50"
            Text="年齡:" />

        <TextBlock
            Grid.Row="1"
            Grid.Column="1"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            FontSize="50"
            Foreground="#0078d4"
            Text="{Binding EmployeeM.Age}" />
        <TextBlock
            Grid.Row="2"
            Grid.Column="0"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            FontSize="50"
            Text="工種:" />

        <TextBlock
            Grid.Row="2"
            Grid.Column="1"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            FontSize="50"
            Foreground="#0078d4"
            Text="{Binding EmployeeM.Profession}" />

        <Button
            Grid.Row="3"
            Grid.ColumnSpan="2"
            Margin="20"
            Content="更新一下信息"
            FontSize="30"
            FontWeight="Bold" />
    </Grid>
</Window>

這時候當我們運行項目的時候,發現界面空空如也,什么數據也沒有:

image-20220105164337683

這是因為,雖然我們已經有了MainWindowVM(ViewModel),但是並沒有將它與MainWindow(View)進行一個關聯,所以需要在MainWindow的后台指定一下當前的ViewModel是誰,設置MainWindowDataContext即可:

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

            this.DataContext = new MainWindowVM();
        }
    }

image-20220105164659556

這下就正常了,下面我們要來演示下MVVM的魔力!

屬性變更

之前已經說了,MVVM是數據驅動的,我們已經綁定了后台的數據,現在改動下后台數據來看看界面的更新效果。

為了演示,我們給界面中的Button按鈕,添加一個點擊事件:

        <Button
            Grid.Row="3"
            Grid.ColumnSpan="2"
            Margin="20"
            Click="Button_Click"
            Content="更新一下信息"
            FontSize="30"
            FontWeight="Bold" />

在點擊事件中,我們修改當前綁定的MainWindowVM(ViewModel)中的EmployeeM屬性,觀察界面的變化,為了在Click事件中獲取綁定的實例,需要為MainWindowVM創建一個外部的字段,代碼如下:

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

            mainWindowVM = new MainWindowVM();
            this.DataContext = mainWindowVM;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            EmployeeModel model = new EmployeeModel();
            model.Name = "王明(修改)";
            model.Age = 38;
            model.Profession = "外賣員";

            mainWindowVM.EmployeeM = model;
        }
    }

此時,我們在按鈕的點擊事件中重新給EmployeeM賦值一個新值,運行程序,點擊按鈕,發現什么都沒變,斷點跟進去,發現屬性的值已經被修改,但是界面的數據並未發生更改:

image-20220105171927963

這跟之前說的MVVM模式介紹有出入,按理說應該是數據更新界面也更新。

原來綁定屬性才是第一步,僅僅綁定還不行,還需要讓屬性具備通知界面更新的能力。

那我們就來給屬性添加一下這個能力。

WPF中讓屬性具備通知界面更新的能力,需要讓我們的ViewModel也就是MainWindowVM,繼承類型INotifyPropertyChanged,並實現它的PropertyChanged屬性:

    public class MainWindowVM: INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
                handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

完成這一步呢,還沒完, 還差一個小地方,那就是我們EmployeeM屬性的Set中,需要再補上一句,這樣當我們的EmployeeM被設置值的時候,就會調用更新界面的方法RaisePropertyChanged

        private EmployeeModel _employee;
        /// <summary>
        /// 員工數據
        /// </summary>
        public EmployeeModel EmployeeM
        {
            get { return _employee; }
            set
            {
                _employee = value;
                //當前屬性的名稱
                RaisePropertyChanged("EmployeeM");
            }
        }

大功告成,現在運行項目,點擊按鈕, 就能夠看到界面進行更新了。

這時候有些老哥就有疑問了,你這給EmployeeM賦值怎么不一樣呢,為啥要新建一個數據模型,再賦值呢,我實際的情況是想在原有的數據上改個名字而已:

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            mainWindowVM.EmployeeM.Name = "王明(原屬性修改)";
        }

這種修改方式,界面還是沒變呢!

那我們就再來看看類型屬性修改怎么通知界面:

類型屬性修改

上面我們知道數據變動通知界面更新,是我們在EmployeeMSet中調用RaisePropertyChanged方法實現的,所以要想實現類型屬性在原有數據的基礎上進行界面通知,我們就需要為該類型(EmployeeModel)中屬性(Name)的Set中調用RaisePropertyChanged方法,也就是:

        /// <summary>
        /// 姓名
        /// </summary>
        public string Name
        {
            set
            {
                //value;
                RaisePropertyChanged("Name")
            }
        }

這樣需要什么條件呢,需要我們讓EmployeeModel也要繼承INotifyPropertyChanged的接口,並實現對應的方法。很先然違背了我們設計的初衷,畢竟我們希望所有的處理都放在橋梁MainWindowVM(ViewModel)中去弄,所以看來只能另辟蹊徑了!

不過這難不倒我們,現在我這邊有兩種方式可以這么弄,一種中規中矩的,一種比較暴力,先看中規中矩的吧!

既然我們的在屬性的Set中調用通知界面更新的方法,那么我們完全可以在MainWindowVM(ViewModel)新建幾個屬性,這些屬性返回我們模型數據的屬性,如下:

        /// <summary>
        /// 名稱
        /// </summary>
        public string EmployeeName
        {
            get { return EmployeeM.Name; }
            set
            {
                EmployeeM.Name = value;
                RaisePropertyChanged("EmployeeName");
            }
        }

此時我們的界面綁定的值也不是EmployeeM.Name,而是直接改為EmployeeName:

        <TextBlock
            Grid.Row="0"
            Grid.Column="1"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            FontSize="50"
            Foreground="#0078d4"
            Text="{Binding EmployeeName}" />

現在我們直接修改Name屬性就可以了

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            //mainWindowVM.EmployeeM.Name = "王明(原屬性修改)";
            mainWindowVM.Name = "王明(原屬性修改)";
        }

另外一種方式更加直接,更加暴力的方式,上面都不用修改,直接重新賦值一遍:

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            mainWindowVM.EmployeeM.Name = "王明(原屬性修改)";
            mainWindowVM.EmployeeM = mainWindowVM.EmployeeM;
        }

既然它沒有走Set,我們手動幫助他走一遍!

總的來說,兩種方式都不是特別理想,這邊也想不出比較不錯的方式,如果有好的意見,還請分享下!非常感謝!

下一節來說一下命令,讓我們的界面后台看起來更加的干凈!也就是將Button_Click移動到MainWindowVM(ViewModel)中。


免責聲明!

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



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