XAML DataTemplate只会创建一个实例

5
以下代码仅会创建一个MyTabView实例。我已通过在MyTabView.xaml.cs中构造函数设置断点进行确认。该视图显示在选项卡中,无论我创建多少个选项卡,我都只会触发一次该构造函数。
<DataTemplate DataType="{x:Type vm:MyTabViewModel}" x:Shared="false">
    <vw:MyTabView />
</DataTemplate>

选项卡控件:

    <TabControl
        Grid.Row="1"
        ItemsSource="{Binding Tabs}"
        SelectedItem="{Binding SelectedTab}"
        >
        <TabControl.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding DisplayName}" />
            </DataTemplate>
        </TabControl.ItemTemplate>
    <TabControl>

这会导致所有选项卡反映出任何未绑定到视图模型的状态:如果您移动一个GridSplitter,那么在所有其他选项卡中都是相同的GridSplitter,因此用户看起来好像您移动了它们所有。这太荒谬了。
我不明白。是否有办法使用TabControl并具有多个相同类型的项目?
编辑:在DataTemplate中添加x:Shared="false"
更新:
所以我找到了一些解决方法,但我不太喜欢它们。我将研究编写转换器,将ObservableCollection<Object>转换为ObservableCollection<TabItem> - 类似于实时更新版本。
coll.Select(vm => new TabItem() { Content = vm });

......但我们将看到它是否喜欢从ItemsSource获取TabItem实例。我的钱说不要打赌。但我们会看到的。

更新2:花了一些时间才回到这里。使用交换选项卡项目集合的诀窍是有效的,但SelectedItem存在问题。结果发现还有另一种解决方案(下面),它不会出现这个问题,而且避免了创建一个“中间人”集合所带来的复杂性和荒谬。这个解决方案也可以避免源集合发生变化时需要同步更改“中间人”集合的情况。


1
也许是 https://dev59.com/JGTWa4cB1Zd3GeqPDnhM 的重复?不确定TabControl是否有效地使用虚拟化,因为一次只能显示一件事... - Tim
@Tim 哦,虚拟化。我想你可能把它搞定了。 - 15ee8f99-57ff-4f92-890c-b56153
@Tim 原来可能不是问题所在。我切换到DevExpress网格后,它的(不)行为完全相同。我们在主应用程序中使用它,并且它能正常工作。 - 15ee8f99-57ff-4f92-890c-b56153
由于一个奇怪的古老修补程序,选项卡项目被显式地创建,因此它能够正常工作。一直很好奇为什么要这样做。 - 15ee8f99-57ff-4f92-890c-b56153
3个回答

0
尝试在您的模板中添加属性。

没有效果。抱歉,在示例中忘记包含它了。 - 15ee8f99-57ff-4f92-890c-b56153
你尝试在模板内也应用 <vw:MyTabView />了吗? - Bradley Uffner
1
它说:“'Shared'属性只能用于包含在'ResourceDictionary'中的元素。” - 15ee8f99-57ff-4f92-890c-b56153

-1

我在使用 dataTemplate.LoadContent 的时候,DataTemplate 中的 ElementName 绑定遇到了一些问题。

在 @EdPlunkett 的解决方案基础上,我尝试使用每个 TabItem 中间的一个中间 ContentPresenter,并且它对我的使用场景非常有效。

  • 多个选项卡具有相同的模板和相同的数据上下文类型,但包含不同的实际数据
  • 在 DataTemplate 中使用 GridSplitter
  • 在 DataTemplate 中使用 ElementName 绑定
  • 我不关心 TabControl 虚拟化是否适用于我的选项卡数量(我也没有检查虚拟化在我的情况下会发生什么)

Xaml:

<TabControl.ItemContainerStyle>
    <Style TargetType="TabItem">
        <EventSetter Event="Loaded" Handler="TabItem_Loaded"/>
    </Style>
</TabControl.ItemContainerStyle>
<TabControl.Resources>
    <DataTemplate x:Key="MyTemplateKey">
        ...
    </DataTemplate>
</TabControl.Resources>

代码背后(我将DataTemplateKey更改为基于字符串的键,仅适用于我的具体情况,与命名模板资源一起使用。选择模板的DataTemplateKey方法也应该有效)
private void TabItem_Loaded(object sender, RoutedEventArgs e)
{
    var tabItem = (sender as TabItem);

    if (null != tabItem.DataContext)
    {
        var dataTemplate = (DataTemplate)tabItem.FindResource("MyTemplateKey");

        var cp = new ContentPresenter();
        cp.ContentTemplate = dataTemplate;
        cp.Content = tabItem.DataContext;
        tabItem.Content = cp;
    }
}

-1
我也遇到了这个问题的另一个版本。我有一个带有2列网格的用户控件。从左侧网格列选择TreeView节点时,它会在右侧设置ContentPresenter/Content。我的问题是,ContentPresenter中呈现的DataTemplate/UserControl具有仅实例化一次的ListView。结果是,当从左侧TreeView选择新项时,右侧的UserControl/ListView不会取消选择之前的项。
希望以上内容说得清楚。长话短说,我发现最简单的解决方案是创建一个DataTemplateSelector,它始终从SelectTemplate返回null。
public class DynamicTemplateSelector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        return null;
    }
}

XAML looks like this:


<UserControl.Resources>
    <local:DynamicTemplateSelector x:Key="DynamicSelector" />

    <DataTemplate DataType="{x:Type local:MyViewModel}">
        <local:MyUserControl />
    </DataTemplate>

</UserControl.Resources>
    ...

<ContentControl
    Content="{Binding ElementName=treeView, Path=SelectedItem}"
    ContentTemplateSelector="{StaticResource DynamicSelector}"
    />


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接