【问题标题】:WPF TabItem state lost when selected tab changes选定选项卡更改时 WPF TabItem 状态丢失
【发布时间】:2016-03-18 20:03:27
【问题描述】:

我有一个带有多个标签的TabControl。每个选项卡中都有一个多行 TextBox。

所以在 Tab 1 的可见文本框中,插入点从第 1 行的开头开始。

我将插入点移动到第 2 行的末尾。

我将选定的选项卡更改为选项卡 2,然后返回到选项卡 1。

Tab 1 中的插入点TextBox 总是回到第一行的开头。

两个选项卡都由列表中的视图模型填充:

<TabControl ItemsSource="{Binding TabItemViewModelList}">
    <TabControl.Resources>
        <DataTemplate DataType="TabItemViewModel">
            <Grid>
                <TextBox Text="{Binding Text}" />
            </Grid>
        </DataTemplate>
    </TabControl.Resources>
<TabControl>

这是简化的,但不是很多。

这是因为每次我更改所选项目时它都会重新实例化模板,还是类似的事情?除了痛苦地维护 ViewModel 中的每一点 UI 状态之外,还有什么方法可以解决这个问题?

更新

我是对的,这是虚拟化错误,it's a known bug,设计者可能认为它是一个特性。当然,他疯了:选项卡控件不是列表框。在您为虚拟化准备足够多的项目之前,它就完全无法使用了。

