阻止TabControl重新创建其子级

我有一个IList绑定到的viewmodel TabControl。这IList不会在的使用期限内发生变化TabControl。


<TabControl ItemsSource="{Binding Tabs}" SelectedIndex="0" >

    <TabControl.ItemContainerStyle>

        <Style TargetType="TabItem">

            <Setter Property="Content" Value="{Binding}" />

        </Style>

    </TabControl.ItemContainerStyle>

</TabControl>

每个视图模型都有一个DataTemplate在中指定的ResourceDictionary。


<DataTemplate TargetType={x:Type vm:MyViewModel}>

    <v:MyView/>

</DataTemplate>

DataTemplate中指定的每个视图都占用大量资源,以至于我宁愿只创建一次每个视图,但是当我切换选项卡时,将调用相关视图的构造函数。根据我的阅读,这是的预期行为TabControl,但是对我来说,尚不清楚调用构造函数的机制是什么。


我看过一个类似的问题,它使用了UserControls,但是那里提供的解决方案将需要我绑定到视图,这是不希望的。


米脂
浏览 594回答 3
3回答

红颜莎娜

默认情况下,TabControl共享面板以呈现其内容。若要做您想做的事情(以及许多其他WPF开发人员),您需要TabControl像这样进行扩展:TabControlEx.cs[TemplatePart(Name = "PART_ItemsHolder", Type = typeof(Panel))]public class TabControlEx : TabControl{&nbsp; &nbsp; private Panel ItemsHolderPanel = null;&nbsp; &nbsp; public TabControlEx()&nbsp; &nbsp; &nbsp; &nbsp; : base()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; // This is necessary so that we get the initial databound selected item&nbsp; &nbsp; &nbsp; &nbsp; ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;&nbsp; &nbsp; }&nbsp; &nbsp; /// <summary>&nbsp; &nbsp; /// If containers are done, generate the selected item&nbsp; &nbsp; /// </summary>&nbsp; &nbsp; /// <param name="sender"></param>&nbsp; &nbsp; /// <param name="e"></param>&nbsp; &nbsp; private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; UpdateSelectedItem();&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; /// <summary>&nbsp; &nbsp; /// Get the ItemsHolder and generate any children&nbsp; &nbsp; /// </summary>&nbsp; &nbsp; public override void OnApplyTemplate()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; base.OnApplyTemplate();&nbsp; &nbsp; &nbsp; &nbsp; ItemsHolderPanel = GetTemplateChild("PART_ItemsHolder") as Panel;&nbsp; &nbsp; &nbsp; &nbsp; UpdateSelectedItem();&nbsp; &nbsp; }&nbsp; &nbsp; /// <summary>&nbsp; &nbsp; /// When the items change we remove any generated panel children and add any new ones as necessary&nbsp; &nbsp; /// </summary>&nbsp; &nbsp; /// <param name="e"></param>&nbsp; &nbsp; protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; base.OnItemsChanged(e);&nbsp; &nbsp; &nbsp; &nbsp; if (ItemsHolderPanel == null)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return;&nbsp; &nbsp; &nbsp; &nbsp; switch (e.Action)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case NotifyCollectionChangedAction.Reset:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ItemsHolderPanel.Children.Clear();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case NotifyCollectionChangedAction.Add:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case NotifyCollectionChangedAction.Remove:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (e.OldItems != null)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; foreach (var item in e.OldItems)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ContentPresenter cp = FindChildContentPresenter(item);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (cp != null)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ItemsHolderPanel.Children.Remove(cp);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Don't do anything with new items because we don't want to&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // create visuals that aren't being shown&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; UpdateSelectedItem();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case NotifyCollectionChangedAction.Replace:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw new NotImplementedException("Replace not implemented yet");&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; protected override void OnSelectionChanged(SelectionChangedEventArgs e)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; base.OnSelectionChanged(e);&nbsp; &nbsp; &nbsp; &nbsp; UpdateSelectedItem();&nbsp; &nbsp; }&nbsp; &nbsp; private void UpdateSelectedItem()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; if (ItemsHolderPanel == null)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return;&nbsp; &nbsp; &nbsp; &nbsp; // Generate a ContentPresenter if necessary&nbsp; &nbsp; &nbsp; &nbsp; TabItem item = GetSelectedTabItem();&nbsp; &nbsp; &nbsp; &nbsp; if (item != null)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; CreateChildContentPresenter(item);&nbsp; &nbsp; &nbsp; &nbsp; // show the right child&nbsp; &nbsp; &nbsp; &nbsp; foreach (ContentPresenter child in ItemsHolderPanel.Children)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; child.Visibility = ((child.Tag as TabItem).IsSelected) ? Visibility.Visible : Visibility.Collapsed;&nbsp; &nbsp; }&nbsp; &nbsp; private ContentPresenter CreateChildContentPresenter(object item)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; if (item == null)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return null;&nbsp; &nbsp; &nbsp; &nbsp; ContentPresenter cp = FindChildContentPresenter(item);&nbsp; &nbsp; &nbsp; &nbsp; if (cp != null)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return cp;&nbsp; &nbsp; &nbsp; &nbsp; // the actual child to be added.&nbsp; cp.Tag is a reference to the TabItem&nbsp; &nbsp; &nbsp; &nbsp; cp = new ContentPresenter();&nbsp; &nbsp; &nbsp; &nbsp; cp.Content = (item is TabItem) ? (item as TabItem).Content : item;&nbsp; &nbsp; &nbsp; &nbsp; cp.ContentTemplate = this.SelectedContentTemplate;&nbsp; &nbsp; &nbsp; &nbsp; cp.ContentTemplateSelector = this.SelectedContentTemplateSelector;&nbsp; &nbsp; &nbsp; &nbsp; cp.ContentStringFormat = this.SelectedContentStringFormat;&nbsp; &nbsp; &nbsp; &nbsp; cp.Visibility = Visibility.Collapsed;&nbsp; &nbsp; &nbsp; &nbsp; cp.Tag = (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item));&nbsp; &nbsp; &nbsp; &nbsp; ItemsHolderPanel.Children.Add(cp);&nbsp; &nbsp; &nbsp; &nbsp; return cp;&nbsp; &nbsp; }&nbsp; &nbsp; private ContentPresenter FindChildContentPresenter(object data)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; if (data is TabItem)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; data = (data as TabItem).Content;&nbsp; &nbsp; &nbsp; &nbsp; if (data == null)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return null;&nbsp; &nbsp; &nbsp; &nbsp; if (ItemsHolderPanel == null)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return null;&nbsp; &nbsp; &nbsp; &nbsp; foreach (ContentPresenter cp in ItemsHolderPanel.Children)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (cp.Content == data)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return cp;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; return null;&nbsp; &nbsp; }&nbsp; &nbsp; protected TabItem GetSelectedTabItem()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; object selectedItem = base.SelectedItem;&nbsp; &nbsp; &nbsp; &nbsp; if (selectedItem == null)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return null;&nbsp; &nbsp; &nbsp; &nbsp; TabItem item = selectedItem as TabItem;&nbsp; &nbsp; &nbsp; &nbsp; if (item == null)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; item = base.ItemContainerGenerator.ContainerFromIndex(base.SelectedIndex) as TabItem;&nbsp; &nbsp; &nbsp; &nbsp; return item;&nbsp; &nbsp; }}XAML<Style TargetType="{x:Type controls:TabControlEx}">&nbsp; &nbsp; <Setter Property="Template">&nbsp; &nbsp; &nbsp; &nbsp; <Setter.Value>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <ControlTemplate TargetType="{x:Type TabControl}">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <Grid Background="{TemplateBinding Background}" ClipToBounds="True" KeyboardNavigation.TabNavigation="Local" SnapsToDevicePixels="True">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <Grid.ColumnDefinitions>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <ColumnDefinition x:Name="ColumnDefinition0" />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <ColumnDefinition x:Name="ColumnDefinition1" Width="0" />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </Grid.ColumnDefinitions>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <Grid.RowDefinitions>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <RowDefinition x:Name="RowDefinition0" Height="Auto" />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <RowDefinition x:Name="RowDefinition1" Height="*" />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </Grid.RowDefinitions>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <DockPanel Margin="2,2,0,0" LastChildFill="False">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <TabPanel x:Name="HeaderPanel" Margin="0,0,0,-1" VerticalAlignment="Bottom" Panel.ZIndex="1" DockPanel.Dock="Right"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; IsItemsHost="True" KeyboardNavigation.TabIndex="1" />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </DockPanel>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <Border x:Name="ContentPanel" Grid.Row="1" Grid.Column="0"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Background="{TemplateBinding Background}"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; BorderBrush="{TemplateBinding BorderBrush}"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; BorderThickness="{TemplateBinding BorderThickness}"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; KeyboardNavigation.DirectionalNavigation="Contained" KeyboardNavigation.TabIndex="2" KeyboardNavigation.TabNavigation="Local">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <Grid x:Name="PART_ItemsHolder" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </Border>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </Grid>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </ControlTemplate>&nbsp; &nbsp; &nbsp; &nbsp; </Setter.Value>&nbsp; &nbsp; </Setter></Style>注意:我没有提出这个解决方案。它已在编程论坛中共享了数年,并且相信它现在已成为WPF食谱书之一。我认为最古老或原始的来源是PluralSight .NET博客文章,以及StackOverflow上的答案。

qq_花开花谢_0

有一个不是很明显但很优雅的解决方案。主要思想是通过自定义转换器手动生成TabItem的ContentTree的VisualTree属性。定义一些资源<Window.Resources>&nbsp; &nbsp; <converters:ContentGeneratorConverter x:Key="ContentGeneratorConverter"/>&nbsp; &nbsp; <DataTemplate x:Key="ItemDataTemplate">&nbsp; &nbsp; &nbsp; &nbsp; <StackPanel>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <TextBox Text="Try to change this text and choose another tab"/>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <TextBlock Text="{Binding}"/>&nbsp; &nbsp; &nbsp; &nbsp; </StackPanel>&nbsp; &nbsp; </DataTemplate>&nbsp; &nbsp; <markup:Set x:Key="Items">&nbsp; &nbsp; &nbsp; &nbsp; <system:String>Red</system:String>&nbsp; &nbsp; &nbsp; &nbsp; <system:String>Green</system:String>&nbsp; &nbsp; &nbsp; &nbsp; <system:String>Blue</system:String>&nbsp; &nbsp; </markup:Set></Window.Resources>哪里public class ContentGeneratorConverter : IValueConverter{&nbsp; &nbsp; public object Convert(object value, Type targetType, object parameter, CultureInfo culture)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; var control = new ContentControl {ContentTemplate = (DataTemplate) parameter};&nbsp; &nbsp; &nbsp; &nbsp; control.SetBinding(ContentControl.ContentProperty, new Binding());&nbsp; &nbsp; &nbsp; &nbsp; return control;&nbsp; &nbsp; }&nbsp; &nbsp; public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>&nbsp; &nbsp; &nbsp; &nbsp; throw new NotImplementedException();}Set是这样的public class Set : List<object> { }然后改为经典使用ContentTemplate属性&nbsp; &nbsp; <TabControl&nbsp; &nbsp; &nbsp; &nbsp; ItemsSource="{StaticResource Items}"&nbsp; &nbsp; &nbsp; &nbsp; ContentTemplate="{StaticResource ItemDataTemplate}">&nbsp; &nbsp; </TabControl>我们应该通过以下方式指定ItemContainerStyle&nbsp; &nbsp; <TabControl&nbsp; &nbsp; &nbsp; &nbsp; ItemsSource="{StaticResource Items}">&nbsp; &nbsp; &nbsp; &nbsp; <TabControl.ItemContainerStyle>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <Style TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <Setter Property="Content" Value="{Binding Converter={StaticResource ContentGeneratorConverter}, ConverterParameter={StaticResource ItemDataTemplate}}"/>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </Style>&nbsp; &nbsp; &nbsp; &nbsp; </TabControl.ItemContainerStyle>&nbsp; &nbsp; </TabControl>现在,尝试比较两种变体,以查看选项卡切换期间ItemDataTemplate处TextBox行为的不同。
打开App,查看更多内容
随时随地看视频慕课网APP