阻止 TabControl 重新创建其子项 [英] Stop TabControl from recreating its children

查看:24
本文介绍了阻止 TabControl 重新创建其子项的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个绑定到 TabControl 的视图模型的 IList.这个 IListTabControl 的生命周期内不会改变.

I have an IList of viewmodels which are bound to a TabControl. This IList will not change over the lifetime of the TabControl.

<TabControl ItemsSource="{Binding Tabs}" SelectedIndex="0" >
    <TabControl.ItemContainerStyle>
        <Style TargetType="TabItem">
            <Setter Property="Content" Value="{Binding}" />
        </Style>
    </TabControl.ItemContainerStyle>
</TabControl>

每个视图模型都有一个 DataTemplate,它在 ResourceDictionary 中指定.

Each viewmodel has a DataTemplate which is specified in a ResourceDictionary.

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

DataTemplate 中指定的每个视图的资源密集程度足以创建我宁愿只创建每个视图一次,但是当我切换选项卡时,相关视图的构造函数被调用.根据我的阅读,这是 TabControl 的预期行为,但我不清楚调用构造函数的机制是什么.

Each of the views specified in the DataTemplate are resource intensive enough to create that I would rather create each view just once, but when I switch tabs, the constructor for the relevant view is called. From what I have read, this is the expected behavior for the TabControl, but it is not clear to me what the mechanism is which calls the constructor.

我查看了 a使用 UserControls 的类似问题,但那里提供的解决方案需要我绑定到不需要的视图.

I have taken a look at a similar question which uses UserControls but the solution offered there would require me to bind to views which is undesirable.

推荐答案

默认情况下,TabControl 共享一个面板来呈现其内容.为了做你想做的事(和许多其他 WPF 开发者),你需要像这样扩展 TabControl:

By default, the TabControl shares a panel to render it's content. To do what you want (and many other WPF developers), you need to extend TabControl like so:

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 上的答案.

Note: I did not come up with this solution. It has been shared in programming forums for several years and believe that it is in now one of those WPF recipes books. The oldest or original source for I believe was PluralSight .NET blog post and this answer on StackOverflow.

HTH,

这篇关于阻止 TabControl 重新创建其子项的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