WPF preserve Tab control state

后端 未结 2 1115
無奈伤痛
無奈伤痛 2021-01-03 15:46

I have read the posts @ How to stop Wpf Tabcontrol to unload Visual tree on Tab change, but I could not get it work, I must have missed something. Pls help. thanks

相关标签:
2条回答
  • 2021-01-03 16:11

    I gave up with the workaround, it just did not work in my case. I purchase a third party WPF library (DevExpress WPF) which solves the issue. thanks

    0 讨论(0)
  • 2021-01-03 16:28

    I use the code from that link all the time without a problem, although I notice that the website has changed since when I first got the code. You may want to check for any code differences between what I have and what the website has.

    Here's the code I got before they changed the site:

    // Extended TabControl which saves the displayed item so you don't get the performance hit of 
    // unloading and reloading the VisualTree when switching tabs
    
    // Obtained from http://www.pluralsight-training.net/community/blogs/eburke/archive/2009/04/30/keeping-the-wpf-tab-control-from-destroying-its-children.aspx
    // and made a some modifications so it reuses a TabItem's ContentPresenter when doing drag/drop operations
    
    [TemplatePart(Name = "PART_ItemsHolder", Type = typeof(Panel))]
    public class TabControlEx : System.Windows.Controls.TabControl
    {
        // Holds all items, but only marks the current tab's item as visible
        private Panel _itemsHolder = null;
    
        // Temporaily holds deleted item in case this was a drag/drop operation
        private object _deletedObject = null;
    
        public TabControlEx()
            : base()
        {
            // this is necessary so that we get the initial databound selected item
            this.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
        }
    
        /// <summary>
        /// if containers are done, generate the selected item
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        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();
            _itemsHolder = 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 (_itemsHolder == null)
            {
                return;
            }
    
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Reset:
                    _itemsHolder.Children.Clear();
    
                    if (base.Items.Count > 0)
                    {
                        base.SelectedItem = base.Items[0];
                        UpdateSelectedItem();
                    }
    
                    break;
    
                case NotifyCollectionChangedAction.Add:
                case NotifyCollectionChangedAction.Remove:
    
                    // Search for recently deleted items caused by a Drag/Drop operation
                    if (e.NewItems != null && _deletedObject != null)
                    {
                        foreach (var item in e.NewItems)
                        {
                            if (_deletedObject == item)
                            {
                                // If the new item is the same as the recently deleted one (i.e. a drag/drop event)
                                // then cancel the deletion and reuse the ContentPresenter so it doesn't have to be 
                                // redrawn. We do need to link the presenter to the new item though (using the Tag)
                                ContentPresenter cp = FindChildContentPresenter(_deletedObject);
                                if (cp != null)
                                {
                                    int index = _itemsHolder.Children.IndexOf(cp);
    
                                    (_itemsHolder.Children[index] as ContentPresenter).Tag =
                                        (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item));
                                }
                                _deletedObject = null;
                            }
                        }
                    }
    
                    if (e.OldItems != null)
                    {
                        foreach (var item in e.OldItems)
                        {
    
                            _deletedObject = item;
    
                            // We want to run this at a slightly later priority in case this
                            // is a drag/drop operation so that we can reuse the template
                            this.Dispatcher.BeginInvoke(DispatcherPriority.DataBind,
                                new Action(delegate()
                            {
                                if (_deletedObject != null)
                                {
                                    ContentPresenter cp = FindChildContentPresenter(_deletedObject);
                                    if (cp != null)
                                    {
                                        this._itemsHolder.Children.Remove(cp);
                                    }
                                }
                            }
                            ));
                        }
                    }
    
                    UpdateSelectedItem();
                    break;
    
                case NotifyCollectionChangedAction.Replace:
                    throw new NotImplementedException("Replace not implemented yet");
            }
        }
    
        /// <summary>
        /// update the visible child in the ItemsHolder
        /// </summary>
        /// <param name="e"></param>
        protected override void OnSelectionChanged(SelectionChangedEventArgs e)
        {
            base.OnSelectionChanged(e);
            UpdateSelectedItem();
        }
    
        /// <summary>
        /// generate a ContentPresenter for the selected item
        /// </summary>
        void UpdateSelectedItem()
        {
            if (_itemsHolder == null)
            {
                return;
            }
    
            // generate a ContentPresenter if necessary
            TabItem item = GetSelectedTabItem();
            if (item != null)
            {
                CreateChildContentPresenter(item);
            }
    
            // show the right child
            foreach (ContentPresenter child in _itemsHolder.Children)
            {
                child.Visibility = ((child.Tag as TabItem).IsSelected) ? Visibility.Visible : Visibility.Collapsed;
            }
        }
    
        /// <summary>
        /// create the child ContentPresenter for the given item (could be data or a TabItem)
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        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));
            _itemsHolder.Children.Add(cp);
            return cp;
        }
    
        /// <summary>
        /// Find the CP for the given object.  data could be a TabItem or a piece of data
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        ContentPresenter FindChildContentPresenter(object data)
        {
            if (data is TabItem)
            {
                data = (data as TabItem).Content;
            }
    
            if (data == null)
            {
                return null;
            }
    
            if (_itemsHolder == null)
            {
                return null;
            }
    
            foreach (ContentPresenter cp in _itemsHolder.Children)
            {
                if (cp.Content == data)
                {
                    return cp;
                }
            }
    
            return null;
        }
    
        /// <summary>
        /// copied from TabControl; wish it were protected in that class instead of private
        /// </summary>
        /// <returns></returns>
        protected TabItem GetSelectedTabItem()
        {
            object selectedItem = base.SelectedItem;
            if (selectedItem == null)
            {
                return null;
            }
    
            if (_deletedObject == selectedItem)
            { 
    
            }
    
            TabItem item = selectedItem as TabItem;
            if (item == null)
            {
                item = base.ItemContainerGenerator.ContainerFromIndex(base.SelectedIndex) as TabItem;
            }
            return item;
        }
    }
    

    I did make some changes to the original code because I was dragging/dropping tabs and didn't want to re-create the tabs when removing an item and re-adding it, but honestly it's been so long that I forget what part is mine and what isn't.

    I did a quick test to double-check, and yes it does keep your non-bound values such as selected or expanded values.

    EDIT

    In response to your comments below, put DataTemplates in your Resources somewhere that define which UserControl to use for each Tab. When the TabControl tries to draw each Tab's Content, it will use whatever DataTemplate you defined for that data type

    <Window.Resources>
        <DataTemplate TargetType="{x:Type local:TabAViewModel}">
            <local:TabAView />
        </DataTemplate>
        <DataTemplate TargetType="{x:Type local:TabBViewModel}">
            <local:TabBView />
        </DataTemplate>
    </Window.Resources>
    
    <TabControl ItemsSource="{Binding TabItems}">
        <TabControl.ItemContainerStyle> 
            <Style TargetType="{x:Type TabItem}"> 
                <Setter Property="Header" Value="{Binding Header}" />
            </Style> 
        </TabControl.ItemContainerStyle>
    </TabControl>
    

    ViewModel...

    public MyViewModelConstructor()
    {
        TabItems.Add(New TabAViewModel { Header = "Tab A" });
        TabItems.Add(New TabBViewModel { Header = "Tab B" });
    }
    

    EDIT #2

    Text XAML, as you requested

    <local:TabControlEx>
        <TabItem Header="Test1">
            <DataGrid ItemsSource="{Binding Test}">
                <DataGrid.Columns>
                    <DataGridTextColumn Binding="{Binding TestValue}" Header="Test" />
                </DataGrid.Columns>
            </DataGrid>
        </TabItem>
        <TabItem Header="Test2">
            <DataGrid ItemsSource="{Binding Test}">
                <DataGrid.Columns>
                    <DataGridTextColumn Binding="{Binding TestValue}" Header="Test" />
                </DataGrid.Columns>
            </DataGrid>
        </TabItem>
    </local:TabControlEx>
    
    0 讨论(0)
提交回复
热议问题