首页 > 代码库 > 利刃 MVVMLight 7:命令深入

利刃 MVVMLight 7:命令深入

  上面一篇我们大致了解了命令的基本使用方法和基础原理,但是实际在运用命令的时候会复杂的多,并且会遇到各种各样的情况。

一、命令带参数的情况:

如果视图控件所绑定的命令想要传输参数,需要配置 CommandParameter 属性 ,用来传输参数出去。

而继承制Icommand接口的 RelayCommand又支持泛型的能力,这样就可以接受来自客户端请求的参数。

public RelayCommand(Action<T> execute);构造函数传入的是委托类型的参数,Execute 和 CanExecute执行委托方法。

所以,修改上篇的代码如下:

View代码:

 1  <StackPanel Margin="10,20,0,50"> 2                     <TextBlock Text="传递单个参数" FontWeight="Bold" FontSize="12" Margin="0,5,0,5" ></TextBlock> 3                     <DockPanel x:Name="ArgStr" > 4                         <StackPanel DockPanel.Dock="Left" Width="240" Orientation="Horizontal" > 5                             <TextBox x:Name="ArgStrFrom" Width="100" Margin="0,0,10,0"></TextBox> 6                             <Button Content="传递参数" Width="100" HorizontalAlignment="Left" Command="{Binding PassArgStrCommand}"  7                                     CommandParameter="{Binding ElementName=ArgStrFrom,Path=Text}"  ></Button> 8                         </StackPanel> 9                         <StackPanel DockPanel.Dock="Right" Width="240" Orientation="Horizontal">10                             <TextBlock Text="{Binding ArgStrTo,StringFormat=‘接收到参数:\{0\}‘}" ></TextBlock>11                         </StackPanel>12                     </DockPanel>13   </StackPanel>

 ViewModel代码:

 1 #region 传递单个参数 2  3         private String argStrTo; 4         //目标参数 5         public String ArgStrTo 6         { 7             get { return argStrTo; } 8             set { argStrTo = value; RaisePropertyChanged(() => ArgStrTo); } 9         }10 11 #endregion12 13 #region 命令14 15         private RelayCommand<String> passArgStrCommand;16         /// <summary>17         /// 传递单个参数命令18         /// </summary>19         public RelayCommand<String> PassArgStrCommand20         {21             get22             {23                 if (passArgStrCommand == null)24                     passArgStrCommand = new RelayCommand<String>((p) => ExecutePassArgStr(p));25                 return passArgStrCommand;26 27             }28             set { passArgStrCommand = value; }29         }30         private void ExecutePassArgStr(String arg)31         {32             ArgStrTo = arg;33         }34 35 #endregion

  结果如下:

技术分享

  

二、多个参数的情况

上面是单个参数传输的,如果需要传入多个参数,可能就需要以参数对象方式传入,如下:

Model代码:

 1    public class UserParam 2     { 3         public String UserName { get; set; } 4  5         public String UserPhone { get; set; } 6  7         public String UserAdd { get; set; } 8  9         public String UserSex { get; set; }10     }

  View代码:

1 xmlns:model="clr-namespace:MVVMLightDemo.Model"
 1  <StackPanel Margin="10,0,0,50"> 2                     <TextBlock Text="传递对象参数" FontWeight="Bold" FontSize="12" Margin="0,5,0,5" ></TextBlock> 3                     <DockPanel> 4                         <StackPanel DockPanel.Dock="Left" Width="240"> 5                             <Button Command="{Binding PassArgObjCmd}"  Content="传递多个参数" Height="23" HorizontalAlignment="Left" Width="100"> 6                                 <Button.CommandParameter> 7                                     <model:UserParam UserName="Brand" UserPhone="88888888" UserAdd="地址" UserSex="男" ></model:UserParam> 8                                 </Button.CommandParameter> 9                             </Button>10                         </StackPanel>11                         <StackPanel DockPanel.Dock="Right" Width="240" Orientation="Vertical">12                             <TextBlock Text="{Binding ObjParam.UserName,StringFormat=‘姓名:\{0\}‘}" ></TextBlock>13                             <TextBlock Text="{Binding ObjParam.UserPhone,StringFormat=‘电话:\{0\}‘}" ></TextBlock>14                             <TextBlock Text="{Binding ObjParam.UserAdd,StringFormat=‘地址:\{0\}‘}" ></TextBlock>15                             <TextBlock Text="{Binding ObjParam.UserSex,StringFormat=‘性别:\{0\}‘}" ></TextBlock>16                         </StackPanel>17                     </DockPanel>18                 </StackPanel>

 ViewModel代码:

 1  #region 传递参数对象 2  3         private UserParam objParam; 4         public UserParam ObjParam 5         { 6             get { return objParam; } 7             set { objParam = value; RaisePropertyChanged(() => ObjParam); } 8         } 9 10  #endregion11 12  #region 命令13         private RelayCommand<UserParam> passArgObjCmd;14         public RelayCommand<UserParam> PassArgObjCmd15         {16             get17             {18                 if (passArgObjCmd == null)19                     passArgObjCmd = new RelayCommand<UserParam>((p) => ExecutePassArgObj(p));20                 return passArgObjCmd;    21             }22             set { passArgObjCmd = value; }23         }24         private void ExecutePassArgObj(UserParam up)25         {26             ObjParam = up;27         }28  #endregion

  结果如下:

