XAML DataTemplate只创建一个实例

时间:2015-06-12 19:18:40

标签: wpf xaml datatemplate tabcontrol

以下只会创建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用于同一类型的多个项目?

编辑:将x:Shared="false"添加到DataTemplate

更新:

所以我发现了一些修复,但我不太喜欢它们。我打算看看编写一个将ObservableCollection<Object>转换为ObservableCollection<TabItem>的转换器 - 有点像

的实时更新版本
coll.Select(vm => new TabItem() { Content = vm });

...但我们会看是否喜欢从TabItem获取ItemsSource个实例。我的钱说没有赌它。但我们会看到。

更新2:花了一段时间才回到此处。交换标签项集合的噱头有效,但SelectedItem有问题。事实证明,另一个解决方案(下面)并没有创造出这个问题,也避免了创建一个&#34;中间人&#34;的复杂性和愚蠢性。必须镜像源集合中的更改的集合。

4 个答案:

答案 0 :(得分:1)

更新:非常确定这里真正的问题是通常的TabControl虚拟化bu ^ H ^ Hfeature。

解决方案是在加载每个选项卡时跳转并显式实例化模板。

XAML

<TabControl
    Grid.Row="1"
    ItemsSource="{Binding Tabs}"
    >
    <TabControl.Resources>
        <Style TargetType="TabItem">
            <EventSetter Event="Loaded" Handler="TabItem_Loaded" />
        </Style>
    </TabControl.Resources>
</TabControl>

C#

private void TabItem_Loaded(object sender, RoutedEventArgs e)
{
    var tabItem = (sender as TabItem);

    if (null != tabItem.DataContext)
    {
        //  Hey, what about TabControl.ItemTemplate, eh? 

        var dataTemplateKey = new System.Windows.DataTemplateKey(tabItem.DataContext.GetType());
        var dataTemplate = (DataTemplate)tabItem.FindResource(dataTemplateKey);

        tabItem.Content = dataTemplate.LoadContent();
        (tabItem.Content as FrameworkElement).DataContext = tabItem.DataContext;
    }
}

...但是当然有更好的错误处理。

您无法通过DataContextChanged处理EventSetter,因为它不是路由事件。你明白了:

System.Windows.FrameworkElement.DataContextChanged =“TabItem_DataContextChanged”无效。 'DataContextChanged'必须是注册的RoutedEvent,其名称以关键字“Event”结尾。

Loaded在这种情况下做得很好。

答案 1 :(得分:0)

尝试在模板中添加x:Shared="false"属性。

答案 2 :(得分:0)

ElementName使用已接受的方法时,DataTemplate内的dataTemplate.LoadContent绑定存在一些问题。

在@EdPlunkett的解决方案的基础上,我尝试使用每个ContentPresenter的中间TabItem,它适用于我的使用场景。

  • 具有相同模板和相同datacontext的多个选项卡类型但包含不同的实际数据
  • DataTemplate中的GridSplitter
  • ElementName绑定在DataTemplate
  • 我不关心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;
    }
}

答案 3 :(得分:0)

我也有这个问题的另一个版本。我有一个带有2个网格列的UserControl。从左侧网格列中选择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}"
    />