From d6c80194c7d322fd77bbcc3639943e71293bad57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=83=AD=E5=BF=83=E5=B8=82=E6=B0=91=E7=9F=B3=E5=85=88?= =?UTF-8?q?=E7=94=9F?= <1249467256@qq.com> Date: Sat, 21 Mar 2026 22:50:44 +0800 Subject: [PATCH] Feat: Preserve editor state via CachedTabControl Introduces a custom CachedTabControl with visual tree caching. This replaces the default UI virtualization behavior by using Visibility.Collapsed to hide unselected pages, permanently resolving the issue where editor views lose their scroll positions and search states when switching tabs. --- AssetEditor/Views/CachedTabControl.cs | 90 +++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 AssetEditor/Views/CachedTabControl.cs diff --git a/AssetEditor/Views/CachedTabControl.cs b/AssetEditor/Views/CachedTabControl.cs new file mode 100644 index 00000000..55cbd326 --- /dev/null +++ b/AssetEditor/Views/CachedTabControl.cs @@ -0,0 +1,90 @@ +using System.Collections.Specialized; +using System.Windows; +using System.Windows.Controls; + +namespace AssetEditor.Views +{ + [TemplatePart(Name = "PART_ItemsHolder", Type = typeof(Panel))] + public class CachedTabControl : TabControl + { + private Panel _itemsHolderPanel = null; + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + // Retrieve the container defined in XAML + _itemsHolderPanel = GetTemplateChild("PART_ItemsHolder") as Panel; + UpdateSelectedItem(); + } + + protected override void OnSelectionChanged(SelectionChangedEventArgs e) + { + base.OnSelectionChanged(e); + UpdateSelectedItem(); + } + + private void UpdateSelectedItem() + { + if (_itemsHolderPanel == null) return; + + object selectedItem = this.SelectedItem; + if (selectedItem == null) return; + + ContentPresenter cp = FindChildContentPresenter(selectedItem); + if (cp == null) + { + cp = new ContentPresenter + { + Content = selectedItem, + ContentTemplate = this.SelectedContentTemplate, + ContentTemplateSelector = this.ContentTemplateSelector, + ContentStringFormat = this.SelectedContentStringFormat, + // Force stretch to fill the entire container to prevent layout collapsing + HorizontalAlignment = HorizontalAlignment.Stretch, + VerticalAlignment = VerticalAlignment.Stretch + }; + _itemsHolderPanel.Children.Add(cp); + } + + // Core caching logic: Show the selected item and hide the unselected ones + foreach (ContentPresenter child in _itemsHolderPanel.Children) + { + child.Visibility = (child.Content == selectedItem) ? Visibility.Visible : Visibility.Collapsed; + } + } + + protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) + { + base.OnItemsChanged(e); + if (_itemsHolderPanel == null) return; + + // Handle tab closure to prevent memory leaks + if (e.Action == NotifyCollectionChangedAction.Remove || e.Action == NotifyCollectionChangedAction.Replace) + { + if (e.OldItems != null) + { + foreach (var item in e.OldItems) + { + var cp = FindChildContentPresenter(item); + if (cp != null) _itemsHolderPanel.Children.Remove(cp); + } + } + } + else if (e.Action == NotifyCollectionChangedAction.Reset) + { + _itemsHolderPanel.Children.Clear(); + } + } + + private ContentPresenter FindChildContentPresenter(object data) + { + if (_itemsHolderPanel == null) return null; + foreach (ContentPresenter cp in _itemsHolderPanel.Children) + { + if (cp.Content == data) return cp; + } + return null; + } + } +}