DataTemplate在WPF中传递了不正确的命令参数 [英] DataTemplate passing incorrect command parameter in WPF
问题描述
我在WPF项目中具有以下内容:
I have the following in a WPF project:
主窗口
<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(用于SubViewModel的视图)
<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>
问题在于 SelectionChanged
EventTrigger
在 SubView
中。
PrintCurrentItemsCommand
接受 ListView
作为参数,并通过执行以下方法来打印其项的计数:
PrintCurrentItemsCommand
accepts a ListView
as a parameter and prints a count of its items by executing the following method:
private void PrintCurrentItems(ListView listView)
{
System.Diagnostics.Debug.WriteLine("{0}: {1} items.", DisplayText, listView.Items.Count);
}
当我从一个 SubView导航时
(选择了 ListView
中的某些项目)到另一个 SubView
的在第一个
事件。这将在正确的 SubView
的 ListView
上触发SelectionChanged SubViewModel
上执行 PrintCurrentItemsCommand
,但传递新的(错误的) ListView
作为参数。 (要么,要么该事件被新的 ListView
触发,并且该命令正在使用 DataContext
When I navigate from one SubView
(where some items in its ListView
are selected) to another SubView
, the SelectionChanged
event is fired on the ListView
of the first SubView
. This executes the PrintCurrentItemsCommand
on the correct SubViewModel
, but passes the new (incorrect) ListView
as a parameter. (Either that, or the event is being fired by the new ListView
, and the command is using the DataContext
from the old ListView
.)
因此,而 SubViewModel
与 Sub1的 DisplayText
的 Models
集合中有2个项目,而 Sub2有3个项目,我看到了在输出窗口中输入以下内容:
Thus, while SubViewModel
with DisplayText
of "Sub1" has 2 items in its Models
collection, and "Sub2" has 3 items, I see the following in the Output window:
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
将被传递。
Obviously the expected behaviour would be that the correct ListView
would be passed.
主要的困惑是,例如, Sub1的命令能够访问 ListView
完全代表 Sub2。
The main confusion is that, for example, the command for "Sub1" is able to access the ListView
for "Sub2" at all.
我读了一些有关 WPF缓存模板,并认为我已经找到了在 DataTemplate
上设置 x:Shared = False
的解决方案,但这没有任何改变。
I read something about WPF caching templates, and thought I had found the solution in setting x:Shared = "False"
on the DataTemplate
, but this didn't change anything.
这种行为是否有解释?
Is there an explanation for this behaviour? And is there a way around it?
推荐答案
事实证明,问题是由<$ c $的持久性引起的。 c> DataTemplate 。
如Ed Plunkett所观察到的, ListView
一直以来,只有 DataContext
发生了变化。我想象发生的事情是导航发生了,然后触发了该事件,而此时 DataContext
已经更改-一个简单的属性更改。
As Ed Plunkett observed, it was the same ListView
the whole time, and only the DataContext
was changing. I imagine that what was happening was that the navigation took place, then the event was fired, and by this time the DataContext
had changed - a simple property change.
在期望的行为中,旧的 ListView
会触发该事件,并执行第一个ViewModel的命令,这将在导航,因此其项将计为0。但是,在共享 DataTemplate
的情况下,第一个 ListView
是第二个 ListView
,因此它的项目不计为0,它们已替换为第二个ViewModel中的项目。这是在导航之后发生的,因此可以预期 RelativeSource
将以第二个ViewModel作为其返回的 ListView
DataContext
。
In the hoped for behaviour, the old ListView
would fire the event, and execute the first ViewModel's command, and this would occur after the navigation, hence, its items would be counted at 0. But with DataTemplate
sharing, the first ListView
is the second ListView
, so its items are not counted at 0, they've been replaced with the items from the second ViewModel. This occurs after the navigation, so it would be expected that the RelativeSource
would return the ListView
with the second ViewModel as its DataContext
.
我设法使用自定义的 DataTemplateSelector
类:
I have managed to override this default behaviour by using a custom DataTemplateSelector
class:
public class ViewSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (container is FrameworkElement element && item is SubViewModel)
{
return element.FindResource("subviewmodel_template") as DataTemplate;
}
return null;
}
}
DataTemplate
存储在 ResourceDictionary
(合并到App.xaml中)中:
The DataTemplate
is stored in a ResourceDictionary
(merged in App.xaml):
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DataTemplateEventTesting.Views"
xmlns:vm="clr-namespace:DataTemplateEventTesting.ViewModels">
<DataTemplate x:Shared="False" x:Key="subviewmodel_template" DataType="{x:Type vm:SubViewModel}">
<local:SubView />
</DataTemplate>
</ResourceDictionary>
事实证明,在 ResourceDictionary
中, x:Shared = False
具有我想要的关键作用(显然,这仅在 ResourceDictionary中有效 code> )-它可以使每个ViewModel模板保持隔离。
It turns out that in a ResourceDictionary
, x:Shared="False"
has the critical effect that I want it to have (apparently this is effective only in a ResourceDictionary
) - it keeps the templates isolated per ViewModel.
主窗口现在写为:
<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>
<Window.Resources>
<vw:ViewSelector x:Key="view_selector" />
</Window.Resources>
<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}"
ContentTemplateSelector="{StaticResource view_selector}" />
</Grid>
</Window>
有趣的是,我发现在此特定示例中需要同时满足以下两个条件:
Interestingly, I found that both of the following need to be in place in this particular example:
一个
DataTemplate
放在 ResourceDictionary
中,并带有 x:Shared = False
。
两个
使用 DataTemplateSelector
。
例如,当我满足第一个条件并使用< ContentControl ... ContentTemplate = {StaticResource subviewmodel_template} /> $ c $
E.g., when I satisfy the first condition, and use <ContentControl ... ContentTemplate="{StaticResource subviewmodel_template}" />
, the issue prevails.
类似地,当 x:Shared = False
不存在时, DataTemplateSelector
不再有效。
Similarly, when x:Shared="False"
is not present, the DataTemplateSelector
is no longer effective.
这两个条件到位后,输出窗口将向我显示:
Once these two conditions are in place, the Output window shows me:
Sub1: 2 items. // selected an item
Sub1: 0 items. // navigated to Sub2
Sub2: 3 items. // selected an item
Sub2: 0 items. // navigated to Sub1
Sub1: 2 items. // selected an item
Sub1: 0 items. // navigated to Sub2
Sub2: 3 items. // selected an item
Sub2: 0 items. // navigated to Sub1
这是预期的行为,我之前在ViewModels之间切换时曾观察到
This is the expected behaviour, which I'd previously observed when switching between ViewModels of different types.
为什么使用DataTemplateSelector?
阅读 x:Shared 的文档,我在至少需要有关为何为何要使用 DataTemplateSelector
的理论。
After reading the documentation for x:Shared, I have at least a theory on why DataTemplateSelector
seems to be required for this to work.
如该文档所述: / p>
As stated in that documentation:
在WPF中,资源的默认
x:Shared
条件为true
。这种情况意味着任何给定的资源请求总是返回相同的实例。
In WPF, the default
x:Shared
condition for resources istrue
. This condition means that any given resource request always returns the same instance.
此处的关键字是 request 。
在不使用 DataTemplateSelector
的情况下,WPF可以确定需要使用哪种资源。因此,它只需要获取一次-一个请求。
Without using a DataTemplateSelector
, WPF has certainty on which resource it needs to use. Therefore, it only needs to fetch it once - one request.
使用 DataTemplateSelector
,因此不确定,因为即使对于相同类型的ViewModel,在 DataTemplateSelector
中可能还有其他逻辑。因此, DataTemplateSelector
强制对 Content
中的每个更改以及每个更改进行请求。 x:Shared = False
, ResourceDictionary
将始终返回一个新实例。
With a DataTemplateSelector
, there is no certainty, as there may be further logic within the DataTemplateSelector
even for ViewModels of the same type. Therefore, DataTemplateSelector
forces a request to be made with each change in Content
, and with x:Shared="False"
, the ResourceDictionary
will always return a new instance.
这篇关于DataTemplate在WPF中传递了不正确的命令参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!