利刃 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 #endregion
12
13 #region 命令
14
15         private RelayCommand<String> passArgStrCommand;
16         /// <summary>
17         /// 传递单个参数命令
18         /// </summary>
19         public RelayCommand<String> PassArgStrCommand
20         {
21             get
22             {
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  #endregion
11
12  #region 命令
13         private RelayCommand<UserParam> passArgObjCmd;
14         public RelayCommand<UserParam> PassArgObjCmd
15         {
16             get
17             {
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 Age
10         {
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    #endregion
14  //=================================================================================================================
15         private RelayCommand<UserParam> dynamicParamCmd;
16         /// <summary>
17         /// 动态参数传递
18         /// </summary>
19         public RelayCommand<UserParam> DynamicParamCmd
20         {
21             get
22             {
23                 if (dynamicParamCmd == null)
24                     dynamicParamCmd = new RelayCommand<UserParam>(p => ExecuteDynPar(p));
25                 return dynamicParamCmd;
26             }
27             set
28             {
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         }

  结果如下:

示例代码下载

时间: 2024-10-13 06:53:53

利刃 MVVMLight 7:命令深入的相关文章

利刃 MVVMLight

已经很久没有写系列文章了,上一次是2012年写的HTLM5系列,想想我们应该是较早一批使用HTML5做项目的人. 相比我当时动不动100+的粉丝增长和两天3000+的阅读量,MVVM Light只能算小众技术了,也是因为她小众,才更具意义,不希望有人跟我一样网上找案例找半天. 写的初衷也是希望同项目组的同事能更好的融入和接收已有项目.其中两篇<DispatchHelper在多线程和调度中的使用>和<Messenger>是在上林院长课的时候抱着笔记本写的,现在想想挺后悔的, 再回头上

利刃 MVVMLight 6:命令基础

在MVVM Light框架中,事件是WPF应用程序中UI与后台代码进行交互的最主要方式,与传统方式不同,mvvm中主要通过绑定到命令来进行事件的处理, 因此要了解mvvm中处理事件的方式,就必须先熟悉命令的工作原理. RelayCommand命令:    WPF命令是通过实现 ICommand 接口创建的. ICommand 公开了两个方法(Execute 及 CanExecute)和一个事件(CanExecuteChanged). Execute方法 执行与命令关联的操作 CanExecute

利刃 MVVMLight 3:双向数据绑定

上篇我们已经了解了MVVM的框架结构和运行原理.这里我们来看一下伟大的双向数据绑定. 说到双向绑定,大家比较熟悉的应该就是AngularJS了,几乎所有的AngularJS 系列教程的开篇几章都要涉及到,真的是很好用. 表达的效果很简单:就是在界面的操作对数据模型的修改能实时反映到数据:而数据的变更能实时展现到界面.即视图数据模型(ViewModel)和视图(View)之间的双向绑定和触发. 我们来操作一个试试看: 第一步:先写一个Model,里面包含我们需要的数据信息,代码如下: 1 ///

利刃 MVVMLight 2:Model、View、ViewModel结构以及全局视图模型注入器的说明

上一篇我们已经介绍了如何使用NuGet把MVVMLight应用到我们的WPF项目中.这篇我们来了解下一个基本的MVVMLight框架所必须的结构和运行模式. MVVMLight安装之后,我们可以看到简易的框架布局,如上篇,生成了一个ViewModel文件夹,ViewModel层的内容都放在这边,除了Main对象的ViewModel之外,还包含一个ViewModelLocator文件, 用来注入当前的ViewModel全局实例. 一.先来说说分层结构: 如图: 1.View负责前端展示,与View

利刃 MVVMLight 1:MVVMLight介绍以及在项目中的使用

一.MVVM 和 MVVMLight介绍 MVVM是Model-View-ViewModel的简写.类似于目前比较流行的MVC.MVP设计模式,主要目的是为了分离视图(View)和模型(Model)的耦合. 它是一种极度优秀的设计模式,但并非框架级别的东西,由MVP(Model-View-Presenter)模式与WPF结合的应用方式时发展演变过来的一种新型架构. 立足于原有MVP框架并且把WPF的新特性糅合进去,以应对PC端开发日益复杂的需求变化. 结构如图所示: 相对于之前把逻辑结构写在Co

利刃 MVVMLight 5:绑定在表单验证上的应用

表单验证是MVVM体系中的重要一块.而绑定除了推动 Model-View-ViewModel (MVVM) 模式松散耦合 逻辑.数据 和 UI定义 的关系之外,还为业务数据验证方案提供强大而灵活的支持. WPF 中的数据绑定机制包括多个选项,可用于在创建可编辑视图时校验输入数据的有效性. 常见的表单验证机制有如下几种: 验证类型 说明 Exception 验证 通过在某个 Binding 对象上设置 ValidatesOnExceptions 属性,如果源对象属性设置已修改的值的过程中引发异常,

利刃 MVVMLight 8:DispatchHelper在多线程和调度中的使用

在应用程序中,线程可以被看做是应用程序的一个较小的执行单位.每个应用程序都至少拥有一个线程,我们称为主线程,这是在启动时调用应用程序的主方法时由操作系统分配启动的线程. 当调用和操作主线程的时候,该操作将动作添加到一个队列中.每个操作均按照将它们添加到队列中的顺序连续执行,但是可以通过为这些动作指定优先级来影响执行顺序,而负责管理此队列的对象称之为线程调度程序. 在很多情况下,我们启动新的线程主目的是执行操作(或等待某个操作的结果),而不会导致应用程序的其余部分被阻塞.密集型计算操作.高并发I/

利刃 MVVMLight 4:绑定和绑定的各种使用场景

一.绑定: 主要包含元素绑定和非元素绑定两种. 1.元素绑定,是绑定的最简单形式,源对象是WPF的元素,并且源对象的属性是依赖项属性. 根据我们之前的知识 ,依赖项属性具有内置的更改通知支持.所以当我们的源对象中改变依赖项属性的值时,会立即更新目标对象中的绑定属性. 以上篇的例子来重写,我们不用额外定义全局公开的属性来支持数据的显示. 如下: 1 <StackPanel Orientation="Vertical" HorizontalAlignment="Left&q

Messenger 深入

1.Messager交互结构和消息类型 衔接上篇,Messeger是信使的意思,顾名思义,他的目是用于View和ViewModel 以及 ViewModel和ViewModel 之间的消息通知和接收. Messenger类用于应用程序的通信,接受者只能接受注册的消息类型,另外目标类型可以被指定,用Send<TMessage, TTarget>(TMessage message)实现,在这种情况下信息只能被传递如果接受者类型和目标参数类型匹配, message可以是任何简单或者复杂的对象,你可以