我在 WPF 项目中有以下内容:
主窗口
<Window x:Class="DataTemplateEventTesting.Views.MainWindow"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
...
xmlns:vm="clr-namespace:DataTemplateEventTesting.ViewModels"
xmlns:vw="clr-namespace:DataTemplateEventTesting.Views">
<Window.DataContext>
<vm:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions> ... </Grid.ColumnDefinitions>
<ListView ItemsSource="{Binding SubViewModels}"
SelectedValue="{Binding MainContent, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type vm:SubViewModel}">
<TextBlock Text="{Binding DisplayText}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ContentControl Grid.Column="1" Content="{Binding MainContent}">
<ContentControl.Resources>
<DataTemplate x:Shared="False" DataType="{x:Type vm:SubViewModel}">
<vw:SubView />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Grid>
</Window>
subview ( subview 模型的 View )
<UserControl x:Class="DataTemplateEventTesting.Views.SubView"
...
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
<Grid>
<ListView ItemsSource="{Binding Models}">
<ListView.View> ... </ListView.View>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListView}}}"
Command="{Binding PrintCurrentItemsCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListView>
</Grid>
</UserControl>
问题出在 SubView
中的 SelectionChanged
EventTrigger
。
PrintCurrentItemsCommand
接受一个 ListView
作为参数,并通过执行以下方法打印其项目的计数:
private void PrintCurrentItems(ListView listView)
{
System.Diagnostics.Debug.WriteLine("{0}: {1} items.", DisplayText, listView.Items.Count);
}
当我从一个 SubView
(其中 ListView
中的某些项目被选中)导航到另一个 SubView
时,SelectionChanged
事件在第一个 SubView
的 ListView
上触发。这会在正确的 SubViewModel
上执行 PrintCurrentItemsCommand
,但会将新的(不正确的)ListView
作为参数传递。 (要么,要么事件由新的 ListView
触发,并且该命令使用旧 ListView
中的 DataContext
。)
因此,虽然带有 DisplayText
的“Sub1”的 SubViewModel
在其 Models
集合中有 2 个项目,而“Sub2”有 3 个项目,我在“输出”窗口中看到以下内容:
Sub1: 2 items. // selected an item
Sub1: 3 items. // navigated to Sub2
Sub2: 3 items. // selected an item
Sub2: 2 items. // navigated to Sub1
Sub1: 2 items. // selected an item
Sub1: 3 items. // navigated to Sub2
Sub2: 3 items. // selected an item
Sub2: 2 items. // navigated to Sub1
显然,预期的行为是传递正确的 ListView
。
主要的混淆是,例如,“Sub1”的命令能够访问“Sub2”的 ListView
。
我读了一些关于 WPF caching templates 的内容,并认为我已经找到了在 DataTemplate
上设置 x:Shared = "False"
的解决方案,但这并没有改变任何东西。
对这种行为有解释吗?有解决办法吗?
最佳答案
我能够重现您所看到的行为:我在右侧 ListView 中选择一个项目,然后将选择更改为左侧 ListView 。调用命令时,在 Execute 方法中,! Object.ReferenceEquals(this, listView.DataContext)
。我原以为他们是平等的。
对于 Command
的绑定(bind),它们仍然不相等:
<i:InvokeCommandAction
Command="{Binding DataContext.PrintCurrentItemsCommand, RelativeSource={RelativeSource AncestorType={x:Type ListView}}}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListView}}}"
/>
我对那个实验并没有太大的期望,但是尝试并没有花很长时间。
很遗憾,我现在没有时间深入研究这个问题。我一直没能找到 System.Windows.Interactivity.InvokeCommandAction
的源代码,但它确实看起来好像在伴随更改的一系列事件和更新中的某个地方,事情发生在错误的地方订单。
决议
下面的代码丑得几乎无法忍受,但它的行为符合预期。您可以通过编写自己的行为来使其不那么丑陋。它不需要像 InvokeCommandAction
那样被广泛概括。由于通用性较低,它不太可能以相同的方式出现错误行为,即使出现这种情况,您也可以获得源代码并可以正确调试它。
subview .xaml
<ListView
ItemsSource="{Binding Models}"
SelectionChanged="ListView_SelectionChanged"
>
<!-- snip -->
subview .xaml.ds
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listView = sender as ListView;
var cmd = listView.DataContext?.GetType().GetProperty("PrintCurrentItemsCommand")?.
GetValue(listView.DataContext) as ICommand;
if (cmd?.CanExecute(listView) ?? false)
{
cmd.Execute(listView);
}
}
稍微跑题了,最好这样:
protected void PrintCurrentItems(System.Collections.IEnumerable items)
{
//...
XAML
<i:InvokeCommandAction
Command="{Binding PrintCurrentItemsCommand}"
CommandParameter="{Binding Items, RelativeSource={RelativeSource AncestorType={x:Type ListView}}}"
/>
代码隐藏
if (cmd?.CanExecute(listView) ?? false)
{
cmd.Execute(listView.Items);
}
原因是将 IEnumerable
作为参数的命令比期望将任何项目集合打包在 ListView 中的命令更为普遍。从 ListView 中获取项目集合很容易;在向某人传递项目集合之前需要有一个 ListView 真的很痛苦。始终接受尽可能不具体的参数,而不会搬起石头砸自己的脚。
从 MVVM 的角度来看,让 View 模型了解 UI 的任何具体知识被认为是非常糟糕的做法。如果 UI 设计团队后来决定应该使用 DataGrid 或 ListBox 而不是 ListView 怎么办?如果他们向您传递 Items
,那完全不是问题。如果他们将 ListView
传递给您,他们必须向您发送一封电子邮件,要求您更改参数类型,然后就此与您进行协调,然后进行额外的测试等。所有这些都是为了适应参数实际上根本不需要是 ListView
。
关于c# - DataTemplate 在 WPF 中传递不正确的命令参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47759033/