天天看點

利刃 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 #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      

  結果如下:

利刃 MVVMLight 7:指令深入

二、多個參數的情況

上面是單個參數傳輸的,如果需要傳入多個參數,可能就需要以參數對象方式傳入,如下:

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>      
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      
利刃 MVVMLight 7:指令深入

三、動态綁定多個參數情況

參數過來了,但是我們會發現這樣的參數是我們寫死在代碼中的,比較死,一幫情況下是動态綁定參數傳遞,是以我們修改上面的代碼如下:

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  這樣的一種繼承方式。是以支援綁定特性。

利刃 MVVMLight 7:指令深入

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。

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         }      

  效果如下:

利刃 MVVMLight 7:指令深入

到這邊,指令參數綁定相關的應該就比較清楚了,這種方式也比較好操作。

個人觀點:從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         }      

 結果如下(将檔案拖拽至紅色區域内,會擷取到拖拽來源,并解析參數顯示出來):

利刃 MVVMLight 7:指令深入

五、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>      
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:指令深入
示例代碼下載下傳

轉載請注明出處,謝謝

繼續閱讀