Вопрос по caching, tabcontrol, wpf – WPF TabControl - предотвращение выгрузки при изменении вкладки?

17

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

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

Да, я хотел бы либо кэшировать вкладку, чтобы ей не приходилось перестраивать ее, либо делать какой-то обходной путь, чтобы предотвратить ее выгрузку / перезагрузку содержимого при каждом изменении вкладки. Rachel
Итак, в вашем приложении, когда изменяется выбранная вкладка, она запускает соединение с базой данных для получения данных объекта? ASanch
Я не думаю, что TabItems выгружаются / перезагружаются всякий раз, когда выбранный элемент в элементе управления вкладки изменяется. Я не уверен, но, возможно, необходимо изменить логику SelectionChanged вашего TabControl, чтобы он не перезапрашивал базу данных каждый раз? ASanch
События Loaded / Unloaded в DataTemplates запускаются каждый раз, когда я меняю вкладки (я использую шаблон проектирования MVVM) Rachel

Ваш Ответ

3   ответа
17

Я нашел обходной путь здесь: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 вместо рисования нового)

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

// 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;
    }
}
Ну, «правильно» только в том смысле, что все отображается правильно. Он создан вOnApplyTemplate по звонкуGetTemplateChildи это ноль в тот момент и за его пределами. Стоит отметить, что я новичок в WPF и понятия не имею, для чего предназначен «PART_ItemsHolder». DonBoitnott
@Rachel Ответом на мою проблему стало то, чтоControlTemplate определяется вэтот ответ. DonBoitnott
@DonBoitnott Если он установлен вOnApplyTemplateтогда это должно быть там. Вы уверены, что смотрите на тот же экземпляр объектной модели? В какой момент вы устанавливаете точку останова и видите, что она нулевая? Возможно, лучше задать новый вопрос, если у вас возникла проблема с этим, чтобы мы могли увидеть соответствующий код. Кроме того, я считаю, что "PART_ItemsHolder" является разделом шаблона WPF TabControl по умолчанию Rachel
Я думаю, что StackOverflow просто неправильно анализирует уценку ссылки. Это работает, если вы копируете / вставляете весь URL (или не используете разметку []).web.archive.org/web/20110825185059/http://eric.burke.name/... skst
2

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

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

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

Решение:

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

шаги:

Замените табличку данных в «MainWindow.xaml», которая рисует элемент вкладки обзора, с пустым пользовательским элементом управления с именем «OverviewPlaceholder»

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

Добавьте статическую ссылку на «Overview» в «MainWindow.xaml.cs»

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

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

-1

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

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

замещать

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

от

        <TabItem Header="Tab1">
            <ContentPresenter Content="{Binding Tab1ViewModel}" />
        </TabItem>
Почему за этот ответ проголосовали?ContentPresenter будет выгружен при переключении вкладок, включая его содержимое. Это ничего не делает. Sinatr

Похожие вопросы