MVVM架构浅谈 – 事件绑定

之前讲过使用ICommand在ViewModel中实现命令的实现,但是界面中需要将事件和Command绑定成功才算正在完成一次事件绑定。MVVM模式开发中,界面的一些控件的点击事件是可以直接绑定Command属性,然后在ViewModel中实现该命令。但是大部分控件可不止一个点击事件,比如DoubleClick、SelectionChanged等事件。下面我们就讲讲如何在Xmal设计界面中绑定非点击事件。

通常事件绑定到Command有三种情况。

1、使用MouseBinding

在MouseBinding绑定命令的只针对鼠标的一些点击事件,如下:

  • None:执行任何操作
  • LeftClick:单击鼠标左键
  • RightClick:单击鼠标右键
  • MiddleClick:单击鼠标中建
  • WheelClick:旋转鼠标滚轮
  • LeftDoubleClick:双击鼠标左键
  • RightDoubleClick:双击鼠标右键
  • MiddleDoubleClick:双击鼠标中键

示例代码如下:

<Button Content="Button">
    <Button.InputBindings>
        <MouseBinding MouseAction="LeftClick" Command ={"Binding ClickCommand"}/>
        <MouseBinding MouseAction="RightClick" Command={"Binding RightClickCommand"}/>
    </Button.InputBindings>
</Button>

2、使用触发器

使用触发器的时候需要引用System.Windows.Interactivity.dll(必须安装了Blend才有该组件)。示例代码如下:

使用前引用命名空间

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<Button Content="Button">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="MouseDoubleClick">
             <i:InvokeCommandAction Command="{Binding DoubleClickCommand}"
                   CommandParameter="{Binding Parameter}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Button>

上面代码中EventName可以设置为控件的各种事件,Command是命令名称,CommandParameter是执行命令传入参数,该参数一般是在ViewModel中。

3、使用CommandBehavior绑定

参考 AttachedComandBehavior ,Command之必杀技-AttachedBehavior

MVVM架构浅谈 – Binding Mode

在Xaml中绑定数据的时候,通常会设置Mode属性,下面对Mode属性的几种类型进行说明。

OneWay:使用OneWay绑定时,没当数据源发生变化时,数据就会从源流向目标。

OneTime:绑定也会将数据从源流向目标。但是,仅当启动了应用程序或DataContext发生更改时才会如此操作,因此,它不会侦听源中的更改通知。

OneWayToSource:绑定会将数据从目标发送到源。

TwoWay:绑定会将源数据发送到目标,但如果目标属性的值发生变化,则会将它们发回给源。

Default:Binding的模式根据实际情况来定,如果是可编辑的就是TwoWay,只读的就是OneWay。

MVVM架构浅谈 – IValueConverter

如果想要在界面绑定属性的时候,想要实现绑定的ViewModel数据类型和界面的数据类型不同,并实现相互的转换。举个简单的例子,在邮件系统的数据库设计的时候,邮件状态0表示未读,1表示已读,但是我们希望未读的时候显示未读标识图片,已读时候图片消失。这时候就需要使用数据转换器来实现了,也就是下面要说的IValueConverter。所谓的转换器就是通过一个条件或者一个类型,转换成另一种结果或者类型。
IValueConverter接口中有两个方法,Convert和ConvertBack。Convert函数表示从数据源到目标的值转换,ConvertBack函数表示从目标到数据源的值转换。因此如果绑定模式是一次绑定或者单向绑定,只需要实现Convert函数;如果绑定模式是双向绑定,需要实现Convert和ConvertBack函数。

下面实现一个邮件标记到图片的转换器:

[ValueConversion(typeof(int), typeof(string))]
public class MailStatusConverter: IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        int statusValue= Convert.ToInt32(value);
        if(statusValue==0)
        {
            return "unread.png";
        }
        return "read.png";
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        string strValue=value.ToString();
        if(strValue=="unread.png") return 0;
        return 1;
    }
}

完成了转换器之后需要在Xaml中运用到。首先要引用转换器命名空间,

xmlns:local="clr-namespace:xxx"

如果是在UWP中将“clr-namespace”换成“Using”,之后创建资源,

<Page.Resources>
    <local:MailStatusConverter x:Key="converter">
</Page.Resources>

最后绑定数据。

<Image source="Binding ReadState,Converter={StaticResource converter},Mode=OneWay" Width="15" Height="15">

