上面一篇咱們大體瞭解了命令的基本使用方法和基礎原理,可是實際在運用命令的時候會複雜的多,而且會遇到各類各樣的狀況。mvvm
1、命令帶參數的狀況:函數
若是視圖控件所綁定的命令想要傳輸參數,須要配置 CommandParameter 屬性 ,用來傳輸參數出去。ui
而繼承製Icommand接口的 RelayCommand又支持泛型的能力,這樣就能夠接受來自客戶端請求的參數。編碼
public RelayCommand(Action<T> execute);構造函數傳入的是委託類型的參數,Execute 和 CanExecute執行委託方法。spa
因此,修改上篇的代碼以下:.net
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代碼:3d
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
結果以下:code
2、多個參數的狀況orm
上面是單個參數傳輸的,若是須要傳入多個參數,可能就須要以參數對象方式傳入,以下:
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
結果以下:
3、動態綁定多個參數狀況
參數過來了,可是咱們會發現這樣的參數是咱們硬編碼在代碼中的,比較死,一幫狀況下是動態綁定參數傳遞,因此咱們修改上面的代碼以下:
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之類的功能屬性來輔助開發。
4、傳遞原事件參數
若是在一些特殊環境裏,咱們須要傳遞原事件的參數,那也很簡單,只要設置 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 }
結果以下(將文件拖拽至紅色區域內,會獲取到拖拽來源,並解析參數顯示出來):
5、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 }
結果以下:
轉載請註明出處,謝謝