From e81cf31e291eed52ee665e2e4985b43c4934b542 Mon Sep 17 00:00:00 2001 From: "Simon Zhao (BEYONDSOFT CONSULTING INC)" Date: Thu, 11 Jun 2026 10:55:58 +0800 Subject: [PATCH 1/2] Fix issue 14624: [PropertyGrid] FolderNameEditor does not use the modern FolderBrowserDialog --- .../src/PublicAPI.Shipped.txt | 2 +- .../Windows/Forms/Design/FolderNameEditor.cs | 16 ++-- .../Forms/Design/InitialDirectoryEditor.cs | 4 +- .../Forms/Design/SelectedPathEditor.cs | 4 +- .../Forms/Design/FolderNameEditorTests.cs | 73 ++++--------------- .../FolderNameEditorTests.cs | 6 +- 6 files changed, 30 insertions(+), 75 deletions(-) diff --git a/src/System.Windows.Forms.Design/src/PublicAPI.Shipped.txt b/src/System.Windows.Forms.Design/src/PublicAPI.Shipped.txt index 13bdf66e43a..df83d2bef32 100644 --- a/src/System.Windows.Forms.Design/src/PublicAPI.Shipped.txt +++ b/src/System.Windows.Forms.Design/src/PublicAPI.Shipped.txt @@ -1278,7 +1278,7 @@ virtual System.Windows.Forms.Design.DesignerOptions.UseSmartTags.set -> void virtual System.Windows.Forms.Design.DesignerOptions.UseSnapLines.get -> bool virtual System.Windows.Forms.Design.DesignerOptions.UseSnapLines.set -> void virtual System.Windows.Forms.Design.FileNameEditor.InitializeDialog(System.Windows.Forms.OpenFileDialog! openFileDialog) -> void -virtual System.Windows.Forms.Design.FolderNameEditor.InitializeDialog(System.Windows.Forms.Design.FolderNameEditor.FolderBrowser! folderBrowser) -> void +virtual System.Windows.Forms.Design.FolderNameEditor.InitializeDialog(System.Windows.Forms.FolderBrowserDialog! folderBrowserDialog) -> void virtual System.Windows.Forms.Design.MaskDescriptor.Culture.get -> System.Globalization.CultureInfo! virtual System.Windows.Forms.Design.ParentControlDesigner.AllowControlLasso.get -> bool virtual System.Windows.Forms.Design.ParentControlDesigner.AllowGenericDragBox.get -> bool diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/FolderNameEditor.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/FolderNameEditor.cs index 49c1d937950..0b2ec14f338 100644 --- a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/FolderNameEditor.cs +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/FolderNameEditor.cs @@ -12,19 +12,21 @@ namespace System.Windows.Forms.Design; [CLSCompliant(false)] public partial class FolderNameEditor : UITypeEditor { - private FolderBrowser? _folderBrowser; + private FolderBrowserDialog? _folderBrowserDialog; public override object? EditValue(ITypeDescriptorContext? context, IServiceProvider provider, object? value) { - if (_folderBrowser is null) + if (_folderBrowserDialog is null) { - _folderBrowser = new FolderBrowser(); - InitializeDialog(_folderBrowser); + _folderBrowserDialog = new FolderBrowserDialog(); + InitializeDialog(_folderBrowserDialog); } - if (_folderBrowser.ShowDialog() == DialogResult.OK) + _folderBrowserDialog.SelectedPath = value as string ?? string.Empty; + + if (_folderBrowserDialog.ShowDialog() == DialogResult.OK) { - return _folderBrowser.DirectoryPath; + return _folderBrowserDialog.SelectedPath; } return value; @@ -37,7 +39,7 @@ public partial class FolderNameEditor : UITypeEditor /// Initializes the folder browser dialog when it is created. This gives you an opportunity /// to configure the dialog as you please. The default implementation provides a generic folder browser. /// - protected virtual void InitializeDialog(FolderBrowser folderBrowser) + protected virtual void InitializeDialog(FolderBrowserDialog folderBrowserDialog) { } } diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/InitialDirectoryEditor.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/InitialDirectoryEditor.cs index 00ab84f5c3c..03d9e74beec 100644 --- a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/InitialDirectoryEditor.cs +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/InitialDirectoryEditor.cs @@ -8,8 +8,8 @@ namespace System.Windows.Forms.Design; /// internal class InitialDirectoryEditor : FolderNameEditor { - protected override void InitializeDialog(FolderBrowser folderBrowser) + protected override void InitializeDialog(FolderBrowserDialog folderBrowserDialog) { - folderBrowser.Description = SR.InitialDirectoryEditorLabel; + folderBrowserDialog.Description = SR.InitialDirectoryEditorLabel; } } diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/SelectedPathEditor.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/SelectedPathEditor.cs index 037d9f44c42..350805e75e6 100644 --- a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/SelectedPathEditor.cs +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/SelectedPathEditor.cs @@ -8,8 +8,8 @@ namespace System.Windows.Forms.Design; /// internal class SelectedPathEditor : FolderNameEditor { - protected override void InitializeDialog(FolderBrowser folderBrowser) + protected override void InitializeDialog(FolderBrowserDialog folderBrowserDialog) { - folderBrowser.Description = SR.SelectedPathEditorLabel; + folderBrowserDialog.Description = SR.SelectedPathEditorLabel; } } diff --git a/src/System.Windows.Forms.Design/tests/UnitTests/System/Windows/Forms/Design/FolderNameEditorTests.cs b/src/System.Windows.Forms.Design/tests/UnitTests/System/Windows/Forms/Design/FolderNameEditorTests.cs index cfb9ee97af3..1b7bc3c60cb 100644 --- a/src/System.Windows.Forms.Design/tests/UnitTests/System/Windows/Forms/Design/FolderNameEditorTests.cs +++ b/src/System.Windows.Forms.Design/tests/UnitTests/System/Windows/Forms/Design/FolderNameEditorTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #nullable disable @@ -38,71 +38,24 @@ public void FolderNameEditor_GetPaintValueSupported_Invoke_ReturnsFalse(ITypeDes public void FolderNameEditor_InitializeDialog_Invoke_Nop() { SubFolderNameEditor editor = new(); - editor.InitializeDialog(); - } - - public class FolderBrowserTests : FolderNameEditor - { - [Fact] - public void FolderBrowser_Ctor_Default() - { - FolderBrowser browser = new(); - Assert.Empty(browser.DirectoryPath); - Assert.Empty(browser.Description); - Assert.Equal(FolderBrowserStyles.RestrictToFilesystem, browser.Style); - Assert.Equal(FolderBrowserFolder.Desktop, browser.StartLocation); - } - - [Theory] - [NormalizedStringData] - public void FolderBrowser_Description_Set_GetReturnsExpected(string value, string expected) - { - FolderBrowser browser = new() - { - Description = value - }; - Assert.Equal(expected, browser.Description); - - // Set same. - browser.Description = value; - Assert.Equal(expected, browser.Description); - } - - [Theory] - [EnumData] - [InvalidEnumData] - protected void FolderBrowser_StartLocation_Set_GetReturnsExpected(FolderBrowserFolder value) - { - FolderBrowser browser = new() - { - StartLocation = value - }; - Assert.Equal(value, browser.StartLocation); + using FolderBrowserDialog dialog = new(); - // Set same. - browser.StartLocation = value; - Assert.Equal(value, browser.StartLocation); - } + // The base implementation is intentionally a no-op; invoking it should not + // throw and should leave the dialog's defaults untouched. + string originalSelectedPath = dialog.SelectedPath; + string originalDescription = dialog.Description; + Environment.SpecialFolder originalRootFolder = dialog.RootFolder; - [Theory] - [EnumData] - [InvalidEnumData] - protected void FolderBrowser_Style_Set_GetReturnsExpected(FolderBrowserStyles value) - { - FolderBrowser browser = new() - { - Style = value - }; - Assert.Equal(value, browser.Style); + editor.InitializeDialog(dialog); - // Set same. - browser.Style = value; - Assert.Equal(value, browser.Style); - } + Assert.Equal(originalSelectedPath, dialog.SelectedPath); + Assert.Equal(originalDescription, dialog.Description); + Assert.Equal(originalRootFolder, dialog.RootFolder); } private class SubFolderNameEditor : FolderNameEditor { - public void InitializeDialog() => base.InitializeDialog(null); + public new void InitializeDialog(FolderBrowserDialog folderBrowserDialog) => + base.InitializeDialog(folderBrowserDialog); } } diff --git a/src/test/integration/UIIntegrationTests/FolderNameEditorTests.cs b/src/test/integration/UIIntegrationTests/FolderNameEditorTests.cs index d39106f790f..f5ac2e2d4d4 100644 --- a/src/test/integration/UIIntegrationTests/FolderNameEditorTests.cs +++ b/src/test/integration/UIIntegrationTests/FolderNameEditorTests.cs @@ -30,7 +30,7 @@ public void FolderNameEditor_EditValue_ReturnsExpected() private class TestFolderNameEditor : FolderNameEditor { - private FolderBrowser? _folderBrowser; + private FolderBrowserDialog? _folderBrowser; public override object? EditValue(ITypeDescriptorContext? context, IServiceProvider provider, object? value) { @@ -38,13 +38,13 @@ private class TestFolderNameEditor : FolderNameEditor if (_folderBrowser is null) { - _folderBrowser = new FolderBrowser(); + _folderBrowser = new FolderBrowserDialog(); InitializeDialog(_folderBrowser); } if (_folderBrowser.ShowDialog(dialogOwnerForm) == DialogResult.OK) { - return _folderBrowser.DirectoryPath; + return _folderBrowser.SelectedPath; } return value; From 1132aa6a917198e9111f0268586348ce8a92fe4b Mon Sep 17 00:00:00 2001 From: "Simon Zhao (BEYONDSOFT CONSULTING INC)" Date: Mon, 15 Jun 2026 13:57:09 +0800 Subject: [PATCH 2/2] Handle feedback --- .../src/PublicAPI.Shipped.txt | 2 +- .../src/PublicAPI.Unshipped.txt | 1 + .../Windows/Forms/Design/FolderNameEditor.cs | 28 ++++++++++++------- .../FolderNameEditorTests.cs | 12 ++++---- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/System.Windows.Forms.Design/src/PublicAPI.Shipped.txt b/src/System.Windows.Forms.Design/src/PublicAPI.Shipped.txt index df83d2bef32..13bdf66e43a 100644 --- a/src/System.Windows.Forms.Design/src/PublicAPI.Shipped.txt +++ b/src/System.Windows.Forms.Design/src/PublicAPI.Shipped.txt @@ -1278,7 +1278,7 @@ virtual System.Windows.Forms.Design.DesignerOptions.UseSmartTags.set -> void virtual System.Windows.Forms.Design.DesignerOptions.UseSnapLines.get -> bool virtual System.Windows.Forms.Design.DesignerOptions.UseSnapLines.set -> void virtual System.Windows.Forms.Design.FileNameEditor.InitializeDialog(System.Windows.Forms.OpenFileDialog! openFileDialog) -> void -virtual System.Windows.Forms.Design.FolderNameEditor.InitializeDialog(System.Windows.Forms.FolderBrowserDialog! folderBrowserDialog) -> void +virtual System.Windows.Forms.Design.FolderNameEditor.InitializeDialog(System.Windows.Forms.Design.FolderNameEditor.FolderBrowser! folderBrowser) -> void virtual System.Windows.Forms.Design.MaskDescriptor.Culture.get -> System.Globalization.CultureInfo! virtual System.Windows.Forms.Design.ParentControlDesigner.AllowControlLasso.get -> bool virtual System.Windows.Forms.Design.ParentControlDesigner.AllowGenericDragBox.get -> bool diff --git a/src/System.Windows.Forms.Design/src/PublicAPI.Unshipped.txt b/src/System.Windows.Forms.Design/src/PublicAPI.Unshipped.txt index e69de29bb2d..d5929c4099f 100644 --- a/src/System.Windows.Forms.Design/src/PublicAPI.Unshipped.txt +++ b/src/System.Windows.Forms.Design/src/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ +virtual System.Windows.Forms.Design.FolderNameEditor.InitializeDialog(System.Windows.Forms.FolderBrowserDialog! folderBrowserDialog) -> void diff --git a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/FolderNameEditor.cs b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/FolderNameEditor.cs index 0b2ec14f338..aff82a81648 100644 --- a/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/FolderNameEditor.cs +++ b/src/System.Windows.Forms.Design/src/System/Windows/Forms/Design/FolderNameEditor.cs @@ -12,21 +12,19 @@ namespace System.Windows.Forms.Design; [CLSCompliant(false)] public partial class FolderNameEditor : UITypeEditor { - private FolderBrowserDialog? _folderBrowserDialog; - public override object? EditValue(ITypeDescriptorContext? context, IServiceProvider provider, object? value) { - if (_folderBrowserDialog is null) - { - _folderBrowserDialog = new FolderBrowserDialog(); - InitializeDialog(_folderBrowserDialog); - } + // The dialog is created locally and disposed at the end of the call so its + // native resources (Component/CommonDialog state) are released, and no stale + // configuration leaks between successive invocations of EditValue. + using FolderBrowserDialog folderBrowserDialog = new(); + InitializeDialog(folderBrowserDialog); - _folderBrowserDialog.SelectedPath = value as string ?? string.Empty; + folderBrowserDialog.SelectedPath = value as string ?? string.Empty; - if (_folderBrowserDialog.ShowDialog() == DialogResult.OK) + if (folderBrowserDialog.ShowDialog() == DialogResult.OK) { - return _folderBrowserDialog.SelectedPath; + return folderBrowserDialog.SelectedPath; } return value; @@ -35,6 +33,16 @@ public partial class FolderNameEditor : UITypeEditor /// public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext? context) => UITypeEditorEditStyle.Modal; + /// + /// Initializes the folder browser dialog when it is created. This gives you an opportunity + /// to configure the dialog as you please. The default implementation provides a generic folder browser. + /// + [Obsolete] + [EditorBrowsable(EditorBrowsableState.Never)] + protected virtual void InitializeDialog(FolderBrowser folderBrowser) + { + } + /// /// Initializes the folder browser dialog when it is created. This gives you an opportunity /// to configure the dialog as you please. The default implementation provides a generic folder browser. diff --git a/src/test/integration/UIIntegrationTests/FolderNameEditorTests.cs b/src/test/integration/UIIntegrationTests/FolderNameEditorTests.cs index f5ac2e2d4d4..4c2cf105256 100644 --- a/src/test/integration/UIIntegrationTests/FolderNameEditorTests.cs +++ b/src/test/integration/UIIntegrationTests/FolderNameEditorTests.cs @@ -30,21 +30,21 @@ public void FolderNameEditor_EditValue_ReturnsExpected() private class TestFolderNameEditor : FolderNameEditor { - private FolderBrowserDialog? _folderBrowser; + private FolderBrowserDialog? _folderBrowserDialog; public override object? EditValue(ITypeDescriptorContext? context, IServiceProvider provider, object? value) { using DialogHostForm dialogOwnerForm = new(); - if (_folderBrowser is null) + if (_folderBrowserDialog is null) { - _folderBrowser = new FolderBrowserDialog(); - InitializeDialog(_folderBrowser); + _folderBrowserDialog = new FolderBrowserDialog(); + InitializeDialog(_folderBrowserDialog); } - if (_folderBrowser.ShowDialog(dialogOwnerForm) == DialogResult.OK) + if (_folderBrowserDialog.ShowDialog(dialogOwnerForm) == DialogResult.OK) { - return _folderBrowser.SelectedPath; + return _folderBrowserDialog.SelectedPath = value as string ?? string.Empty; } return value;