代码之家  ›  专栏  ›  技术社区  ›  Rachel

WPF TabControl-防止标签更改时卸载?

  •  17
  • Rachel  · 技术社区  · 15 年前

    例如,一个选项卡的UI是完全可定制的,并存储在数据库中。当用户选择要处理的对象时,自定义布局中的项将填充该对象的数据。用户希望在初始加载或检索数据时有一点延迟,但在选项卡之间来回更改时没有,更改选项卡时的延迟非常明显。

    3 回复  |  直到 15 年前
        1
  •  19
  •   Scott Solmer    6 年前

    我在这里找到了一个解决方法: https://web.archive.org/web/20120429044747/http://eric.burke.name/dotnetmania/2009/04/26/22.09.28

    这是正确的链接: http://web.archive.org/web/20110825185059/http://eric.burke.name/dotnetmania/2009/04/26/22.09.28

    它基本上存储选项卡的ContentPresenter,并在切换选项卡时加载它,而不是重新绘制它。由于这是一个删除/添加操作,所以在拖放选项卡时仍然会导致延迟,但是经过一些修改,我也消除了这种情况(以较低的调度程序优先级运行删除代码,然后运行添加代码,因此添加操作有机会取消删除操作,并使用旧的ContentPresenter而不是绘图)一个新的)

    上面的链接似乎不再工作,所以我将粘贴一个代码的副本在这里。它被修改了一点以允许拖放,但是它仍然应该以相同的方式工作。

    using System;
    using System.Windows;
    using System.Windows.Threading;
    using System.Windows.Controls;
    using System.Windows.Controls.Primitives;
    using System.Collections.Specialized;
    
    // 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://eric.burke.name/dotnetmania/2009/04/26/22.09.28
    // 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;
        }
    }
    
        2
  •  2
  •   RobJohnson    13 年前

    除此之外,我还遇到了一个类似的问题,并通过缓存一个表示代码中tab项内容的用户控件来解决了这个问题。

    在我的项目中,我有一个绑定到集合(MVVM)的选项卡控件。但是,第一个选项卡是一个概述,显示列表视图中所有其他选项卡的摘要。我遇到的问题是,每当用户将所选内容从“项目”选项卡移动到“概述”选项卡时,都会使用所有摘要数据重新绘制概述,这可能需要10-15秒,具体时间取决于集合中的项目数。(请注意,它们不是从db或任何东西中重新加载实际数据,而是纯粹的摘要视图的绘制过程)。

    我想要的是,这个摘要视图的加载只在第一次加载数据上下文时发生一次,随后在选项卡之间的任何切换都是瞬时的。

    解决方案:

    涉及的类别: 主窗口.xaml-包含选项卡控件的主页面。 主窗口.xaml.cs-上面的代码。 概述.xaml-用于绘制“概述”选项卡项内容的用户控件。

    步骤:

    1. 替换'主窗口.xaml'绘制带有名为'OverviewPlaceholder'的空白用户控件的overview选项卡项'

    2. 将“OverviewModel”的引用设置为“public within”主窗口视图模型.cs'

    3. 将事件处理程序添加到用户控件“OverviewPlaceholder”的已加载事件中,在此方法中,仅当“Overview”为null时才实例化静态引用,将此引用的datacontext设置为当前datacontext(即“MainWindowViewModel”)中的“OverviewModel”引用,并将占位符的内容设置为“概述”的静态引用。

    现在只绘制一次overview页面,因为每次加载它(即用户单击overview选项卡)时,它都会将已经呈现的静态用户控件放回页面上。

        3
  •  -2
  •   jsm    11 年前

    我有一个非常简单的解决方案,避免在标签更改时重新加载标签, 在tabItem中使用contentPresenter,而不是content属性。

    e、 g.(MVVM样式)

    代替

          <TabItem Header="Tab1" Content="{Binding Tab1ViewModel}" />
    

            <TabItem Header="Tab1">
                <ContentPresenter Content="{Binding Tab1ViewModel}" />
            </TabItem>