[WPF]MVVM的数据绑定

啥是MVVM?

我理解的MVVM是Model(数据),View(界面),ViewModel(数据与界面之间的桥梁)的缩写,是一种编程模式。前期需要多花一些时间去编辑绑定,在后期维护方便。只需要关注数据即可。如:传统的界面更新需要去刷新界面才能读取到最新数据,在MVVM模式下只需要关注数据的更改,在该模式下可以实现控制界面自动更新。

一.数据是如何绑定到界面的?一步一步来!

  • 1.创建一个WPF项目,如在VS2022下创建WPF应用程序。(其他版本类似)

  • 2.项目创建好后,这里先创建Model数据类型。新建一个Model文件夹,添加一个ClassModel类,在此类中我们就写3个字段。

  • 3.新建View文件夹,添加一个MainView.xaml界面。再把启动界面改成MainView.xaml,这时候可以把项目自动生成的MainWindow.xaml删除掉了。

  • 4.布局MainView界面。

<Page x:Class="WPFMVVM.View.MainView"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:local="clr-namespace:WPFMVVM.View"
      mc:Ignorable="d" 
      d:DesignHeight="450" d:DesignWidth="500"
      Title="MainView" Background="AliceBlue" FontFamily="行楷">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

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

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

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

        <TextBlock
            Grid.Row="1"
            Grid.Column="1"
            HorizontalAlignment="Left"
            VerticalAlignment="Center"
            FontSize="50"
            Foreground="#0078d4"
            Text="20" />
        <TextBlock
            Grid.Row="2"
            Grid.Column="0"
            HorizontalAlignment="Right"
            VerticalAlignment="Center"
            FontSize="50"
            Text="性别:" />

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

        <Button
            Grid.Row="3"
            Grid.ColumnSpan="2"
            Width="300"
            Margin="10"
            Content="刷新"
            FontSize="30"
            FontWeight="Bold" 
            Background="Aqua"
            BorderBrush="Azure"/>
    </Grid>
</Page>

  • 5.以上界面的数据是写死的。我们需要将显示的地方绑定我们的数据。在绑定之前先建一个ViewModel文件夹。为了和MainView界面对应,要再添加一个MainViewVM类,在此类中我们使用刚创建的ClassMode数据模型,并在构造函数中初始化以下信息。
   public class MainViewVM
    {
        private ClassModel _classM;

        public ClassModel ClassM
        {
            get { return _classM; }
            set { _classM = value; }
        }
        public MainViewVM()
        {
            ClassM = new ClassModel();
            ClassM.Name = "王刚";
            ClassM.Age = 10;
            ClassM.Sex = "男";
        }
    }
  • 6.在MainView界面绑定ClassM的属性就行。
 <TextBlock
            Grid.Row="0"
            Grid.Column="1"
            HorizontalAlignment="Left"
            VerticalAlignment="Center"
            FontSize="50"
            Foreground="#0078d4"
            Text="{Binding ClassM.Name}" />

但是这时候运行项目,发现什么数据也没有。

这是因为数据和界面都有了,我们并没有把他们关联在一起。要把View(MainView)与ViewModel(MainViewVM)关联一起。在MainView.xaml.cs里添加using WPFMVVM.ViewModel引用。再指定MainView的DataContext即可:

using WPFMVVM.ViewModel;
namespace WPFMVVM.View
{
    public partial class MainView : Page
    {
        public MainView()
        {
            InitializeComponent();
            this.DataContext = new MainViewVM();
        }}}

启动解决方案,发现数据已经绑定上去了。

二.属性变更

  • 1.MVVM是数据驱动的,我们现在改变以下属性数据,看看界面效果。先简单的给button添加一个click事件:
   <Button
            Grid.Row="3"
            Grid.ColumnSpan="2"
            Width="300"
            Margin="10"
            Content="刷新"
            FontSize="30"
            FontWeight="Bold" 
            Background="Aqua"
            BorderBrush="Azure"
            Click="Button_Click"/>

 public partial class MainView : Page
    {
        MainViewVM mainViewVM;
        public MainView()
        {
            InitializeComponent();
            mainViewVM = new MainViewVM();
            this.DataContext = mainViewVM;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            mainViewVM.ClassM.Name = "王大康";
            mainViewVM.ClassM = mainViewVM.ClassM;//需要再次给ClassM复制才会触发界面刷新
        }
    }
  • 2.我们还需要让属性具有通知界面更新的能力。WPF中让属性具备通知更新的能力,要让ViewModel继承类型INotifyPropertyChanged,并实现PropertyChanged属性。此例MainViewVM添加如下:
    public class MainViewVM : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler? PropertyChanged;

        private void RaisePropertyChange(string propertyName)
        {
            PropertyChangedEventHandler hander = PropertyChanged;
            if (hander != null)
            {
                hander(this, new PropertyChangedEventArgs(propertyName));
            }
        }
  • 3.完成以上步骤,我们还需要在ViewModel的数据属性set中调用更新界面的方法RaisePropertyChanged:
        public ClassModel ClassM
        {
            get { return _classM; }
            set
            {
                _classM = value;
                RaisePropertyChange(nameof(ClassM));
            }
        }

现在点击Button按钮,可以实现信息的刷新!

