欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

WPF 原生绑定和命令功能使用指南

程序员文章站 2022-05-03 11:10:20
如今,当谈到 WPF 时,我们言必称 MVVM、框架(如 Prism)等,似乎已经忘了不用这些的话该怎么使用 WPF 了。当然,这里说的不用框架和 MVVM,并不是说像使用 Winform 那样使用 WPF,而是追本溯源,重识 WPF 与生俱来的绑定和命令的风采。 ......

wpf 原生绑定命令功能使用指南

如今,当谈到 wpf 时,我们言必称 mvvm、框架(如 prism)等,似乎已经忘了不用这些的话该怎么使用 wpf 了。当然,这里说的不用框架和 mvvm,并不是说像使用 winform 那样使用 wpf,而是追本溯源,重识 wpf 与生俱来的绑定命令的风采。

 

一、绑定的使用

 

目标:前台页面通过绑定获取后台属性的值。

这个目标实际上分为两部分,一是前台获取后台的属性值,二是属性值变动后能够及时体现出来。

要实现目标的第一部分,实际只需在窗体后台的构造函数中添加一行代码即可:

this.datacontext = this;

 

这行代码很关键,mvvm 模式中页面与 viewmodel 关联也是通过指定页面类的 datacontext 为相应的 viewmodel 对象来实现的。

 

下面再来说说如何实现目标的第二部分,也就是属性变化后能及时体现出来,包括后台属性变化后前台显示自动变化,以及前台修改了内容,后台属性的值跟着改变。众所周知,这就是绑定,而要实现这一功能,需要相关类实现一个属性变动通知接口 —— inotifypropertychanged 。具体演变过程可参考网上的文章《 .net 4.5 (c#):inotifypropertychanged 执行的演变:从表达式树到调用方信息的 bindablebase 类型 | mgen》,这里直接给出最后的结果。

 

首先,实现 inotifypropertychanged 当然是必要的,如果是要绑定其他类,则让该类实现之,如果是直接在窗口后台做相关功能,则最终窗口类看上去像这样:

public partial class mainwindow : window, inotifypropertychanged

 

然后添加一个事件和两个方法:

public event propertychangedeventhandler propertychanged;
 
protected void onpropertychanged([callermembername] string propertyname = null)
{
    var eventhandler = this.propertychanged;
    eventhandler?.invoke(this, new propertychangedeventargs(propertyname));
}
 
protected bool setproperty<t>(ref t storage, t value, [callermembername] string propertyname = null)
{
    if (equals(storage, value)) return false;
 
    storage = value;
    this.onpropertychanged(propertyname);
    return true;
}

 

最后就是要提供绑定的属性了,可以像下面这样写:

private string _username = "wlh";
public string username
{
    get => _username;
    set => setproperty(ref _username, value);
}

 

前台绑定就很简单了:

二、命令 icommand

 

wpf 和 winform 的重大区别就是,用户的交互、数据的变化等,在 winform 中,都需要程序员一点一点仔细地手动处理,而在 wpf 中,数据是绑定的,交互通过命令传递,所以很多事情其实 wpf 这个大框架就可以帮我们自动处理了。说了这么多,其实就是说 winform 是事件驱动的,而 wpf 是数据驱动的,所以在 winform 中常用的按钮点击事件等各种事件,在 wpf 中是不怎么用了,而是使用命令。

命令也是绑定的,先来看看前台的样子:

<textbox text="{binding username, mode=twoway}"></textbox>

 

至于后台怎么写,先不急,通过《[wpf] icommand 最佳使用方法》一文,我们知道首先需要一个辅助类:

public class relaycommand : icommand
{
    private readonly predicate<object> _canexecute;
    private readonly action<object> _execute;
 
    public relaycommand(predicate<object> canexecute, action<object> execute)
    {
        this._canexecute = canexecute;
        this._execute = execute;
    }
 
    public event eventhandler canexecutechanged
    {
        add => commandmanager.requerysuggested += value;
        remove => commandmanager.requerysuggested -= value;
    }
 
    public bool canexecute(object parameter)
    {
        return _canexecute(parameter);
    }
 
    public void execute(object parameter)
    {
        _execute(parameter);
    }
}

 

可见 icommand 中主要有两个方法,一个检查命令是否可用的 canexecute (),以及实际干活的 execute () 。

 

然后在后台添加一个 “dosomething” 的命令,也就是上面新建的 relaycommand 类型:

private icommand _dosomething;
public icommand dosomethingcommand
{
    get
    {
        return _dosomething ??= new relaycommand(
            o => _candosomething(o),
            o => { _dosomethingmethod(o); });
    }
}
 
private readonly predicate<object> _candosomething = o => true;
 
// 可在之后再赋值,避免方法体中访问属性等受阻;
private readonly action<object> _dosomethingmethod = o =>
{
    // do something
};

 

这些还可以进一步简化为:

public icommand dosomethingcommand { get; set; }
 
/// <summary>
/// 命令方法赋值(在构造方法中调用)
/// </summary>
private void setcommandmethod()
{
    dosomethingcommand ??= new relaycommand(o => true, async o =>
    {
        // do something
    });
}

 

最后来看看对应前台”gettokencommand” 命令的实际业务代码:

public icommand gettokencommand { get; set; }
 
/// <summary>
/// 命令方法赋值 (在构造函数中调用)
/// </summary>
private void setcommandmethod()
{
    gettokencommand ??= new relaycommand(o => !(string.isnullorempty(username) || string.isnullorempty(password)), async o =>
    {
        var req = new reqgettoken()
        {
            username = username,
            password = password,
        };
 
        var res = await gettoken(req);
        if (res.code)
        {
            token = res.token;
        }
    });
}

 

可以看到,在检查命令是否可用的部分,没有像样板代码那样直接返回 true ,而是按照实际情况判断,这样的效果就是,当条件不满足时,前台相关控件自动禁用:

WPF 原生绑定和命令功能使用指南

 

最后,经过我们这样写,其实和 mvvm 模式已经很接近了,只要把后台所有代码都移到另一个类,然后将页面的 datacontext 重新指定一下,就能实现页面显示和业务逻辑分离了。