上面一篇我們大緻了解了指令的基本使用方法和基礎原理,但是實際在運用指令的時候會複雜的多,并且會遇到各種各樣的情況。
一、指令帶參數的情況:
如果視圖控件所綁定的指令想要傳輸參數,需要配置 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>
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。
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>
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 }
示例代碼下載下傳 轉載請注明出處,謝謝