三.点击事件

  • 1.WPF给我们提供了Command属性,当然在MVVM模式下我们要用Command替换Click方法:
        <Button
            Grid.Row="3"
            Grid.ColumnSpan="2"
            Width="300"
            Margin="10"
            Content="刷新"
            FontSize="30"
            FontWeight="Bold" 
            Background="Aqua"
            BorderBrush="Azure"
            Command="{Binding ClickAction}"
        />
  • 2.要实现绑定的ClickAction方法,需要继承ICommand接口。我们可以自己创建类型去实现 CanExecute、Execute、CanExecuteChanged接口。
    下面我们在MainViewVM.cs新建一个RealayCommand类:
  public class RelayCommand<T> : ICommand
    {
        /// <summary>
        /// 命令是否执行
        /// </summary>
        readonly Func<bool> _canExecute;
        /// <summary>
        /// 命令执行的方法
        /// </summary>
        readonly Action<T> _execute;
        /// <summary>
        /// 命令的构造函数
        /// </summary>
        /// <param name="canExecute"></param>
        /// <param name="execute"></param>
        public RelayCommand(Func<bool> canExecute, Action<T> execute)
        {
            _canExecute = canExecute;
            _execute = execute;
        }
        /// <summary>
        /// 判断命令是否可执行
        /// </summary>
        /// <param name="parameter"></param>
        /// <returns></returns>
        public bool CanExecute(object? parameter)
        {
            if (_canExecute == null)
            {
                return true;
            }
            return _canExecute();
        }
        /// <summary>
        /// 执行命令
        /// </summary>
        /// <param name="parameter"></param>
        public void Execute(object parameter)
        {
            _execute((T)parameter);
        }
        /// <summary>
        /// 事件追加、移除
        /// </summary>
        public event EventHandler? CanExecuteChanged
        {
            add
            {
                if (_canExecute != null)
                {
                    CommandManager.RequerySuggested += value;
                }
            }
            remove
            {
                if (_canExecute != null)
                {
                    CommandManager.RequerySuggested -= value;
                }
            }
        }
    }

这个类接收两个参数,一个是命令执行的方法,另一个是返回值方法。

  • 3.我们在MainViewVM下编写以下代码调用RelayCommand:
        /// <summary>
        /// 创建Button绑定的命令
        /// </summary>
        public ICommand ClickAction
        {
            get
            {
                return new RelayCommand<object>(CanUpdateExecute, UpdateNameExecute);
            }
        }
        /// <summary>
        /// 命令是否可执行
        /// </summary>
        /// <returns></returns>
        bool CanUpdateExecute()
        {
            return true;
        }
        /// <summary>
        /// 命令执行的方法
        /// </summary>
        /// <param name="parameter"></param>
        void UpdateNameExecute(object parameter) 
        {
                ClassM.Name = "王大康";
                ClassM = ClassM;
        }

此时我们运行程序,点击按钮,可以看到界面可以内容可以实现刷新了!

我们创建的RealayCommand是泛型类,执行命令的时候可以从界面把参数传过来。例如我们可以在Button添加CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}}"属性,然后把信息传到后台使用:

前端代码:
  <Button
            Grid.Row="3"
            Grid.ColumnSpan="2"
            Width="300"
            Margin="10"
            Content="刷新"
            FontSize="30"
            FontWeight="Bold" 
            Background="Aqua"
            BorderBrush="Azure"
            Command="{Binding ClickAction}"
            CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}}"/>
button 绑定的命令:
    void UpdateNameExecute(object parameter) 
        {
            Button? button =parameter as Button;//parameter前端绑定的Button
            if (button.Background !=Brushes.BlanchedAlmond) 
            {
                button.Background = Brushes.BlanchedAlmond ;
                ClassM.Name = "王大康";
                ClassM = ClassM;
            }
            else
            {
                button.Background=Brushes.DarkBlue; 
                ClassM.Name = "李小康";
                ClassM = ClassM;
            }
        }

到此,简单的MVVM数据绑定完成👌


其实吧大可不必那么麻烦,我们可以安装引用GalaSoft.MvvmLight库,RaisePropertyChange/RelayCommand都不用自定义了,省事了。如下是引用库后的MainViewVM代码:

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using WPFMVVM.Model;

namespace WPFMVVM.ViewModel
{
    public class MainViewVM : ViewModelBase
    {
        private ClassModel _classM;
        public ClassModel ClassM
        {
            get { return _classM; }
            set
            {
                _classM = value;
                RaisePropertyChanged(nameof(ClassM));
            }
        }
        public MainViewVM()
        {
            ClassM = new ClassModel();
            ClassM.Name = "王xiao刚";
            ClassM.Age = 10;
            ClassM.Sex = "男";
        }

        /// <summary>
        /// 创建Button绑定的命令
        /// </summary>
        public ICommand ClickAction
        {
            get
            {
                return new RelayCommand<object>(UpdateNameExecute);
            }
        }
        /// <summary>
        /// 命令执行的函数
        /// </summary>
        /// <param name="parameter"></param>
        private void UpdateNameExecute(object parameter)
        {
            Button? button = parameter as Button;
            if (button.Background != Brushes.BlanchedAlmond)
            {
                button.Background = Brushes.BlanchedAlmond;
                ClassM.Name = "王大康";
                ClassM = ClassM;
            }
            else
            {
                button.Background = Brushes.DarkBlue;
                ClassM.Name = "李小康";
                ClassM = ClassM;
            }
        }
    }
}
posted @ 2023-02-17 18:28  肥皂泡泡oO  阅读(690)  评论(0编辑  收藏  举报