DataTemplate在WPF中传递了不正确的命令参数 [英] DataTemplate passing incorrect command parameter in WPF

查看:48
本文介绍了DataTemplate在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} />

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 is true. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