WPF MVVM系统入门-上

WPF MVVM系统入门-上

本文详细讲解WPF,MVVM开发,实现UI与逻辑的解耦。

Models:存放数据的模型,实体对象

Views:可视化界面

ViewModels:业务逻辑。ViewModels与Models的联系会更紧密,而Views页面会主动绑定ViewModels中的数据,原则上ViewModels不要直接去操作Views,被动的被Views来获取数据即可。

一般遵循MVVM模式的项目下,都会有Models、Views、ViewModels三个文件夹来存放不同的代码工程。

案例入门

实现一个加法计算器,输入两个值,进行相加,并返回结果。

Model

一共需要三个double类型,两个输入,一个输出。

public class MainModel
{
    public double Value1 { get; set; }
    public double Value2 { get; set; }
    public double Value3 { get; set; }
}

ViewModel

编写相加的逻辑代码

public class MainViewModel
{
    //声明一个Model类型的属性
    public MainModel mainModel { get; set; } = new MainModel();
	//业务逻辑
    public void Add()
    {
        mainModel.Value3 = mainModel.Value2 + mainModel.Value1;
    }
    public MainViewModel()
    {
        //业务逻辑的调用
        //为了便于观察,延迟3s后调用方法
        Task.Factory.StartNew(() =>
        {
            Task.Delay(3000).Wait();
            Add();
        });
    }
}

View

view中,利用一个Textbox和一个slider作为输入分别绑定Value1和value2,使用另一个TextBox绑定value3

<Window.DataContext>
    <vm:MainViewModel />
</Window.DataContext>
<Grid>
    <StackPanel>
        <TextBox Text="{Binding mainModel.Value1, UpdateSourceTrigger=PropertyChanged}" />
        <Slider
            Maximum="100"
            Minimum="0"
            Value="{Binding mainModel.Value2}" />
        <TextBox Text="{Binding mainModel.Value3}" />
    </StackPanel>
</Grid>

这样等待3秒,下面的TextBox中的数值一直没有更新

img

造成这样的原因是,Value3虽然更新了,但是并没有通知绑定他的控件,所以Value3作为输出,需要在更新时触发一个事件,让该事件的订阅者(也就是绑定该值得控件)进行响应。

改进Model

public class MainModel:INotifyPropertyChanged
{
    public double Value1 { get; set; }
    public double Value2 { get; set; }

    private double _value3;

    public double Value3
    {
        get { return _value3; }
        set
        {
            _value3 = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value3"));
        }
    }
    public event PropertyChangedEventHandler? PropertyChanged;
}

img

在本案例中,只有三个属性,但是属性很多,难道都需要这样触发事件吗?有没有简单的方式?

PropertyChanged.Fody插件

PropertyChanged.Fody插件可让用户简化事件的通知,Nuget安装PropertyChanged.Fody

[AddINotifyPropertyChangedInterface]
public class MainModel
{
    public double Value1 { get; set; }
    public double Value2 { get; set; }
    public double Value3 { get; set; }
}

只需要这样就可以完成上面的工作,它的原理是在编译的时候,把所有的属性都会使用PropertyChanged.Invoke来触发事件。但有时有些属性不需要进行触发,比如本案例的Value1和Value2,所以可以这样

[AddINotifyPropertyChangedInterface]
public class MainModel
{
    [DoNotNotify]
    public double Value1 { get; set; }
    [DoNotNotify]
    public double Value2 { get; set; }
    public double Value3 { get; set; }
}
//其他的特性
/// AlsoNotifyFoAttribute     实现通知的时候,同时通知其属性
/// DoNotNotify 指定不需要通知相关的代码
/// DependsOn 指定哪些属性变化的时候,通知当前属性变化
/// DoNotCheckEquality    强制不做旧值比对(默认情况会自动添加比对代码)

命令Command

在上面案例中是使用了构造函数调用了Add方法,但是如果我想增加一个按钮,在点击的时候才执行Add方法要怎么办,当然可以绑定按钮的Click事件,但是这样的话Click事件要放置在View的后台类中,不能很好的利用绑定与ViewModel建立联系,所以引出了命令Command

命令的用途

  1. 将调用命令的对象与执行命令的逻辑分开,这允许多个源调用相同的命令逻辑
  2. 可以指示命令是否可用,如登陆时,用户名为空则登陆按钮不可用

