原文: 【WPF】CommandParameter解決多傳參問題
方法一:傳參按鈕控件自身綁定的ItemSource
用WAF架構實作MVVM,按鈕的點選事件都要通過Command來傳遞到這個View對應的ViewModel上,再通過ViewModel傳遞到上層的Controller層,在Controller層通過DelegateCommand處理按鈕真正的事件。有時候需要給該Command附加上一些參數(CommandParameter),但是預設CommandParameter隻能傳遞一個參數。谷歌搜到的解決方法很複雜,于是想了個辦法CommandParameter參數傳遞的是這個按鈕控件自身綁定的ItemSource,然後通過從ItemSource身上的DataContext來拿到資料,再截取字元串分割得到想要的部分資料(或者強轉為ItemSource對應的實體類)。
正常情況下,Button的綁定:
<Button Command="{Binding BtnCommand}" />
這個Command會沿着View –> ViewModle –> Controller層傳遞。
如果這個Button是ListBox的Item,這個ListBox的Item要使用資料模闆,且ItemsSource綁定到了這組Button的資料源,是這樣綁:
<ListBox x:Name="listBox" ItemsSource="{Binding DataList}" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid x:Name="grid">
<local:WaitingProgress/>
<Button Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=DataContext.BtnCommand}"
CommandParameter="{Binding RelativeSource={x:Static RelativeSource.Self}}"> <!-- 傳參是Button控件綁定的ItemsSource,即這裡是DataList清單 -->
<Image Tag="{Binding id}" x:Name="img" Stretch="UniformToFill" Width="150" Height="150" webImg:ImageDecoder.Source="{Binding icon}">
</Image>
</Button>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Name="wrapPanel" HorizontalAlignment="Stretch" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
這個ItemSource綁定的DataList是ViewModle中的一個執行個體清單。ViewModel關鍵代碼如下:
private ICommand refreshDesignCommand; // 向上傳遞這個Command:View-->ViewModel-->Controller
public ICommand RefreshDesignCommand
{
get { return refreshDesignCommand; }
set { SetProperty(ref refreshDesignCommand, value); }
}
private ObservableCollection<GoodsJsonData> dataList = null;
public ObservableCollection<GoodsJsonData> DataList
{
get { return dataList; }
set { dataList = value; }
}
實體類:
public class GoodsJsonData
{
public string id { get; set; } // 還可用于圖檔被點選調時,标記出是哪個縮略圖被點選
public string icon { get; set; } // 縮略圖
public string image { get; set; } // 大圖
public string model { get; set; } // 該商品對應的模型XML
public override string ToString()
{
return "id = " + id + " , icon = " + icon + " , image = " + image + " , model = " + model;
}
}
Controller層的關鍵代碼:
private readonly DelegateCommand refreshDesignCommand; // 縮略圖的點選回調
[ImportingConstructor]
public WebImageController()
{
this.refreshDesignCommand = new DelegateCommand(p => RefreshDesignCommand((Button)p));
}
private void RefreshDesignCommand(Button btn)
{
// 方法一:将DataContext列印字元串,截取出目标資料
string dataContext = btn.DataContext.ToString();
System.Console.WriteLine(dataContext); // id = 000201 , icon = http://192.168.1.222/mjl/4-01.png , image = 2/造型/4-01.png , model = xml/qiang07.xml
// 截取字元串來獲得目标資料。
// 方法二:将DataContext強轉為ItemSource對應的實體類類型
GoodsJsonData data = (GoodsJsonData)btn.DataContext;
// do what you want !
坑點:
- 如果這個DataList清單的内容需要同步重新整理,則類型**必須是**ObservableCollection。否則就算控件與資料綁定成功,控件隻在初始化時能夠正确顯示資料,之後資料發生改變時,控件不會自動重新整理。
- WPF可以傳遞控件自身綁定的ItemSource資料,通過ItemSource攜帶的DataContext内容,來代替CommandParameter多傳參的蛋疼問題。
其他建議:
- 想要在一個控件上傳遞多個參數,可以傳遞控件自身,用控件的Tag和Uid屬性綁定上資料。
今天在StackOverflow看到一個關于解決Command和CommandParameter的工具:
http://xcommand.codeplex.com/以後可能會用到,先Mark。之後抽空看看。
方法二:多路綁定 MultiBinding
結合轉換器 Converter的使用
MultiBinding
Converter的使用
該方法是網上搜到的主流方式。
- https://stackoverflow.com/questions/1350598/passing-two-command-parameters-using-a-wpf-binding
- http://www.codearsenal.net/2013/12/wpf-multibinding-example.html#.WnUeaFWWaUk
方法三:其他Trick
思路:用其他控件的屬性來記錄資料。傳參時傳遞按鈕控件自身,再通過按鈕控件的視覺樹布局找到找到綁定了其他資料的控件。
XAML:
<Grid>
<Button Content="測試按鈕"
Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=DataContext.YourCommand}"
CommandParameter="{Binding RelativeSource={x:Static RelativeSource.Self}}">
</Button>
<!-- 用于給點選按鈕時,傳遞多個參數 -->
<Grid x:Name="studentIdGrid" Tag="{Binding studentId}"/>
<Grid x:Name="studentNameGrid" Tag="{Binding studentName}"/>
<Grid x:Name="studentAgeGrid" Tag="{Binding studentAge}"/>
</Grid>
Controller:
// 按鈕點選觸發的事件
private void YourCommand(object p)
{
Button btn = (Button)p; // 傳參傳遞的是控件自身
DependencyObject parent = VisualTreeHelper.GetParent(btn);
List<Grid> list = this.FindVisualChildren<Grid>(parent);
string studentId = list[0].Tag.ToString();
string studentName = (int)(list[1].Tag);
int studentAge = list[2].Tag.ToString();
// do something...
}
// 從視覺樹找到目标控件的所有子控件
private List<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
List<T> list = new List<T>();
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
list.Add((T)child);
}
List<T> childItems = FindVisualChildren<T>(child); // 遞歸
if (childItems != null && childItems.Count() > 0)
{
foreach (var item in childItems)
{
list.Add(item);
}
}
}
}
return list;
}
小結:
- 按鈕的CommandParameter綁定了自身,将自身作為點選後觸發的回調函數的參數傳入。再用該按鈕控件去找到其他控件或UI元素。
- 使用了按鈕的兄弟節點Grid的Tag屬性來綁定目标資料,選擇用Grid是因為我們隻想用它來傳參,而不需要看到它。是以用其他UI元素的Tag屬性來傳參,再設定Visibility="Collapse"也是可行的。
- 同樣選擇用兄弟節點也不是必須的。也可以是父節點或子節點,隻要能通過視覺樹VisualTreeHelper找到就行。