From c860f251f5795ed9593e971ffe0a716974364820 Mon Sep 17 00:00:00 2001 From: v-leafshi Date: Wed, 22 Oct 2025 01:46:55 -0700 Subject: [PATCH 1/2] Fix 13976: Enhance TreeNode.UpdateImage() to support inherited image keys and selected image rendering --- .../Forms/Controls/TreeView/TreeNode.cs | 78 ++++++++++++++++--- 1 file changed, 69 insertions(+), 9 deletions(-) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeNode.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeNode.cs index d97e3d70b82..210964cb56d 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeNode.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeNode.cs @@ -2195,24 +2195,84 @@ static bool IsSpecialImageIndex(int actualIndex) internal unsafe void UpdateImage() { - TreeView tv = TreeView!; - if (tv.IsDisposed) + if (TreeView is not { IsDisposed: false } treeView) { return; } + int imageIndex = 0; + int selectedImageIndex = 0; + + if (treeView.ImageList is { } imageList) + { + string effectiveImageKey = ImageKey; + + // If the current node does not set ImageKey, try inheriting TreeView's ImageKey + if (string.IsNullOrEmpty(effectiveImageKey)) + { + effectiveImageKey = treeView.ImageKey; + } + + // If the current node has an ImageKey set, prefer using the valid ImageKey + if (!string.IsNullOrEmpty(effectiveImageKey) && imageList.Images.ContainsKey(effectiveImageKey)) + { + imageIndex = imageList.Images.IndexOfKey(effectiveImageKey); + } + else if (ImageIndexer.ActualIndex >= 0 && ImageIndexer.ActualIndex < imageList.Images.Count) + { + // Otherwise use the node's own ImageIndex + imageIndex = ImageIndexer.ActualIndex; + } + else if (treeView.ImageIndexer.ActualIndex >= 0 && treeView.ImageIndexer.ActualIndex < imageList.Images.Count) + { + // Then try using TreeView's ImageIndex + imageIndex = treeView.ImageIndexer.ActualIndex; + } + else + { + // Fallback to default image + imageIndex = 0; + } + + // Resolve the effective SelectedImageKey for the node + // If the node's SelectedImageKey is not set, it will be using "Default", fallback to TreeView's SelectedImageKey + string effectiveSelectedImageKey = SelectedImageKey; + if (string.IsNullOrEmpty(effectiveSelectedImageKey)) + { + effectiveSelectedImageKey = treeView.SelectedImageKey; + } + + // Determine selected image index based on effective SelectedImageKey + if (!string.IsNullOrEmpty(effectiveSelectedImageKey) && imageList.Images.ContainsKey(effectiveSelectedImageKey)) + { + selectedImageIndex = imageList.Images.IndexOfKey(effectiveSelectedImageKey); + } + else if (SelectedImageIndexer.ActualIndex >= 0 && SelectedImageIndexer.ActualIndex < imageList.Images.Count) + { + // If SelectedImageKey is invalid, fallback to node's SelectedImageIndex + selectedImageIndex = SelectedImageIndexer.ActualIndex; + } + else if (treeView.SelectedImageIndexer.ActualIndex >= 0 && treeView.SelectedImageIndexer.ActualIndex < imageList.Images.Count) + { + // If node's SelectedImageIndex is invalid, fallback to TreeView's SelectedImageIndex + selectedImageIndex = treeView.SelectedImageIndexer.ActualIndex; + } + else + { + // Final fallback to index 0 + selectedImageIndex = 0; + } + } + TVITEMW item = new() { - mask = TVITEM_MASK.TVIF_HANDLE | TVITEM_MASK.TVIF_IMAGE, + mask = TVITEM_MASK.TVIF_HANDLE | TVITEM_MASK.TVIF_IMAGE | TVITEM_MASK.TVIF_SELECTEDIMAGE, hItem = HTREEITEM, - iImage = Math.Max( - 0, - tv.ImageList is { } imageList && ImageIndexer.ActualIndex >= imageList.Images.Count - ? imageList.Images.Count - 1 - : ImageIndexer.ActualIndex) + iImage = imageIndex, + iSelectedImage = selectedImageIndex }; - PInvokeCore.SendMessage(tv, PInvoke.TVM_SETITEMW, 0, ref item); + PInvokeCore.SendMessage(treeView, PInvoke.TVM_SETITEMW, 0, ref item); } /// From b2d57576fad0be11201dba74216b43ed536dfbee Mon Sep 17 00:00:00 2001 From: v-leafshi Date: Wed, 22 Oct 2025 20:10:45 -0700 Subject: [PATCH 2/2] Extract the code in UpdateImage and add the switch PreserveUnassignedTreeNodeImages --- .../LocalAppContextSwitches.cs | 11 ++ .../Forms/Controls/TreeView/TreeNode.cs | 101 ++++++++---------- 2 files changed, 57 insertions(+), 55 deletions(-) diff --git a/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs b/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs index 5b728b3472a..5492ea09bad 100644 --- a/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs +++ b/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs @@ -26,6 +26,7 @@ internal static partial class LocalAppContextSwitches internal const string EnableMsoComponentManagerSwitchName = "Switch.System.Windows.Forms.EnableMsoComponentManager"; internal const string TreeNodeCollectionAddRangeRespectsSortOrderSwitchName = "System.Windows.Forms.TreeNodeCollectionAddRangeRespectsSortOrder"; internal const string MoveTreeViewTextLocationOnePixelSwitchName = "System.Windows.Forms.TreeView.MoveTreeViewTextLocationOnePixel"; + internal const string PreserveUnassignedTreeNodeImagesSwitchName = "System.Windows.Forms.TreeView.PreserveUnassignedTreeNodeImages"; private static int s_scaleTopLevelFormMinMaxSizeForDpi; private static int s_anchorLayoutV2; @@ -39,6 +40,7 @@ internal static partial class LocalAppContextSwitches private static int s_treeNodeCollectionAddRangeRespectsSortOrder; private static int s_moveTreeViewTextLocationOnePixel; + private static int s_preserveUnassignedTreeNodeImages; private static FrameworkName? s_targetFrameworkName; @@ -231,4 +233,13 @@ public static bool MoveTreeViewTextLocationOnePixel [MethodImpl(MethodImplOptions.AggressiveInlining)] get => GetCachedSwitchValue(MoveTreeViewTextLocationOnePixelSwitchName, ref s_moveTreeViewTextLocationOnePixel); } + + /// + /// Indicates whether keep the node image that has not set an image when the ImageList changes dynamically. + /// + public static bool PreserveUnassignedTreeNodeImages + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => GetCachedSwitchValue(PreserveUnassignedTreeNodeImagesSwitchName, ref s_preserveUnassignedTreeNodeImages); + } } diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeNode.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeNode.cs index 210964cb56d..e3ae94eb408 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeNode.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeNode.cs @@ -2205,76 +2205,67 @@ internal unsafe void UpdateImage() if (treeView.ImageList is { } imageList) { - string effectiveImageKey = ImageKey; - - // If the current node does not set ImageKey, try inheriting TreeView's ImageKey - if (string.IsNullOrEmpty(effectiveImageKey)) - { - effectiveImageKey = treeView.ImageKey; - } - - // If the current node has an ImageKey set, prefer using the valid ImageKey - if (!string.IsNullOrEmpty(effectiveImageKey) && imageList.Images.ContainsKey(effectiveImageKey)) - { - imageIndex = imageList.Images.IndexOfKey(effectiveImageKey); - } - else if (ImageIndexer.ActualIndex >= 0 && ImageIndexer.ActualIndex < imageList.Images.Count) - { - // Otherwise use the node's own ImageIndex - imageIndex = ImageIndexer.ActualIndex; - } - else if (treeView.ImageIndexer.ActualIndex >= 0 && treeView.ImageIndexer.ActualIndex < imageList.Images.Count) - { - // Then try using TreeView's ImageIndex - imageIndex = treeView.ImageIndexer.ActualIndex; - } - else - { - // Fallback to default image - imageIndex = 0; - } - - // Resolve the effective SelectedImageKey for the node - // If the node's SelectedImageKey is not set, it will be using "Default", fallback to TreeView's SelectedImageKey - string effectiveSelectedImageKey = SelectedImageKey; - if (string.IsNullOrEmpty(effectiveSelectedImageKey)) - { - effectiveSelectedImageKey = treeView.SelectedImageKey; - } - - // Determine selected image index based on effective SelectedImageKey - if (!string.IsNullOrEmpty(effectiveSelectedImageKey) && imageList.Images.ContainsKey(effectiveSelectedImageKey)) - { - selectedImageIndex = imageList.Images.IndexOfKey(effectiveSelectedImageKey); - } - else if (SelectedImageIndexer.ActualIndex >= 0 && SelectedImageIndexer.ActualIndex < imageList.Images.Count) - { - // If SelectedImageKey is invalid, fallback to node's SelectedImageIndex - selectedImageIndex = SelectedImageIndexer.ActualIndex; - } - else if (treeView.SelectedImageIndexer.ActualIndex >= 0 && treeView.SelectedImageIndexer.ActualIndex < imageList.Images.Count) + if (AppContextSwitches.PreserveUnassignedTreeNodeImages) { - // If node's SelectedImageIndex is invalid, fallback to TreeView's SelectedImageIndex - selectedImageIndex = treeView.SelectedImageIndexer.ActualIndex; + imageIndex = GetEffectiveImageIndex(treeView, imageList, ImageKey); + selectedImageIndex = GetEffectiveImageIndex(treeView, imageList, SelectedImageKey); } else { - // Final fallback to index 0 - selectedImageIndex = 0; + imageIndex = Math.Clamp(ImageIndexer.ActualIndex, 0, imageList.Images.Count - 1); } } TVITEMW item = new() { - mask = TVITEM_MASK.TVIF_HANDLE | TVITEM_MASK.TVIF_IMAGE | TVITEM_MASK.TVIF_SELECTEDIMAGE, + mask = TVITEM_MASK.TVIF_HANDLE | TVITEM_MASK.TVIF_IMAGE, hItem = HTREEITEM, - iImage = imageIndex, - iSelectedImage = selectedImageIndex + iImage = imageIndex }; + if (AppContextSwitches.PreserveUnassignedTreeNodeImages) + { + item.mask |= TVITEM_MASK.TVIF_SELECTEDIMAGE; + item.iSelectedImage = selectedImageIndex; + } + PInvokeCore.SendMessage(treeView, PInvoke.TVM_SETITEMW, 0, ref item); } + private unsafe int GetEffectiveImageIndex(TreeView treeView, ImageList imageList, string effectiveImageKey) + { + int imageIndex; + + // If the current node does not set ImageKey, try inheriting TreeView's ImageKey + if (string.IsNullOrEmpty(effectiveImageKey)) + { + effectiveImageKey = treeView.ImageKey; + } + + // If the current node has an ImageKey set, prefer using the valid ImageKey + if (!string.IsNullOrEmpty(effectiveImageKey) && imageList.Images.ContainsKey(effectiveImageKey)) + { + imageIndex = imageList.Images.IndexOfKey(effectiveImageKey); + } + else if (ImageIndexer.ActualIndex >= 0 && ImageIndexer.ActualIndex < imageList.Images.Count) + { + // Otherwise use the node's own ImageIndex + imageIndex = ImageIndexer.ActualIndex; + } + else if (treeView.ImageIndexer.ActualIndex >= 0 && treeView.ImageIndexer.ActualIndex < imageList.Images.Count) + { + // Then try using TreeView's ImageIndex + imageIndex = treeView.ImageIndexer.ActualIndex; + } + else + { + // Fallback to default image + imageIndex = 0; + } + + return imageIndex; + } + /// /// ISerializable private implementation ///