zoukankan      html  css  js  c++  java
  • Mvvm Light Toolkit for wpf/silverlight系列之Command和Events

          <TextBox x:Name="TextBox1" Margin="5,0,5,0" Width="100">
            <i:Interaction.Triggers>
              <i:EventTrigger EventName="TextChanged">
                <i:InvokeCommandAction Command="{Binding BehaviourCommand, Mode=OneWay}"
                                       CommandParameter="{Binding Text,ElementName=TextBox1}"/>
              </i:EventTrigger>
            </i:Interaction.Triggers>
          </TextBox>

    事件是WPF/SL应用程序中UI与后台代码进行交互的最主要方式,与传统方式不同,mvvm中主要通过绑定到命令来进行事件的处理,因此要了解mvvm中处理事件的方式,就必须先熟悉命令的工作原理。

     

    一、RelayCommand命令

    WPF/SL命令是通过实现 ICommand 接口创建的。 ICommand 公开两个方法(ExecuteCanExecute)和一个事件(CanExecuteChanged)。 Execute 执行与命令关联的操作。CanExecute 确定是否可以在当前命令目标上执行命令。在MvvmLight中实现ICommand接口的类是RelayCommand,RelayCommand通过构造函数初始化ExecuteCanExecute方法,因此,构造函数传入的是委托类型的参数,ExecuteCanExecute则执行的是委托的方法,RelayCommand相关代码如下:

            public RelayCommand(Action execute, Func<bool> canExecute)
            {
                if (execute == null)
                {
                    throw new ArgumentNullException("execute");
                }
    
                _execute = execute;
                _canExecute = canExecute;
            }
    
            [DebuggerStepThrough]
            public bool CanExecute(object parameter)
            {
                return _canExecute == null ? true : _canExecute();
            }
    
            public void Execute(object parameter)
            {
                _execute();
            }

    二、 Comand属性绑定

    通常实现了 ICommandSource的控件可以使用Comand属性绑定,实现 ICommandSource 的 WPF 类包括:ButtonBaseMenuItemHyperlink 以及 InputBinding

    简单绑定示例:

    xaml:

    <Button Command="{Binding SimpleCommand}" Content="简单命令" />

    ViewModel:

     

    注意:SL4才开始支持Command属性绑定,之前的版本不能使用Command属性绑定

    CanExecute命令绑定示例:

    xaml:

          <Button Command="{Binding CanExecuteCommand}" Content="CanExecute命令" Margin="5,0,5,0"/>
          <CheckBox x:Name="chkCanClick" IsChecked="{Binding CanClick,Mode=TwoWay}" 
                    Content="勾上复选框,CanExecute命令按钮才能变为可用"/>

    viewmodel:

            bool _CanClick;
            public bool CanClick
            {
                get { return _CanClick; }
    
                set
                {
                    if (_CanClick == value)
                        return;
    
                    _CanClick = value;
    
                    RaisePropertyChanged("CanClick");
                    
                    // SL中需要手动调用RaiseCanExecuteChanged方法更新按钮可用s状态
                    CanExecuteCommand.RaiseCanExecuteChanged();
    
                }
            }
    
            public MainViewModel()
            {
                ...
    
                CanExecuteCommand = new RelayCommand
                   (
                       () =>
                       {
                           CommandResult = "执行CanExecute命令";
                       },
                       () => CanClick  // 等价于()=>{return CanClick;}
                   );
                   ...
            }

    与简单命令绑定不同的是,CanExecuteCommand构造函数包含两个参数,第二个参数的返回值表示是否可以在按钮上执行命令操作,返回False,则不能在按钮上执行命令,因此按钮为不可用,返回True,则能够在按钮上执行命令,按钮可用。以上示例中,CanExecute返回值与CheckBox的IsChecked属性关联,这样更改CheckBox的勾选状态,按钮的可用状态会随之更改。

     

    带参数的命令绑定示例:

    xaml:

          <Button Command="{Binding ParamCommand}" Content="带参数的CanExecute命令"
                  CommandParameter="{Binding CanClick,Mode=OneWay}" Margin="5,0,5,0"/>

      viewmodel:

            public MainViewModel()
            {
                 ...
    
                 ParamCommand = new RelayCommand<bool?>
                    (
                        (p) =>
                        {
                            CommandResult = string.Format("执行带参数的命令(参数值:{0})", p);
                        },
                        (p) => p??false
                    );
                 ...
            }

    这里ParamCommand接收TextBox的值作为参数,泛型版本表示参数类型为string,此时Execute和CanExecute参数也必须是带参数的版本。

     

    注意:

       1、在SL4中带参数的CanExecute与不带参数的CanExecute之间的区别;带参数的CanExecute,与参数绑定的属性(CanClick)更改,会自动触发命令的CanExecute方法,而不带参数的CanExecute方法,则需要手动调用CanExecuteCommand.RaiseCanExecuteChanged()方法更新按钮的可用状态。

    2、在WPF中,RelayCommand通过CommandManager不停地侦听按钮的CanExecute的状态,因此WPF中按钮的CanExecute状态会随时响应CanExecute方法中的更改,WPF中可以不调用命令的RaiseCanExecuteChanged方法

     

          三、使用行为绑定命令

    Command属性绑定只能绑定ICommandSource类型的控件的点击事件,对于其他控件事件,比如TextChanged事件,不能直接绑定到Command,这时我们可以使用Blend的InvokeCommandAction行为来绑定事件到命令,使用步骤如下:

    用Blend4打开解决方案,选中要触发事件的控件,比如TextBox,在资产面板中选择行为,在列表中选择InvokeCommandAction,如图:

     

    双击InvokeCommandAction后会为TextBox生成InvokeCommandAction行为,在属性栏可以设置行为的属性:

    在EventName栏选择触发的事件名称TextChanged,Command绑定跟Button的绑定方式一样,最后我们生成的代码如下:

    xaml:

          <TextBox x:Name="TextBox1" Margin="5,0,5,0" Width="100">
            <i:Interaction.Triggers>
              <i:EventTrigger EventName="TextChanged">
                <i:InvokeCommandAction Command="{Binding BehaviourCommand, Mode=OneWay}"
                                       CommandParameter="{Binding Text,ElementName=TextBox1}"/>
              </i:EventTrigger>
            </i:Interaction.Triggers>
          </TextBox>

      ViewModel:

            public MainViewModel()
            {
                ...
    
                BehaviourCommand = new RelayCommand<string>
                   (
                       (p) =>
                       {
                           CommandResult = string.Format("执行TextChanged命令,触发命令的TextBox值为{0}",p);
                       },
                       (p) => 
                       { 
                           return !string.IsNullOrEmpty(p); 
                       }
                   );
                ...
            }

    这样就可以间接的将TextBox的MouseRightButtonDown事件绑定到Command。

    注意:

    这种方式相当于将事件映射到Command,CanExecute的返回值只能决定命令是否会被执行,而不能是使得命令目标的可用状态发生改变。以上示例中,输入第一个字母时,命令并没有执行,此时命令无效,但文本框仍然有效,输入第二个字母命令才执行

     

    四、使用MvvmLight行为EventToCommand绑定命令

    虽然InvokeCommandAction行为可以将控件的事件转换到Command绑定,也可以通过CommandParameter向ViewModel传递参数,但是对于一些特殊的事件,比如MouseMove,我们需要在事件处理方法中得到鼠标位置信息,使用上面的方式仍不能完成任务;这时我们就需要使用EventToCommand行为,它是MvvmLight封装的行为,要使用行为需要添加GalaSoft.MvvmLight.Extras.dll和System.Windows.Interactivity.dll的引用。

    同样,在Blend4中打开解决方案,选中要触发事件的控件 ,在资产面板中选择行为,在列表中选择EventToCommand,双击生成行为,然后设置EventName为MouseMove,然后设置Command绑定,同时需要设置PassEventArgsToCommand="True",也就是将事件参数传递给Command,生成的代码如下:

    xaml:

    <Grid>
            <Ellipse Fill="AliceBlue" Height="180" Stroke="Black" Margin="10,8">
            <i:Interaction.Triggers>
              <i:EventTrigger EventName="MouseMove">
                <GalaSoft_MvvmLight_Command:EventToCommand PassEventArgsToCommand="True"
                                                Command="{Binding MoveMouseCommand}" />
              </i:EventTrigger>
            </i:Interaction.Triggers>
          </Ellipse>
    
          <TextBlock HorizontalAlignment="Center" Text="带事件参数的命令 (鼠标移动事件)"
                     TextWrapping="Wrap" Grid.Row="7" d:LayoutOverrides="Height"
                     Grid.ColumnSpan="2" VerticalAlignment="Center"
                     FontSize="20" FontWeight="Bold"
                     IsHitTestVisible="False" />
        </Grid>

     

    viewmodel:

                MoveMouseCommand = new RelayCommand<MouseEventArgs>
                    (
                        (e) => 
                            {
                                var element = e.OriginalSource as UIElement;
                                var point = e.GetPosition(element);
    
                                CommandResult = string.Format("执行带MouseEventArgs事件参数的命令,鼠标位置:X-{0},Y-{1}",point.X,point.Y);
                            }
                    );

     

    这里命令的初始化方式与带参数的命令一样,只需将参数类型换成事件参数类型

     

    EventToCommand不仅可以用来传递事件参数,他还可以将CanExecute返回值与命令目标的IsEnable属性关联,我们只需将MustToggleIsEnabled的属性设置为True就可以了,示例代码如下:

    xaml:

    <TextBox x:Name="TextBox2" Text="为空时CanExecute为false" Margin="5,0,5,0" Width="200">
            <i:Interaction.Triggers>
              <i:EventTrigger EventName="TextChanged">
                <GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding BehaviourCommand}"
                                                           MustToggleIsEnabled="{Binding IsChecked,ElementName=chkMustToggle}"  
                                                           CommandParameter="{Binding Text,ElementName=TextBox2}" />
              </i:EventTrigger>
            </i:Interaction.Triggers>
          </TextBox>
          <CheckBox x:Name="chkMustToggle" IsChecked="False" Content="MustToggleIsEnabled,勾选则TextBox的可用状态与CanExecute返回值关联"/>

     

    五、使用自定义行为绑定命令

    如果以上方法都不能满足你的要求,你还可以自定义行为来绑定命令,以下是WPF中自定义行为的代码(SL代码请在文章最后下载示例代码对照阅读):

    首先,我们创建一个命令参数类型:

    using System;
    using System.Windows;
    
    namespace MvvmlightCommand.WPF4.TriggerActions
    {
        public class EventInformation<TEventArgsType>
        {
            public object Sender { get; set; }
            public TEventArgsType EventArgs { get; set; }
            public object CommandArgument { get; set; }
        }
    
    }

     

    然后创建行为类:

    using System;
    using System.Windows;
    using System.Windows.Interactivity;
    using System.Windows.Input;
    
    
    namespace MvvmlightCommand.WPF4.TriggerActions
    {
        public class MapEventToCommand : MapEventToCommandBase<EventArgs>
        {
        }
    
        public class MapRoutedEventToCommand : MapEventToCommandBase<RoutedEventArgs>
        {
        }
    
        public abstract class MapEventToCommandBase<TEventArgsType> : TriggerAction<FrameworkElement>
            where TEventArgsType : EventArgs
        {
            public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(MapEventToCommandBase<TEventArgsType>), new PropertyMetadata(null, OnCommandPropertyChanged));
            public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(MapEventToCommandBase<TEventArgsType>), new PropertyMetadata(null, OnCommandParameterPropertyChanged));
    
            private static void OnCommandParameterPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                var invokeCommand = d as MapEventToCommand;
                if (invokeCommand != null)
                {
                    invokeCommand.SetValue(CommandParameterProperty, e.NewValue);
                }
            }
    
            private static void OnCommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                var invokeCommand = d as MapEventToCommand;
                if (invokeCommand != null)
                {
                    invokeCommand.SetValue(CommandProperty, e.NewValue);
                }
            }
    
            protected override void Invoke(object parameter)
            {
                if (this.Command == null)
                {
                    return;
                }
    
                var eventInfo = new EventInformation<TEventArgsType>
                {
                    EventArgs = parameter as TEventArgsType,
                    Sender = this.AssociatedObject,
                    CommandArgument = GetValue(CommandParameterProperty)
                };
    
                if (this.Command.CanExecute(eventInfo))
                {
                    this.Command.Execute(eventInfo);
                }
            }
    
            public ICommand Command
            {
                get
                {
                    return (ICommand)base.GetValue(CommandProperty);
                }
                set
                {
                    base.SetValue(CommandProperty, value);
                }
            }
    
            public object CommandParameter
            {
                get
                {
                    return base.GetValue(CommandParameterProperty);
                }
                set
                {
                    base.SetValue(CommandParameterProperty, value);
                }
            }
        }
    
    }

     

    编译生成项目,在Blend4中打开解决方案,选中要触发事件的控件 ,在资产面板中选择行为,在列表中选择MapRoutedEventToCommand ,双击生成行为,然后设置EventName为TextChanged,然后设置Command绑定,代码如下:

    xaml:

          <TextBox x:Name="TextBox3" Text="更改文本框的值" Margin="5,0,5,0" Width="200">
            <i:Interaction.Triggers>
              <i:EventTrigger EventName="TextChanged">
                <my:MapRoutedEventToCommand Command="{Binding CustomBehaviorCommand}" CommandParameter="P1"/>
              </i:EventTrigger>
            </i:Interaction.Triggers>
          </TextBox>

     

    viewmodel:

                CustomBehaviorCommand = new RelayCommand<EventInformation<RoutedEventArgs>>
                   (
                       (ei) =>
                       {
                           EventInformation<RoutedEventArgs> eventInfo = ei as EventInformation<RoutedEventArgs>;
    
                           System.Windows.Controls.TextBox sender = eventInfo.Sender as System.Windows.Controls.TextBox;
    
                           CommandResult = string.Format("执行{0}的TextChanged命令,文本框的值:{1},传递的参数:{2},事件参数:{3}",
                               sender.Name,
                               sender.Text,
                               ei.CommandArgument,
                               ei.EventArgs.GetType().ToString());
                       },
                       (ei) =>
                       {
                           return true;
                       }
                   );

     

    这样,我们就可以同时将sender、CommandParameter、和事件参数传递到Command的参数中了

     

    本章节主要介绍MvvmLight中命令和事件的处理方法,下章我们将介绍MvvmLight中的Messenger的使用方法,以下是本章源代码下载:

     

    http://download.csdn.net/source/3262832

    作者:王春明 出处:http://wangchunming.cnblogs.com/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    奶酪(NOIP2017 Day2 T1)
    图的遍历(某谷P3916)
    20154331 EXP9web安全基础实践
    20154331 EXP8 web基础
    20154331EXP7 网络欺诈
    20154331 Exp6 信息搜集与漏洞扫描
    Exp5 MSF基础应用
    Exp4 恶意代码分析
    Exp3 免杀原理与实践
    20154331黄芮EXP2 后门原理与实践
  • 原文地址:https://www.cnblogs.com/wangchunming/p/2670956.html
Copyright © 2011-2022 走看看