Как сохранить состояние управления в элементах табуляции в TabControl

Я новичок в WPF, пытаясь построить проект, который следует рекомендациям превосходной статьи Джоша Смита, описывающей шаблон дизайна Model-View-ViewModel .

Используя пример кода Джоша в качестве базы, я создал простое приложение, которое содержит ряд «рабочих пространств», каждый из которых представлен вкладкой TabControl. В моем приложении рабочая область – это редактор документов, который позволяет управлять иерархическим документом через элемент управления TreeView.

Хотя мне удалось открыть несколько рабочих областей и просмотреть их содержимое в связанном элементе управления TreeView, я обнаружил, что TreeView «забывает» его состояние при переключении между вкладками. Например, если TreeView в Tab1 частично расширен, он будет показан как полностью свернутый после перехода на Tab2 и возврата к Tab1. Это поведение, по-видимому, относится ко всем аспектам состояния управления для всех элементов управления.

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

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

Спасибо, Тим

Обновить:

В соответствии с просьбой я попытаюсь опубликовать код, демонстрирующий эту проблему. Однако, поскольку данные, лежащие в основе TreeView, сложны, я опубликую упрощенный пример, демонстрирующий те же самые симтомы. Вот XAML из главного windows:

            

Вышеупомянутый XAML корректно связывается с ObservableCollection DocumentViewModel, посредством чего каждый член представляется через DocumentView.

Для простоты этого примера я удалил TreeView (упомянутый выше) из DocumentView и заменил его TabControl, содержащим 3 фиксированных вкладки:

      

В этом случае между DocumentView и DocumentViewModel нет привязки. Когда код запускается, внутренний TabControl не может запомнить его выбор при переключении внешнего TabControl.

Однако, если я явно привязываю внутреннее свойство SelectedIndex TabControl …

      

… к соответствующему фиктивному свойству в DocumentViewModel …

 public int SelecteDocumentIndex { get; set; } 

… внутренняя вкладка может запомнить ее выбор.

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

6 Solutions collect form web for “Как сохранить состояние управления в элементах табуляции в TabControl”

Пример использования Writer приложения WPF Application Framework (WAF) показывает, как решить вашу проблему. Он создает новый UserControl для каждого TabItem. Таким образом, состояние сохраняется, когда пользователь меняет активную вкладку.

Я получил решение с этим советом WPF TabControl: Turning Off Tab Virtualization на http://www.codeproject.com/Articles/460989/WPF-TabControl-Turning-Off-Tab-Virtualization это class TabContent с свойством IsCached.

У меня была та же проблема, и я нашел хорошее решение, которое вы можете использовать, как обычный TabControl насколько я его тестировал. В случае, если для вас здесь важна Текущая лицензия