【问题讨论】:

    标签: wpf xaml mvvm textbox focus


    【解决方案1】:

    答案似乎是Ivan Krivyakov 的TabContent.IsCached attached property,在这个问题中找到:Turning off Virtualization for Tabcontrol without itemsource - WPF。这不是他问题的答案,而是我的答案。

    在 XAML 中看起来像这样:

    <TabControl
        xmlns:ikriv="clr-namespace:IKriv.Windows.Controls.Behaviors"
        ikriv:TabContent.IsCached="True"
        ItemsSource="{Binding DocumentList.Documents}"
        SelectedItem="{Binding DocumentList.ActiveDocument}"
        />
    

    这是 Krivyakov 解决方案的完整源代码,以防 CodeProject 被小行星撞击:

    // TabContent.cs, version 1.2
    // The code in this file is Copyright (c) Ivan Krivyakov
    // See http://www.ikriv.com/legal.php for more information
    //
    using System;
    using System.ComponentModel;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Markup;
    
    /// <summary>
    /// http://www.codeproject.com/Articles/460989/WPF-TabControl-Turning-Off-Tab-Virtualization
    /// </summary>
    namespace IKriv.Windows.Controls.Behaviors
    {
        /// <summary>
        /// Attached properties for persistent tab control
        /// </summary>
        /// <remarks>By default WPF TabControl bound to an ItemsSource destroys visual state of invisible tabs. 
        /// Set ikriv:TabContent.IsCached="True" to preserve visual state of each tab.
        /// </remarks>
        public static class TabContent
        {
            public static bool GetIsCached(DependencyObject obj)
            {
                return (bool)obj.GetValue(IsCachedProperty);
            }
    
            public static void SetIsCached(DependencyObject obj, bool value)
            {
                obj.SetValue(IsCachedProperty, value);
            }
    
            /// <summary>
            /// Controls whether tab content is cached or not
            /// </summary>
            /// <remarks>When TabContent.IsCached is true, visual state of each tab is preserved (cached), even when the tab is hidden</remarks>
            public static readonly DependencyProperty IsCachedProperty =
                DependencyProperty.RegisterAttached("IsCached", typeof(bool), typeof(TabContent), new UIPropertyMetadata(false, OnIsCachedChanged));
    
    
            public static DataTemplate GetTemplate(DependencyObject obj)
            {
                return (DataTemplate)obj.GetValue(TemplateProperty);
            }
    
            public static void SetTemplate(DependencyObject obj, DataTemplate value)
            {
                obj.SetValue(TemplateProperty, value);
            }
    
            /// <summary>
            /// Used instead of TabControl.ContentTemplate for cached tabs
            /// </summary>
            public static readonly DependencyProperty TemplateProperty =
                DependencyProperty.RegisterAttached("Template", typeof(DataTemplate), typeof(TabContent), new UIPropertyMetadata(null));
    
    
            public static DataTemplateSelector GetTemplateSelector(DependencyObject obj)
            {
                return (DataTemplateSelector)obj.GetValue(TemplateSelectorProperty);
            }
    
            public static void SetTemplateSelector(DependencyObject obj, DataTemplateSelector value)
            {
                obj.SetValue(TemplateSelectorProperty, value);
            }
    
            /// <summary>
            /// Used instead of TabControl.ContentTemplateSelector for cached tabs
            /// </summary>
            public static readonly DependencyProperty TemplateSelectorProperty =
                DependencyProperty.RegisterAttached("TemplateSelector", typeof(DataTemplateSelector), typeof(TabContent), new UIPropertyMetadata(null));
    
            [EditorBrowsable(EditorBrowsableState.Never)]
            public static TabControl GetInternalTabControl(DependencyObject obj)
            {
                return (TabControl)obj.GetValue(InternalTabControlProperty);
            }
    
            [EditorBrowsable(EditorBrowsableState.Never)]
            public static void SetInternalTabControl(DependencyObject obj, TabControl value)
            {
                obj.SetValue(InternalTabControlProperty, value);
            }
    
            // Using a DependencyProperty as the backing store for InternalTabControl.  This enables animation, styling, binding, etc...
            [EditorBrowsable(EditorBrowsableState.Never)]
            public static readonly DependencyProperty InternalTabControlProperty =
                DependencyProperty.RegisterAttached("InternalTabControl", typeof(TabControl), typeof(TabContent), new UIPropertyMetadata(null, OnInternalTabControlChanged));
    
    
            [EditorBrowsable(EditorBrowsableState.Never)]
            public static ContentControl GetInternalCachedContent(DependencyObject obj)
            {
                return (ContentControl)obj.GetValue(InternalCachedContentProperty);
            }
    
            [EditorBrowsable(EditorBrowsableState.Never)]
            public static void SetInternalCachedContent(DependencyObject obj, ContentControl value)
            {
                obj.SetValue(InternalCachedContentProperty, value);
            }
    
            // Using a DependencyProperty as the backing store for InternalCachedContent.  This enables animation, styling, binding, etc...
            [EditorBrowsable(EditorBrowsableState.Never)]
            public static readonly DependencyProperty InternalCachedContentProperty =
                DependencyProperty.RegisterAttached("InternalCachedContent", typeof(ContentControl), typeof(TabContent), new UIPropertyMetadata(null));
    
            [EditorBrowsable(EditorBrowsableState.Never)]
            public static object GetInternalContentManager(DependencyObject obj)
            {
                return (object)obj.GetValue(InternalContentManagerProperty);
            }
    
            [EditorBrowsable(EditorBrowsableState.Never)]
            public static void SetInternalContentManager(DependencyObject obj, object value)
            {
                obj.SetValue(InternalContentManagerProperty, value);
            }
    
            // Using a DependencyProperty as the backing store for InternalContentManager.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty InternalContentManagerProperty =
                DependencyProperty.RegisterAttached("InternalContentManager", typeof(object), typeof(TabContent), new UIPropertyMetadata(null));
    
            private static void OnIsCachedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
            {
                if (obj == null) return;
    
                var tabControl = obj as TabControl;
                if (tabControl == null)
                {
                    throw new InvalidOperationException("Cannot set TabContent.IsCached on object of type " + args.NewValue.GetType().Name +
                        ". Only objects of type TabControl can have TabContent.IsCached property.");
                }
    
                bool newValue = (bool)args.NewValue;
    
                if (!newValue)
                {
                    if (args.OldValue != null && ((bool)args.OldValue))
                    {
                        throw new NotImplementedException("Cannot change TabContent.IsCached from True to False. Turning tab caching off is not implemented");
                    }
    
                    return;
                }
    
                EnsureContentTemplateIsNull(tabControl);
                tabControl.ContentTemplate = CreateContentTemplate();
                EnsureContentTemplateIsNotModified(tabControl);
            }
    
            private static DataTemplate CreateContentTemplate()
            {
                const string xaml =
                    "<DataTemplate><Border b:TabContent.InternalTabControl=\"{Binding RelativeSource={RelativeSource AncestorType=TabControl}}\" /></DataTemplate>";
    
                var context = new ParserContext();
    
                context.XamlTypeMapper = new XamlTypeMapper(new string[0]);
                context.XamlTypeMapper.AddMappingProcessingInstruction("b", typeof(TabContent).Namespace, typeof(TabContent).Assembly.FullName);
    
                context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
                context.XmlnsDictionary.Add("b", "b");
    
                var template = (DataTemplate)XamlReader.Parse(xaml, context);
                return template;
            }
    
            private static void OnInternalTabControlChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
            {
                if (obj == null) return;
                var container = obj as Decorator;
    
                if (container == null)
                {
                    var message = "Cannot set TabContent.InternalTabControl on object of type " + obj.GetType().Name +
                        ". Only controls that derive from Decorator, such as Border can have a TabContent.InternalTabControl.";
                    throw new InvalidOperationException(message);
                }
    
                if (args.NewValue == null) return;
                if (!(args.NewValue is TabControl))
                {
                    throw new InvalidOperationException("Value of TabContent.InternalTabControl cannot be of type " + args.NewValue.GetType().Name +", it must be of type TabControl");
                }
    
                var tabControl = (TabControl)args.NewValue;
                var contentManager = GetContentManager(tabControl, container);
                contentManager.UpdateSelectedTab();
            }
    
            private static ContentManager GetContentManager(TabControl tabControl, Decorator container)
            {
                var contentManager = (ContentManager)GetInternalContentManager(tabControl);
                if (contentManager != null)
                {
                    /*
                     * Content manager already exists for the tab control. This means that tab content template is applied 
                     * again, and new instance of the Border control (container) has been created. The old container 
                     * referenced by the content manager is no longer visible and needs to be replaced
                     */
                    contentManager.ReplaceContainer(container);
                }
                else
                {
                    // create content manager for the first time
                    contentManager = new ContentManager(tabControl, container);
                    SetInternalContentManager(tabControl, contentManager);
                }
    
                return contentManager;
            }
    
            private static void EnsureContentTemplateIsNull(TabControl tabControl)
            {
                if (tabControl.ContentTemplate != null)
                {
                    throw new InvalidOperationException("TabControl.ContentTemplate value is not null. If TabContent.IsCached is True, use TabContent.Template instead of ContentTemplate");
                }
            }
    
            private static void EnsureContentTemplateIsNotModified(TabControl tabControl)
            {
                var descriptor = DependencyPropertyDescriptor.FromProperty(TabControl.ContentTemplateProperty, typeof(TabControl));
                descriptor.AddValueChanged(tabControl, (sender, args) =>
                    {
                        throw new InvalidOperationException("Cannot assign to TabControl.ContentTemplate when TabContent.IsCached is True. Use TabContent.Template instead");
                    });
            }
    
            public class ContentManager
            {
                TabControl _tabControl;
                Decorator _border;
    
                public ContentManager(TabControl tabControl, Decorator border)
                {
                    _tabControl = tabControl;
                    _border = border;
                    _tabControl.SelectionChanged += (sender, args) => { UpdateSelectedTab(); };
                }
    
                public void ReplaceContainer(Decorator newBorder)
                {
                    if (Object.ReferenceEquals(_border, newBorder)) return;
    
                    _border.Child = null; // detach any tab content that old border may hold
                    _border = newBorder;
                }
    
                public void UpdateSelectedTab()
                {
                    _border.Child = GetCurrentContent();
                }
    
                private ContentControl GetCurrentContent()
                {
                    var item = _tabControl.SelectedItem;
                    if (item == null) return null;
    
                    var tabItem = _tabControl.ItemContainerGenerator.ContainerFromItem(item);
                    if (tabItem == null) return null;
    
                    var cachedContent = TabContent.GetInternalCachedContent(tabItem);
                    if (cachedContent == null)
                    {
                        cachedContent = new ContentControl 
                        { 
                            DataContext = item,
                            ContentTemplate = TabContent.GetTemplate(_tabControl), 
                            ContentTemplateSelector = TabContent.GetTemplateSelector(_tabControl)
                        };
    
                        cachedContent.SetBinding(ContentControl.ContentProperty, new Binding());
                        TabContent.SetInternalCachedContent(tabItem, cachedContent);
                    }
    
                    return cachedContent;
                }
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      每次在您的情况下重新初始化文本时,carret 位置都会更新。您可以在文本框上放置一个行为以将其设置为焦点...请参阅以下内容:Set the caret/cursor position to the end of the string value WPF textbox

      【讨论】:

      • 当您说“文本被重新初始化”时,您指的是 TextBox 的 Text 属性,对吗?我希望找到一个不涉及手动维护视图模型中所有控件状态的解决方案。
      猜你喜欢
      • 2011-04-04
      • 2015-03-03
      • 2013-03-03
      • 2012-06-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-07
      • 1970-01-01
      相关资源
      最近更新 更多