方法一:传参按钮控件自身绑定的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}}">
<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;
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; }
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)
{
string dataContext = btn.DataContext.ToString();
System.Console.WriteLine(dataContext);
GoodsJsonData data = (GoodsJsonData)btn.DataContext;
坑点:
- 如果这个DataList列表的内容需要同步刷新,则类型**必须是**ObservableCollection。否则就算控件与数据绑定成功,控件只在初始化时能够正确显示数据,之后数据发生改变时,控件不会自动刷新。
- WPF可以传递控件自身绑定的ItemSource数据,通过ItemSource携带的DataContext内容,来代替CommandParameter多传参的蛋疼问题。
其他建议:
- 想要在一个控件上传递多个参数,可以传递控件自身,用控件的Tag和Uid属性绑定上数据。
今天在StackOverflow看到一个关于解决Command和CommandParameter的工具: http://xcommand.codeplex.com/ 以后可能会用到,先Mark。之后抽空看看。
方法二:多路绑定MultiBinding 结合转换器Converter的使用
该方法是网上搜到的主流方式。
方法三:其他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找到就行。
|