Здесь Код на случай, когда ссылка идет вниз:

 using System; using System.Collections.Specialized; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; namespace CefSharp.Wpf.Example.Controls { ///  /// Extended TabControl which saves the displayed item so you don't get the performance hit of /// unloading and reloading the VisualTree when switching tabs ///  ///  /// Based on example from http://stackoverflow.com/a/9802346, which in turn is based on /// http://www.pluralsight-training.net/community/blogs/eburke/archive/2009/04/30/keeping-the-wpf-tab-control-from-destroying-its-children.aspx /// with some modifications so it reuses a TabItem's ContentPresenter when doing drag/drop operations ///  [TemplatePart(Name = "PART_ItemsHolder", Type = typeof(Panel))] public class NonReloadingTabControl : TabControl { private Panel itemsHolderPanel; public NonReloadingTabControl() { // This is necessary so that we get the initial databound selected item ItemContainerGenerator.StatusChanged += ItemContainerGeneratorStatusChanged; } ///  /// If containers are done, generate the selected item ///  /// The sender. /// The  instance containing the event data. private void ItemContainerGeneratorStatusChanged(object sender, EventArgs e) { if (ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) { ItemContainerGenerator.StatusChanged -= ItemContainerGeneratorStatusChanged; UpdateSelectedItem(); } } ///  /// Get the ItemsHolder and generate any children ///  public override void OnApplyTemplate() { base.OnApplyTemplate(); itemsHolderPanel = GetTemplateChild("PART_ItemsHolder") as Panel; UpdateSelectedItem(); } ///  /// When the items change we remove any generated panel children and add any new ones as necessary ///  /// The  instance containing the event data. 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) { var 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 var 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; var cp = FindChildContentPresenter(item); if (cp != null) return cp; var tabItem = item as TabItem; cp = new ContentPresenter { Content = (tabItem != null) ? tabItem.Content : item, ContentTemplate = this.SelectedContentTemplate, ContentTemplateSelector = this.SelectedContentTemplateSelector, ContentStringFormat = this.SelectedContentStringFormat, Visibility = Visibility.Collapsed, Tag = tabItem ?? (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() { var selectedItem = SelectedItem; if (selectedItem == null) return null; var item = selectedItem as TabItem ?? ItemContainerGenerator.ContainerFromIndex(SelectedIndex) as TabItem; return item; } } } 

Лицензия на Copietime

 // Copyright © 2010-2016 The CefSharp Authors // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // // * Neither the name of Google Inc. nor the name Chromium Embedded // Framework nor the name CefSharp nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 

Используя идею WAF, я прихожу к этому простому решению, которое, похоже, решает проблему.

Я использую Interactivity Behavior, но то же самое можно сделать с прикрепленным свойством, если библиотека Interactivity не указана

 ///  /// Wraps tab item contents in UserControl to prevent TabControl from re-using its content ///  public class TabControlUcWrapperBehavior : Behavior { private TabControl AssociatedTabControl { get { return (TabControl) AssociatedObject; } } protected override void OnAttached() { ((INotifyCollectionChanged)AssociatedTabControl.Items).CollectionChanged += TabControlUcWrapperBehavior_CollectionChanged; base.OnAttached(); } protected override void OnDetaching() { ((INotifyCollectionChanged)AssociatedTabControl.Items).CollectionChanged -= TabControlUcWrapperBehavior_CollectionChanged; base.OnDetaching(); } void TabControlUcWrapperBehavior_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.Action != NotifyCollectionChangedAction.Add) return; foreach (var newItem in e.NewItems) { var ti = AssociatedTabControl.ItemContainerGenerator.ContainerFromItem(newItem) as TabItem; if (ti != null && !(ti.Content is UserControl)) ti.Content = new UserControl { Content = ti.Content }; } } } 

И использование

      

Основываясь на ответе @ Arsen выше, вот еще одно поведение:

  1. Не требует дополнительных ссылок. (если вы не поместили код во внешнюю библиотеку)
  2. Он не использует базовый class.
  3. Он обрабатывает как сброс, так и добавление изменений коллекции.

Использовать его

Объявите пространство имен в xaml:

  

Обновите стиль:

  

Или напрямую обновите TabControl:

  

И вот код для поведения:

 using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Windows; using System.Windows.Controls; namespace My.Behaviors { ///  /// Wraps tab item contents in UserControl to prevent TabControl from re-using its content ///  public class TabControlBehavior { private static readonly HashSet _tabControls = new HashSet(); private static readonly Dictionary _tabControlItemCollections = new Dictionary(); public static bool GetDoNotCacheControls(TabControl tabControl) { return (bool)tabControl.GetValue(DoNotCacheControlsProperty); } public static void SetDoNotCacheControls(TabControl tabControl, bool value) { tabControl.SetValue(DoNotCacheControlsProperty, value); } public static readonly DependencyProperty DoNotCacheControlsProperty = DependencyProperty.RegisterAttached( "DoNotCacheControls", typeof(bool), typeof(TabControlBehavior), new UIPropertyMetadata(false, OnDoNotCacheControlsChanged)); private static void OnDoNotCacheControlsChanged( DependencyObject depObj, DependencyPropertyChangedEventArgs e) { var tabControl = depObj as TabControl; if (null == tabControl) return; if (e.NewValue is bool == false) return; if ((bool)e.NewValue) Attach(tabControl); else Detach(tabControl); } private static void Attach(TabControl tabControl) { if (!_tabControls.Add(tabControl)) return; _tabControlItemCollections.Add(tabControl.Items, tabControl); ((INotifyCollectionChanged)tabControl.Items).CollectionChanged += TabControlUcWrapperBehavior_CollectionChanged; } private static void Detach(TabControl tabControl) { if (!_tabControls.Remove(tabControl)) return; _tabControlItemCollections.Remove(tabControl.Items); ((INotifyCollectionChanged)tabControl.Items).CollectionChanged -= TabControlUcWrapperBehavior_CollectionChanged; } private static void TabControlUcWrapperBehavior_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { var itemCollection = (ItemCollection)sender; var tabControl = _tabControlItemCollections[itemCollection]; IList items; if (e.Action == NotifyCollectionChangedAction.Reset) { /* our ObservableArray swops out the whole collection */ items = (ItemCollection)sender; } else { if (e.Action != NotifyCollectionChangedAction.Add) return; items = e.NewItems; } foreach (var newItem in items) { var ti = tabControl.ItemContainerGenerator.ContainerFromItem(newItem) as TabItem; if (ti != null) { var userControl = ti.Content as UserControl; if (null == userControl) ti.Content = new UserControl { Content = ti.Content }; } } } } } 

Я отправил ответ на аналогичный вопрос. В моем случае вручную создание TabItems решило проблему создания представления снова и снова. Проверить здесь

  • Элементы управления диаграммой WPF
  • Как я могу найти элементы управления WPF по имени или типу?
  • Есть ли хороший способ конвертировать между BitmapSource и Bitmap?
  • Утечка памяти WPF CreateBitmapSourceFromHBitmap ()
  • Связывание свойства enum с ComboBox в WPF
  • Каков наилучший способ передать событие в ViewModel?
  • Передача двух параметров команды с использованием привязки WPF
  • Как получить TextBox, чтобы принимать только числовые данные в WPF?
  • Загрузка XAML во время выполнения?
  • Получение каталога приложения из приложения WPF
  • Изменение цвета фона для выбранного элемента ListBox
  • Давайте будем гением компьютера.