技术分享

 

三、动态绑定多个参数情况

参数过来了,但是我们会发现这样的参数是我们硬编码在代码中的,比较死,一帮情况下是动态绑定参数传递,所以我们修改上面的代码如下:

1  <StackPanel DockPanel.Dock="Left" Width="240">2                             <Button Command="{Binding PassArgObjCmd}"  Content="传递多个参数" Height="23" HorizontalAlignment="Left" Width="100">3                                 <Button.CommandParameter>4                                     <model:UserParam UserName="{Binding ElementName=ArgStrFrom,Path=Text}" UserPhone="88888888" UserAdd="地址" UserSex="" ></model:UserParam>5                                 </Button.CommandParameter>6                             </Button>7   </StackPanel>

   这时候编译运行,他会提示:不能在“UserParam”类型的“UserName”属性上设置“Binding”。只能在 DependencyObject 的 DependencyProperty 上设置“Binding”

原来,我们的绑定属性只能用在 DependencyObject 类型的控件对象上。像我们的 TextBox、Button、StackPanel等等控件都是

 System.Windows.FrameworkElement => System.Windows.UIElement=>  System.Windows.Media.Visual => System.Windows.DependencyObject  这样的一种继承方式。所以支持绑定特性。

 

 技术分享

Wpf的所有UI控件都是依赖对象。

一种方式就是将 UserParam类 改成 支持具有依赖属性的对象,如下:

 1    /// <summary> 2     /// 自定义类型 3     /// </summary> 4     public class UserParam : FrameworkElement //继承于FrameworkElement 5     { 6         /// <summary> 7         /// .net属性封装 8         /// </summary> 9         public int Age10         {11             get //读访问器12             {13                 return (int)GetValue(AgeProperty);14             }15             set //写访问器16             {17                 SetValue(AgeProperty, value);18             }19         }20         21 22         /// <summary>23         /// 声明并创建依赖项属性24         /// </summary>25         public static readonly DependencyProperty AgeProperty =26             DependencyProperty.Register("Age", typeof(int), typeof(CustomClass), new PropertyMetadata(0, CustomPropertyChangedCallback), CustomValidateValueCallback);27         28 29         /// <summary>30         /// 属性值更改回调方法31         /// </summary>32         /// <param name="d"></param>33         /// <param name="e"></param>34         private static void CustomPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)35         {36 37         }38 39         /// <summary>40         /// 属性值验证回调方法41         /// </summary>42         /// <param name="value"></param>43         /// <returns></returns>44         private static bool CustomValidateValueCallback(object value)45         {46             return true;47         }48     }

   但是这种方式不建议。仅仅是为了传输参数而大费周章,写一堆额外的功能,而且通用性差,几乎每个实例都要写一个对象,也破坏了Wpf文档树的设计结构。

更建议的方式如下,用多绑定的方式。将多绑定的各个值转换成你想要的对象或者实例模型,再传递给ViewModel。

View代码:

