我有一个IList绑定到的viewmodel TabControl。这IList不会在的使用期限内发生变化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
ResourceDictionary
<DataTemplate TargetType={x:Type vm:MyViewModel}> <v:MyView/> </DataTemplate>
DataTemplate中指定的每个视图都占用大量资源,以至于我宁愿只创建一次每个视图,但是当我切换选项卡时,将调用相关视图的构造函数。根据我的阅读,这是的预期行为TabControl,但是对我来说,尚不清楚调用构造函数的机制是什么。
我看过一个类似的问题,它使用了UserControls,但是那里提供的解决方案将需要我绑定到视图,这是不希望的。
UserControl
默认情况下,TabControl共享面板以呈现其内容。若要做您想做的事情(以及许多其他WPF开发人员),您需要TabControl像这样进行扩展:
TabControlEx.cs
[TemplatePart(Name = "PART_ItemsHolder", Type = typeof(Panel))] public class TabControlEx : TabControl { private Panel ItemsHolderPanel = null; public TabControlEx() : base() { // This is necessary so that we get the initial databound selected item ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged; } /// <summary> /// If containers are done, generate the selected item /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e) { if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) { this.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged; UpdateSelectedItem(); } } /// <summary> /// Get the ItemsHolder and generate any children /// </summary> public override void OnApplyTemplate() { base.OnApplyTemplate(); ItemsHolderPanel = GetTemplateChild("PART_ItemsHolder") as Panel; UpdateSelectedItem(); } /// <summary> /// When the items change we remove any generated panel children and add any new ones as necessary /// </summary> /// <param name="e"></param> protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) { base.OnItemsChanged(e); if (ItemsHolderPanel == null) return; switch (e.Action) { case NotifyCollectionChangedAction.Reset: ItemsHolderPanel.Children.Clear(); break; case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Remove: if (e.OldItems != null) { foreach (var item in e.OldItems) { ContentPresenter cp = FindChildContentPresenter(item); if (cp != null) ItemsHolderPanel.Children.Remove(cp); } } // Don't do anything with new items because we don't want to // create visuals that aren't being shown UpdateSelectedItem(); break; case NotifyCollectionChangedAction.Replace: throw new NotImplementedException("Replace not implemented yet"); } } protected override void OnSelectionChanged(SelectionChangedEventArgs e) { base.OnSelectionChanged(e); UpdateSelectedItem(); } private void UpdateSelectedItem() { if (ItemsHolderPanel == null) return; // Generate a ContentPresenter if necessary TabItem item = GetSelectedTabItem(); if (item != null) CreateChildContentPresenter(item); // show the right child foreach (ContentPresenter child in ItemsHolderPanel.Children) child.Visibility = ((child.Tag as TabItem).IsSelected) ? Visibility.Visible : Visibility.Collapsed; } private ContentPresenter CreateChildContentPresenter(object item) { if (item == null) return null; ContentPresenter cp = FindChildContentPresenter(item); if (cp != null) return cp; // the actual child to be added. cp.Tag is a reference to the TabItem cp = new ContentPresenter(); cp.Content = (item is TabItem) ? (item as TabItem).Content : item; cp.ContentTemplate = this.SelectedContentTemplate; cp.ContentTemplateSelector = this.SelectedContentTemplateSelector; cp.ContentStringFormat = this.SelectedContentStringFormat; cp.Visibility = Visibility.Collapsed; cp.Tag = (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item)); ItemsHolderPanel.Children.Add(cp); return cp; } private ContentPresenter FindChildContentPresenter(object data) { if (data is TabItem) data = (data as TabItem).Content; if (data == null) return null; if (ItemsHolderPanel == null) return null; foreach (ContentPresenter cp in ItemsHolderPanel.Children) { if (cp.Content == data) return cp; } return null; } protected TabItem GetSelectedTabItem() { object selectedItem = base.SelectedItem; if (selectedItem == null) return null; TabItem item = selectedItem as TabItem; if (item == null) item = base.ItemContainerGenerator.ContainerFromIndex(base.SelectedIndex) as TabItem; return item; } }
XAML
<Style TargetType="{x:Type controls:TabControlEx}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TabControl}"> <Grid Background="{TemplateBinding Background}" ClipToBounds="True" KeyboardNavigation.TabNavigation="Local" SnapsToDevicePixels="True"> <Grid.ColumnDefinitions> <ColumnDefinition x:Name="ColumnDefinition0" /> <ColumnDefinition x:Name="ColumnDefinition1" Width="0" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition x:Name="RowDefinition0" Height="Auto" /> <RowDefinition x:Name="RowDefinition1" Height="*" /> </Grid.RowDefinitions> <DockPanel Margin="2,2,0,0" LastChildFill="False"> <TabPanel x:Name="HeaderPanel" Margin="0,0,0,-1" VerticalAlignment="Bottom" Panel.ZIndex="1" DockPanel.Dock="Right" IsItemsHost="True" KeyboardNavigation.TabIndex="1" /> </DockPanel> <Border x:Name="ContentPanel" Grid.Row="1" Grid.Column="0" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" KeyboardNavigation.DirectionalNavigation="Contained" KeyboardNavigation.TabIndex="2" KeyboardNavigation.TabNavigation="Local"> <Grid x:Name="PART_ItemsHolder" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" /> </Border> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style>
注意: 我没有提出这个解决方案。它已在编程论坛中共享了几年,并且相信它现在已成为WPF食谱书之一。我认为最古老或原始的来源是PluralSight .NET博客文章,以及StackOverflow上的答案。
HTH,