• MVVM之Event and Command


    Event: 

    在Silverlight和WPF中没有使用.net的LCR事件,而是使用Routed路由事件,根本原因是因为Silverlight控件的节点树。

    一个简单的示例:

    public static readonly RoutedEvent MyRoutedEvent =EventManager.RegisterRoutedEvent("MyEvent", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyClass));

    是不是很熟悉,没错和定义附加属性(依赖属性)的方式类似,解释下参数:

    public static RoutedEvent RegisterRoutedEvent(
    string name,
    RoutingStrategy routingStrategy,
    Type handlerType,
    Type ownerType
    )

    Name:第一个就是事件的名字(也就是一个public,类型和handlerType一致的属性),这个对于同一个类是唯一的;
    routingStrategy:指示路由事件的路由策略。枚举值
                Tunnel:路由事件使用隧道策略,以便事件实例通过树向下路由(从根到源元素)。
                Bubble:路由事件使用冒泡策略,以便事件实例通过树向上路由(从事件元素到根)。
                Direct: 路由事件不通过元素树路由,但支持其他路由事件功能,例如类处理、EventTrigger 或 EventSetter
    handlerType:事件的类型,例子为RoutedEventHandler。
    ownerType:事件所属的类,通常就是当前类。

     限制:Silverlight和WPF的路由事件的一个最大的限制,就是需要把代码写在后置代码中,这样就无法在其他类中进行操作。
             这个例子最能说明问题

    <TextBox Text="{Binding Source={StaticResource myDomainObject}, Path=StringProperty}"
    TextChanged="TextBox_TextChanged" />

    这样的代码经常去写,给TextBox添加TextChanged事件,然后在事件中去更新什么东西,可是如果这么做了就打破了MVVM的完整性。都知道默认的Binding更新数据源是在LostFocus的时候去提交,可以通过UpdateSourceTrigger(枚举)去设置,修改后如下:

    <TextBox Text=”{Binding Source={StaticResource myDomainObject}, Path=StringProperty,
    UpdateSourceTrigger=PropertyChanged}” />

    没错,修改默认的UpdateSoureTrigger为PropertyChanged即值改变后立马提交

    public string StringProperty
    {
    get { return _stringProperty; }
    set
    {
    _stringProperty = value;
    ProcessNewStringProperty(_stringProperty);
    }
    }

    绑定的属性则调用INotifyPropertyChanged接口的PropertyChanged事件进行更新通知。

    Command:

    ICommandSource :定义了解如何调用命令的对象,WPF 中可实现 ICommandSource 的类包括: ButtonBaseMenuItem 和 Hyperlink

    属性:
    Command  获取将在调用命令源时执行的命令。
    CommandParameter 表示可在执行命令时传递给该命令的用户定义的数据值。
    CommandTarget      将在其上执行命令的对象。

    说了这么多,主要是为了引出ICommand接口,也就是Command的类型。
     ICommand:定义一个命令。
     属性:
     CanExecuteChanged  当出现影响是否应执行该命令的更改时发生。
     函数:
    CanExecute  定义用于确定此命令是否可以在其当前状态下执行的方法。(根据绑定方法的逻辑,来控制按钮是否禁用状态)
    Execute       定义在调用此命令时调用的方法。 

    接下来看张图,RouteEvent的实现关系



    RoutedCommand执行的调用的方法CommandManager,然后搜索元素树,找出匹配的连接一个ICommand和Handler的CommandBinding。这CommandBinding阶级作为一个关联类多对多类的关系,同事出现在ICommand间发生和他们的Handler。

    看下自定义Command的实现设计图:

    我们主要是在创建一个Command,同时实现Execute方法,然后绑定到Target去,这样就完成了一个完整的触发之定义Command。

    定义Commander要求:

    1.ViewModel(调用者)和Command(执行者)在不同的ViewModel和Command(自定义Command类)

    2.Command作为ViewModel的成员(公有)

    3.Command的Hander应该和其在同一个类(在同一个ViewModel完成Commande初始化和绑定)

    4.CanExecute方法必须被实现,以完成禁用(启用)Target

    5.CanExecute该参数是可选的,即可以不指定此委托

    看个Command的示例:

      public class RelayCommand:ICommand
    {
    private readonly Action<object> _execute;
    private readonly Predicate<object> _canExecute;

    public RelayCommand(Action<object> execute)
    : this(execute, null)
    {
    }
    public RelayCommand(Action<object> execute,Predicate<object> canExecute)
    {
    if(execute==null)
    throw new ArgumentNullException("execute");
    _canExecute = canExecute;
    _execute = execute;
    }

    public bool CanExecute(object parameter)
    {
    return _canExecute == null ? true : _canExecute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
    add { CommandManager.RequerySuggested += value; }
    remove { CommandManager.RequerySuggested -= value; }
    }

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


    可以看到自定义的Command有两个属性,类型分别为Action<object>和 Predicate<object>,前者为一个委托,封装一个方法,参数就是类型,当前为object;后者为表示定义一组条件并确定指定对象是否符合这些条件的方法,也是一个委托,返回值为bool。
    同时我们的自定义Command还有两个方法Execute和CanExecute ,前者就是Command的主要方法,执行绑定的函数,参数为object;后者也提到了是用来实现控件是否禁用。
    其中还有一个很重要的属性就是 CanExecuteChanged,它用来监听用户界面的改变,光标从一个Control移动到另一个Control这样的改变,来确定Element的状态。

    写完了Commande实现,还需实现ViewModel的代码:

    public class LogInViewModel
    {
    private LogInModel _logInModel;
    private RelayCommand _logInCommand;

    public string UserName
    {
    get;
    set;
    }

    public string Password
    {
    get;
    set;
    }

    public RelayCommand LogInCommand
    {
    get
    {
    return _logInCommand;
    }
    }
    public LogInViewModel()
    {
    _logInModel = new LogInModel();
    _logInCommand = new RelayCommand(param=>this.AttemptLogIn(),param=> this.CanAttemptLogIn());
    }

    private void AttemptLogIn()
    {
    _logInModel.LogIn(UserName, Password);
    }

    private bool CanAttemptLogIn()
    {
    return !string.IsNullOrWhiteSpace(UserName) && !string.IsNullOrWhiteSpace(Password);
    }
    }


    其实代码也很好理解,在ViewModel中有四个属性,一个Model,一个Command,两个用户界面要输入的属性。

    在构造函数中实例化Model和Command,在实例化Command时参数很奇怪,使用了lambda表达式,这样在实例化Command的时候param表示一个对方法的引用而不是去执行方法。AttemptLogIn方法在Command被触发的时候执行,所以要实现View的代码部分:

    <Window x:Class="View.Wpf.LogInView"
    xmlns
    ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x
    ="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:model
    ="clr-namespace:ViewModel;assembly=ViewModel"
    Title
    ="LogInView" Height="300" Width="300">
    <Window.Resources>
    <model:LogInViewModel x:Key="loginModel"></model:LogInViewModel>
    </Window.Resources>
    <Grid DataContext="{StaticResource ResourceKey=loginModel}">
    <Grid.RowDefinitions>
    <RowDefinition Height="auto"></RowDefinition>
    <RowDefinition Height="auto"></RowDefinition>
    <RowDefinition Height="auto"></RowDefinition>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
    <ColumnDefinition></ColumnDefinition>
    <ColumnDefinition></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <Label Content="UserName:" Grid.Row="0" Grid.Column="0" Margin=" 0 5 0 0"></Label>
    <TextBox Grid.Row="0" Grid.Column="1" x:Name="txtUserName" Text="{Binding Path=UserName,UpdateSourceTrigger=PropertyChanged}"
    Margin=" 0 5 0 0"></TextBox>
            <Label Content="Password:" Grid.Row="1" Grid.Column="0" Margin=" 0 5 0 0"></Label>
    <TextBox Grid.Row="1" Grid.Column="1" x:Name="txtPassword" Text="{Binding Path=Password, UpdateSourceTrigger=PropertyChanged}" Margin=" 0 5 0 0" ></TextBox>
    <Button Content="LogIn" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Height="25" Width="200" Margin=" 0 5 0 0"
    Command
    ="{Binding Path=LogInCommand}" />
    </Grid>
    </Window>


    Xaml代码也是很简单,两个文本框分别表示用户名和密码,一个按钮用来触发Command,绑定两个TextBox的Text分别为UserName和Password,然后绑定按钮的Command为LogInCommand,这样一个完整的自定义Command就完成了,当我们输入完毕用户名和密码,则按钮自动启用,然后点击登录就搞定了。

  • 相关阅读:
    前端优化技巧
    AngularJS 细节
    Xamarin.ios 目录结构
    Java编程中“为了性能”需做的26件事
    Java中的反射Reflection
    在MyEclipse中导入Datebase方法以及在MyEclipse项目工程里加载jar驱动的方法
    Solr 使用 Log4j
    solr学习笔记linux下配置solr
    Java程序员应该了解的10个面向对象设计原则
    Java堆内存的10个要点
  • 原文地址:https://www.cnblogs.com/ListenFly/p/2409387.html
Copyright © 2020-2023  润新知