1 xmlns:common="clr-namespace:MVVMLightDemo.Common"
1   <Grid.Resources>2             <common:UserInfoConvert x:Key="uic" />3   </Grid.Resources>
 1   <StackPanel Margin="10,0,0,50"> 2                     <TextBlock Text="动态参数传递" FontWeight="Bold" FontSize="12" Margin="0,5,0,5" ></TextBlock> 3                     <StackPanel Orientation="Horizontal" > 4                         <StackPanel Orientation="Vertical" Margin="0,0,10,0" > 5                             <StackPanel Orientation="Horizontal" Margin="0,0,0,5" > 6                                 <TextBlock Text="姓名" Width="80" ></TextBlock> 7                                 <TextBox x:Name="txtUName" Width="200" /> 8                             </StackPanel> 9                             <StackPanel Orientation="Horizontal" Margin="0,0,0,5" >10                                 <TextBlock Text="电话" Width="80" ></TextBlock>11                                 <TextBox x:Name="txtUPhone" Width="200" />12                             </StackPanel>13                             <StackPanel Orientation="Horizontal" Margin="0,0,0,5" >14                                 <TextBlock Text="地址" Width="80"></TextBlock>15                                 <TextBox x:Name="txtUAdd" Width="200"/>16                             </StackPanel>17                             <StackPanel Orientation="Horizontal" Margin="0,0,0,5" >18                                 <TextBlock Text="性别" Width="80" ></TextBlock>19                                 <TextBox x:Name="txtUSex" Width="200" />20                             </StackPanel>                          21                         </StackPanel>22 23                         <StackPanel>24                             <Button Content="点击传递" Command="{Binding DynamicParamCmd}">25                                 <Button.CommandParameter>26                                     <MultiBinding Converter="{StaticResource uic}">27                                         <Binding ElementName="txtUName" Path="Text"/>28                                         <Binding ElementName="txtUSex" Path="Text"/>29                                         <Binding ElementName="txtUPhone" Path="Text"/>30                                         <Binding ElementName="txtUAdd" Path="Text"/>31                                     </MultiBinding>32                                 </Button.CommandParameter>33                             </Button>34                         </StackPanel>35 36                         <StackPanel Width="240" Orientation="Vertical" Margin="10,0,0,0" >37                             <TextBlock Text="{Binding ArgsTo.UserName,StringFormat=‘姓名:\{0\}‘}" ></TextBlock>38                             <TextBlock Text="{Binding ArgsTo.UserPhone,StringFormat=‘电话:\{0\}‘}" ></TextBlock>39                             <TextBlock Text="{Binding ArgsTo.UserAdd,StringFormat=‘地址:\{0\}‘}" ></TextBlock>40                             <TextBlock Text="{Binding ArgsTo.UserSex,StringFormat=‘性别:\{0\}‘}" ></TextBlock>41                         </StackPanel>                        42                     </StackPanel>43   </StackPanel>

 转换器 UserInfoConvert 代码:

 1   public class UserInfoConvert : IMultiValueConverter 2     { 3         /// <summary> 4         /// 对象转换 5         /// </summary> 6         /// <param name="values">所绑定的源的值</param> 7         /// <param name="targetType">目标的类型</param> 8         /// <param name="parameter">绑定时所传递的参数</param> 9         /// <param name="culture"><系统语言等信息</param>10         /// <returns></returns>11         public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)12         {13             if (!values.Cast<string>().Any(text => string.IsNullOrEmpty(text)) && values.Count() == 4)14             {15                 UserParam up = new UserParam() { UserName = values[0].ToString(), UserSex = values[1].ToString(), UserPhone = values[2].ToString(), UserAdd = values[3].ToString() };16                 return up;17             }18 19             return null;20         }21 22         public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)23         {24             throw new NotImplementedException();25         }26     }

  ViewModel代码:

 1    #region 动态参数传递 2  3         private UserParam argsTo; 4         /// <summary> 5         /// 动态参数传递 6         /// </summary> 7         public UserParam ArgsTo 8         { 9             get { return argsTo; }10             set { argsTo = value; RaisePropertyChanged(() => ArgsTo); }11         }12 13    #endregion14  //=================================================================================================================15         private RelayCommand<UserParam> dynamicParamCmd;16         /// <summary>17         /// 动态参数传递18         /// </summary>19         public RelayCommand<UserParam> DynamicParamCmd20         {21             get22             {23                 if (dynamicParamCmd == null)24                     dynamicParamCmd = new RelayCommand<UserParam>(p => ExecuteDynPar(p));25                 return dynamicParamCmd;26             }27             set28             {29                30                 dynamicParamCmd = value;31             }32         }33 34         private void ExecuteDynPar(UserParam up)35         {36             ArgsTo = up;37         }

  效果如下:

技术分享

到这边,命令参数绑定相关的应该就比较清楚了,这种方式也比较好操作。

个人观点:从MVVM的模式来说,其实命令中的参数传递未必是必要的。MVVM 的目标就是消除View和ViewModel开发人员之间过于频繁的数据交互。

去维护一段额外的参数代码,还不如把所有的交互参数细化成在当前DataContext下的全局属性。View开发人员和ViewModel开发人员共同维护好这份命令清单和属性清单即可。

而微软的很多控件也提供了类似  SelectedItem 和 SelectedValue之类的功能属性来辅助开发。

 

四、传递原事件参数

如果在一些特殊环境里,我们需要传递原事件的参数,那也很简单,只要设置 PassEventArgsToCommand="True" 即可,

在ViewModel中对应接收参数即可。

 1  private RelayCommand<DragEventArgs> dropCommand; 2         /// <summary> 3         /// 传递原事件参数 4         /// </summary> 5         public RelayCommand<DragEventArgs> DropCommand 6         { 7             get 8             { 9                 if (dropCommand == null)10                     dropCommand = new RelayCommand<DragEventArgs>(e => ExecuteDrop(e));11                 return dropCommand;12             }13             set { dropCommand = value; }14         }    15 16         private void ExecuteDrop(DragEventArgs e)17         {18             FileAdd = ((System.Array)e.Data.GetData(System.Windows.DataFormats.FileDrop)).GetValue(0).ToString(); 19         }

 结果如下(将文件拖拽至红色区域内,会获取到拖拽来源,并解析参数显示出来):

技术分享

 

五、EventToCommand

     在WPF中,并不是所有控件都有Command,例如TextBox,那么当文本改变,我们需要处理一些逻辑,这些逻辑在ViewModel中,没有Command如何绑定呢?这

个时候我们就用到EventToCommand,事件转命令,可以将一些事件例如TextChanged,Checked等转换成命令的方式。接下来我们就以下拉控件为例子,来看看具体的实例:

View代码:(这边声明了i特性和mvvm特性,一个是为了拥有触发器和行为附加属性的能力,当事件触发时,会去调用相应的命令,EventName代表触发的事件名称;一个是为了使用MVVMLight中 EventToCommand功能。)

这边就是当ComboBox执行SelectionChanged事件的时候,会相应去执行 SelectCommand 命令。

1     xmlns:mvvm="http://www.galasoft.ch/mvvmlight"2     xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
 1   <StackPanel Margin="10,0,0,50"> 2                     <TextBlock Text="事件转命令执行" FontWeight="Bold" FontSize="12" Margin="0,5,0,5" ></TextBlock> 3                     <DockPanel x:Name="EventToCommand" > 4                         <StackPanel DockPanel.Dock="Left" Width="240" Orientation="Horizontal" > 5                             <ComboBox Width="130" ItemsSource="{Binding ResType.List}" DisplayMemberPath="Text" SelectedValuePath="Key"  6                           SelectedIndex="{Binding ResType.SelectIndex}" > 7                                 <i:Interaction.Triggers> 8                                     <i:EventTrigger EventName="SelectionChanged"> 9                                         <mvvm:EventToCommand Command="{Binding SelectCommand}"/>10                                     </i:EventTrigger>11                                 </i:Interaction.Triggers>12                             </ComboBox>13                         </StackPanel>14                         <StackPanel DockPanel.Dock="Right" Width="240" Orientation="Horizontal">15                             <TextBlock Text="{Binding SelectInfo,StringFormat=‘选中值:\{0\}‘}" ></TextBlock>16                         </StackPanel>17                     </DockPanel>18  </StackPanel>

 ViewModel代码:

 1    private RelayCommand selectCommand; 2         /// <summary> 3         /// 事件转命令执行 4         /// </summary> 5         public RelayCommand SelectCommand 6         { 7             get 8             { 9                 if (selectCommand == null)10                     selectCommand = new RelayCommand(() => ExecuteSelect());11                 return selectCommand;12             }13             set { selectCommand = value; }14         }15         private void ExecuteSelect()16         {17             if (ResType != null && ResType.SelectIndex > 0)18             {19                 SelectInfo = ResType.List[ResType.SelectIndex].Text;20             }21         }

  结果如下:

技术分享

 

示例代码下载

转载请注明出处,谢谢

利刃 MVVMLight 7:命令深入