Подтвердить что ты не робот

WPF TabControl - предотвращение разгрузки при изменении вкладки?

Есть ли способ предотвратить выгрузку/перезагрузку Tab при изменении вкладки в элементе управления вкладкой WPF? Или, если это невозможно, существует ли рекомендуемый метод кэширования содержимого табуляции, чтобы они не нуждались в регенерации при каждом изменении вкладок?

Например, пользовательский интерфейс одной вкладки полностью настраивается и сохраняется в базе данных. Когда пользователь выбирает объект для работы, элементы в настраиваемом макете заполняются данными объекта. Пользователи ожидают незначительную задержку при начальной загрузке или при извлечении данных, но не при изменении взад и вперед между вкладками и задержке при изменении вкладок очень заметно.

4b9b3361

Ответ 1

Я нашел обходной путь здесь: https://web.archive.org/web/20120429044747/http://eric.burke.name/dotnetmania/2009/04/26/22.09.28

Edit: This is the corrected link: 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 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 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 does not 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

Просто, чтобы добавить к этому, у меня была аналогичная проблема, и мне удалось решить ее путем кэширования пользовательского элемента управления, который представлял содержимое элемента табуляции в коде.

В моем проекте у меня есть элемент управления вкладкой, связанный с коллекцией (MVVM). Однако первая вкладка представляет собой обзор, который показывает сводку всех других вкладок в виде списка. Проблема, с которой я столкнулась, заключалась в том, что всякий раз, когда пользователь перемещает свой выбор с вкладки элементов на вкладку "Обзор" , обзор перерисовывается со всеми сводными данными, которые могут занимать 10-15 секунд в зависимости от количества элементов в коллекции. (обратите внимание, что это не перезагрузка фактических данных из db или чего-либо другого, это чисто рисунок суммарного представления, которое занимало время).

Я хотел, чтобы эта загрузка сводного представления возникала только один раз, когда сначала загружается контекст данных, и любое последующее переключение между вкладками должно быть мгновенным.

Решение:

Занятые классы: MainWindow.xaml - главная страница, содержащая элемент управления вкладкой. MainWindow.xaml.cs - Код за выше. MainWindowViewModel.cs - Просмотр модели для вышеуказанного представления, содержит коллекцию. Overview.xaml - Пользовательский элемент управления, который рисует содержимое элемента вкладки обзора. OverviewViewModel.cs - просмотр модели для вышеуказанного представления.

Шаги:

  • Замените шаблон данных в "MainWindow.xaml", который рисует элемент вкладки "Обзор" с помощью пустого пользовательского элемента управления с именем "OverviewPlaceholder"

  • Сделайте ссылку на "OverviewViewModel" общедоступной в "MainWindowViewModel.cs"

  • Добавить статическую ссылку на "Обзор" в "MainWindow.xaml.cs"

  • Добавьте обработчик событий в загруженное событие пользовательского элемента управления "OverviewPlaceholder", в рамках этого метода создайте статическую ссылку на "Обзор" только в том случае, если она равна null, установите для этого ссылочного типа ссылку на ссылку "ОбзорViewModel" в текущем datacontext (то есть "MainWindowViewModel" ) и установите для содержимого держателя места статическую ссылку на "Обзор" .

Теперь обзорная страница рисуется только один раз, потому что каждый раз, когда она загружается (т.е. пользователь нажимает на вкладку обзора), он помещает уже обработанный статический пользовательский элемент управления обратно на страницу.

Ответ 3

У меня есть очень простое решение, чтобы избежать перезагрузки табуляции при изменении вкладок, используйте contentPresenter в tabItem вместо свойства content.

например (в стиле MVVM)

заменить

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

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