From 8da2d0484779b75af0fd16b3dbedfdc1f244525c Mon Sep 17 00:00:00 2001 From: "Leaf Shi (BEYONDSOFT CONSULTING INC)" Date: Thu, 6 Mar 2025 17:04:12 +0800 Subject: [PATCH 1/5] Rebase main --- .../Forms/Controls/TreeView/TreeView.cs | 89 ++++++++++++++++--- 1 file changed, 79 insertions(+), 10 deletions(-) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeView.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeView.cs index 64203a7eb2b..eb79b40d6e2 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeView.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeView.cs @@ -22,7 +22,7 @@ namespace System.Windows.Forms; [DefaultProperty(nameof(Nodes))] [DefaultEvent(nameof(AfterSelect))] [Docking(DockingBehavior.Ask)] -[Designer($"System.Windows.Forms.Design.TreeViewDesigner, {Assemblies.SystemDesign}")] +[Designer($"System.Windows.Forms.Design.TreeViewDesigner, {AssemblyRef.SystemDesign}")] [SRDescription(nameof(SR.DescriptionTreeView))] public partial class TreeView : Control { @@ -528,7 +528,7 @@ public bool HotTracking [Localizable(true)] [RefreshProperties(RefreshProperties.Repaint)] [TypeConverter(typeof(NoneExcludedImageIndexConverter))] - [Editor($"System.Windows.Forms.Design.ImageIndexEditor, {Assemblies.SystemDesign}", typeof(UITypeEditor))] + [Editor($"System.Windows.Forms.Design.ImageIndexEditor, {AssemblyRef.SystemDesign}", typeof(UITypeEditor))] [SRDescription(nameof(SR.TreeViewImageIndexDescr))] [RelatedImageList("ImageList")] public int ImageIndex @@ -577,7 +577,7 @@ public int ImageIndex [SRCategory(nameof(SR.CatBehavior))] [Localizable(true)] [TypeConverter(typeof(ImageKeyConverter))] - [Editor($"System.Windows.Forms.Design.ImageIndexEditor, {Assemblies.SystemDesign}", typeof(UITypeEditor))] + [Editor($"System.Windows.Forms.Design.ImageIndexEditor, {AssemblyRef.SystemDesign}", typeof(UITypeEditor))] [DefaultValue(ImageList.Indexer.DefaultKey)] [RefreshProperties(RefreshProperties.Repaint)] [SRDescription(nameof(SR.TreeViewImageKeyDescr))] @@ -999,7 +999,7 @@ public bool Scrollable [SRCategory(nameof(SR.CatBehavior))] [TypeConverter(typeof(NoneExcludedImageIndexConverter))] [Localizable(true)] - [Editor($"System.Windows.Forms.Design.ImageIndexEditor, {Assemblies.SystemDesign}", typeof(UITypeEditor))] + [Editor($"System.Windows.Forms.Design.ImageIndexEditor, {AssemblyRef.SystemDesign}", typeof(UITypeEditor))] [SRDescription(nameof(SR.TreeViewSelectedImageIndexDescr))] [RelatedImageList("ImageList")] public int SelectedImageIndex @@ -1048,7 +1048,7 @@ public int SelectedImageIndex [SRCategory(nameof(SR.CatBehavior))] [Localizable(true)] [TypeConverter(typeof(ImageKeyConverter))] - [Editor($"System.Windows.Forms.Design.ImageIndexEditor, {Assemblies.SystemDesign}", typeof(UITypeEditor))] + [Editor($"System.Windows.Forms.Design.ImageIndexEditor, {AssemblyRef.SystemDesign}", typeof(UITypeEditor))] [DefaultValue(ImageList.Indexer.DefaultKey)] [RefreshProperties(RefreshProperties.Repaint)] [SRDescription(nameof(SR.TreeViewSelectedImageKeyDescr))] @@ -2734,6 +2734,13 @@ private unsafe void CustomDraw(ref Message m) // as background color. if (_drawMode == TreeViewDrawMode.OwnerDrawText) { + if (node is { IsEditing: true }) + { + nmtvcd->clrText = ColorTranslator.ToWin32(BackColor); + nmtvcd->clrTextBk = ColorTranslator.ToWin32(BackColor); + goto default; + } + nmtvcd->clrText = nmtvcd->clrTextBk; m.ResultInternal = (LRESULT)(nint)(PInvoke.CDRF_NEWFONT | PInvoke.CDRF_NOTIFYPOSTPAINT); return; @@ -2792,6 +2799,11 @@ private unsafe void CustomDraw(ref Message m) nmtvcd->clrTextBk = ColorTranslator.ToWin32(riBack); } + if (node is { IsEditing: true }) + { + nmtvcd->clrTextBk = ColorTranslator.ToWin32(BackColor); + } + if (renderinfo is not null && renderinfo.Font is not null) { // Mess with the DC directly... @@ -2826,8 +2838,36 @@ private unsafe void CustomDraw(ref Message m) { Rectangle bounds = node.Bounds; Size textSize = TextRenderer.MeasureText(node.Text, node.TreeView!.Font); - Point textLoc = new(AppContextSwitches.MoveTreeViewTextLocationOnePixel ? bounds.X : bounds.X - 1, bounds.Y); - bounds = new Rectangle(textLoc, new Size(textSize.Width, bounds.Height)); + Point textLocation = new(AppContextSwitches.MoveTreeViewTextLocationOnePixel ? bounds.X : bounds.X - 1, bounds.Y); + bounds = new Rectangle(textLocation, new Size(textSize.Width, bounds.Height)); + + Rectangle fillRectangle = new Rectangle(textLocation, new(textSize.Width, bounds.Height)); + Rectangle focusRectangle = new Rectangle(textLocation, new(textSize.Width, bounds.Height)); + + if (RightToLeft == RightToLeft.Yes && RightToLeftLayout) + { + int borderWidth = BorderStyle switch + { + BorderStyle.FixedSingle => 1, + BorderStyle.Fixed3D => 2, + _ => 0 + }; + + // Reverse the X-axis drawing coordinates of the rectangle. + int invertedX = Width - bounds.X - textSize.Width - borderWidth * 2; + + // Subtract the scroll bar width when the scroll bar appears. + if (Height - borderWidth * 2 < PreferredHeight) + { + float dpiScale = (float)DeviceDpi / (float)ScaleHelper.InitialSystemDpi; + invertedX -= (int)(SystemInformation.VerticalScrollBarWidth * Math.Round(dpiScale, 2)); + } + + // To ensure that the right side of the fillRectangle does not + // touch the left edge of the node prefix symbol, 1 pixel is subtracted here. + fillRectangle = new Rectangle(new Point(invertedX - 1, bounds.Y), new(textSize.Width, bounds.Height)); + focusRectangle = new Rectangle(new Point(invertedX, bounds.Y), new(textSize.Width, bounds.Height)); + } DrawTreeNodeEventArgs e = new(g, node, bounds, (TreeNodeStates)(nmtvcd->nmcd.uItemState)); OnDrawNode(e); @@ -2843,14 +2883,14 @@ private unsafe void CustomDraw(ref Message m) // Draw the actual node. if ((curState & TreeNodeStates.Selected) == TreeNodeStates.Selected) { - g.FillRectangle(SystemBrushes.Highlight, bounds); - ControlPaint.DrawFocusRectangle(g, bounds, color, SystemColors.Highlight); + g.FillRectangle(SystemBrushes.Highlight, fillRectangle); + ControlPaint.DrawFocusRectangle(g, focusRectangle, color, SystemColors.Highlight); TextRenderer.DrawText(g, node.Text, font, bounds, color, TextFormatFlags.Default); } else { using var brush = BackColor.GetCachedSolidBrushScope(); - g.FillRectangle(brush, bounds); + g.FillRectangle(brush, fillRectangle); TextRenderer.DrawText(g, node.Text, font, bounds, color, TextFormatFlags.Default); } @@ -2870,6 +2910,35 @@ private unsafe void CustomDraw(ref Message m) } } + private int PreferredHeight + { + get + { + int height = 0; + foreach (TreeNode node in Nodes) + { + height += GetNodeHeight(node); + } + + return height; + } + } + + private int GetNodeHeight(TreeNode node) + { + int height = ItemHeight; + + if (node.IsExpanded) + { + foreach (TreeNode childNode in node.Nodes) + { + height += GetNodeHeight(childNode); + } + } + + return height; + } + /// /// Generates colors for each item. This can be overridden to provide colors on a per state/per node /// basis, rather than using the ForeColor/BackColor/NodeFont properties on TreeNode. From c817ae8843558fd7b048d93c8f5d729f6a090ae8 Mon Sep 17 00:00:00 2001 From: "Leaf Shi (BEYONDSOFT CONSULTING INC)" Date: Thu, 6 Mar 2025 17:17:39 +0800 Subject: [PATCH 2/5] Replace AssemblyRef with Assemblies --- .../System/Windows/Forms/Controls/TreeView/TreeView.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Controls/TreeView/TreeView.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Controls/TreeView/TreeView.cs index 28d779a19b3..64203a7eb2b 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Controls/TreeView/TreeView.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Controls/TreeView/TreeView.cs @@ -22,7 +22,7 @@ namespace System.Windows.Forms; [DefaultProperty(nameof(Nodes))] [DefaultEvent(nameof(AfterSelect))] [Docking(DockingBehavior.Ask)] -[Designer($"System.Windows.Forms.Design.TreeViewDesigner, {AssemblyRef.SystemDesign}")] +[Designer($"System.Windows.Forms.Design.TreeViewDesigner, {Assemblies.SystemDesign}")] [SRDescription(nameof(SR.DescriptionTreeView))] public partial class TreeView : Control { @@ -528,7 +528,7 @@ public bool HotTracking [Localizable(true)] [RefreshProperties(RefreshProperties.Repaint)] [TypeConverter(typeof(NoneExcludedImageIndexConverter))] - [Editor($"System.Windows.Forms.Design.ImageIndexEditor, {AssemblyRef.SystemDesign}", typeof(UITypeEditor))] + [Editor($"System.Windows.Forms.Design.ImageIndexEditor, {Assemblies.SystemDesign}", typeof(UITypeEditor))] [SRDescription(nameof(SR.TreeViewImageIndexDescr))] [RelatedImageList("ImageList")] public int ImageIndex @@ -577,7 +577,7 @@ public int ImageIndex [SRCategory(nameof(SR.CatBehavior))] [Localizable(true)] [TypeConverter(typeof(ImageKeyConverter))] - [Editor($"System.Windows.Forms.Design.ImageIndexEditor, {AssemblyRef.SystemDesign}", typeof(UITypeEditor))] + [Editor($"System.Windows.Forms.Design.ImageIndexEditor, {Assemblies.SystemDesign}", typeof(UITypeEditor))] [DefaultValue(ImageList.Indexer.DefaultKey)] [RefreshProperties(RefreshProperties.Repaint)] [SRDescription(nameof(SR.TreeViewImageKeyDescr))] @@ -999,7 +999,7 @@ public bool Scrollable [SRCategory(nameof(SR.CatBehavior))] [TypeConverter(typeof(NoneExcludedImageIndexConverter))] [Localizable(true)] - [Editor($"System.Windows.Forms.Design.ImageIndexEditor, {AssemblyRef.SystemDesign}", typeof(UITypeEditor))] + [Editor($"System.Windows.Forms.Design.ImageIndexEditor, {Assemblies.SystemDesign}", typeof(UITypeEditor))] [SRDescription(nameof(SR.TreeViewSelectedImageIndexDescr))] [RelatedImageList("ImageList")] public int SelectedImageIndex @@ -1048,7 +1048,7 @@ public int SelectedImageIndex [SRCategory(nameof(SR.CatBehavior))] [Localizable(true)] [TypeConverter(typeof(ImageKeyConverter))] - [Editor($"System.Windows.Forms.Design.ImageIndexEditor, {AssemblyRef.SystemDesign}", typeof(UITypeEditor))] + [Editor($"System.Windows.Forms.Design.ImageIndexEditor, {Assemblies.SystemDesign}", typeof(UITypeEditor))] [DefaultValue(ImageList.Indexer.DefaultKey)] [RefreshProperties(RefreshProperties.Repaint)] [SRDescription(nameof(SR.TreeViewSelectedImageKeyDescr))] From 5cb9458cc94e7075074e8032751c43de24500d62 Mon Sep 17 00:00:00 2001 From: "Leaf Shi (BEYONDSOFT CONSULTING INC)" Date: Fri, 7 Mar 2025 11:13:55 +0800 Subject: [PATCH 3/5] Redraw editTextBox --- .../Forms/Controls/TreeView/TreeView.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeView.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeView.cs index 0270d15d2fb..c93cebb5839 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeView.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeView.cs @@ -2579,7 +2579,26 @@ private LRESULT TvnBeginLabelEdit(NMTVDISPINFOW nmtvdi) if (!e.CancelEdit) { _labelEdit = new TreeViewLabelEditNativeWindow(this); + IntPtr editHandle = PInvokeCore.SendMessage(this, PInvoke.TVM_GETEDITCONTROL); _labelEdit.AssignHandle(PInvokeCore.SendMessage(this, PInvoke.TVM_GETEDITCONTROL)); + + BeginInvoke((MethodInvoker)(() => + { + if (e.Node is not null) + { + float dpiScale = (float)DeviceDpi / ScaleHelper.OneHundredPercentLogicalDpi; + + PInvoke.SetWindowPos((HWND)editHandle, + HWND.Null, + e.Node.Bounds.X, + e.Node.Bounds.Y, + e.Node.Bounds.Width + (int)(dpiScale * 10), + e.Node.Bounds.Height + 2, + SET_WINDOW_POS_FLAGS.SWP_NOZORDER | + SET_WINDOW_POS_FLAGS.SWP_FRAMECHANGED | + SET_WINDOW_POS_FLAGS.SWP_SHOWWINDOW); + } + })); } return (LRESULT)(e.CancelEdit ? 1 : 0); From e67cf6d7ced8cf2e4b43f69bf8f0d0731c362c9d Mon Sep 17 00:00:00 2001 From: "Leaf Shi (BEYONDSOFT CONSULTING INC)" Date: Wed, 12 Mar 2025 18:03:40 +0800 Subject: [PATCH 4/5] Add comment for BeginInvoke((MethodInvoker) --- .../Forms/Controls/TreeView/TreeView.cs | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeView.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeView.cs index c93cebb5839..21f1799990f 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeView.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeView.cs @@ -2584,19 +2584,26 @@ private LRESULT TvnBeginLabelEdit(NMTVDISPINFOW nmtvdi) BeginInvoke((MethodInvoker)(() => { + // Use BeginInvoke to queue the operation for later execution: + // 1. Deferred Execution: Ensures that the logic runs after the TreeView control's edit control is fully initialized. + // 2. Thread Switching: Windows Forms controls can only be accessed on the thread they were created on. + // BeginInvoke ensures this code runs on the UI thread to avoid cross-thread access exceptions. + // + // This code adjusts the position and size of the edit control to accommodate the DPI scaling for high-resolution displays. if (e.Node is not null) { float dpiScale = (float)DeviceDpi / ScaleHelper.OneHundredPercentLogicalDpi; - PInvoke.SetWindowPos((HWND)editHandle, - HWND.Null, - e.Node.Bounds.X, - e.Node.Bounds.Y, - e.Node.Bounds.Width + (int)(dpiScale * 10), - e.Node.Bounds.Height + 2, - SET_WINDOW_POS_FLAGS.SWP_NOZORDER | - SET_WINDOW_POS_FLAGS.SWP_FRAMECHANGED | - SET_WINDOW_POS_FLAGS.SWP_SHOWWINDOW); + PInvoke.SetWindowPos( + (HWND)editHandle, + HWND.Null, + e.Node.Bounds.X, + e.Node.Bounds.Y, + e.Node.Bounds.Width + (int)(dpiScale * 10), + e.Node.Bounds.Height + 2, + SET_WINDOW_POS_FLAGS.SWP_NOZORDER | + SET_WINDOW_POS_FLAGS.SWP_FRAMECHANGED | + SET_WINDOW_POS_FLAGS.SWP_SHOWWINDOW); } })); } From 69e6e3d645d96921ec0ca48334067074f565d51c Mon Sep 17 00:00:00 2001 From: "Leaf Shi (BEYONDSOFT CONSULTING INC)" Date: Thu, 13 Mar 2025 16:31:56 +0800 Subject: [PATCH 5/5] Add update comment and optimize method CalculatePreferredHeight --- .../Forms/Controls/TreeView/TreeView.cs | 59 ++++++++++--------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeView.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeView.cs index 21f1799990f..d4490840e6f 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeView.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/TreeView/TreeView.cs @@ -2584,12 +2584,10 @@ private LRESULT TvnBeginLabelEdit(NMTVDISPINFOW nmtvdi) BeginInvoke((MethodInvoker)(() => { - // Use BeginInvoke to queue the operation for later execution: - // 1. Deferred Execution: Ensures that the logic runs after the TreeView control's edit control is fully initialized. - // 2. Thread Switching: Windows Forms controls can only be accessed on the thread they were created on. - // BeginInvoke ensures this code runs on the UI thread to avoid cross-thread access exceptions. - // - // This code adjusts the position and size of the edit control to accommodate the DPI scaling for high-resolution displays. + // Use BeginInvoke to queue the operation for later execution, + // Ensures that the logic runs after the TreeView control's edit control is fully initialized. + // And this code adjusts the position and size of the edit control + // to accommodate the DPI scaling for high-resolution displays. if (e.Node is not null) { float dpiScale = (float)DeviceDpi / ScaleHelper.OneHundredPercentLogicalDpi; @@ -2601,9 +2599,9 @@ private LRESULT TvnBeginLabelEdit(NMTVDISPINFOW nmtvdi) e.Node.Bounds.Y, e.Node.Bounds.Width + (int)(dpiScale * 10), e.Node.Bounds.Height + 2, - SET_WINDOW_POS_FLAGS.SWP_NOZORDER | - SET_WINDOW_POS_FLAGS.SWP_FRAMECHANGED | - SET_WINDOW_POS_FLAGS.SWP_SHOWWINDOW); + SET_WINDOW_POS_FLAGS.SWP_NOZORDER + | SET_WINDOW_POS_FLAGS.SWP_FRAMECHANGED + | SET_WINDOW_POS_FLAGS.SWP_SHOWWINDOW); } })); } @@ -2760,6 +2758,9 @@ private unsafe void CustomDraw(ref Message m) // as background color. if (_drawMode == TreeViewDrawMode.OwnerDrawText) { + // When the node is being edited, + // set the text color and background color to the same color so that the text is hidden + // goto default to ensure the default drawing behavior is executed instead of a custom drawing. if (node is { IsEditing: true }) { nmtvcd->clrText = ColorTranslator.ToWin32(BackColor); @@ -2883,7 +2884,7 @@ private unsafe void CustomDraw(ref Message m) int invertedX = Width - bounds.X - textSize.Width - borderWidth * 2; // Subtract the scroll bar width when the scroll bar appears. - if (Height - borderWidth * 2 < PreferredHeight) + if (Height - borderWidth * 2 < CalculatePreferredHeight()) { float dpiScale = (float)DeviceDpi / (float)ScaleHelper.InitialSystemDpi; invertedX -= (int)(SystemInformation.VerticalScrollBarWidth * Math.Round(dpiScale, 2)); @@ -2936,33 +2937,37 @@ private unsafe void CustomDraw(ref Message m) } } - private int PreferredHeight + /// + /// Calculate the preferred height of the entire tree. + /// + private int CalculatePreferredHeight() { - get + // Nested method to recursively calculate the height of each node and its child nodes. + static int CountExpandedNodes(TreeNode node) { - int height = 0; - foreach (TreeNode node in Nodes) + int count = 1; // Count the current node + + // If the node is expanded, recursively calculate count its child nodes. + if (node.IsExpanded) { - height += GetNodeHeight(node); + foreach (TreeNode childNode in node.Nodes) + { + count += CountExpandedNodes(childNode); + } } - return height; + return count; } - } - - private int GetNodeHeight(TreeNode node) - { - int height = ItemHeight; - if (node.IsExpanded) + int expandedNodeCount = 0; + // Iterate through all top-level nodes to count the total number of expanded nodes. + foreach (TreeNode node in Nodes) { - foreach (TreeNode childNode in node.Nodes) - { - height += GetNodeHeight(childNode); - } + expandedNodeCount += CountExpandedNodes(node); } - return height; + // Calculate the total height based on the number of expanded nodes. + return expandedNodeCount * ItemHeight; } ///