Вопрос по binding, wpf, state, tabcontrol, mvvm – Как сохранить состояние элемента управления в элементах табуляции в TabControl

37

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

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

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

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

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

Спасибо Тим

Обновить:

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


    
        
            
        
    

    
        
            
        
    

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

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


    
    
    

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

Однако, если я явно связать внутренний TabControl 's SelectedIndex свойство ...


    
    
    

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

public int SelecteDocumentIndex { get; set; }

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

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

Можете ли вы показать нам, как вы решили это, пожалуйста? Ответ ниже, я чувствую, не отвечает на вопрос ... MoonKnight
Управление в WPFПомните' их состояние по умолчанию, тот факт, что элементы управления в элементах вкладки "забыть» их состояние является результатом каких-то явных действий с вашей стороны. Покажите XAML для ваших элементов вкладки и соответствующий код модели представления для содержащихся в них привязок. Aviad P.
@ Скотт Уитлок - да, я согласен, Андерсон Аймс Комментарий кажется настолько близким, насколько я, вероятно, смогу найти решение этой проблемы. +1. Tim Coulter
Есть ли способ просмотреть визуальное дерево, чтобы увидеть, есть ли изменения? Я пытаюсь применить ValidationRules ко всем привязкам на FrameworkElements в моем визуальном дереве. Поскольку дерево меняется, у меня возникают проблемы с применением привязок к элементам. Frinavale

Ваш Ответ

6   ответов
1

Безразлично»не нужны какие-либо дополнительные ссылки. (если вы не поместите код во внешнюю библиотеку)Это нене использовать базовый класс.Он обрабатывает как Сброс, так и Добавление изменений коллекции.

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

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

<resourcedictionary ...="" xmlns:behaviors="clr-namespace:My.Behaviors;assembly=My.Wpf.Assembly">
</resourcedictionary>

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

<style targettype="TabControl" x:key="TabControl">
    ...
    <Setter Property="behaviors:TabControlBehavior.DoNotCacheControls" Value="True" />
    ...
</style>

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

<tabcontrol behaviors:tabcontrolbehavior.donotcachecontrols="True" itemssource="{Binding Tabs}" selecteditem="{Binding SelectedTab}">
</tabcontrol>

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

using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls;

namespace My.Behaviors
{
    /// <summary>
    /// Wraps tab item contents in UserControl to prevent TabControl from re-using its content
    /// </summary>
    public class TabControlBehavior
    {
        private static readonly HashSet<tabcontrol> _tabControls = new HashSet<tabcontrol>();
        private static readonly Dictionary<itemcollection, tabcontrol=""> _tabControlItemCollections = new Dictionary<itemcollection, tabcontrol="">();

        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<t> 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 };
                }
            }
        }
    }
}
</t></itemcollection,></itemcollection,></tabcontrol></tabcontrol>
Мне нравится это решение, как вы можете изменить его для использования ContentTemplate? ScottFoster1000
2

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

Здесь Код на случай, если Ссылка выходит из строя:
using System;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;

namespace CefSharp.Wpf.Example.Controls
{
    /// <summary>
    /// Extended TabControl which saves the displayed item so you don't get the performance hit of
    /// unloading and reloading the VisualTree when switching tabs
    /// </summary>
    /// <remarks>
    /// 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
    /// </remarks>
    [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
        /// &l,t;/summary>
        /// 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.
3

WPF TabControl: Turning Off Tab Virtualization вhttp://www.codeproject.com/Articles/460989/WPF-TabControl-Turning-Off-Tab-Virtualization это класс TabContent со свойством IsCached.

Работал как по волшебству! Спасибо!! Vijay Chavda
-1

TabItems решило проблему создания представления снова и снова. ПроверьтеВот

1

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

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

/// <summary>
/// Wraps tab item contents in UserControl to prevent TabControl from re-using its content
/// </summary>
public class TabControlUcWrapperBehavior 
    : Behavior<uielement>
{
    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 };
        }
    }
}
</uielement>

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

<tabcontrol itemssource="...">
    <i:interaction.behaviors>
        <controls:tabcontrolucwrapperbehavior>
    </controls:tabcontrolucwrapperbehavior></i:interaction.behaviors>
</tabcontrol>
Проверьте, вызываете ли вы метод ItemFromContainer вместо ContainerFromItem? Arsen Mkrtchyan
Я пробую ваше решение, но что яЯ вижу, что ti.Content - это модель представления, а не элемент управления из шаблона. Есть идеи? Walter Williams
Можете ли вы выслать мне пример, который не работает на mkrtchyan.arsen на gmail com, я проверю Arsen Mkrtchyan
Я скопировал и вставил ваш код выше точно. Walter Williams
1

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

@LineloDude Я думаю, он имеет в виду это наоборот? Так что установив UserControl.Content для объекта данных. (то есть)myUserControl.Content = myDataObject Peter
Как дела?установить объект данных в UserControl.Content "? LineloDude
Я неНе думаю, что это хороший ответ. Это указывает на большое приложение - как вы решаете проблему, изложенную выше явно ?? MoonKnight
Упоминание о создании новогоUserControl для каждогоTabItem это то, что помогло мне Я просто обернул свой объект данных вUserControl установив объект данных вUserControl.Content собственность и это решило мою проблему производительности. E-rich
Я тоже нене считаю этот ответ очень полезным. Мне потребовалось довольно много времени, чтобы понять, как это было сделано в примере приложения Writer. Я заметил следующее: они сохраняют ссылку на представление в своей модели представления. Я предполагаю, что это предотвращает потерю View и его состояния. Однако я не имеюt проверил, действительно ли это решает мою конкретную проблему (которая точно такая же, как у OP). Я бы предпочел решение, в котором ViewModel не нужно знать представление (а вместо этого использовать DataTemplates, как в OP). Robert Hegner

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