以下只会创建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;的复杂性和愚蠢性。必须镜像源集合中的更改的集合。
答案 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
,它适用于我的使用场景。
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}"
/>