WPF MVVM

  • Post author:
  • Post category:WPF
  • Post comments:0评论

WPF(Windows Presentation Foundation):WPF 是基于 Windows 的应用程序框架,在 WPF 中多为数据驱动 UI ,框架提供数据绑定和模板支持

MVVM(Model – View – ViewModel):MVVM软件架构模式把应用程序分成三部分,分别是模型(Model)、视图(View)、视图模型(ViewMdel);设计思想在于隔离界面和业务逻辑

  • 模型(Model):数据和业务逻辑
  • 视图(View):交互界面
  • 视图模型(ViewModel):为视图提供可绑定的数据和命令

基于WPF中的数据绑定机制, MVVM 模式可以很好地支持双向绑定和数据验证;由于 WPF 是基于 XAML 的,这使得 MVVM 模式可以很好地支持视图和视图模型的分离。

示例:

Model,此处创建一个设备的模型

namespace MVVMDemo
{
    /// <summary>
    /// 设备
    /// </summary>
    public class Device
    {
        /// <summary>
        /// 设备名称
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 设备序列号
        /// </summary>
        public string Sn { get; set; }

        /// <summary>
        /// 是否运行中
        /// </summary>
        public bool IsActive { get; private set; } = false;

        /// <summary>
        /// 运行
        /// </summary>
        /// <returns>已运行或运行失败返回false</returns>
        public bool Run()
        {
            if (IsActive)
                return false;
            IsActive = true;
            return true;
        }

        /// <summary>
        /// 停止
        /// </summary>
        /// <returns>已停止或停止失败返回false</returns>
        public bool Stop()
        {
            if (!IsActive)
                return false;
            IsActive = false;
            return true;
        }
    }
}

此处先实现一个 ICommand ,在视图模型中需要定义一些 Command 绑定到视图中的按钮上以便与用户交互;当然也可以使用 MVVM 工具库(例如Prism,MvvmLight等)中实现好的 Command

using System;
using System.Windows.Input;

namespace MVVMDemo
{
    public class DelegateCommand : ICommand
    {
        private readonly Func<bool> _canExecute;

        private readonly Action _execute;

        public event EventHandler CanExecuteChanged
        {
            add
            {
                if (_canExecute != null)
                    CommandManager.RequerySuggested += value;
            }

            remove
            {
                if (_canExecute != null)
                    CommandManager.RequerySuggested -= value;
            }
        }

        public DelegateCommand(Action execute) : this(execute, null)
        {
        }

        public DelegateCommand(Action execute, Func<bool> canExecute)
        {
            _execute = execute ?? throw new ArgumentNullException(nameof(execute));
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            return _canExecute?.Invoke() ?? true;
        }

        public void Execute(object parameter)
        {
            _execute();
        }
    }
}

ViewModel,为 MainWindow 创建的视图模型,在视图模型添加模型中需要与用户交互的属性和 Command ;在 MainViewModel 中实现 INotifyPropertyChanged 接口,INotifyPropertyChanged 是 WPF 中用于实现数据绑定的接口,它定义了一个 PropertyChanged 事件,当视图模型中的属性发生变化时,可以触发这个事件来通知视图进行更新,具体实现方式通常是在属性的 setter 中触发这个事件;在视图模型中需要创建了一个静态的VM(Instance)实例用于视图绑定;除此之外还可以有多种方式创建和绑定,例如在 MainWindow.xaml.cs 中创建并指定,例如:DataContext = new MainViewModel();

using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;

namespace MVVMDemo
{
    public class MainViewModel : INotifyPropertyChanged
    {
        private readonly Device _machine;

        public static MainViewModel Instance => new MainViewModel();

        public string Name
        {
            get => _machine.Name;
            set
            {
                _machine.Name = value;
                OnPropertyChanged();
            }
        }

        /// <summary>
        /// 编辑的名称,由于没有从VM属性更新到UI,故不路由属性变更,仅接收从UI到VM的变化
        /// </summary>
        public string EditName { get; set; }

        public string Sn => _machine.Sn;

        public bool IsActive => _machine.IsActive;

        public ICommand RunStopCommand { get; }

        public ICommand UpdateCommand { get; }

        public MainViewModel()
        {
            //初始化一个设备
            _machine = new Device { Name = "Machine", Sn = "A20230128" };
            //将命令绑定接到模型的逻辑上
            RunStopCommand = new DelegateCommand(() =>
            {
                if (_machine.IsActive)
                    _machine.Stop();
                else
                    _machine.Run();
                //通知属性变更,UI会重新get这个属性
                OnPropertyChanged(nameof(IsActive));
            });
            //更新编辑的名称到真正的名称
            UpdateCommand = new DelegateCommand(() =>
            {
                if (string.IsNullOrWhiteSpace(EditName))
                    return;
                Name = EditName;
            });
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

在实现视图之前先创建一个 BooleanToBrush 的 Converter,这个 Converter 用于将视图模型中的 Bool 属性在视图上显示成不同的颜色;这个 Converter 需要继承 MarkupExtension 和实现 IValueConverter

using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Markup;
using System.Windows.Media;

namespace MVVMDemo
{
    public class BooleanToBrush : MarkupExtension, IValueConverter
    {
        private readonly Brush RunBrush = new SolidColorBrush(Colors.LawnGreen);

        private readonly Brush StopBrush = new SolidColorBrush(Colors.Red);

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return this;
        }

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            bool isActive = (bool)value;
            if (isActive)
                return RunBrush;
            else
                return StopBrush;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

View,视图中,视图中通过d:DataContext="{d:DesignInstance local:MainViewModel}"指定调试时的视图模型,通过DataContext="{x:Static local:MainViewModel.Instance}"指定实际绑定的视图模型,如果通过MainWindow.xaml.cs 中创建并指定则不需要在视图中指定,相关控件的属性都与视图模型中的NameSnEditNameIsActiveUpdateCommandRunStopCommand进行绑定

<Window
    x:Class="MVVMDemo.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:MVVMDemo"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800"
    Height="450"
    d:DataContext="{d:DesignInstance local:MainViewModel}"
    DataContext="{x:Static local:MainViewModel.Instance}"
    mc:Ignorable="d">
    <StackPanel Margin="20" Orientation="Vertical">
        <StackPanel Margin="0,10" Orientation="Horizontal">
            <TextBlock FontWeight="DemiBold" Text="Name: " />
            <TextBlock Text="{Binding Name}" />
            <TextBlock
                Margin="20,0,0,0"
                FontWeight="DemiBold"
                Text="Sn: " />
            <TextBlock Text="{Binding Sn}" />
            <TextBlock
                Margin="20,0,0,0"
                FontWeight="DemiBold"
                Text="IsActive: " />
            <Ellipse
                Width="14"
                Height="14"
                Fill="{Binding IsActive, Converter={local:BooleanToBrush}}"
                Stroke="Black" />
        </StackPanel>
        <StackPanel Margin="0,10" Orientation="Horizontal">
            <TextBlock FontWeight="DemiBold" Text="Edit Name: " />
            <TextBox MinWidth="100" Text="{Binding EditName}" />
            <Button Command="{Binding UpdateCommand}" Content="Update" />
            <Button
                Margin="20,0,0,0"
                Command="{Binding RunStopCommand}"
                Content="Run/Stop" />
        </StackPanel>
    </StackPanel>
</Window>

效果演示

发表回复