命令要实现ICommand接口,该接口中包含

  • CanExecute:是否可执行方法
  • Execute:主要执行逻辑
  • CanExecuteChanged:触发检查是否可执行

案例入门

  1. 定义一个类实现ICommand接口
public class CommandBase : ICommand
{
    public event EventHandler? CanExecuteChanged;

    public bool CanExecute(object? parameter)
    {
        return true;//返回true表示命令可用
    }

    //这样做可以在外部给DoExecute委托赋值,根据不同的逻辑业务赋予不同的值
    public Action<object> DoExecute { get; set; }
    public void Execute(object? parameter)
    {
        DoExecute?.Invoke(parameter);
    }
}
  1. 在ViewModel中定义一个CommandBase属性
public class MainViewModel
{
    public MainModel mainModel { get; set; } = new MainModel();

    public void Add(object obj)
    {
        mainModel.Value3 = mainModel.Value2 + mainModel.Value1;
    }

    public CommandBase BtnCommand { get; set; }//命令
    public MainViewModel()
    {
        BtnCommand = new CommandBase() {DoExecute   = new Action<object>(Add) };
    }
}
  1. 页面增加一个Button按键并进行绑定<Button Command="{Binding BtnCommand}" Content="Ok" />

img

检测是否可执行

上面的CommandBase中,直接将CanExecute返回为true,其实可以利用这个方法来实现检测是当前按钮是否可以使用

将上面的CommandBase改为

public Func<object,bool> DoCanExecute { get; set; }
public bool CanExecute(object? parameter)
{
    return DoCanExecute?.Invoke(parameter) == true;
}

更改ViewModel

public class MainViewModel
{
    public MainModel mainModel { get; set; } = new MainModel();

    public void Add(object obj)
    {
        mainModel.Value3 = mainModel.Value2 + mainModel.Value1;
    }

    public bool CanCal(object obj)
    {
        return mainModel.Value1 != 0;
    }

    public CommandBase BtnCommand { get; set; }//命令
    public MainViewModel()
    {
        BtnCommand = new CommandBase() {
            DoExecute = new Action<object>(Add),
            DoCanExecute = new Func<object, bool>(CanCal) };
    }
}

运行后可以看出OK按钮为不可用状态,但是将上面的文本框改为非零状态,仍然是不可用,这是因为在更改value1后,并没有触发检测事件CanExecuteChanged也就是说此时并没有再次执行CanExecute方法。所以需要触发CanExecuteChanged事件,框架会自动执行CanExecute方法。

img

因为事件必须在本类中进行触发,所以在CommandBase中增加DoCanExecuteChanged方法并触发CanExecuteChanged事件。

public void DoCanExecuteChanged()
{
    CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}

因为需要在Value1赋值的时候,触发 CanExecuteChanged事件,所以需要将Model中定义一个CommandBase。并将ViewModel中的CommandBase删除。

public class MainModel:INotifyPropertyChanged
{
    private double _value1;

    public double Value1
    {
        get { return _value1; }
        set {
            _value1 = value;
            BtnCommand.DoCanExecuteChanged();//在更改时触发CanExecuteChanged事件,该事件触发后会执行CanExecute方法
        }
    }

    public double Value2 { get; set; }
    private double _value3;
    public double Value3
    {
        get { return _value3; }
        set
        {
            _value3 = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value3"));
        }
    }
    public event PropertyChangedEventHandler? PropertyChanged;

    public CommandBase BtnCommand { get; set; }//命令

    public MainModel()
    {
        BtnCommand = new CommandBase()
        {
            DoExecute = new Action<object>(Add),
            DoCanExecute = new Func<object, bool>(CanCal)
        };
    }

    public void Add(object obj)
    {
        Value3 =Value2 + Value1;
    }

    public bool CanCal(object obj)
    {
        return Value1 != 0;
    }
}

View中的Button更换command绑定<Button Command="{Binding mainModel.BtnCommand}" Content="Ok" />

img

但是这样的话就必须将Command定义在Model中。那Command如何定义在ViewModel中,请看MVVM系统入门-下。

posted @ 2023-02-16 16:19  步、步、为营  阅读(213)  评论(0编辑  收藏  举报