如何在TabControl中的选项卡项中保留控件状态

时间:2010-01-17 11:11:42

标签: wpf mvvm binding tabcontrol state

我是WPF的新手,试图建立一个遵循Josh Smith的优秀文章The Model-View-ViewModel Design Pattern的建议的项目。

使用Josh的示例代码作为基础,我创建了一个包含许多“工作区”的简单应用程序,每个工作区由TabControl中的选项卡表示。在我的应用程序中,工作空间是一个文档编辑器,它允许通过TreeView控件操作分层文档。

虽然我已成功打开多个工作区并在绑定的TreeView控件中查看其文档内容,但我发现TreeView在选项卡之间切换时“忘记”其状态。例如,如果Tab1中的TreeView部分展开,则在切换到Tab2并返回到Tab1后,它将显示为完全折叠。此行为似乎适用于所有控件的控件状态的所有方面。

经过一些实验,我意识到我可以通过将每个控件状态属性显式绑定到底层ViewModel上的专用属性来保留TabItem中的状态。然而,这似乎是很多额外的工作,当我只想让我的所有控件在工作区之间切换时记住它们的状态。

我认为我遗漏了一些简单的东西,但我不知道在哪里寻找答案。任何指导都将非常感谢。

谢谢, 添

更新

根据要求,我将尝试发布一些演示此问题的代码。但是,由于作为TreeView基础的数据很复杂,我将发布一个展示相同symtoms的简化示例。这是主窗口中的XAML:

<TabControl IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Path=Docs}">
    <TabControl.ItemTemplate>
        <DataTemplate>
            <ContentPresenter Content="{Binding Path=Name}" />
        </DataTemplate>
    </TabControl.ItemTemplate>

    <TabControl.ContentTemplate>
        <DataTemplate>
            <view:DocumentView />
        </DataTemplate>
    </TabControl.ContentTemplate>
</TabControl>

上面的XAML正确绑定到DocumentViewModel的ObservableCollection,每个成员都通过DocumentView呈现。

为了简化这个例子,我从DocumentView中删除了TreeView(如上所述),并将其替换为包含3个固定标签的TabControl:

<TabControl>
    <TabItem Header="A" />
    <TabItem Header="B" />
    <TabItem Header="C" />
</TabControl>

在这种情况下,DocumentView和DocumentViewModel之间没有绑定。运行代码时,当切换外部TabControl时,内部TabControl无法记住其选择。

但是,如果我显式绑定内部TabControl的SelectedIndex属性......

<TabControl SelectedIndex="{Binding Path=SelectedDocumentIndex}">
    <TabItem Header="A" />
    <TabItem Header="B" />
    <TabItem Header="C" />
</TabControl>

...到DocumentViewModel上的相应虚拟属性...

public int SelecteDocumentIndex { get; set; }

...内部标签能够记住它的选择。

我知道通过将这种技术应用到每个控件的每个视觉属性,我可以有效地解决我的问题,但我希望有一个更优雅的解决方案。

6 个答案:

答案 0 :(得分:3)

我通过http://www.codeproject.com/Articles/460989/WPF-TabControl-Turning-Off-Tab-Virtualization在{{3}}这个带有属性IsCached属性的TabContent来解决这个问题。

答案 1 :(得分:2)

我有同样的问题,发现nice solution你可以像普通的TabControl一样使用它,就我测试它而言。如果它对您来说很重要Current License

这里的代码是关于Link关闭的情况:

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;
        }

        /// <summary>
        /// If containers are done, generate the selected item
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        private void ItemContainerGeneratorStatusChanged(object sender, EventArgs e)
        {
            if (ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
            {
                ItemContainerGenerator.StatusChanged -= ItemContainerGeneratorStatusChanged;
                UpdateSelectedItem();
            }
        }

        /// <summary>
        /// Get the ItemsHolder and generate any children
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            itemsHolderPanel = 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">The <see cref="NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
        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.

答案 2 :(得分:1)

WPF Application Framework (WAF)的Writer示例应用程序演示了如何解决您的问题。它为每个TabItem创建一个新的UserControl。因此,当用户更改活动Tab时,将保留状态。

答案 3 :(得分:1)

使用WAF的想法我来到这个简单的解决方案,似乎解决了这个问题。

我使用了交互行为,但如果未引用交互库,则可以使用附加属性进行相同的操作

/// <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 };
        }
    }
}

使用

<TabControl ItemsSource="...">
    <i:Interaction.Behaviors>
        <controls:TabControlUcWrapperBehavior/>
    </i:Interaction.Behaviors>
</TabControl>

答案 4 :(得分:1)

根据@ Arsen上面的回答,这是另一种行为:

  1. 不需要任何其他参考。 (除非你把代码放在外部库中)
  2. 它不使用基类。
  3. 它处理重置和添加集合更改。
  4. 使用

    在xaml中声明名称空间:

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

    更新样式:

    <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}">
    

    以下是行为的代码:

    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 };
                    }
                }
            }
        }
    }
    

答案 5 :(得分:-1)

我已经发布了类似问题的答案。在我的情况下,手动创建TabItems已经解决了一次又一次创建View的问题。查看here