MVVM架构浅谈 – ICommand

WPF或者UWP在使用MVVM框架开发的时候,不仅要绑定数据,还需要绑定命令(用户交互的操作)。在ViewModel中我们使用INotifyPropertyChanged接口对数据进行更新,同时我们需要使用实现ICommand对命令进行触发。下面就讲讲ICommand的使用。

ICommand的结构

public interface ICommand
{
    //当出现影响是否应执行命令的更改时发生
    event EventHandler CanExecuteChanged;

    //定义用于确定此命令是否在其当前状态下执行的方法
    bool CanExecute(object parameter);

    //定义在调用此命令时调用的方法
    void Execute(object parameter);
}

一般绑定的命令集成上面的接口,然后执行 Execute方法即可。但是为了方便使用,我们可以定义一个Command公共类对ICommand继承封装。

ICommand公共类 – DelegateCommand


public class DelegateCommand : ICommand
{
    private readonly Action _executeMethod=null;
    private readonly Func<T,bool> _canExecuteMethod=null;

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

    public DelegateCommand(Action executedMethod, Func<T, bool> canExecuteMethod)
    {
        if(executeMethod==null)
            throw new ArgumentNullException("executedMethod");
        _executeMethod=executeMethod;
        _canExecuteMethod=canExecuteMethod;
    }

    public bool CanExecute(T parameter)
    {
        return _canExecuteMethod!=null ? _canExecuteMethod(parameter) : true;
    }

    public void Execute(T parameter)
    {
        _executeMethod?.Invoke(parameter);
    }

    //这个事件需要多了解
    public event EventHandler ICommand.CanExecuteChanged
    {
        add{ CommandManager.RequerySuggested += value;}
        remove{ CommandManager.RequerySuggested -= value;}
    }

    public bool CanExecute(object parameter)
    {
         if(parameter ==null && typeof(T).IsValueType)
         {
             return (_canExecuteMethod==null);
         }
         return CanExecute((T)parameter);
    }

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

在View Model中Command使用如下:


private DelegateCommand _addCommand;
public ICommand AddCommand
{
    get
    {
        if(_addCommand ==null)
        {
            _addCommand =new DelegateCommand(AddCommandExecute);
        }
        return _addCommand;
    }
}

public void AddCommandExecute(object obj)
{......}

MVVM架构浅谈 – INotifyPropertyChanged

在开发WPF或这UWP时,通常我们会使用MVVM模式。在使用该模式框架时,我们使用INotifyPropertyChanged通知页面修改。下面聊聊INotifyPropertyChanged的使用。

先创建一个NotifyPropertyChanged的共用类。

public class NotifyPropertyChanged : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

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

   protected void UpdateProperty(ref T oldValue, T newValue,
         [CallerMemberName] string propertyName = "")
    {
        if (Equals(newValue, oldValue)) return;
        oldValue = newValue;

        if (string.IsNullOrEmpty(propertyName)) return;
        OnPropertyChanged(propertyName);
    }
}

然后创建一个ViewModel类继承上面代码,该类绑定了界面的控件。

public class Person: NotifyPropertyChanged
{
    string name;
    public string Name
    {
        get {return name;}
        set
        {
            if (Equals(name,value)) return;

            name=value;
            OnPropertyChanged(nameof(Name));
        }
    }

    int age;
    public int Age
    {
        get { return age;}
        set
        {
            if(Equals(age,value)) return;

            age=value;
            OnPropertyChanged1();
        }
    }

    string gender;
    public string Gender
    {
        get {return gender;}
        set
        {
            UpdateProperty(ref gender,value);
        }
    }
}

上述代码中,三个属性分别使用了不同的通知属性修改事件的写法。

1、Name属性使用了 OnPropertyChanged(nameof(Name))。这就是我们一般使用的方法,使用 nameof(Name) 会比我们直接硬输入 “Name” 要好些,直接输入字符串容易出现错误。

2、Age属性使用了 OnPropertyChanged1()。我们可以看到在这个方法里 PropertyName前面加了一个修饰特性 [CallerMemberName],该特性能够自动获取属性的名称。该特性只支持.Net 4.5以上的框架。

3、Gender属性使用了 UpdateProperty(…)。在这个方法里就包含了赋值方式,以及[CallerMemberName]修饰特性,减少了外部的重复代码量。

这三种方法都可以,根据实际情况选择不同的写法。