diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
new file mode 100644
index 00000000000..36c4a4a3def
--- /dev/null
+++ b/.github/workflows/build-test.yml
@@ -0,0 +1,51 @@
+name: Build and Test
+
+on:
+ push:
+ branches: [ master, wtg-net10 ]
+ pull_request:
+ branches: [ master, wtg-net10 ]
+ workflow_dispatch:
+
+jobs:
+ build:
+ name: Build - ${{ matrix.configuration }}
+
+ strategy:
+ matrix:
+ configuration: [ Debug ]
+
+ runs-on: windows-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: |
+ 10.0.x
+
+ - name: Install dependencies
+ run: dotnet restore WinformsLegacy.sln
+
+ - name: Build
+ run: dotnet build WinformsLegacy.sln --configuration ${{ matrix.configuration }} --no-restore
+
+ - name: Test
+ if: matrix.configuration == 'Debug'
+ run: >
+ dotnet test
+ src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/System.Windows.Forms.Legacy.Tests.csproj
+ --configuration Debug
+ --no-build
+ --logger trx
+ --results-directory TestResults
+
+ - name: Upload packages
+ if: matrix.configuration == 'Release'
+ uses: actions/upload-artifact@v4
+ with:
+ name: package
+ path: Bin/Release/**/*.nupkg
+ retention-days: 5
diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml
new file mode 100644
index 00000000000..78ca43f09a3
--- /dev/null
+++ b/.github/workflows/create-release.yml
@@ -0,0 +1,64 @@
+name: Create Release
+
+on:
+ workflow_dispatch:
+ inputs:
+ version:
+ description: Package version to publish, for example 1.2.3 or 1.2.3-preview.1
+ required: true
+ type: string
+
+env:
+ PACKAGE_PATH: Bin/Release/**/*.nupkg
+
+jobs:
+ release:
+ name: Build - Release
+
+ runs-on: windows-latest
+
+ steps:
+
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: |
+ 10.0.x
+
+ - name: Validate version
+ shell: pwsh
+ run: |
+ $version = '${{ inputs.version }}'
+
+ if ($version -notmatch '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$') {
+ throw "The version '$version' is not a valid SemVer 2.0.0 version."
+ }
+
+ - name: Install dependencies
+ run: dotnet restore WinformsLegacy.sln
+
+ # Pacakge is created on build
+ - name: Build
+ run: dotnet build WinformsLegacy.sln --configuration Release --no-restore /p:PackageVersion=${{ inputs.version }}
+
+ # Upload package as an atrifact to the GitHub action
+ - name: Upload packages artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: package
+ path: ${{ env.PACKAGE_PATH }}
+ retention-days: 5
+
+ # Create a new release and upload the package to the release
+ - name: Create GitHub Release
+ uses: softprops/action-gh-release@v1
+ with:
+ files: ${{ env.PACKAGE_PATH }}
+ name: ${{ inputs.version }}
+ tag_name: ${{ inputs.version }}
+
+ - name: Push NuGet Package to NuGet Gallery
+ run: dotnet nuget push ${{ env.PACKAGE_PATH }} --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
diff --git a/Directory.Build.targets b/Directory.Build.targets
index 85d104b510a..6f9674f797f 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -91,7 +91,9 @@
- $(MicrosoftNETCoreAppRefPackageVersion)
+
+
+ $(MicrosoftNETCoreAppRefPackageVersion)
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 233aeb64347..5f1f5b0243a 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -34,6 +34,7 @@
+
diff --git a/WinformsLegacy.sln b/WinformsLegacy.sln
new file mode 100644
index 00000000000..0301a00b9e5
--- /dev/null
+++ b/WinformsLegacy.sln
@@ -0,0 +1,140 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 18
+VisualStudioVersion = 18.3.11520.95
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Windows.Forms", "src\System.Windows.Forms\System.Windows.Forms.csproj", "{0D23A41B-2626-4703-9E4A-87C07F69B0B2}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Windows.Forms.Design", "src\System.Windows.Forms.Design\src\System.Windows.Forms.Design.csproj", "{61D06BBD-B0CF-4CE1-9139-1CC4B82F0F9B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Windows.Forms.Primitives", "src\System.Windows.Forms.Primitives\src\System.Windows.Forms.Primitives.csproj", "{90B27178-F535-43F7-886E-0AB75203F246}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Windows.Forms.PrivateSourceGenerators", "src\System.Windows.Forms.PrivateSourceGenerators\src\System.Windows.Forms.PrivateSourceGenerators.csproj", "{3BB220A9-7BC9-4D45-9DC7-B5A1D659B478}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Windows.Forms.Legacy.Demo", "src\System.Windows.Forms.Legacy\System.Windows.Forms.Legacy.Demo\System.Windows.Forms.Legacy.Demo.csproj", "{313D5215-7E76-4864-AEBF-0C15819D0B34}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Windows.Forms.Legacy.Tests", "src\System.Windows.Forms.Legacy\System.Windows.Forms.Legacy.Tests\System.Windows.Forms.Legacy.Tests.csproj", "{EAF018D1-D51E-4F67-BC7F-6F7F5702C411}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "winforms", "winforms", "{FFD82D6B-78FA-4D85-A19D-FF0A12FC47F2}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|arm64 = Debug|arm64
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|arm64 = Release|arm64
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {0D23A41B-2626-4703-9E4A-87C07F69B0B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0D23A41B-2626-4703-9E4A-87C07F69B0B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0D23A41B-2626-4703-9E4A-87C07F69B0B2}.Debug|arm64.ActiveCfg = Debug|Any CPU
+ {0D23A41B-2626-4703-9E4A-87C07F69B0B2}.Debug|arm64.Build.0 = Debug|Any CPU
+ {0D23A41B-2626-4703-9E4A-87C07F69B0B2}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0D23A41B-2626-4703-9E4A-87C07F69B0B2}.Debug|x64.Build.0 = Debug|Any CPU
+ {0D23A41B-2626-4703-9E4A-87C07F69B0B2}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0D23A41B-2626-4703-9E4A-87C07F69B0B2}.Debug|x86.Build.0 = Debug|Any CPU
+ {0D23A41B-2626-4703-9E4A-87C07F69B0B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0D23A41B-2626-4703-9E4A-87C07F69B0B2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0D23A41B-2626-4703-9E4A-87C07F69B0B2}.Release|arm64.ActiveCfg = Release|Any CPU
+ {0D23A41B-2626-4703-9E4A-87C07F69B0B2}.Release|arm64.Build.0 = Release|Any CPU
+ {0D23A41B-2626-4703-9E4A-87C07F69B0B2}.Release|x64.ActiveCfg = Release|Any CPU
+ {0D23A41B-2626-4703-9E4A-87C07F69B0B2}.Release|x64.Build.0 = Release|Any CPU
+ {0D23A41B-2626-4703-9E4A-87C07F69B0B2}.Release|x86.ActiveCfg = Release|Any CPU
+ {0D23A41B-2626-4703-9E4A-87C07F69B0B2}.Release|x86.Build.0 = Release|Any CPU
+ {61D06BBD-B0CF-4CE1-9139-1CC4B82F0F9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {61D06BBD-B0CF-4CE1-9139-1CC4B82F0F9B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {61D06BBD-B0CF-4CE1-9139-1CC4B82F0F9B}.Debug|arm64.ActiveCfg = Debug|Any CPU
+ {61D06BBD-B0CF-4CE1-9139-1CC4B82F0F9B}.Debug|arm64.Build.0 = Debug|Any CPU
+ {61D06BBD-B0CF-4CE1-9139-1CC4B82F0F9B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {61D06BBD-B0CF-4CE1-9139-1CC4B82F0F9B}.Debug|x64.Build.0 = Debug|Any CPU
+ {61D06BBD-B0CF-4CE1-9139-1CC4B82F0F9B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {61D06BBD-B0CF-4CE1-9139-1CC4B82F0F9B}.Debug|x86.Build.0 = Debug|Any CPU
+ {61D06BBD-B0CF-4CE1-9139-1CC4B82F0F9B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {61D06BBD-B0CF-4CE1-9139-1CC4B82F0F9B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {61D06BBD-B0CF-4CE1-9139-1CC4B82F0F9B}.Release|arm64.ActiveCfg = Release|Any CPU
+ {61D06BBD-B0CF-4CE1-9139-1CC4B82F0F9B}.Release|arm64.Build.0 = Release|Any CPU
+ {61D06BBD-B0CF-4CE1-9139-1CC4B82F0F9B}.Release|x64.ActiveCfg = Release|Any CPU
+ {61D06BBD-B0CF-4CE1-9139-1CC4B82F0F9B}.Release|x64.Build.0 = Release|Any CPU
+ {61D06BBD-B0CF-4CE1-9139-1CC4B82F0F9B}.Release|x86.ActiveCfg = Release|Any CPU
+ {61D06BBD-B0CF-4CE1-9139-1CC4B82F0F9B}.Release|x86.Build.0 = Release|Any CPU
+ {90B27178-F535-43F7-886E-0AB75203F246}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {90B27178-F535-43F7-886E-0AB75203F246}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {90B27178-F535-43F7-886E-0AB75203F246}.Debug|arm64.ActiveCfg = Debug|Any CPU
+ {90B27178-F535-43F7-886E-0AB75203F246}.Debug|arm64.Build.0 = Debug|Any CPU
+ {90B27178-F535-43F7-886E-0AB75203F246}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {90B27178-F535-43F7-886E-0AB75203F246}.Debug|x64.Build.0 = Debug|Any CPU
+ {90B27178-F535-43F7-886E-0AB75203F246}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {90B27178-F535-43F7-886E-0AB75203F246}.Debug|x86.Build.0 = Debug|Any CPU
+ {90B27178-F535-43F7-886E-0AB75203F246}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {90B27178-F535-43F7-886E-0AB75203F246}.Release|Any CPU.Build.0 = Release|Any CPU
+ {90B27178-F535-43F7-886E-0AB75203F246}.Release|arm64.ActiveCfg = Release|Any CPU
+ {90B27178-F535-43F7-886E-0AB75203F246}.Release|arm64.Build.0 = Release|Any CPU
+ {90B27178-F535-43F7-886E-0AB75203F246}.Release|x64.ActiveCfg = Release|Any CPU
+ {90B27178-F535-43F7-886E-0AB75203F246}.Release|x64.Build.0 = Release|Any CPU
+ {90B27178-F535-43F7-886E-0AB75203F246}.Release|x86.ActiveCfg = Release|Any CPU
+ {90B27178-F535-43F7-886E-0AB75203F246}.Release|x86.Build.0 = Release|Any CPU
+ {3BB220A9-7BC9-4D45-9DC7-B5A1D659B478}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3BB220A9-7BC9-4D45-9DC7-B5A1D659B478}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3BB220A9-7BC9-4D45-9DC7-B5A1D659B478}.Debug|arm64.ActiveCfg = Debug|Any CPU
+ {3BB220A9-7BC9-4D45-9DC7-B5A1D659B478}.Debug|arm64.Build.0 = Debug|Any CPU
+ {3BB220A9-7BC9-4D45-9DC7-B5A1D659B478}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {3BB220A9-7BC9-4D45-9DC7-B5A1D659B478}.Debug|x64.Build.0 = Debug|Any CPU
+ {3BB220A9-7BC9-4D45-9DC7-B5A1D659B478}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3BB220A9-7BC9-4D45-9DC7-B5A1D659B478}.Debug|x86.Build.0 = Debug|Any CPU
+ {3BB220A9-7BC9-4D45-9DC7-B5A1D659B478}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3BB220A9-7BC9-4D45-9DC7-B5A1D659B478}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3BB220A9-7BC9-4D45-9DC7-B5A1D659B478}.Release|arm64.ActiveCfg = Release|Any CPU
+ {3BB220A9-7BC9-4D45-9DC7-B5A1D659B478}.Release|arm64.Build.0 = Release|Any CPU
+ {3BB220A9-7BC9-4D45-9DC7-B5A1D659B478}.Release|x64.ActiveCfg = Release|Any CPU
+ {3BB220A9-7BC9-4D45-9DC7-B5A1D659B478}.Release|x64.Build.0 = Release|Any CPU
+ {3BB220A9-7BC9-4D45-9DC7-B5A1D659B478}.Release|x86.ActiveCfg = Release|Any CPU
+ {3BB220A9-7BC9-4D45-9DC7-B5A1D659B478}.Release|x86.Build.0 = Release|Any CPU
+ {313D5215-7E76-4864-AEBF-0C15819D0B34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {313D5215-7E76-4864-AEBF-0C15819D0B34}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {313D5215-7E76-4864-AEBF-0C15819D0B34}.Debug|arm64.ActiveCfg = Debug|Any CPU
+ {313D5215-7E76-4864-AEBF-0C15819D0B34}.Debug|arm64.Build.0 = Debug|Any CPU
+ {313D5215-7E76-4864-AEBF-0C15819D0B34}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {313D5215-7E76-4864-AEBF-0C15819D0B34}.Debug|x64.Build.0 = Debug|Any CPU
+ {313D5215-7E76-4864-AEBF-0C15819D0B34}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {313D5215-7E76-4864-AEBF-0C15819D0B34}.Debug|x86.Build.0 = Debug|Any CPU
+ {313D5215-7E76-4864-AEBF-0C15819D0B34}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {313D5215-7E76-4864-AEBF-0C15819D0B34}.Release|Any CPU.Build.0 = Release|Any CPU
+ {313D5215-7E76-4864-AEBF-0C15819D0B34}.Release|arm64.ActiveCfg = Release|Any CPU
+ {313D5215-7E76-4864-AEBF-0C15819D0B34}.Release|arm64.Build.0 = Release|Any CPU
+ {313D5215-7E76-4864-AEBF-0C15819D0B34}.Release|x64.ActiveCfg = Release|Any CPU
+ {313D5215-7E76-4864-AEBF-0C15819D0B34}.Release|x64.Build.0 = Release|Any CPU
+ {313D5215-7E76-4864-AEBF-0C15819D0B34}.Release|x86.ActiveCfg = Release|Any CPU
+ {313D5215-7E76-4864-AEBF-0C15819D0B34}.Release|x86.Build.0 = Release|Any CPU
+ {EAF018D1-D51E-4F67-BC7F-6F7F5702C411}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EAF018D1-D51E-4F67-BC7F-6F7F5702C411}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EAF018D1-D51E-4F67-BC7F-6F7F5702C411}.Debug|arm64.ActiveCfg = Debug|Any CPU
+ {EAF018D1-D51E-4F67-BC7F-6F7F5702C411}.Debug|arm64.Build.0 = Debug|Any CPU
+ {EAF018D1-D51E-4F67-BC7F-6F7F5702C411}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {EAF018D1-D51E-4F67-BC7F-6F7F5702C411}.Debug|x64.Build.0 = Debug|Any CPU
+ {EAF018D1-D51E-4F67-BC7F-6F7F5702C411}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {EAF018D1-D51E-4F67-BC7F-6F7F5702C411}.Debug|x86.Build.0 = Debug|Any CPU
+ {EAF018D1-D51E-4F67-BC7F-6F7F5702C411}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EAF018D1-D51E-4F67-BC7F-6F7F5702C411}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EAF018D1-D51E-4F67-BC7F-6F7F5702C411}.Release|arm64.ActiveCfg = Release|Any CPU
+ {EAF018D1-D51E-4F67-BC7F-6F7F5702C411}.Release|arm64.Build.0 = Release|Any CPU
+ {EAF018D1-D51E-4F67-BC7F-6F7F5702C411}.Release|x64.ActiveCfg = Release|Any CPU
+ {EAF018D1-D51E-4F67-BC7F-6F7F5702C411}.Release|x64.Build.0 = Release|Any CPU
+ {EAF018D1-D51E-4F67-BC7F-6F7F5702C411}.Release|x86.ActiveCfg = Release|Any CPU
+ {EAF018D1-D51E-4F67-BC7F-6F7F5702C411}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {0D23A41B-2626-4703-9E4A-87C07F69B0B2} = {FFD82D6B-78FA-4D85-A19D-FF0A12FC47F2}
+ {61D06BBD-B0CF-4CE1-9139-1CC4B82F0F9B} = {FFD82D6B-78FA-4D85-A19D-FF0A12FC47F2}
+ {90B27178-F535-43F7-886E-0AB75203F246} = {FFD82D6B-78FA-4D85-A19D-FF0A12FC47F2}
+ {3BB220A9-7BC9-4D45-9DC7-B5A1D659B478} = {FFD82D6B-78FA-4D85-A19D-FF0A12FC47F2}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {7B1B0433-F612-4E5A-BE7E-FCF5B9F6E136}
+ EndGlobalSection
+EndGlobal
diff --git a/eng/Versions.props b/eng/Versions.props
index 6f47e8b3e52..404337fff8b 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -2,9 +2,9 @@
- 10
+ 0
0
- 6
+ 25
servicing
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/DataGridForm.Designer.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/DataGridForm.Designer.cs
new file mode 100644
index 00000000000..73eb97fd33a
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/DataGridForm.Designer.cs
@@ -0,0 +1,188 @@
+using System.Drawing;
+using System.Windows.Forms;
+
+namespace Demo
+{
+ partial class DataGridForm
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+ private GroupBox classicFeaturesGroupBox;
+ private Label classicFeaturesLabel;
+ private GroupBox classicOptionsGroupBox;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+
+ private void InitializeStatusBar()
+ {
+ StatusBar mainStatusBar = new StatusBar();
+
+ mainStatusBar.Dock = DockStyle.Bottom;
+ mainStatusBar.Height = 70;
+
+ StatusBarPanel statusPanel = new StatusBarPanel();
+ StatusBarPanel datetimePanel = new StatusBarPanel();
+
+ statusPanel.BorderStyle = StatusBarPanelBorderStyle.Sunken;
+ statusPanel.Text = "Status Bar Example";
+ statusPanel.AutoSize = StatusBarPanelAutoSize.Spring;
+ mainStatusBar.Panels.Add(statusPanel);
+
+ datetimePanel.BorderStyle = StatusBarPanelBorderStyle.Raised;
+ datetimePanel.Text = System.DateTime.Today.ToLongDateString();
+ datetimePanel.AutoSize = StatusBarPanelAutoSize.Contents;
+ mainStatusBar.Panels.Add(datetimePanel);
+
+ mainStatusBar.ShowPanels = true;
+
+ Controls.Add(mainStatusBar);
+ }
+
+ private void InitializeDataGrid()
+ {
+ button1 = new System.Windows.Forms.Button();
+ button2 = new System.Windows.Forms.Button();
+ button3 = new System.Windows.Forms.Button();
+ button4 = new System.Windows.Forms.Button();
+ myDataGrid = new DataGrid();
+ featureListBox = new System.Windows.Forms.ListBox();
+ selectionSummaryLabel = new System.Windows.Forms.Label();
+ captionVisibleCheckBox = new System.Windows.Forms.CheckBox();
+ parentRowsVisibleCheckBox = new System.Windows.Forms.CheckBox();
+ rowHeadersVisibleCheckBox = new System.Windows.Forms.CheckBox();
+ readOnlyCheckBox = new System.Windows.Forms.CheckBox();
+ allowNavigationCheckBox = new System.Windows.Forms.CheckBox();
+ classicOptionsHintLabel = new System.Windows.Forms.Label();
+
+ button1.Location = new Point(24, 60);
+ button1.Size = new Size(160, 30);
+ button1.Text = "Change Appearance";
+ button1.Click += new System.EventHandler(Button1_Click);
+
+ button2.Location = new Point(192, 60);
+ button2.Size = new Size(160, 30);
+ button2.Text = "Get Binding Manager";
+ button2.Click += new System.EventHandler(Button2_Click);
+
+ button3.Location = new Point(360, 60);
+ button3.Size = new Size(140, 30);
+ button3.Text = "Use Flat Mode";
+ button3.Click += new System.EventHandler(Button3_Click);
+
+ button4.Location = new Point(508, 60);
+ button4.Size = new Size(116, 30);
+ button4.Text = "Next Customer";
+ button4.Click += new System.EventHandler(Button4_Click);
+
+ myDataGrid.Location = new Point(24, 100);
+ myDataGrid.Size = new Size(620, 440);
+ myDataGrid.CaptionText = "Microsoft DataGrid Control";
+ myDataGrid.MouseUp += new MouseEventHandler(Grid_MouseUp);
+
+ selectionSummaryLabel.BorderStyle = BorderStyle.FixedSingle;
+ selectionSummaryLabel.Location = new Point(24, 548);
+ selectionSummaryLabel.Size = new Size(620, 52);
+ selectionSummaryLabel.Text = "Classic demo summary will appear after the grid is initialized.";
+ selectionSummaryLabel.TextAlign = ContentAlignment.MiddleLeft;
+
+ featureListBox.BorderStyle = BorderStyle.None;
+ featureListBox.Dock = DockStyle.Fill;
+ featureListBox.IntegralHeight = false;
+
+ classicFeaturesGroupBox = new GroupBox();
+ classicFeaturesLabel = new Label();
+ classicOptionsGroupBox = new GroupBox();
+ classicFeaturesGroupBox.Location = new Point(668, 100);
+ classicFeaturesGroupBox.Size = new Size(320, 252);
+ classicFeaturesGroupBox.Text = "Basic DataGrid Features";
+
+ classicFeaturesLabel.Dock = DockStyle.Top;
+ classicFeaturesLabel.Height = 54;
+ classicFeaturesLabel.Text = "This panel lists the classic behaviors demonstrated by the legacy DataGrid sample.";
+
+ classicFeaturesGroupBox.Controls.Add(featureListBox);
+ classicFeaturesGroupBox.Controls.Add(classicFeaturesLabel);
+
+ classicOptionsGroupBox.Location = new Point(668, 364);
+ classicOptionsGroupBox.Size = new Size(320, 236);
+ classicOptionsGroupBox.Text = "Try Classic Options";
+
+ captionVisibleCheckBox.Location = new Point(16, 28);
+ captionVisibleCheckBox.Size = new Size(160, 24);
+ captionVisibleCheckBox.Text = "Caption Visible";
+ captionVisibleCheckBox.CheckedChanged += new System.EventHandler(CaptionVisibleCheckBox_CheckedChanged);
+
+ parentRowsVisibleCheckBox.Location = new Point(16, 58);
+ parentRowsVisibleCheckBox.Size = new Size(160, 24);
+ parentRowsVisibleCheckBox.Text = "Parent Rows Visible";
+ parentRowsVisibleCheckBox.CheckedChanged += new System.EventHandler(ParentRowsVisibleCheckBox_CheckedChanged);
+
+ rowHeadersVisibleCheckBox.Location = new Point(16, 88);
+ rowHeadersVisibleCheckBox.Size = new Size(160, 24);
+ rowHeadersVisibleCheckBox.Text = "Row Headers Visible";
+ rowHeadersVisibleCheckBox.CheckedChanged += new System.EventHandler(RowHeadersVisibleCheckBox_CheckedChanged);
+
+ readOnlyCheckBox.Location = new Point(16, 118);
+ readOnlyCheckBox.Size = new Size(160, 24);
+ readOnlyCheckBox.Text = "Read Only";
+ readOnlyCheckBox.CheckedChanged += new System.EventHandler(ReadOnlyCheckBox_CheckedChanged);
+
+ allowNavigationCheckBox.Location = new Point(16, 148);
+ allowNavigationCheckBox.Size = new Size(160, 24);
+ allowNavigationCheckBox.Text = "Allow Navigation";
+ allowNavigationCheckBox.CheckedChanged += new System.EventHandler(AllowNavigationCheckBox_CheckedChanged);
+
+ classicOptionsHintLabel.Location = new Point(16, 180);
+ classicOptionsHintLabel.Size = new Size(288, 42);
+ classicOptionsHintLabel.Text = "Toggle caption, parent rows, row headers, read-only mode, and relation navigation to exercise the classic DataGrid surface live.";
+
+ classicOptionsGroupBox.Controls.Add(captionVisibleCheckBox);
+ classicOptionsGroupBox.Controls.Add(parentRowsVisibleCheckBox);
+ classicOptionsGroupBox.Controls.Add(rowHeadersVisibleCheckBox);
+ classicOptionsGroupBox.Controls.Add(readOnlyCheckBox);
+ classicOptionsGroupBox.Controls.Add(allowNavigationCheckBox);
+ classicOptionsGroupBox.Controls.Add(classicOptionsHintLabel);
+
+ Controls.Add(button1);
+ Controls.Add(button2);
+ Controls.Add(button3);
+ Controls.Add(button4);
+ Controls.Add(myDataGrid);
+ Controls.Add(selectionSummaryLabel);
+ Controls.Add(classicFeaturesGroupBox);
+ Controls.Add(classicOptionsGroupBox);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ components = new System.ComponentModel.Container();
+ SuspendLayout();
+ AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ ClientSize = new System.Drawing.Size(1024, 680);
+ Text = "DataGrid Demo";
+ ResumeLayout(false);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/DataGridForm.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/DataGridForm.cs
new file mode 100644
index 00000000000..57e60031b9c
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/DataGridForm.cs
@@ -0,0 +1,299 @@
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Windows.Forms;
+
+#nullable disable
+
+namespace Demo
+{
+ ///
+ /// Phase 4 DataGrid demo surface.
+ ///
+ public partial class DataGridForm : Form
+ {
+ private DataGrid myDataGrid;
+ private DataSet myDataSet;
+ private bool TablesAlreadyAdded;
+ private Button button1;
+ private Button button2;
+ private Button button3;
+ private Button button4;
+ private ListBox featureListBox;
+ private Label selectionSummaryLabel;
+ private CheckBox captionVisibleCheckBox;
+ private CheckBox parentRowsVisibleCheckBox;
+ private CheckBox rowHeadersVisibleCheckBox;
+ private CheckBox readOnlyCheckBox;
+ private CheckBox allowNavigationCheckBox;
+ private Label classicOptionsHintLabel;
+
+ public DataGridForm()
+ {
+ InitializeComponent();
+
+ Shown += DataGridForm_Shown;
+ }
+
+ private void DataGridForm_Shown(object sender, EventArgs e)
+ {
+ InitializeDataGrid();
+ SetUp();
+ PopulateFeatureList();
+ UpdateFeatureToggleStates();
+ UpdateSelectionSummary();
+ InitializeStatusBar();
+ }
+
+ private void SetUp()
+ {
+ MakeDataSet();
+ myDataGrid.SetDataBinding(myDataSet, "Customers");
+ BindingContext[myDataSet, "Customers"].PositionChanged += CustomersBindingManager_PositionChanged;
+ }
+
+ private void Button1_Click(object sender, EventArgs e)
+ {
+ if (TablesAlreadyAdded)
+ {
+ return;
+ }
+
+ AddCustomDataTableStyle();
+ }
+
+ private void AddCustomDataTableStyle()
+ {
+ DataGridTableStyle ts1 = new()
+ {
+ MappingName = "Customers",
+ AlternatingBackColor = Color.LightGray
+ };
+
+ DataGridColumnStyle boolCol = new DataGridBoolColumn
+ {
+ MappingName = "Current",
+ HeaderText = "IsCurrent Customer",
+ Width = 150
+ };
+ ts1.GridColumnStyles.Add(boolCol);
+
+ DataGridColumnStyle textCol = new DataGridTextBoxColumn
+ {
+ MappingName = "custName",
+ HeaderText = "Customer Name",
+ Width = 250
+ };
+ ts1.GridColumnStyles.Add(textCol);
+
+ DataGridTableStyle ts2 = new()
+ {
+ MappingName = "Orders",
+ AlternatingBackColor = Color.LightBlue
+ };
+
+ DataGridTextBoxColumn cOrderDate = new();
+ cOrderDate.MappingName = "OrderDate";
+ cOrderDate.HeaderText = "Order Date";
+ cOrderDate.Width = 100;
+ ts2.GridColumnStyles.Add(cOrderDate);
+
+ PropertyDescriptorCollection pcol = BindingContext[myDataSet, "Customers.custToOrders"].GetItemProperties();
+
+ DataGridTextBoxColumn csOrderAmount = new(pcol["OrderAmount"], "c", true)
+ {
+ MappingName = "OrderAmount",
+ HeaderText = "Total",
+ Width = 100
+ };
+ ts2.GridColumnStyles.Add(csOrderAmount);
+
+ myDataGrid.TableStyles.Add(ts1);
+ myDataGrid.TableStyles.Add(ts2);
+
+ TablesAlreadyAdded = true;
+ }
+
+ private void Button2_Click(object sender, EventArgs e)
+ {
+ BindingManagerBase bmGrid = BindingContext[myDataSet, "Customers"];
+ MessageBox.Show("Current BindingManager Position: " + bmGrid.Position);
+ }
+
+ private void Button3_Click(object sender, EventArgs e)
+ {
+ myDataGrid.FlatMode = !myDataGrid.FlatMode;
+ button3.Text = myDataGrid.FlatMode ? "Use 3D Borders" : "Use Flat Mode";
+ UpdateSelectionSummary();
+ }
+
+ private void Button4_Click(object sender, EventArgs e)
+ {
+ CurrencyManager customersManager = (CurrencyManager)BindingContext[myDataSet, "Customers"];
+ int nextPosition = customersManager.Position + 1;
+
+ if (nextPosition >= customersManager.Count)
+ {
+ nextPosition = 0;
+ }
+
+ customersManager.Position = nextPosition;
+ UpdateSelectionSummary();
+ }
+
+ private void CaptionVisibleCheckBox_CheckedChanged(object sender, EventArgs e)
+ {
+ myDataGrid.CaptionVisible = captionVisibleCheckBox.Checked;
+ UpdateSelectionSummary();
+ }
+
+ private void ParentRowsVisibleCheckBox_CheckedChanged(object sender, EventArgs e)
+ {
+ myDataGrid.ParentRowsVisible = parentRowsVisibleCheckBox.Checked;
+ UpdateSelectionSummary();
+ }
+
+ private void RowHeadersVisibleCheckBox_CheckedChanged(object sender, EventArgs e)
+ {
+ myDataGrid.RowHeadersVisible = rowHeadersVisibleCheckBox.Checked;
+ UpdateSelectionSummary();
+ }
+
+ private void ReadOnlyCheckBox_CheckedChanged(object sender, EventArgs e)
+ {
+ myDataGrid.ReadOnly = readOnlyCheckBox.Checked;
+ UpdateSelectionSummary();
+ }
+
+ private void AllowNavigationCheckBox_CheckedChanged(object sender, EventArgs e)
+ {
+ myDataGrid.AllowNavigation = allowNavigationCheckBox.Checked;
+ UpdateSelectionSummary();
+ }
+
+ private void CustomersBindingManager_PositionChanged(object sender, EventArgs e)
+ {
+ UpdateSelectionSummary();
+ }
+
+ private void Grid_MouseUp(object sender, MouseEventArgs e)
+ {
+ DataGrid myGrid = (DataGrid)sender;
+ DataGrid.HitTestInfo myHitInfo = myGrid.HitTest(e.X, e.Y);
+ Console.WriteLine(myHitInfo);
+ Console.WriteLine(myHitInfo.Type);
+ Console.WriteLine(myHitInfo.Row);
+ Console.WriteLine(myHitInfo.Column);
+
+ UpdateSelectionSummary();
+ }
+
+ private void PopulateFeatureList()
+ {
+ featureListBox.Items.Clear();
+ featureListBox.Items.Add("Parent/child navigation with Customers -> Orders relation");
+ featureListBox.Items.Add("Automatic binding through DataSet and BindingContext");
+ featureListBox.Items.Add("Custom table styles for Customers and Orders");
+ featureListBox.Items.Add("Boolean column rendering with DataGridBoolColumn");
+ featureListBox.Items.Add("Formatted currency and date columns");
+ featureListBox.Items.Add("Hit testing for rows, columns, and captions");
+ featureListBox.Items.Add("Row navigation using the binding manager");
+ featureListBox.Items.Add("Classic flat or 3D border rendering modes");
+ }
+
+ private void UpdateFeatureToggleStates()
+ {
+ captionVisibleCheckBox.Checked = myDataGrid.CaptionVisible;
+ parentRowsVisibleCheckBox.Checked = myDataGrid.ParentRowsVisible;
+ rowHeadersVisibleCheckBox.Checked = myDataGrid.RowHeadersVisible;
+ readOnlyCheckBox.Checked = myDataGrid.ReadOnly;
+ allowNavigationCheckBox.Checked = myDataGrid.AllowNavigation;
+ }
+
+ private void MakeDataSet()
+ {
+ myDataSet = new DataSet("myDataSet");
+
+ DataTable tCust = new DataTable("Customers");
+ DataTable tOrders = new DataTable("Orders");
+
+ DataColumn cCustID = new DataColumn("CustID", typeof(int));
+ DataColumn cCustName = new DataColumn("CustName");
+ DataColumn cCurrent = new DataColumn("Current", typeof(bool));
+ tCust.Columns.Add(cCustID);
+ tCust.Columns.Add(cCustName);
+ tCust.Columns.Add(cCurrent);
+
+ DataColumn cID = new DataColumn("CustID", typeof(int));
+ DataColumn cOrderDate = new DataColumn("orderDate", typeof(DateTime));
+ DataColumn cOrderAmount = new DataColumn("OrderAmount", typeof(decimal));
+ tOrders.Columns.Add(cOrderAmount);
+ tOrders.Columns.Add(cID);
+ tOrders.Columns.Add(cOrderDate);
+
+ myDataSet.Tables.Add(tCust);
+ myDataSet.Tables.Add(tOrders);
+
+ DataRelation dr = new DataRelation("custToOrders", cCustID, cID);
+ myDataSet.Relations.Add(dr);
+
+ DataRow newRow1;
+ DataRow newRow2;
+
+ for (int i = 1; i < 4; i++)
+ {
+ newRow1 = tCust.NewRow();
+ newRow1["custID"] = i;
+ tCust.Rows.Add(newRow1);
+ }
+
+ tCust.Rows[0]["custName"] = "Customer1";
+ tCust.Rows[1]["custName"] = "Customer2";
+ tCust.Rows[2]["custName"] = "Customer3";
+
+ tCust.Rows[0]["Current"] = true;
+ tCust.Rows[1]["Current"] = true;
+ tCust.Rows[2]["Current"] = false;
+
+ for (int i = 1; i < 4; i++)
+ {
+ for (int j = 1; j < 6; j++)
+ {
+ newRow2 = tOrders.NewRow();
+ newRow2["CustID"] = i;
+ newRow2["orderDate"] = new DateTime(2001, i, j * 2);
+ newRow2["OrderAmount"] = i * 10 + j * .1;
+ tOrders.Rows.Add(newRow2);
+ }
+ }
+ }
+
+ private void UpdateSelectionSummary()
+ {
+ if (myDataSet is null)
+ {
+ selectionSummaryLabel.Text = "Classic demo summary will appear after the grid is initialized.";
+
+ return;
+ }
+
+ CurrencyManager customersManager = (CurrencyManager)BindingContext[myDataSet, "Customers"];
+
+ if (customersManager.Current is not DataRowView customerView)
+ {
+ selectionSummaryLabel.Text = "No active customer row.";
+
+ return;
+ }
+
+ DataRow[] relatedOrders = customerView.Row.GetChildRows("custToOrders");
+ string customerName = customerView["CustName"].ToString();
+ string currentFlag = customerView["Current"].ToString();
+ string borderMode = myDataGrid is not null && myDataGrid.FlatMode ? "Flat" : "3D";
+ string captionMode = myDataGrid.CaptionVisible ? "Caption on" : "Caption off";
+ string relationMode = myDataGrid.AllowNavigation ? "Navigation on" : "Navigation off";
+
+ selectionSummaryLabel.Text = $"Current customer: {customerName} | Active: {currentFlag} | Orders: {relatedOrders.Length} | Border mode: {borderMode} | {captionMode} | {relationMode}";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/Directory.Build.props b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/Directory.Build.props
new file mode 100644
index 00000000000..9cffb09dc68
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/Directory.Build.props
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/Directory.Build.targets b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/Directory.Build.targets
new file mode 100644
index 00000000000..9cffb09dc68
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/Directory.Build.targets
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/MainForm.Designer.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/MainForm.Designer.cs
new file mode 100644
index 00000000000..09dfbb4f9b6
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/MainForm.Designer.cs
@@ -0,0 +1,113 @@
+using System.ComponentModel;
+using System.Drawing;
+using System.Windows.Forms;
+
+#nullable disable
+
+namespace Demo;
+
+partial class MainForm
+{
+ private IContainer components = null;
+ private Label _titleLabel = null!;
+ private Label _descriptionLabel = null!;
+ private Button _menuStackButton = null!;
+ private Button _statusBarButton = null!;
+ private Button _dataGridButton = null!;
+ private Button _toolBarButton = null!;
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && components is not null)
+ {
+ components.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+
+ private void InitializeComponent()
+ {
+ components = new Container();
+ _titleLabel = new Label();
+ _descriptionLabel = new Label();
+ _menuStackButton = new Button();
+ _statusBarButton = new Button();
+ _dataGridButton = new Button();
+ _toolBarButton = new Button();
+ SuspendLayout();
+ //
+ // _titleLabel
+ //
+ _titleLabel.Font = new Font("Segoe UI", 14F, FontStyle.Bold, GraphicsUnit.Point);
+ _titleLabel.Location = new Point(20, 20);
+ _titleLabel.Name = "_titleLabel";
+ _titleLabel.Size = new Size(440, 30);
+ _titleLabel.TabIndex = 0;
+ _titleLabel.Text = "WinForms Legacy Controls Demo";
+ _titleLabel.TextAlign = ContentAlignment.MiddleCenter;
+ //
+ // _descriptionLabel
+ //
+ _descriptionLabel.Location = new Point(20, 58);
+ _descriptionLabel.Name = "_descriptionLabel";
+ _descriptionLabel.Size = new Size(440, 36);
+ _descriptionLabel.TabIndex = 1;
+ _descriptionLabel.Text = "Select a demo to open. Each form independently exercises one legacy control family.";
+ _descriptionLabel.TextAlign = ContentAlignment.MiddleCenter;
+ //
+ // _menuStackButton
+ //
+ _menuStackButton.Location = new Point(50, 108);
+ _menuStackButton.Name = "_menuStackButton";
+ _menuStackButton.Size = new Size(160, 60);
+ _menuStackButton.TabIndex = 2;
+ _menuStackButton.Text = "Menu Stack";
+ _menuStackButton.Click += MenuStackButton_Click;
+ //
+ // _statusBarButton
+ //
+ _statusBarButton.Location = new Point(270, 108);
+ _statusBarButton.Name = "_statusBarButton";
+ _statusBarButton.Size = new Size(160, 60);
+ _statusBarButton.TabIndex = 3;
+ _statusBarButton.Text = "StatusBar";
+ _statusBarButton.Click += StatusBarButton_Click;
+ //
+ // _dataGridButton
+ //
+ _dataGridButton.Location = new Point(50, 186);
+ _dataGridButton.Name = "_dataGridButton";
+ _dataGridButton.Size = new Size(160, 60);
+ _dataGridButton.TabIndex = 4;
+ _dataGridButton.Text = "DataGrid";
+ _dataGridButton.Click += DataGridButton_Click;
+ //
+ // _toolBarButton
+ //
+ _toolBarButton.Location = new Point(270, 186);
+ _toolBarButton.Name = "_toolBarButton";
+ _toolBarButton.Size = new Size(160, 60);
+ _toolBarButton.TabIndex = 5;
+ _toolBarButton.Text = "ToolBar";
+ _toolBarButton.Click += ToolBarButton_Click;
+ //
+ // MainForm
+ //
+ AutoScaleDimensions = new SizeF(7F, 15F);
+ AutoScaleMode = AutoScaleMode.Font;
+ ClientSize = new Size(480, 274);
+ Controls.Add(_toolBarButton);
+ Controls.Add(_dataGridButton);
+ Controls.Add(_statusBarButton);
+ Controls.Add(_menuStackButton);
+ Controls.Add(_descriptionLabel);
+ Controls.Add(_titleLabel);
+ FormBorderStyle = FormBorderStyle.FixedSingle;
+ MaximizeBox = false;
+ Name = "MainForm";
+ StartPosition = FormStartPosition.CenterScreen;
+ Text = "Demo Launcher";
+ ResumeLayout(false);
+ }
+}
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/MainForm.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/MainForm.cs
new file mode 100644
index 00000000000..9660b4be738
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/MainForm.cs
@@ -0,0 +1,35 @@
+using System.Windows.Forms;
+
+namespace Demo;
+
+public partial class MainForm : Form
+{
+ public MainForm()
+ {
+ InitializeComponent();
+ }
+
+ private void MenuStackButton_Click(object? sender, EventArgs e)
+ {
+ MenuStackForm form = new();
+ form.Show(this);
+ }
+
+ private void StatusBarButton_Click(object? sender, EventArgs e)
+ {
+ StatusBarForm form = new();
+ form.Show(this);
+ }
+
+ private void DataGridButton_Click(object? sender, EventArgs e)
+ {
+ DataGridForm form = new();
+ form.Show(this);
+ }
+
+ private void ToolBarButton_Click(object? sender, EventArgs e)
+ {
+ ToolBarForm form = new();
+ form.Show(this);
+ }
+}
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/MenuStackForm.Designer.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/MenuStackForm.Designer.cs
new file mode 100644
index 00000000000..0ee79b72412
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/MenuStackForm.Designer.cs
@@ -0,0 +1,147 @@
+using System.ComponentModel;
+using System.Drawing;
+using System.Windows.Forms;
+
+#nullable disable
+
+namespace Demo;
+
+partial class MenuStackForm
+{
+ private IContainer components = null;
+ private Label _summaryLabel = null!;
+ private Button _showSurfaceContextMenuButton = null!;
+ private Button _clearLogButton = null!;
+ private Panel _demoSurface = null!;
+ private Label _surfaceMessageLabel = null!;
+ private TreeView _menuTreeView = null!;
+ private ListBox _eventLog = null!;
+ private Label _treeViewLabel = null!;
+ private Label _logLabel = null!;
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && components is not null)
+ {
+ components.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+
+ private void InitializeComponent()
+ {
+ components = new Container();
+ _summaryLabel = new Label();
+ _showSurfaceContextMenuButton = new Button();
+ _clearLogButton = new Button();
+ _demoSurface = new Panel();
+ _surfaceMessageLabel = new Label();
+ _menuTreeView = new TreeView();
+ _eventLog = new ListBox();
+ _treeViewLabel = new Label();
+ _logLabel = new Label();
+ _demoSurface.SuspendLayout();
+ SuspendLayout();
+ //
+ // _summaryLabel
+ //
+ _summaryLabel.Location = new Point(18, 16);
+ _summaryLabel.Name = "_summaryLabel";
+ _summaryLabel.Size = new Size(774, 40);
+ _summaryLabel.TabIndex = 0;
+ _summaryLabel.Text = "Menu stack demo exercising the legacy MainMenu, MenuItem, ContextMenu, dynamic popups, owner-draw items, and TreeNode.ContextMenu.";
+ //
+ // _showSurfaceContextMenuButton
+ //
+ _showSurfaceContextMenuButton.Location = new Point(18, 65);
+ _showSurfaceContextMenuButton.Name = "_showSurfaceContextMenuButton";
+ _showSurfaceContextMenuButton.Size = new Size(188, 30);
+ _showSurfaceContextMenuButton.TabIndex = 1;
+ _showSurfaceContextMenuButton.Text = "Show Surface Context Menu";
+ _showSurfaceContextMenuButton.Click += ShowSurfaceContextMenuButton_Click;
+ //
+ // _clearLogButton
+ //
+ _clearLogButton.Location = new Point(218, 65);
+ _clearLogButton.Name = "_clearLogButton";
+ _clearLogButton.Size = new Size(120, 30);
+ _clearLogButton.TabIndex = 2;
+ _clearLogButton.Text = "Clear Log";
+ _clearLogButton.Click += ClearLogButton_Click;
+ //
+ // _demoSurface
+ //
+ _demoSurface.BackColor = Color.WhiteSmoke;
+ _demoSurface.BorderStyle = BorderStyle.FixedSingle;
+ _demoSurface.Controls.Add(_surfaceMessageLabel);
+ _demoSurface.Location = new Point(18, 112);
+ _demoSurface.Name = "_demoSurface";
+ _demoSurface.Size = new Size(384, 220);
+ _demoSurface.TabIndex = 5;
+ //
+ // _surfaceMessageLabel
+ //
+ _surfaceMessageLabel.Dock = DockStyle.Fill;
+ _surfaceMessageLabel.Location = new Point(0, 0);
+ _surfaceMessageLabel.Name = "_surfaceMessageLabel";
+ _surfaceMessageLabel.Size = new Size(382, 218);
+ _surfaceMessageLabel.TabIndex = 0;
+ _surfaceMessageLabel.Text = "Right-click this surface or the TreeView nodes to exercise ContextMenu and TreeNode.ContextMenu support. Use the MainMenu above for menu actions.";
+ _surfaceMessageLabel.TextAlign = ContentAlignment.MiddleCenter;
+ //
+ // _menuTreeView
+ //
+ _menuTreeView.HideSelection = false;
+ _menuTreeView.Location = new Point(424, 134);
+ _menuTreeView.Name = "_menuTreeView";
+ _menuTreeView.Size = new Size(368, 198);
+ _menuTreeView.TabIndex = 7;
+ _menuTreeView.NodeMouseClick += MenuTreeView_NodeMouseClick;
+ //
+ // _eventLog
+ //
+ _eventLog.FormattingEnabled = true;
+ _eventLog.HorizontalScrollbar = true;
+ _eventLog.Location = new Point(18, 368);
+ _eventLog.Name = "_eventLog";
+ _eventLog.Size = new Size(774, 184);
+ _eventLog.TabIndex = 9;
+ //
+ // _treeViewLabel
+ //
+ _treeViewLabel.Location = new Point(424, 112);
+ _treeViewLabel.Name = "_treeViewLabel";
+ _treeViewLabel.Size = new Size(210, 18);
+ _treeViewLabel.TabIndex = 6;
+ _treeViewLabel.Text = "TreeView and TreeNode.ContextMenu demo";
+ //
+ // _logLabel
+ //
+ _logLabel.Location = new Point(18, 346);
+ _logLabel.Name = "_logLabel";
+ _logLabel.Size = new Size(180, 18);
+ _logLabel.TabIndex = 8;
+ _logLabel.Text = "Menu event log";
+ //
+ // MenuStackForm
+ //
+ AutoScaleDimensions = new SizeF(7F, 15F);
+ AutoScaleMode = AutoScaleMode.Font;
+ ClientSize = new Size(810, 572);
+ Controls.Add(_logLabel);
+ Controls.Add(_treeViewLabel);
+ Controls.Add(_eventLog);
+ Controls.Add(_menuTreeView);
+ Controls.Add(_demoSurface);
+ Controls.Add(_clearLogButton);
+ Controls.Add(_showSurfaceContextMenuButton);
+ Controls.Add(_summaryLabel);
+ MinimumSize = new Size(826, 611);
+ Name = "MenuStackForm";
+ StartPosition = FormStartPosition.CenterParent;
+ Text = "Phase 1: Menu Stack";
+ _demoSurface.ResumeLayout(false);
+ ResumeLayout(false);
+ }
+}
\ No newline at end of file
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/MenuStackForm.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/MenuStackForm.cs
new file mode 100644
index 00000000000..73e33156071
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/MenuStackForm.cs
@@ -0,0 +1,376 @@
+using System.Drawing;
+using System.Windows.Forms;
+
+namespace Demo;
+
+public partial class MenuStackForm : Form
+{
+ private readonly ContextMenu _surfaceContextMenu;
+ private readonly ContextMenu _treeViewContextMenu;
+ private int _dynamicMenuGeneration;
+
+ public MenuStackForm()
+ {
+ InitializeComponent();
+
+ Menu = CreateMainMenu();
+
+ _surfaceContextMenu = CreateSurfaceContextMenu();
+ _demoSurface.ContextMenu = _surfaceContextMenu;
+
+ _treeViewContextMenu = CreateTreeViewContextMenu();
+ _menuTreeView.ContextMenu = _treeViewContextMenu;
+
+ InitializeTreeView();
+ AppendLog("Menu stack demo ready.");
+ }
+
+ private MainMenu CreateMainMenu()
+ {
+ MenuItem fileMenuItem = new("File");
+ MenuItem newMenuItem = new("New");
+ MenuItem newProjectItem = new("Project...", OnMenuActionClicked);
+ MenuItem newRepositoryItem = new("Repository...", OnMenuActionClicked);
+ MenuItem newFileItem = new("File...", OnMenuActionClicked);
+ MenuItem openMenuItem = new("Open", OnMenuActionClicked)
+ {
+ Shortcut = Shortcut.AltF12,
+ ShowShortcut = true
+ };
+ MenuItem saveMenuItem = new("Save", OnMenuActionClicked, Shortcut.CtrlS)
+ {
+ Checked = true,
+ RadioCheck = true
+ };
+ MenuItem exitMenuItem = new("Close Demo", (_, _) => Close());
+
+ newMenuItem.MenuItems.Add(newProjectItem);
+ newMenuItem.MenuItems.Add(newRepositoryItem);
+ newMenuItem.MenuItems.Add(newFileItem);
+
+ fileMenuItem.MenuItems.Add(newMenuItem);
+ fileMenuItem.MenuItems.Add(openMenuItem);
+ fileMenuItem.MenuItems.Add(saveMenuItem);
+ fileMenuItem.MenuItems.Add(new MenuItem("-"));
+ fileMenuItem.MenuItems.Add(exitMenuItem);
+
+ MenuItem viewMenuItem = new("View");
+ viewMenuItem.MenuItems.Add(new MenuItem("Toolbox", OnMenuActionClicked));
+ viewMenuItem.MenuItems.Add(new MenuItem("Terminal", OnMenuActionClicked));
+ viewMenuItem.MenuItems.Add(new MenuItem("Output", OnMenuActionClicked));
+
+ MenuItem dynamicMenuItem = new("Dynamic");
+ dynamicMenuItem.Popup += DynamicMenuItem_Popup;
+ dynamicMenuItem.MenuItems.Add(new MenuItem("Dynamic code has not run yet"));
+
+ MenuItem ownerDrawMenuItem = new("Owner Draw Demo");
+ ownerDrawMenuItem.MenuItems.Add(new MenuItem("Standard Item", OnMenuActionClicked));
+ ownerDrawMenuItem.MenuItems.Add(new MenuItem("-"));
+ ownerDrawMenuItem.MenuItems.Add(CreateOwnerDrawMenuItem("Custom Draw Item 1"));
+ ownerDrawMenuItem.MenuItems.Add(CreateOwnerDrawMenuItem("Custom Draw Item 2", createNestedItems: true));
+
+ MenuItem contextActionsItem = new("Context Actions");
+ contextActionsItem.MenuItems.Add(new MenuItem("Show Surface Menu", ShowSurfaceMenuMenuItem_Click));
+ contextActionsItem.MenuItems.Add(new MenuItem("Clear Log", (_, _) => _eventLog.Items.Clear()));
+
+ AttachMenuTracing(fileMenuItem);
+ AttachMenuTracing(viewMenuItem);
+ AttachMenuTracing(dynamicMenuItem);
+ AttachMenuTracing(ownerDrawMenuItem);
+ AttachMenuTracing(contextActionsItem);
+
+ MainMenu mainMenu = new();
+ mainMenu.MenuItems.Add(fileMenuItem);
+ mainMenu.MenuItems.Add(viewMenuItem);
+ mainMenu.MenuItems.Add(dynamicMenuItem);
+ mainMenu.MenuItems.Add(ownerDrawMenuItem);
+ mainMenu.MenuItems.Add(contextActionsItem);
+
+ return mainMenu;
+ }
+
+ private ContextMenu CreateSurfaceContextMenu()
+ {
+ ContextMenu contextMenu = new();
+
+ MenuItem inspectItem = new("Inspect Surface", OnContextMenuActionClicked);
+ MenuItem toggleHighlightItem = new("Toggle Highlight", ToggleSurfaceHighlightMenuItem_Click);
+ MenuItem timestampItem = new("Insert Timestamp", InsertTimestampMenuItem_Click);
+
+ contextMenu.MenuItems.Add(inspectItem);
+ contextMenu.MenuItems.Add(toggleHighlightItem);
+ contextMenu.MenuItems.Add(new MenuItem("-"));
+ contextMenu.MenuItems.Add(timestampItem);
+
+ contextMenu.Popup += (_, _) => AppendLog("Surface context menu opened.");
+
+ return contextMenu;
+ }
+
+ private ContextMenu CreateTreeViewContextMenu()
+ {
+ ContextMenu contextMenu = new();
+ MenuItem inspectTreeItem = new("Inspect TreeView", (_, _) => AppendLog("Inspect TreeView context action executed."));
+ MenuItem expandAllItem = new("Expand All Nodes", (_, _) =>
+ {
+ _menuTreeView.ExpandAll();
+ AppendLog("TreeView context action: expand all nodes.");
+ });
+ MenuItem collapseAllItem = new("Collapse All Nodes", (_, _) =>
+ {
+ _menuTreeView.CollapseAll();
+ AppendLog("TreeView context action: collapse all nodes.");
+ });
+
+ contextMenu.MenuItems.Add(inspectTreeItem);
+ contextMenu.MenuItems.Add(new MenuItem("-"));
+ contextMenu.MenuItems.Add(expandAllItem);
+ contextMenu.MenuItems.Add(collapseAllItem);
+ contextMenu.Popup += (_, _) => AppendLog("TreeView context menu opened.");
+
+ return contextMenu;
+ }
+
+ private OwnerDrawMenuItem CreateOwnerDrawMenuItem(string text, bool createNestedItems = false)
+ {
+ OwnerDrawMenuItem item = new()
+ {
+ Text = text
+ };
+
+ item.Click += OnOwnerDrawItemClicked;
+
+ if (!createNestedItems)
+ {
+ item.MenuItems.Add(new OwnerDrawMenuItem { Text = text + " / Sub Item A" });
+ item.MenuItems.Add(new OwnerDrawMenuItem { Text = text + " / Sub Item B" });
+
+ foreach (MenuItem subItem in item.MenuItems)
+ {
+ subItem.Click += OnOwnerDrawItemClicked;
+ }
+
+ return item;
+ }
+
+ OwnerDrawMenuItem nestedItemA = new()
+ {
+ Text = "Nested Custom Item A"
+ };
+ OwnerDrawMenuItem nestedItemB = new()
+ {
+ Text = "Nested Custom Item B"
+ };
+ MenuItem deepSubmenu = new("Deep Submenu");
+ OwnerDrawMenuItem deepItem1 = new()
+ {
+ Text = "Deep Custom Item 1"
+ };
+ OwnerDrawMenuItem deepItem2 = new()
+ {
+ Text = "Deep Custom Item 2"
+ };
+
+ nestedItemA.Click += OnOwnerDrawItemClicked;
+ nestedItemB.Click += OnOwnerDrawItemClicked;
+ deepItem1.Click += OnOwnerDrawItemClicked;
+ deepItem2.Click += OnOwnerDrawItemClicked;
+
+ deepSubmenu.MenuItems.Add(deepItem1);
+ deepSubmenu.MenuItems.Add(deepItem2);
+
+ item.MenuItems.Add(nestedItemA);
+ item.MenuItems.Add(nestedItemB);
+ item.MenuItems.Add(new MenuItem("-"));
+ item.MenuItems.Add(deepSubmenu);
+
+ return item;
+ }
+
+ private void AttachMenuTracing(MenuItem rootItem)
+ {
+ rootItem.Select += (_, _) => AppendLog($"Selected menu item: {rootItem.Text}");
+
+ if (rootItem.MenuItems.Count > 0)
+ {
+ rootItem.Popup += (_, _) => AppendLog($"Popup menu opened: {rootItem.Text}");
+ }
+
+ foreach (MenuItem childItem in rootItem.MenuItems)
+ {
+ AttachMenuTracing(childItem);
+ }
+ }
+
+ private void InitializeTreeView()
+ {
+ TreeNode rootNode = new("Menu Demo Root")
+ {
+ Nodes =
+ {
+ new TreeNode("Context Menu Node"),
+ new TreeNode("Owner Draw Node")
+ {
+ Nodes =
+ {
+ new TreeNode("Nested Node A"),
+ new TreeNode("Nested Node B")
+ }
+ },
+ new TreeNode("Shortcut Node")
+ }
+ };
+
+ _menuTreeView.Nodes.Clear();
+ _menuTreeView.Nodes.Add(rootNode);
+ rootNode.Expand();
+
+ AddContextMenusToNodes(_menuTreeView.Nodes);
+ }
+
+ private void AddContextMenusToNodes(TreeNodeCollection nodes)
+ {
+ foreach (TreeNode node in nodes)
+ {
+ ContextMenu nodeContextMenu = new();
+ MenuItem inspectNodeItem = new($"Inspect {node.Text}", (_, _) =>
+ {
+ _surfaceMessageLabel.Text = "Inspecting node: " + node.Text;
+ AppendLog($"TreeNode.ContextMenu action: inspect {node.Text}");
+ });
+ MenuItem toggleCheckedItem = new($"Toggle check state for {node.Text}", (_, _) =>
+ {
+ node.Checked = !node.Checked;
+ AppendLog($"TreeNode.ContextMenu action: {(node.Checked ? "checked" : "unchecked")} {node.Text}");
+ });
+ MenuItem runNodeItem = new($"Run action for {node.Text}", (_, _) => MessageBox.Show(this, $"Action executed for {node.Text}", Text));
+
+ nodeContextMenu.MenuItems.Add(inspectNodeItem);
+ nodeContextMenu.MenuItems.Add(toggleCheckedItem);
+ nodeContextMenu.MenuItems.Add(runNodeItem);
+ nodeContextMenu.Popup += (_, _) => AppendLog($"TreeNode.ContextMenu opened for {node.Text}");
+ node.ContextMenu = nodeContextMenu;
+
+ if (node.Nodes.Count > 0)
+ {
+ AddContextMenusToNodes(node.Nodes);
+ }
+ }
+ }
+
+ private void DynamicMenuItem_Popup(object? sender, EventArgs e)
+ {
+ if (sender is not MenuItem dynamicMenuItem)
+ {
+ return;
+ }
+
+ _dynamicMenuGeneration++;
+ dynamicMenuItem.MenuItems.Clear();
+
+ for (int i = 1; i <= 5; i++)
+ {
+ MenuItem item = new($"Dynamic Item {_dynamicMenuGeneration}.{i}");
+ item.Click += OnMenuActionClicked;
+ dynamicMenuItem.MenuItems.Add(item);
+ }
+
+ AppendLog($"Dynamic menu rebuilt #{_dynamicMenuGeneration}.");
+ }
+
+ private void OnMenuActionClicked(object? sender, EventArgs e)
+ {
+ if (sender is not MenuItem menuItem)
+ {
+ return;
+ }
+
+ menuItem.Checked = !menuItem.Checked && menuItem.Text.EndsWith("...", StringComparison.Ordinal);
+ AppendLog($"Menu click: {menuItem.Text}");
+ MessageBox.Show(this, $"{menuItem.Text} clicked.", Text);
+ }
+
+ private void OnOwnerDrawItemClicked(object? sender, EventArgs e)
+ {
+ if (sender is not MenuItem menuItem)
+ {
+ return;
+ }
+
+ AppendLog($"Owner-draw click: {menuItem.Text}");
+ MessageBox.Show(this, $"Owner-draw item clicked: {menuItem.Text}", Text);
+ }
+
+ private void OnContextMenuActionClicked(object? sender, EventArgs e)
+ {
+ if (sender is not MenuItem menuItem)
+ {
+ return;
+ }
+
+ AppendLog($"Context action: {menuItem.Text}");
+ MessageBox.Show(this, $"{menuItem.Text} executed.", Text);
+ }
+
+ private void ToggleSurfaceHighlightMenuItem_Click(object? sender, EventArgs e)
+ {
+ bool useHighlight = _demoSurface.BackColor != Color.LightGoldenrodYellow;
+ _demoSurface.BackColor = useHighlight ? Color.LightGoldenrodYellow : Color.WhiteSmoke;
+
+ AppendLog(useHighlight ? "Surface highlight enabled." : "Surface highlight cleared.");
+ }
+
+ private void InsertTimestampMenuItem_Click(object? sender, EventArgs e)
+ {
+ string timestamp = DateTime.Now.ToString("HH:mm:ss");
+ _surfaceMessageLabel.Text = "Last context action at " + timestamp;
+ AppendLog("Timestamp inserted: " + timestamp);
+ }
+
+ private void ShowSurfaceMenuMenuItem_Click(object? sender, EventArgs e)
+ {
+ ShowSurfaceContextMenu();
+ }
+
+ private void ShowSurfaceContextMenuButton_Click(object? sender, EventArgs e)
+ {
+ ShowSurfaceContextMenu();
+ }
+
+ private void ClearLogButton_Click(object? sender, EventArgs e)
+ {
+ _eventLog.Items.Clear();
+ AppendLog("Log cleared.");
+ }
+
+ private void MenuTreeView_NodeMouseClick(object? sender, TreeNodeMouseClickEventArgs e)
+ {
+ if (e.Node is null)
+ {
+ return;
+ }
+
+ _menuTreeView.SelectedNode = e.Node;
+ AppendLog($"Tree node clicked: {e.Node.Text}");
+ }
+
+ private void ShowSurfaceContextMenu()
+ {
+ Point screenPoint = _demoSurface.PointToClient(MousePosition);
+ Point menuPoint = screenPoint.X >= 0 && screenPoint.Y >= 0
+ ? screenPoint
+ : new Point(_demoSurface.Width / 2, _demoSurface.Height / 2);
+
+ _surfaceContextMenu.Show(_demoSurface, menuPoint);
+ }
+
+ private void AppendLog(string message)
+ {
+ _eventLog.Items.Insert(0, $"[{DateTime.Now:HH:mm:ss}] {message}");
+
+ if (_eventLog.Items.Count > 200)
+ {
+ _eventLog.Items.RemoveAt(_eventLog.Items.Count - 1);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/OwnerDrawMenuItem.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/OwnerDrawMenuItem.cs
new file mode 100644
index 00000000000..59fd0f3d47b
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/OwnerDrawMenuItem.cs
@@ -0,0 +1,109 @@
+#if NET
+using System.Drawing;
+using System.Windows.Forms;
+#endif
+
+namespace Demo
+{
+ internal sealed class OwnerDrawMenuItem : MenuItem
+ {
+ public OwnerDrawMenuItem() : base()
+ {
+ OwnerDraw = true;
+ }
+
+ protected override void OnDrawItem(DrawItemEventArgs e)
+ {
+ // Get the drawing area
+ Rectangle bounds = e.Bounds;
+ Graphics g = e.Graphics;
+
+ // Determine colors based on item state
+ Color backColor = e.BackColor;
+ Color textColor = e.ForeColor;
+
+ // Custom styling for selected/highlighted state
+ if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
+ {
+ backColor = Color.LightBlue;
+ textColor = Color.DarkBlue;
+ }
+
+ // Draw background
+ using (Brush backBrush = new SolidBrush(backColor))
+ {
+ g.FillRectangle(backBrush, bounds);
+ }
+
+ // Draw an icon area (simple colored rectangle as demo)
+ Rectangle iconRect = new Rectangle(bounds.X + 4, bounds.Y + 2, 16, 16);
+ using (Brush iconBrush = new SolidBrush(Color.Green))
+ {
+ g.FillRectangle(iconBrush, iconRect);
+ }
+
+ // Draw a simple "check mark" in the icon area
+ using (Pen checkPen = new Pen(Color.White, 2))
+ {
+ Point[] checkPoints = {
+ new (iconRect.X + 3, iconRect.Y + 8),
+ new (iconRect.X + 7, iconRect.Y + 12),
+ new (iconRect.X + 13, iconRect.Y + 4)
+ };
+ g.DrawLines(checkPen, checkPoints);
+ }
+
+ // Calculate text area (leaving space for icon and margins)
+ Rectangle textRect = new Rectangle(
+ bounds.X + 24, // Start after icon area (4 + 16 + 4 spacing)
+ bounds.Y + 1,
+ bounds.Width - 28, // Total margin: 4 (left) + 16 (icon) + 4 (spacing) + 4 (right) = 28
+ bounds.Height - 2
+ );
+
+ // Draw the menu text
+ using (Brush textBrush = new SolidBrush(textColor))
+ {
+ StringFormat format = new StringFormat()
+ {
+ Alignment = StringAlignment.Near,
+ LineAlignment = StringAlignment.Center
+ };
+
+ Font font = e.Font ?? SystemInformation.MenuFont;
+ g.DrawString(Text, font, textBrush, textRect, format);
+ }
+
+ // Draw focus rectangle if the item has focus
+ if ((e.State & DrawItemState.Focus) == DrawItemState.Focus)
+ {
+ e.DrawFocusRectangle();
+ }
+
+ // Draw a subtle shadow effect at the bottom
+ using (Pen shadowPen = new Pen(Color.FromArgb(50, 0, 0, 0)))
+ {
+ g.DrawLine(shadowPen, bounds.X, bounds.Bottom - 1, bounds.Right, bounds.Bottom - 1);
+ }
+ }
+
+ protected override void OnMeasureItem(MeasureItemEventArgs e)
+ {
+ Font font = SystemInformation.MenuFont;
+
+ string text = Text ?? string.Empty;
+ if (string.IsNullOrEmpty(text))
+ {
+ text = " ";
+ }
+
+ var stringSize = e.Graphics.MeasureString(text, font);
+ e.ItemWidth = (int)Math.Ceiling(stringSize.Width) + 28;
+
+ int minHeightForIcon = 20;
+ int textHeight = (int)Math.Ceiling(stringSize.Height) + 4;
+
+ e.ItemHeight = Math.Max(minHeightForIcon, textHeight);
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/Program.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/Program.cs
new file mode 100644
index 00000000000..5cae1e14eeb
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/Program.cs
@@ -0,0 +1,38 @@
+#if NET
+using System.Windows.Forms;
+#endif
+
+namespace Demo
+{
+ internal static class Program
+ {
+ ///
+ /// The main entry point for the application.
+ ///
+ [STAThread]
+ static void Main()
+ {
+ // To customize application configuration such as set high DPI settings or default font,
+ // see https://aka.ms/applicationconfiguration.
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
+ // Set High DPI mode to DpiUnaware, as currently there are some scaling issues when setting to other values
+ // https://github.com/WiseTechGlobal/Modernization.Content/blob/main/Controls/work-items/Difference%20display%20between%20migrated%20forms%20and%20original%20forms.md
+#if NET
+ Application.SetHighDpiMode(HighDpiMode.DpiUnaware);
+#endif
+ //ApplicationConfiguration.Initialize();
+ Application.Run(new MainForm());
+ }
+ }
+}
+
+#if NETFRAMEWORK
+namespace System.Runtime.Versioning
+{
+ class SupportedOSPlatformAttribute : Attribute
+ {
+ public SupportedOSPlatformAttribute(string platformName) { }
+ }
+}
+#endif
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/StatusBarForm.Designer.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/StatusBarForm.Designer.cs
new file mode 100644
index 00000000000..1058ed50e41
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/StatusBarForm.Designer.cs
@@ -0,0 +1,141 @@
+using System.ComponentModel;
+using System.Drawing;
+using System.Windows.Forms;
+using Timer = System.Windows.Forms.Timer;
+
+#nullable disable
+
+namespace Demo;
+
+partial class StatusBarForm
+{
+ private IContainer components = null;
+ private Label _summaryLabel = null!;
+ private Button _showPanelsButton = null!;
+ private Button _showSimpleTextButton = null!;
+ private Button _addPanelButton = null!;
+ private Button _removePanelButton = null!;
+ private Button _clearLogButton = null!;
+ private Label _logLabel = null!;
+ private ListBox _eventLog = null!;
+ private Timer _clockTimer = null!;
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && components is not null)
+ {
+ components.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+
+ private void InitializeComponent()
+ {
+ components = new Container();
+ _summaryLabel = new Label();
+ _showPanelsButton = new Button();
+ _showSimpleTextButton = new Button();
+ _addPanelButton = new Button();
+ _removePanelButton = new Button();
+ _clearLogButton = new Button();
+ _logLabel = new Label();
+ _eventLog = new ListBox();
+ _clockTimer = new Timer(components);
+ SuspendLayout();
+ //
+ // _summaryLabel
+ //
+ _summaryLabel.Location = new Point(18, 16);
+ _summaryLabel.Name = "_summaryLabel";
+ _summaryLabel.Size = new Size(654, 40);
+ _summaryLabel.TabIndex = 0;
+ _summaryLabel.Text = "Phase 2 status bar demo covering legacy StatusBar panel layout, owner-draw rendering, dynamic panel mutation, panel-region tooltips, and click events.";
+ //
+ // _showPanelsButton
+ //
+ _showPanelsButton.Location = new Point(18, 72);
+ _showPanelsButton.Name = "_showPanelsButton";
+ _showPanelsButton.Size = new Size(120, 30);
+ _showPanelsButton.TabIndex = 1;
+ _showPanelsButton.Text = "Show Panels";
+ _showPanelsButton.Click += ShowPanelsButton_Click;
+ //
+ // _showSimpleTextButton
+ //
+ _showSimpleTextButton.Location = new Point(150, 72);
+ _showSimpleTextButton.Name = "_showSimpleTextButton";
+ _showSimpleTextButton.Size = new Size(142, 30);
+ _showSimpleTextButton.TabIndex = 2;
+ _showSimpleTextButton.Text = "Show Simple Text";
+ _showSimpleTextButton.Click += ShowSimpleTextButton_Click;
+ //
+ // _addPanelButton
+ //
+ _addPanelButton.Location = new Point(304, 72);
+ _addPanelButton.Name = "_addPanelButton";
+ _addPanelButton.Size = new Size(112, 30);
+ _addPanelButton.TabIndex = 3;
+ _addPanelButton.Text = "Add Panel";
+ _addPanelButton.Click += AddPanelButton_Click;
+ //
+ // _removePanelButton
+ //
+ _removePanelButton.Location = new Point(428, 72);
+ _removePanelButton.Name = "_removePanelButton";
+ _removePanelButton.Size = new Size(120, 30);
+ _removePanelButton.TabIndex = 4;
+ _removePanelButton.Text = "Remove Panel";
+ _removePanelButton.Click += RemovePanelButton_Click;
+ //
+ // _clearLogButton
+ //
+ _clearLogButton.Location = new Point(560, 72);
+ _clearLogButton.Name = "_clearLogButton";
+ _clearLogButton.Size = new Size(112, 30);
+ _clearLogButton.TabIndex = 5;
+ _clearLogButton.Text = "Clear Log";
+ _clearLogButton.Click += ClearLogButton_Click;
+ //
+ // _logLabel
+ //
+ _logLabel.Location = new Point(18, 124);
+ _logLabel.Name = "_logLabel";
+ _logLabel.Size = new Size(180, 18);
+ _logLabel.TabIndex = 6;
+ _logLabel.Text = "StatusBar event log";
+ //
+ // _eventLog
+ //
+ _eventLog.FormattingEnabled = true;
+ _eventLog.HorizontalScrollbar = true;
+ _eventLog.Location = new Point(18, 146);
+ _eventLog.Name = "_eventLog";
+ _eventLog.Size = new Size(654, 214);
+ _eventLog.TabIndex = 7;
+ //
+ // _clockTimer
+ //
+ _clockTimer.Interval = 1000;
+ _clockTimer.Tick += ClockTimer_Tick;
+ //
+ // StatusBarForm
+ //
+ AutoScaleDimensions = new SizeF(7F, 15F);
+ AutoScaleMode = AutoScaleMode.Font;
+ ClientSize = new Size(692, 408);
+ Controls.Add(_eventLog);
+ Controls.Add(_logLabel);
+ Controls.Add(_clearLogButton);
+ Controls.Add(_removePanelButton);
+ Controls.Add(_addPanelButton);
+ Controls.Add(_showSimpleTextButton);
+ Controls.Add(_showPanelsButton);
+ Controls.Add(_summaryLabel);
+ MinimumSize = new Size(708, 447);
+ Name = "StatusBarForm";
+ StartPosition = FormStartPosition.CenterParent;
+ Text = "Phase 2: StatusBar";
+ ResumeLayout(false);
+ }
+}
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/StatusBarForm.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/StatusBarForm.cs
new file mode 100644
index 00000000000..1a9c0ba9703
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/StatusBarForm.cs
@@ -0,0 +1,158 @@
+using System.Drawing;
+using System.Windows.Forms;
+
+namespace Demo;
+
+public partial class StatusBarForm : Form
+{
+ private readonly StatusBar _statusBar;
+ private StatusBarPanel _modePanel;
+ private StatusBarPanel _detailsPanel;
+ private StatusBarPanel _ownerDrawPanel;
+ private StatusBarPanel _clockPanel;
+ private int _dynamicPanelCount;
+
+ public StatusBarForm()
+ {
+ InitializeComponent();
+
+ _statusBar = CreateStatusBar();
+ Controls.Add(_statusBar);
+
+ _clockTimer.Start();
+ AppendLog("StatusBar demo ready.");
+ }
+
+ private StatusBar CreateStatusBar()
+ {
+ StatusBar statusBar = new()
+ {
+ Dock = DockStyle.Bottom,
+ ShowPanels = true,
+ SizingGrip = true
+ };
+
+ _modePanel = new StatusBarPanel
+ {
+ Text = "Panels Mode",
+ AutoSize = StatusBarPanelAutoSize.Contents,
+ ToolTipText = "The current StatusBar presentation mode."
+ };
+ _detailsPanel = new StatusBarPanel
+ {
+ Text = "Click a panel or use the controls above to mutate the legacy StatusBar surface.",
+ AutoSize = StatusBarPanelAutoSize.Spring,
+ ToolTipText = "Spring-sized panel text."
+ };
+ _ownerDrawPanel = new StatusBarPanel
+ {
+ Style = StatusBarPanelStyle.OwnerDraw,
+ Width = 150,
+ ToolTipText = "Owner-drawn panel that displays the current panel count."
+ };
+ _clockPanel = new StatusBarPanel
+ {
+ Text = DateTime.Now.ToString("HH:mm:ss"),
+ AutoSize = StatusBarPanelAutoSize.Contents,
+ Alignment = HorizontalAlignment.Right,
+ ToolTipText = "Clock panel updated once per second."
+ };
+
+ statusBar.Panels.AddRange([_modePanel, _detailsPanel, _ownerDrawPanel, _clockPanel]);
+ statusBar.PanelClick += StatusBar_PanelClick;
+ statusBar.DrawItem += StatusBar_DrawItem;
+
+ return statusBar;
+ }
+
+ private void StatusBar_DrawItem(object sender, StatusBarDrawItemEventArgs e)
+ {
+ using SolidBrush backgroundBrush = new(Color.FromArgb(231, 238, 247));
+ using Pen borderPen = new(Color.SteelBlue);
+
+ e.Graphics.FillRectangle(backgroundBrush, e.Bounds);
+ Rectangle borderBounds = Rectangle.Inflate(e.Bounds, -1, -1);
+ e.Graphics.DrawRectangle(borderPen, borderBounds);
+
+ TextRenderer.DrawText(
+ e.Graphics,
+ $"Panels: {_statusBar.Panels.Count}",
+ Font,
+ e.Bounds,
+ Color.MidnightBlue,
+ TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine);
+ }
+
+ private void StatusBar_PanelClick(object sender, StatusBarPanelClickEventArgs e)
+ {
+ _detailsPanel.Text = "Clicked panel: " + e.StatusBarPanel.Text;
+ AppendLog("Panel click: " + e.StatusBarPanel.Text);
+ }
+
+ private void ShowPanelsButton_Click(object? sender, EventArgs e)
+ {
+ _statusBar.ShowPanels = true;
+ _modePanel.Text = "Panels Mode";
+ _detailsPanel.Text = "Panels restored with spring, owner-draw, and clock behavior.";
+ _statusBar.Invalidate();
+ AppendLog("Switched StatusBar to panel mode.");
+ }
+
+ private void ShowSimpleTextButton_Click(object? sender, EventArgs e)
+ {
+ _statusBar.ShowPanels = false;
+ _statusBar.Text = "Simple text mode at " + DateTime.Now.ToString("HH:mm:ss");
+ AppendLog("Switched StatusBar to simple text mode.");
+ }
+
+ private void AddPanelButton_Click(object? sender, EventArgs e)
+ {
+ _dynamicPanelCount++;
+ StatusBarPanel panel = new()
+ {
+ Text = "Dynamic " + _dynamicPanelCount,
+ AutoSize = StatusBarPanelAutoSize.Contents,
+ ToolTipText = "Runtime-added panel #" + _dynamicPanelCount
+ };
+
+ _statusBar.Panels.Insert(Math.Max(0, _statusBar.Panels.Count - 1), panel);
+ _statusBar.Invalidate();
+ AppendLog("Added dynamic panel: " + panel.Text);
+ }
+
+ private void RemovePanelButton_Click(object? sender, EventArgs e)
+ {
+ if (_statusBar.Panels.Count <= 4)
+ {
+ AppendLog("No dynamic panel available to remove.");
+
+ return;
+ }
+
+ StatusBarPanel panel = _statusBar.Panels[_statusBar.Panels.Count - 2];
+ _statusBar.Panels.Remove(panel);
+ _statusBar.Invalidate();
+ AppendLog("Removed dynamic panel: " + panel.Text);
+ }
+
+ private void ClearLogButton_Click(object? sender, EventArgs e)
+ {
+ _eventLog.Items.Clear();
+ AppendLog("Log cleared.");
+ }
+
+ private void ClockTimer_Tick(object? sender, EventArgs e)
+ {
+ _clockPanel.Text = DateTime.Now.ToString("HH:mm:ss");
+ }
+
+ private void AppendLog(string message)
+ {
+ _eventLog.Items.Insert(0, $"[{DateTime.Now:HH:mm:ss}] {message}");
+
+ if (_eventLog.Items.Count > 200)
+ {
+ _eventLog.Items.RemoveAt(_eventLog.Items.Count - 1);
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/System.Windows.Forms.Legacy.Demo.csproj b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/System.Windows.Forms.Legacy.Demo.csproj
new file mode 100644
index 00000000000..456847a3650
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/System.Windows.Forms.Legacy.Demo.csproj
@@ -0,0 +1,34 @@
+
+
+
+ WinExe
+ net10.0-windows;net48
+ enable
+ preview
+ enable
+ $(NoWarn);SA1400;CS0649;WFDEV006;NU1602
+
+
+
+ true
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/ToolBarForm.Designer.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/ToolBarForm.Designer.cs
new file mode 100644
index 00000000000..1d131f54b19
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/ToolBarForm.Designer.cs
@@ -0,0 +1,131 @@
+using System.ComponentModel;
+using System.Drawing;
+using System.Windows.Forms;
+
+#nullable disable
+
+namespace Demo;
+
+partial class ToolBarForm
+{
+ private IContainer components = null;
+ private Panel _contentPanel = null!;
+ private Label _summaryLabel = null!;
+ private Label _statusLabel = null!;
+ private Label _logLabel = null!;
+ private ListBox _eventLog = null!;
+ private Button _clearLogButton = null!;
+ private Button _toggleAppearanceButton = null!;
+ private Button _toggleWrappableButton = null!;
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && components is not null)
+ {
+ components.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+
+ private void InitializeComponent()
+ {
+ components = new Container();
+ _contentPanel = new Panel();
+ _summaryLabel = new Label();
+ _statusLabel = new Label();
+ _logLabel = new Label();
+ _eventLog = new ListBox();
+ _clearLogButton = new Button();
+ _toggleAppearanceButton = new Button();
+ _toggleWrappableButton = new Button();
+ _contentPanel.SuspendLayout();
+ SuspendLayout();
+ //
+ // _summaryLabel
+ //
+ _summaryLabel.Location = new Point(18, 8);
+ _summaryLabel.Name = "_summaryLabel";
+ _summaryLabel.Size = new Size(554, 40);
+ _summaryLabel.TabIndex = 0;
+ _summaryLabel.Text = "ToolBar demo exercising button styles (push, toggle, separator, drop-down), appearance (Normal/Flat), and the Wrappable property.";
+ //
+ // _toggleAppearanceButton
+ //
+ _toggleAppearanceButton.Location = new Point(18, 62);
+ _toggleAppearanceButton.Name = "_toggleAppearanceButton";
+ _toggleAppearanceButton.Size = new Size(148, 26);
+ _toggleAppearanceButton.TabIndex = 5;
+ _toggleAppearanceButton.Text = "Appearance: Normal";
+ _toggleAppearanceButton.Click += ToggleAppearanceButton_Click;
+ //
+ // _toggleWrappableButton
+ //
+ _toggleWrappableButton.Location = new Point(176, 62);
+ _toggleWrappableButton.Name = "_toggleWrappableButton";
+ _toggleWrappableButton.Size = new Size(148, 26);
+ _toggleWrappableButton.TabIndex = 6;
+ _toggleWrappableButton.Text = "Wrappable: False";
+ _toggleWrappableButton.Click += ToggleWrappableButton_Click;
+ //
+ // _statusLabel
+ //
+ _statusLabel.BorderStyle = BorderStyle.FixedSingle;
+ _statusLabel.Location = new Point(18, 102);
+ _statusLabel.Name = "_statusLabel";
+ _statusLabel.Size = new Size(554, 32);
+ _statusLabel.TabIndex = 1;
+ _statusLabel.Text = "Use the ToolBar buttons and the toggle controls above to exercise ToolBar behavior.";
+ _statusLabel.TextAlign = ContentAlignment.MiddleLeft;
+ //
+ // _logLabel
+ //
+ _logLabel.Location = new Point(18, 148);
+ _logLabel.Name = "_logLabel";
+ _logLabel.Size = new Size(120, 18);
+ _logLabel.TabIndex = 2;
+ _logLabel.Text = "ToolBar event log";
+ //
+ // _clearLogButton
+ //
+ _clearLogButton.Location = new Point(452, 138);
+ _clearLogButton.Name = "_clearLogButton";
+ _clearLogButton.Size = new Size(120, 26);
+ _clearLogButton.TabIndex = 4;
+ _clearLogButton.Text = "Clear Log";
+ _clearLogButton.Click += ClearLogButton_Click;
+ //
+ // _eventLog
+ //
+ _eventLog.FormattingEnabled = true;
+ _eventLog.HorizontalScrollbar = true;
+ _eventLog.Location = new Point(18, 170);
+ _eventLog.Name = "_eventLog";
+ _eventLog.Size = new Size(554, 184);
+ _eventLog.TabIndex = 3;
+ //
+ // _contentPanel
+ //
+ _contentPanel.Dock = DockStyle.Fill;
+ _contentPanel.Controls.Add(_clearLogButton);
+ _contentPanel.Controls.Add(_eventLog);
+ _contentPanel.Controls.Add(_logLabel);
+ _contentPanel.Controls.Add(_statusLabel);
+ _contentPanel.Controls.Add(_toggleWrappableButton);
+ _contentPanel.Controls.Add(_toggleAppearanceButton);
+ _contentPanel.Controls.Add(_summaryLabel);
+ _contentPanel.ResumeLayout(false);
+ //
+ // ToolBarForm
+ //
+ AutoScaleDimensions = new SizeF(7F, 15F);
+ AutoScaleMode = AutoScaleMode.Font;
+ ClientSize = new Size(592, 398);
+ Controls.Add(_contentPanel);
+ MinimumSize = new Size(608, 437);
+ Name = "ToolBarForm";
+ StartPosition = FormStartPosition.CenterParent;
+ Text = "ToolBar Demo";
+ ResumeLayout(false);
+ }
+}
\ No newline at end of file
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/ToolBarForm.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/ToolBarForm.cs
new file mode 100644
index 00000000000..60bd52ac84f
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/ToolBarForm.cs
@@ -0,0 +1,134 @@
+using System.Windows.Forms;
+
+namespace Demo;
+
+public partial class ToolBarForm : Form
+{
+ private readonly ToolBar _demoToolBar;
+
+ public ToolBarForm()
+ {
+ InitializeComponent();
+
+ _demoToolBar = CreateToolBar();
+ Controls.Add(_demoToolBar);
+
+ AppendLog("ToolBar demo ready.");
+ }
+
+ private ToolBar CreateToolBar()
+ {
+ ToolBar toolBar = new()
+ {
+ TextAlign = ToolBarTextAlign.Right,
+ ShowToolTips = true,
+ DropDownArrows = true,
+ Wrappable = false
+ };
+
+ toolBar.Buttons.Add("1st button");
+ ToolBarButton firstButton = toolBar.Buttons[0];
+ firstButton.ToolTipText = "This is the first button";
+
+ ToolBarButton separatorButton = new("sep1")
+ {
+ Style = ToolBarButtonStyle.Separator
+ };
+ toolBar.Buttons.Add(separatorButton);
+
+ ToolBarButton toggleButton = new("btn2 toggle")
+ {
+ Style = ToolBarButtonStyle.ToggleButton,
+ ToolTipText = "This is the second button"
+ };
+ toolBar.Buttons.Add(toggleButton);
+
+ ToolBarButton dropDownButton = new("btn3 drop-down")
+ {
+ Style = ToolBarButtonStyle.DropDownButton,
+ ToolTipText = "This is the third button"
+ };
+
+ MenuItem waveMenuItem = new("Wave", WaveMenuItem_Click);
+ MenuItem statusMenuItem = new("Write Status", WriteStatusMenuItem_Click);
+ dropDownButton.DropDownMenu = new ContextMenu([waveMenuItem, statusMenuItem]);
+ toolBar.Buttons.Add(dropDownButton);
+
+ toolBar.Buttons.Add(new ToolBarButton("sep2") { Style = ToolBarButtonStyle.Separator });
+
+ toolBar.Buttons.Add(new ToolBarButton("btn4 push") { ToolTipText = "Push button 4" });
+ toolBar.Buttons.Add(new ToolBarButton("btn5 toggle") { Style = ToolBarButtonStyle.ToggleButton, ToolTipText = "Toggle button 5" });
+ toolBar.Buttons.Add(new ToolBarButton("sep3") { Style = ToolBarButtonStyle.Separator });
+
+ ToolBarButton dropDownButton2 = new("btn6 drop-down")
+ {
+ Style = ToolBarButtonStyle.DropDownButton,
+ ToolTipText = "Drop-down button 6"
+ };
+ dropDownButton2.DropDownMenu = new ContextMenu([new MenuItem("Action A", WaveMenuItem_Click), new MenuItem("Action B", WriteStatusMenuItem_Click)]);
+ toolBar.Buttons.Add(dropDownButton2);
+
+ toolBar.Buttons.Add(new ToolBarButton("btn7 push") { ToolTipText = "Push button 7" });
+ toolBar.Buttons.Add(new ToolBarButton("btn8 push") { ToolTipText = "Push button 8" });
+
+ toolBar.ButtonClick += DemoToolBar_ButtonClick;
+
+ return toolBar;
+ }
+
+ private void DemoToolBar_ButtonClick(object? sender, ToolBarButtonClickEventArgs e)
+ {
+ _statusLabel.Text = "Clicked: " + e.Button.Text;
+ AppendLog("Toolbar button clicked: " + e.Button.Text);
+ MessageBox.Show(this, "Button clicked. text = " + e.Button.Text, Text);
+ }
+
+ private void WaveMenuItem_Click(object? sender, EventArgs e)
+ {
+ _statusLabel.Text = "Wave back";
+ AppendLog("Toolbar drop-down menu clicked: Wave");
+ MessageBox.Show(this, "Wave back", Text);
+ }
+
+ private void WriteStatusMenuItem_Click(object? sender, EventArgs e)
+ {
+ string timestamp = DateTime.Now.ToString("HH:mm:ss");
+ _statusLabel.Text = "Status updated at " + timestamp;
+ AppendLog("Toolbar drop-down menu clicked: Write Status at " + timestamp);
+ }
+
+ private void ToggleAppearanceButton_Click(object? sender, EventArgs e)
+ {
+ _demoToolBar.Appearance = _demoToolBar.Appearance == ToolBarAppearance.Normal
+ ? ToolBarAppearance.Flat
+ : ToolBarAppearance.Normal;
+
+ _toggleAppearanceButton.Text = "Appearance: " + _demoToolBar.Appearance;
+ _statusLabel.Text = "ToolBar.Appearance → " + _demoToolBar.Appearance;
+ AppendLog("ToolBar.Appearance toggled to: " + _demoToolBar.Appearance);
+ }
+
+ private void ToggleWrappableButton_Click(object? sender, EventArgs e)
+ {
+ _demoToolBar.Wrappable = !_demoToolBar.Wrappable;
+ _toggleWrappableButton.Text = "Wrappable: " + _demoToolBar.Wrappable;
+ _statusLabel.Text = "ToolBar.Wrappable → " + _demoToolBar.Wrappable;
+ AppendLog("ToolBar.Wrappable toggled to: " + _demoToolBar.Wrappable);
+ }
+
+ private void ClearLogButton_Click(object? sender, EventArgs e)
+ {
+ _eventLog.Items.Clear();
+ AppendLog("Log cleared.");
+ }
+
+ private void AppendLog(string message)
+ {
+ _eventLog.Items.Insert(0, $"[{DateTime.Now:HH:mm:ss}] {message}");
+
+ if (_eventLog.Items.Count > 200)
+ {
+ _eventLog.Items.RemoveAt(_eventLog.Items.Count - 1);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/AdjustWindowRectTests.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/AdjustWindowRectTests.cs
new file mode 100644
index 00000000000..c6702498507
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/AdjustWindowRectTests.cs
@@ -0,0 +1,179 @@
+namespace System.Windows.Forms.Tests
+{
+ using System.Drawing;
+
+ public class AdjustWindowRectTests
+ {
+ [StaFact]
+ public void Form_WindowRectCalculation_HandlesDpiScaling()
+ {
+ // Test that menu consideration works across different DPI scenarios
+
+ // Arrange
+ using var form = new Form();
+ var menu = new MainMenu();
+ menu.MenuItems.Add(new MenuItem("Test"));
+ form.Menu = menu;
+
+ var baseClientSize = new Size(400, 300);
+
+ // Act
+ form.ClientSize = baseClientSize;
+ var windowSize = form.Size;
+
+ // Change the client size again to ensure consistency
+ form.ClientSize = new Size(600, 450);
+ var newWindowSize = form.Size;
+
+ // Assert
+ // The window size should scale proportionally with client size
+ var clientSizeChange = new Size(200, 150); // 600-400, 450-300
+ var windowSizeChange = new Size(newWindowSize.Width - windowSize.Width, newWindowSize.Height - windowSize.Height);
+
+ Assert.Equal(clientSizeChange, windowSizeChange);
+ }
+
+ [StaFact]
+ public void Form_CreateParams_MenuConsideredInWindowRectCalculation()
+ {
+ // Test that CreateParams and window rect calculations are consistent
+
+ // Arrange
+ using var formWithMenu = new Form();
+ using var formWithoutMenu = new Form();
+
+ var menu = new MainMenu();
+ menu.MenuItems.Add(new MenuItem("File"));
+ formWithMenu.Menu = menu;
+
+ // Set same size and create handles to trigger CreateParams usage
+ var testSize = new Size(300, 200);
+ formWithMenu.Size = testSize;
+ formWithoutMenu.Size = testSize;
+
+ // Force handle creation to trigger CreateParams
+ _ = formWithMenu.Handle;
+ _ = formWithoutMenu.Handle;
+
+ // Act & Assert
+ // The forms should maintain their size relationships after handle creation
+ // which exercises the CreateParams path
+ formWithMenu.ClientSize = new Size(250, 150);
+ formWithoutMenu.ClientSize = new Size(250, 150);
+
+ Assert.True(formWithMenu.Size.Height > formWithoutMenu.Size.Height,
+ "After handle creation, form with menu should still be taller");
+ }
+
+ [StaFact]
+ public void ToolStripTextBox_InFormWithMenu_IndependentSizing()
+ {
+ // Test that ToolStripTextBox in a form with menu isn't affected by the form's menu
+
+ // Arrange
+ using var formWithMenu = new Form();
+ using var formWithoutMenu = new Form();
+
+ var menu = new MainMenu();
+ menu.MenuItems.Add(new MenuItem("File"));
+ formWithMenu.Menu = menu;
+
+ using var toolStrip1 = new ToolStrip();
+ using var toolStrip2 = new ToolStrip();
+ using var textBox1 = new ToolStripTextBox();
+ using var textBox2 = new ToolStripTextBox();
+
+ toolStrip1.Items.Add(textBox1);
+ toolStrip2.Items.Add(textBox2);
+ formWithMenu.Controls.Add(toolStrip1);
+ formWithoutMenu.Controls.Add(toolStrip2);
+
+ // Act
+ textBox1.Size = new Size(120, 22);
+ textBox2.Size = new Size(120, 22);
+
+ // Assert
+ Assert.Equal(textBox2.Size, textBox1.Size);
+ }
+
+ [StaFact]
+ public void Control_SizeCalculations_ConsistentForDifferentControlTypes()
+ {
+ // Test various control types to ensure they handle menu considerations correctly
+
+ // Arrange & Act & Assert
+ using var form1 = new Form(); // No menu
+ using var form2 = new Form(); // Menu with items
+
+ var menuWithItems = new MainMenu();
+ menuWithItems.MenuItems.Add(new MenuItem("File"));
+ form2.Menu = menuWithItems;
+
+ var clientSize = new Size(400, 300);
+
+ // Act
+ form1.ClientSize = clientSize;
+ form2.ClientSize = clientSize;
+
+ Func[] controlTypes =
+ [
+ () => new Button(),
+ () => new Label(),
+ () => new TextBox(),
+ () => new Panel(),
+ () => new GroupBox()
+ ];
+
+ foreach (var createControl in controlTypes)
+ {
+ using var control1 = createControl();
+ using var control2 = createControl();
+
+ var testSize = new Size(100, 50);
+ control1.Size = testSize;
+ control2.Size = testSize;
+
+ form1.Controls.Add(control1);
+ form2.Controls.Add(control2);
+
+ Assert.Equal(control2.Size, control1.Size);
+ }
+ }
+
+ [StaFact]
+ public void Form_MenuVisibility_AffectsWindowSizeCalculation()
+ {
+ // Test the specific logic: menu only affects size if it has items
+
+ // Arrange
+ using var form1 = new Form(); // No menu
+ using var form2 = new Form(); // Empty menu
+ using var form3 = new Form(); // Menu with items
+
+ var emptyMenu = new MainMenu();
+ form2.Menu = emptyMenu;
+
+ var menuWithItems = new MainMenu();
+ menuWithItems.MenuItems.Add(new MenuItem("File"));
+ form3.Menu = menuWithItems;
+
+ var clientSize = new Size(400, 300);
+
+ // Act
+ form1.ClientSize = clientSize;
+ form2.ClientSize = clientSize;
+ form3.ClientSize = clientSize;
+
+ // Assert
+ // Forms 1 and 2 should have same height (no menu or empty menu)
+ Assert.Equal(form2.Size.Height, form1.Size.Height);
+
+ // Form 3 should be taller (has menu items)
+ Assert.True(form3.Size.Height > form1.Size.Height,
+ "Form with menu items should be taller than form without menu");
+
+ Assert.True(form3.Size.Height > form2.Size.Height,
+ "Form with menu items should be taller than form with empty menu");
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/DataGridCellTests.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/DataGridCellTests.cs
new file mode 100644
index 00000000000..c2c78729603
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/DataGridCellTests.cs
@@ -0,0 +1,142 @@
+namespace System.Windows.Forms.Tests;
+
+public class DataGridCellTests
+{
+ [StaFact]
+ public void DataGridCell_Ctor_Default()
+ {
+ DataGridCell cell = default;
+
+ Assert.Equal(0, cell.RowNumber);
+ Assert.Equal(0, cell.ColumnNumber);
+ }
+
+ [StaFact]
+ public void DataGridCell_Ctor_Int_Int_Zero()
+ {
+ DataGridCell cell = new(0, 0);
+
+ Assert.Equal(0, cell.RowNumber);
+ Assert.Equal(0, cell.ColumnNumber);
+ }
+
+ [StaFact]
+ public void DataGridCell_Ctor_Int_Int_Positive()
+ {
+ DataGridCell cell = new(1, 2);
+
+ Assert.Equal(1, cell.RowNumber);
+ Assert.Equal(2, cell.ColumnNumber);
+ }
+
+ [StaFact]
+ public void DataGridCell_Ctor_Int_Int_Negative()
+ {
+ DataGridCell cell = new(-1, -2);
+
+ Assert.Equal(-1, cell.RowNumber);
+ Assert.Equal(-2, cell.ColumnNumber);
+ }
+
+ [StaFact]
+ public void DataGridCell_RowNumber_Set_GetReturnsExpected()
+ {
+ DataGridCell cell = new()
+ {
+ RowNumber = 5
+ };
+
+ Assert.Equal(5, cell.RowNumber);
+
+ // Set same.
+ cell.RowNumber = 5;
+
+ Assert.Equal(5, cell.RowNumber);
+
+ // Set different.
+ cell.RowNumber = 10;
+
+ Assert.Equal(10, cell.RowNumber);
+ }
+
+ [StaFact]
+ public void DataGridCell_ColumnNumber_Set_GetReturnsExpected()
+ {
+ DataGridCell cell = new()
+ {
+ ColumnNumber = 3
+ };
+
+ Assert.Equal(3, cell.ColumnNumber);
+
+ // Set same.
+ cell.ColumnNumber = 3;
+
+ Assert.Equal(3, cell.ColumnNumber);
+
+ // Set different.
+ cell.ColumnNumber = 7;
+
+ Assert.Equal(7, cell.ColumnNumber);
+ }
+
+ [StaFact]
+ public void DataGridCell_Equals_SameRowAndColumn_ReturnsTrue()
+ {
+ DataGridCell cell1 = new(1, 2);
+ DataGridCell cell2 = new(1, 2);
+
+ Assert.True(cell1.Equals(cell2));
+ Assert.Equal(cell1.GetHashCode(), cell2.GetHashCode());
+ }
+
+ [StaFact]
+ public void DataGridCell_Equals_DifferentRow_ReturnsFalse()
+ {
+ DataGridCell cell1 = new(1, 2);
+ DataGridCell cell2 = new(2, 2);
+
+ Assert.False(cell1.Equals(cell2));
+ }
+
+ [StaFact]
+ public void DataGridCell_Equals_DifferentColumn_ReturnsFalse()
+ {
+ DataGridCell cell1 = new(1, 2);
+ DataGridCell cell2 = new(1, 3);
+
+ Assert.False(cell1.Equals(cell2));
+ }
+
+ [StaFact]
+ public void DataGridCell_Equals_NonDataGridCellObject_ReturnsFalse()
+ {
+ DataGridCell cell = new(1, 2);
+
+ Assert.False(cell.Equals(new object()));
+ }
+
+ [StaFact]
+ public void DataGridCell_Equals_Null_ReturnsFalse()
+ {
+ DataGridCell cell = new(1, 2);
+
+ Assert.False(cell.Equals(null));
+ }
+
+ [StaFact]
+ public void DataGridCell_ToString_ReturnsExpected()
+ {
+ DataGridCell cell = new(1, 2);
+
+ Assert.Equal("DataGridCell {RowNumber = 1, ColumnNumber = 2}", cell.ToString());
+ }
+
+ [StaFact]
+ public void DataGridCell_ToString_DefaultValues_ReturnsExpected()
+ {
+ DataGridCell cell = default;
+
+ Assert.Equal("DataGridCell {RowNumber = 0, ColumnNumber = 0}", cell.ToString());
+ }
+}
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/DataGridTableStyleTests.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/DataGridTableStyleTests.cs
new file mode 100644
index 00000000000..5756c9f88b1
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/DataGridTableStyleTests.cs
@@ -0,0 +1,373 @@
+using System.Drawing;
+
+namespace System.Windows.Forms.Tests;
+
+public class DataGridTableStyleTests
+{
+ [StaFact]
+ public void DataGridTableStyle_Ctor_Default()
+ {
+ using DataGridTableStyle style = new();
+
+ Assert.True(style.AllowSorting);
+ Assert.Equal(SystemColors.Window, style.AlternatingBackColor);
+ Assert.Equal(SystemColors.Window, style.BackColor);
+ Assert.True(style.ColumnHeadersVisible);
+ Assert.Null(style.DataGrid);
+ Assert.Equal(SystemColors.WindowText, style.ForeColor);
+ Assert.Empty(style.GridColumnStyles);
+ Assert.Same(style.GridColumnStyles, style.GridColumnStyles);
+ Assert.Equal(SystemColors.Control, style.GridLineColor);
+ Assert.Equal(DataGridLineStyle.Solid, style.GridLineStyle);
+ Assert.Equal(SystemColors.Control, style.HeaderBackColor);
+ Assert.Same(Control.DefaultFont, style.HeaderFont);
+ Assert.Equal(SystemColors.ControlText, style.HeaderForeColor);
+ Assert.Equal(SystemColors.HotTrack, style.LinkColor);
+ Assert.Equal(SystemColors.HotTrack, style.LinkHoverColor);
+ Assert.Empty(style.MappingName);
+ Assert.Equal(75, style.PreferredColumnWidth);
+ Assert.Equal(Control.DefaultFont.Height + 3, style.PreferredRowHeight);
+ Assert.False(style.ReadOnly);
+ Assert.True(style.RowHeadersVisible);
+ Assert.Equal(35, style.RowHeaderWidth);
+ Assert.Equal(SystemColors.ActiveCaption, style.SelectionBackColor);
+ Assert.Equal(SystemColors.ActiveCaptionText, style.SelectionForeColor);
+ Assert.Null(style.Site);
+ }
+
+ [StaFact]
+ public void DataGridTableStyle_AllowSorting_Set_GetReturnsExpected()
+ {
+ using DataGridTableStyle style = new();
+
+ style.AllowSorting = false;
+
+ Assert.False(style.AllowSorting);
+
+ // Set same.
+ style.AllowSorting = false;
+
+ Assert.False(style.AllowSorting);
+
+ // Set different.
+ style.AllowSorting = true;
+
+ Assert.True(style.AllowSorting);
+ }
+
+ [StaFact]
+ public void DataGridTableStyle_AllowSorting_SetWithHandler_CallsAllowSortingChanged()
+ {
+ using DataGridTableStyle style = new();
+ int callCount = 0;
+ EventHandler handler = (sender, e) =>
+ {
+ Assert.Same(style, sender);
+ Assert.Same(EventArgs.Empty, e);
+ callCount++;
+ };
+ style.AllowSortingChanged += handler;
+
+ // Set different.
+ style.AllowSorting = false;
+
+ Assert.False(style.AllowSorting);
+ Assert.Equal(1, callCount);
+
+ // Set same.
+ style.AllowSorting = false;
+
+ Assert.False(style.AllowSorting);
+ Assert.Equal(1, callCount);
+
+ // Set different.
+ style.AllowSorting = true;
+
+ Assert.True(style.AllowSorting);
+ Assert.Equal(2, callCount);
+
+ // Remove handler.
+ style.AllowSortingChanged -= handler;
+ style.AllowSorting = false;
+
+ Assert.Equal(2, callCount);
+ }
+
+ [StaFact]
+ public void DataGridTableStyle_AllowSorting_SetDefaultTableStyle_ThrowsArgumentException()
+ {
+ using DataGridTableStyle style = new(isDefaultTableStyle: true);
+
+ Assert.Throws(() => style.AllowSorting = false);
+ Assert.True(style.AllowSorting);
+ }
+
+ [StaFact]
+ public void DataGridTableStyle_AlternatingBackColor_Set_GetReturnsExpected()
+ {
+ using DataGridTableStyle style = new();
+
+ style.AlternatingBackColor = Color.Red;
+
+ Assert.Equal(Color.Red, style.AlternatingBackColor);
+
+ // Set same.
+ style.AlternatingBackColor = Color.Red;
+
+ Assert.Equal(Color.Red, style.AlternatingBackColor);
+
+ // Set different.
+ style.AlternatingBackColor = Color.Blue;
+
+ Assert.Equal(Color.Blue, style.AlternatingBackColor);
+ }
+
+ [StaFact]
+ public void DataGridTableStyle_AlternatingBackColor_SetWithHandler_CallsAlternatingBackColorChanged()
+ {
+ using DataGridTableStyle style = new();
+ int callCount = 0;
+ EventHandler handler = (sender, e) =>
+ {
+ Assert.Same(style, sender);
+ Assert.Same(EventArgs.Empty, e);
+ callCount++;
+ };
+ style.AlternatingBackColorChanged += handler;
+
+ // Set different.
+ style.AlternatingBackColor = Color.Red;
+
+ Assert.Equal(Color.Red, style.AlternatingBackColor);
+ Assert.Equal(1, callCount);
+
+ // Set same.
+ style.AlternatingBackColor = Color.Red;
+
+ Assert.Equal(Color.Red, style.AlternatingBackColor);
+ Assert.Equal(1, callCount);
+
+ // Set different.
+ style.AlternatingBackColor = Color.Blue;
+
+ Assert.Equal(Color.Blue, style.AlternatingBackColor);
+ Assert.Equal(2, callCount);
+
+ // Remove handler.
+ style.AlternatingBackColorChanged -= handler;
+ style.AlternatingBackColor = Color.Red;
+
+ Assert.Equal(2, callCount);
+ }
+
+ [StaFact]
+ public void DataGridTableStyle_AlternatingBackColor_SetEmpty_ThrowsArgumentException()
+ {
+ using DataGridTableStyle style = new();
+
+ Assert.Throws(() => style.AlternatingBackColor = Color.Empty);
+ }
+
+ [StaFact]
+ public void DataGridTableStyle_AlternatingBackColor_SetDefaultTableStyle_ThrowsArgumentException()
+ {
+ using DataGridTableStyle style = new(isDefaultTableStyle: true);
+
+ Assert.Throws(() => style.AlternatingBackColor = Color.Red);
+ Assert.Equal(SystemColors.Window, style.AlternatingBackColor);
+ }
+
+ [StaFact]
+ public void DataGridTableStyle_BackColor_SetWithHandler_CallsBackColorChanged()
+ {
+ using DataGridTableStyle style = new();
+ int callCount = 0;
+ EventHandler handler = (sender, e) =>
+ {
+ Assert.Same(style, sender);
+ Assert.Same(EventArgs.Empty, e);
+ callCount++;
+ };
+ style.BackColorChanged += handler;
+
+ // Set different.
+ style.BackColor = Color.Red;
+
+ Assert.Equal(Color.Red, style.BackColor);
+ Assert.Equal(1, callCount);
+
+ // Set same.
+ style.BackColor = Color.Red;
+
+ Assert.Equal(Color.Red, style.BackColor);
+ Assert.Equal(1, callCount);
+
+ // Set different.
+ style.BackColor = Color.Blue;
+
+ Assert.Equal(Color.Blue, style.BackColor);
+ Assert.Equal(2, callCount);
+
+ // Remove handler.
+ style.BackColorChanged -= handler;
+ style.BackColor = Color.Red;
+
+ Assert.Equal(2, callCount);
+ }
+
+ [StaFact]
+ public void DataGridTableStyle_BackColor_SetEmpty_ThrowsArgumentException()
+ {
+ using DataGridTableStyle style = new();
+
+ Assert.Throws(() => style.BackColor = Color.Empty);
+ }
+
+ [StaFact]
+ public void DataGridTableStyle_MappingName_Set_GetReturnsExpected()
+ {
+ using DataGridTableStyle style = new();
+
+ style.MappingName = "Customers";
+
+ Assert.Equal("Customers", style.MappingName);
+
+ // Set same.
+ style.MappingName = "Customers";
+
+ Assert.Equal("Customers", style.MappingName);
+
+ // Set different.
+ style.MappingName = "Orders";
+
+ Assert.Equal("Orders", style.MappingName);
+
+ // Set empty.
+ style.MappingName = string.Empty;
+
+ Assert.Empty(style.MappingName);
+ }
+
+ [StaFact]
+ public void DataGridTableStyle_PreferredColumnWidth_Set_GetReturnsExpected()
+ {
+ using DataGridTableStyle style = new();
+
+ style.PreferredColumnWidth = 200;
+
+ Assert.Equal(200, style.PreferredColumnWidth);
+
+ // Set same.
+ style.PreferredColumnWidth = 200;
+
+ Assert.Equal(200, style.PreferredColumnWidth);
+
+ // Restore default.
+ style.PreferredColumnWidth = 75;
+
+ Assert.Equal(75, style.PreferredColumnWidth);
+ }
+
+ [StaFact]
+ public void DataGridTableStyle_ReadOnly_Set_GetReturnsExpected()
+ {
+ using DataGridTableStyle style = new();
+
+ style.ReadOnly = true;
+
+ Assert.True(style.ReadOnly);
+
+ // Set same.
+ style.ReadOnly = true;
+
+ Assert.True(style.ReadOnly);
+
+ // Set different.
+ style.ReadOnly = false;
+
+ Assert.False(style.ReadOnly);
+ }
+
+ [StaFact]
+ public void DataGridTableStyle_ReadOnly_SetWithHandler_CallsReadOnlyChanged()
+ {
+ using DataGridTableStyle style = new();
+ int callCount = 0;
+ EventHandler handler = (sender, e) =>
+ {
+ Assert.Same(style, sender);
+ Assert.Same(EventArgs.Empty, e);
+ callCount++;
+ };
+ style.ReadOnlyChanged += handler;
+
+ // Set different.
+ style.ReadOnly = true;
+
+ Assert.True(style.ReadOnly);
+ Assert.Equal(1, callCount);
+
+ // Set same.
+ style.ReadOnly = true;
+
+ Assert.True(style.ReadOnly);
+ Assert.Equal(1, callCount);
+
+ // Set different.
+ style.ReadOnly = false;
+
+ Assert.False(style.ReadOnly);
+ Assert.Equal(2, callCount);
+
+ // Remove handler.
+ style.ReadOnlyChanged -= handler;
+ style.ReadOnly = true;
+
+ Assert.Equal(2, callCount);
+ }
+
+ [StaFact]
+ public void DataGridTableStyle_GridLineStyle_Set_GetReturnsExpected()
+ {
+ using DataGridTableStyle style = new();
+
+ style.GridLineStyle = DataGridLineStyle.None;
+
+ Assert.Equal(DataGridLineStyle.None, style.GridLineStyle);
+
+ // Set same.
+ style.GridLineStyle = DataGridLineStyle.None;
+
+ Assert.Equal(DataGridLineStyle.None, style.GridLineStyle);
+
+ // Set different.
+ style.GridLineStyle = DataGridLineStyle.Solid;
+
+ Assert.Equal(DataGridLineStyle.Solid, style.GridLineStyle);
+ }
+
+ [StaFact]
+ public void DataGridTableStyle_GridColumnStyles_AddAndRemove_UpdatesCollection()
+ {
+ using DataGridTableStyle style = new()
+ {
+ MappingName = "Items"
+ };
+
+ DataGridTextBoxColumn column = new()
+ {
+ MappingName = "Name",
+ HeaderText = "Item Name",
+ Width = 150
+ };
+
+ style.GridColumnStyles.Add(column);
+
+ Assert.Single(style.GridColumnStyles);
+ Assert.Same(column, style.GridColumnStyles["Name"]);
+
+ style.GridColumnStyles.Remove(column);
+
+ Assert.Empty(style.GridColumnStyles);
+ }
+}
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/DataGridTests.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/DataGridTests.cs
new file mode 100644
index 00000000000..bb1e1c11008
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/DataGridTests.cs
@@ -0,0 +1,800 @@
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+
+namespace System.Windows.Forms.Tests;
+
+public class DataGridTests
+{
+ [StaFact]
+ public void DataGrid_Ctor_Default()
+ {
+ using SubDataGrid dataGrid = new();
+
+ Assert.True(dataGrid.AllowNavigation);
+ Assert.True(dataGrid.AllowSorting);
+ Assert.Equal(SystemColors.Window, dataGrid.AlternatingBackColor);
+ Assert.Equal(SystemColors.Window, dataGrid.BackColor);
+ Assert.Equal(SystemColors.AppWorkspace, dataGrid.BackgroundColor);
+ Assert.Equal(BorderStyle.Fixed3D, dataGrid.BorderStyle);
+ Assert.Equal(SystemColors.ActiveCaption, dataGrid.CaptionBackColor);
+ Assert.Equal(SystemColors.ActiveCaptionText, dataGrid.CaptionForeColor);
+ Assert.Empty(dataGrid.CaptionText);
+ Assert.True(dataGrid.CaptionVisible);
+ Assert.True(dataGrid.ColumnHeadersVisible);
+ Assert.Equal(0, dataGrid.CurrentCell.RowNumber);
+ Assert.Equal(0, dataGrid.CurrentCell.ColumnNumber);
+ Assert.Equal(-1, dataGrid.CurrentRowIndex);
+ Assert.Empty(dataGrid.DataMember);
+ Assert.Null(dataGrid.DataSource);
+ Assert.Equal(0, dataGrid.FirstVisibleColumn);
+ Assert.False(dataGrid.FlatMode);
+ Assert.Equal(SystemColors.WindowText, dataGrid.ForeColor);
+ Assert.Equal(SystemColors.Control, dataGrid.GridLineColor);
+ Assert.Equal(DataGridLineStyle.Solid, dataGrid.GridLineStyle);
+ Assert.Equal(SystemColors.Control, dataGrid.HeaderBackColor);
+ Assert.Equal(SystemColors.ControlText, dataGrid.HeaderForeColor);
+ Assert.NotNull(dataGrid.HorizScrollBar);
+ Assert.Same(dataGrid.HorizScrollBar, dataGrid.HorizScrollBar);
+ Assert.Equal(SystemColors.HotTrack, dataGrid.LinkColor);
+ Assert.Equal(SystemColors.HotTrack, dataGrid.LinkHoverColor);
+ Assert.Null(dataGrid.ListManager);
+ Assert.Equal(SystemColors.Control, dataGrid.ParentRowsBackColor);
+ Assert.Equal(SystemColors.WindowText, dataGrid.ParentRowsForeColor);
+ Assert.Equal(DataGridParentRowsLabelStyle.Both, dataGrid.ParentRowsLabelStyle);
+ Assert.True(dataGrid.ParentRowsVisible);
+ Assert.Equal(75, dataGrid.PreferredColumnWidth);
+ Assert.Equal(Control.DefaultFont.Height + 3, dataGrid.PreferredRowHeight);
+ Assert.False(dataGrid.ReadOnly);
+ Assert.True(dataGrid.RowHeadersVisible);
+ Assert.Equal(35, dataGrid.RowHeaderWidth);
+ Assert.Equal(SystemColors.ActiveCaption, dataGrid.SelectionBackColor);
+ Assert.Equal(SystemColors.ActiveCaptionText, dataGrid.SelectionForeColor);
+ Assert.Equal(new Size(130, 80), dataGrid.Size);
+ Assert.Empty(dataGrid.TableStyles);
+ Assert.Same(dataGrid.TableStyles, dataGrid.TableStyles);
+ Assert.NotNull(dataGrid.VertScrollBar);
+ Assert.Same(dataGrid.VertScrollBar, dataGrid.VertScrollBar);
+ Assert.Equal(0, dataGrid.VisibleColumnCount);
+ Assert.Equal(0, dataGrid.VisibleRowCount);
+ }
+
+ [StaFact]
+ public void DataGrid_AllowNavigation_Set_GetReturnsExpected()
+ {
+ using DataGrid dataGrid = new();
+
+ dataGrid.AllowNavigation = false;
+
+ Assert.False(dataGrid.AllowNavigation);
+
+ // Set same.
+ dataGrid.AllowNavigation = false;
+
+ Assert.False(dataGrid.AllowNavigation);
+
+ // Set different.
+ dataGrid.AllowNavigation = true;
+
+ Assert.True(dataGrid.AllowNavigation);
+ }
+
+ [StaFact]
+ public void DataGrid_AllowNavigation_SetWithHandler_CallsAllowNavigationChanged()
+ {
+ using DataGrid dataGrid = new();
+ int callCount = 0;
+ EventHandler handler = (sender, e) =>
+ {
+ Assert.Same(dataGrid, sender);
+ Assert.Same(EventArgs.Empty, e);
+ callCount++;
+ };
+ dataGrid.AllowNavigationChanged += handler;
+
+ // Set different.
+ dataGrid.AllowNavigation = false;
+
+ Assert.False(dataGrid.AllowNavigation);
+ Assert.Equal(1, callCount);
+
+ // Set same.
+ dataGrid.AllowNavigation = false;
+
+ Assert.False(dataGrid.AllowNavigation);
+ Assert.Equal(1, callCount);
+
+ // Set different.
+ dataGrid.AllowNavigation = true;
+
+ Assert.True(dataGrid.AllowNavigation);
+ Assert.Equal(2, callCount);
+
+ // Remove handler.
+ dataGrid.AllowNavigationChanged -= handler;
+ dataGrid.AllowNavigation = false;
+
+ Assert.Equal(2, callCount);
+ }
+
+ [StaFact]
+ public void DataGrid_AllowSorting_Set_GetReturnsExpected()
+ {
+ using DataGrid dataGrid = new();
+
+ dataGrid.AllowSorting = false;
+
+ Assert.False(dataGrid.AllowSorting);
+
+ // Set same.
+ dataGrid.AllowSorting = false;
+
+ Assert.False(dataGrid.AllowSorting);
+
+ // Set different.
+ dataGrid.AllowSorting = true;
+
+ Assert.True(dataGrid.AllowSorting);
+ }
+
+ [StaFact]
+ public void DataGrid_AlternatingBackColor_Set_GetReturnsExpected()
+ {
+ using DataGrid dataGrid = new();
+
+ dataGrid.AlternatingBackColor = Color.Red;
+
+ Assert.Equal(Color.Red, dataGrid.AlternatingBackColor);
+
+ // Set same.
+ dataGrid.AlternatingBackColor = Color.Red;
+
+ Assert.Equal(Color.Red, dataGrid.AlternatingBackColor);
+
+ // Set different.
+ dataGrid.AlternatingBackColor = Color.Blue;
+
+ Assert.Equal(Color.Blue, dataGrid.AlternatingBackColor);
+ }
+
+ [StaFact]
+ public void DataGrid_AlternatingBackColor_SetEmpty_ThrowsArgumentException()
+ {
+ using DataGrid dataGrid = new();
+
+ Assert.Throws(() => dataGrid.AlternatingBackColor = Color.Empty);
+ }
+
+ [StaFact]
+ public void DataGrid_BorderStyle_Set_GetReturnsExpected()
+ {
+ using DataGrid dataGrid = new();
+
+ dataGrid.BorderStyle = BorderStyle.FixedSingle;
+
+ Assert.Equal(BorderStyle.FixedSingle, dataGrid.BorderStyle);
+
+ // Set same.
+ dataGrid.BorderStyle = BorderStyle.FixedSingle;
+
+ Assert.Equal(BorderStyle.FixedSingle, dataGrid.BorderStyle);
+
+ // Set different.
+ dataGrid.BorderStyle = BorderStyle.None;
+
+ Assert.Equal(BorderStyle.None, dataGrid.BorderStyle);
+ }
+
+ [StaFact]
+ public void DataGrid_BorderStyle_SetWithHandler_CallsBorderStyleChanged()
+ {
+ using DataGrid dataGrid = new();
+ int callCount = 0;
+ EventHandler handler = (sender, e) =>
+ {
+ Assert.Same(dataGrid, sender);
+ Assert.Same(EventArgs.Empty, e);
+ callCount++;
+ };
+ dataGrid.BorderStyleChanged += handler;
+
+ // Set different.
+ dataGrid.BorderStyle = BorderStyle.FixedSingle;
+
+ Assert.Equal(BorderStyle.FixedSingle, dataGrid.BorderStyle);
+ Assert.Equal(1, callCount);
+
+ // Set same.
+ dataGrid.BorderStyle = BorderStyle.FixedSingle;
+
+ Assert.Equal(BorderStyle.FixedSingle, dataGrid.BorderStyle);
+ Assert.Equal(1, callCount);
+
+ // Set different.
+ dataGrid.BorderStyle = BorderStyle.None;
+
+ Assert.Equal(BorderStyle.None, dataGrid.BorderStyle);
+ Assert.Equal(2, callCount);
+
+ // Remove handler.
+ dataGrid.BorderStyleChanged -= handler;
+ dataGrid.BorderStyle = BorderStyle.Fixed3D;
+
+ Assert.Equal(2, callCount);
+ }
+
+ [StaFact]
+ public void DataGrid_CaptionText_Set_GetReturnsExpected()
+ {
+ using DataGrid dataGrid = new();
+
+ dataGrid.CaptionText = "My Grid";
+
+ Assert.Equal("My Grid", dataGrid.CaptionText);
+
+ // Set same.
+ dataGrid.CaptionText = "My Grid";
+
+ Assert.Equal("My Grid", dataGrid.CaptionText);
+
+ // Set different.
+ dataGrid.CaptionText = string.Empty;
+
+ Assert.Empty(dataGrid.CaptionText);
+ }
+
+ [StaFact]
+ public void DataGrid_CaptionVisible_SetWithHandler_CallsCaptionVisibleChanged()
+ {
+ using DataGrid dataGrid = new();
+ int callCount = 0;
+ EventHandler handler = (sender, e) =>
+ {
+ Assert.Same(dataGrid, sender);
+ Assert.Same(EventArgs.Empty, e);
+ callCount++;
+ };
+ dataGrid.CaptionVisibleChanged += handler;
+
+ // Set different.
+ dataGrid.CaptionVisible = false;
+
+ Assert.False(dataGrid.CaptionVisible);
+ Assert.Equal(1, callCount);
+
+ // Set same.
+ dataGrid.CaptionVisible = false;
+
+ Assert.False(dataGrid.CaptionVisible);
+ Assert.Equal(1, callCount);
+
+ // Set different.
+ dataGrid.CaptionVisible = true;
+
+ Assert.True(dataGrid.CaptionVisible);
+ Assert.Equal(2, callCount);
+
+ // Remove handler.
+ dataGrid.CaptionVisibleChanged -= handler;
+ dataGrid.CaptionVisible = false;
+
+ Assert.Equal(2, callCount);
+ }
+
+ [StaFact]
+ public void DataGrid_FlatMode_Set_GetReturnsExpected()
+ {
+ using DataGrid dataGrid = new();
+
+ dataGrid.FlatMode = true;
+
+ Assert.True(dataGrid.FlatMode);
+
+ // Set same.
+ dataGrid.FlatMode = true;
+
+ Assert.True(dataGrid.FlatMode);
+
+ // Set different.
+ dataGrid.FlatMode = false;
+
+ Assert.False(dataGrid.FlatMode);
+ }
+
+ [StaFact]
+ public void DataGrid_FlatMode_SetWithHandler_CallsFlatModeChanged()
+ {
+ using DataGrid dataGrid = new();
+ int callCount = 0;
+ EventHandler handler = (sender, e) =>
+ {
+ Assert.Same(dataGrid, sender);
+ Assert.Same(EventArgs.Empty, e);
+ callCount++;
+ };
+ dataGrid.FlatModeChanged += handler;
+
+ // Set different.
+ dataGrid.FlatMode = true;
+
+ Assert.True(dataGrid.FlatMode);
+ Assert.Equal(1, callCount);
+
+ // Set same.
+ dataGrid.FlatMode = true;
+
+ Assert.True(dataGrid.FlatMode);
+ Assert.Equal(1, callCount);
+
+ // Set different.
+ dataGrid.FlatMode = false;
+
+ Assert.False(dataGrid.FlatMode);
+ Assert.Equal(2, callCount);
+
+ // Remove handler.
+ dataGrid.FlatModeChanged -= handler;
+ dataGrid.FlatMode = true;
+
+ Assert.Equal(2, callCount);
+ }
+
+ [StaFact]
+ public void DataGrid_GridLineStyle_Set_GetReturnsExpected()
+ {
+ using DataGrid dataGrid = new();
+
+ dataGrid.GridLineStyle = DataGridLineStyle.None;
+
+ Assert.Equal(DataGridLineStyle.None, dataGrid.GridLineStyle);
+
+ // Set same.
+ dataGrid.GridLineStyle = DataGridLineStyle.None;
+
+ Assert.Equal(DataGridLineStyle.None, dataGrid.GridLineStyle);
+
+ // Set different.
+ dataGrid.GridLineStyle = DataGridLineStyle.Solid;
+
+ Assert.Equal(DataGridLineStyle.Solid, dataGrid.GridLineStyle);
+ }
+
+ [StaFact]
+ public void DataGrid_ParentRowsLabelStyle_Set_GetReturnsExpected()
+ {
+ using DataGrid dataGrid = new();
+
+ dataGrid.ParentRowsLabelStyle = DataGridParentRowsLabelStyle.None;
+
+ Assert.Equal(DataGridParentRowsLabelStyle.None, dataGrid.ParentRowsLabelStyle);
+
+ dataGrid.ParentRowsLabelStyle = DataGridParentRowsLabelStyle.TableName;
+
+ Assert.Equal(DataGridParentRowsLabelStyle.TableName, dataGrid.ParentRowsLabelStyle);
+
+ dataGrid.ParentRowsLabelStyle = DataGridParentRowsLabelStyle.ColumnName;
+
+ Assert.Equal(DataGridParentRowsLabelStyle.ColumnName, dataGrid.ParentRowsLabelStyle);
+
+ // Set same.
+ dataGrid.ParentRowsLabelStyle = DataGridParentRowsLabelStyle.ColumnName;
+
+ Assert.Equal(DataGridParentRowsLabelStyle.ColumnName, dataGrid.ParentRowsLabelStyle);
+
+ // Restore default.
+ dataGrid.ParentRowsLabelStyle = DataGridParentRowsLabelStyle.Both;
+
+ Assert.Equal(DataGridParentRowsLabelStyle.Both, dataGrid.ParentRowsLabelStyle);
+ }
+
+ [StaFact]
+ public void DataGrid_ParentRowsVisible_SetWithHandler_CallsParentRowsVisibleChanged()
+ {
+ using DataGrid dataGrid = new();
+ int callCount = 0;
+ EventHandler handler = (sender, e) =>
+ {
+ Assert.Same(dataGrid, sender);
+ Assert.Same(EventArgs.Empty, e);
+ callCount++;
+ };
+ dataGrid.ParentRowsVisibleChanged += handler;
+
+ // Set different.
+ dataGrid.ParentRowsVisible = false;
+
+ Assert.False(dataGrid.ParentRowsVisible);
+ Assert.Equal(1, callCount);
+
+ // Set same.
+ dataGrid.ParentRowsVisible = false;
+
+ Assert.False(dataGrid.ParentRowsVisible);
+ Assert.Equal(1, callCount);
+
+ // Set different.
+ dataGrid.ParentRowsVisible = true;
+
+ Assert.True(dataGrid.ParentRowsVisible);
+ Assert.Equal(2, callCount);
+
+ // Remove handler.
+ dataGrid.ParentRowsVisibleChanged -= handler;
+ dataGrid.ParentRowsVisible = false;
+
+ Assert.Equal(2, callCount);
+ }
+
+ [StaFact]
+ public void DataGrid_ReadOnly_Set_GetReturnsExpected()
+ {
+ using DataGrid dataGrid = new();
+
+ dataGrid.ReadOnly = true;
+
+ Assert.True(dataGrid.ReadOnly);
+
+ // Set same.
+ dataGrid.ReadOnly = true;
+
+ Assert.True(dataGrid.ReadOnly);
+
+ // Set different.
+ dataGrid.ReadOnly = false;
+
+ Assert.False(dataGrid.ReadOnly);
+ }
+
+ [StaFact]
+ public void DataGrid_ReadOnly_SetWithHandler_CallsReadOnlyChanged()
+ {
+ using DataGrid dataGrid = new();
+ int callCount = 0;
+ EventHandler handler = (sender, e) =>
+ {
+ Assert.Same(dataGrid, sender);
+ Assert.Same(EventArgs.Empty, e);
+ callCount++;
+ };
+ dataGrid.ReadOnlyChanged += handler;
+
+ // Set different.
+ dataGrid.ReadOnly = true;
+
+ Assert.True(dataGrid.ReadOnly);
+ Assert.Equal(1, callCount);
+
+ // Set same.
+ dataGrid.ReadOnly = true;
+
+ Assert.True(dataGrid.ReadOnly);
+ Assert.Equal(1, callCount);
+
+ // Set different.
+ dataGrid.ReadOnly = false;
+
+ Assert.False(dataGrid.ReadOnly);
+ Assert.Equal(2, callCount);
+
+ // Remove handler.
+ dataGrid.ReadOnlyChanged -= handler;
+ dataGrid.ReadOnly = true;
+
+ Assert.Equal(2, callCount);
+ }
+
+ [StaFact]
+ public void DataGrid_SelectionBackColor_Set_GetReturnsExpected()
+ {
+ using DataGrid dataGrid = new();
+
+ dataGrid.SelectionBackColor = Color.Navy;
+
+ Assert.Equal(Color.Navy, dataGrid.SelectionBackColor);
+
+ // Set same.
+ dataGrid.SelectionBackColor = Color.Navy;
+
+ Assert.Equal(Color.Navy, dataGrid.SelectionBackColor);
+
+ // Set different.
+ dataGrid.SelectionBackColor = Color.Red;
+
+ Assert.Equal(Color.Red, dataGrid.SelectionBackColor);
+ }
+
+ [StaFact]
+ public void DataGrid_SelectionBackColor_SetEmpty_ThrowsArgumentException()
+ {
+ using DataGrid dataGrid = new();
+
+ Assert.Throws(() => dataGrid.SelectionBackColor = Color.Empty);
+ }
+
+ [StaFact]
+ public void DataGrid_SelectionForeColor_Set_GetReturnsExpected()
+ {
+ using DataGrid dataGrid = new();
+
+ dataGrid.SelectionForeColor = Color.White;
+
+ Assert.Equal(Color.White, dataGrid.SelectionForeColor);
+
+ // Set same.
+ dataGrid.SelectionForeColor = Color.White;
+
+ Assert.Equal(Color.White, dataGrid.SelectionForeColor);
+
+ // Set different.
+ dataGrid.SelectionForeColor = Color.Black;
+
+ Assert.Equal(Color.Black, dataGrid.SelectionForeColor);
+ }
+
+ [StaFact]
+ public void DataGrid_SelectionForeColor_SetEmpty_ThrowsArgumentException()
+ {
+ using DataGrid dataGrid = new();
+
+ Assert.Throws(() => dataGrid.SelectionForeColor = Color.Empty);
+ }
+
+ [StaFact]
+ public void DataGrid_SetDataBinding_SetsDataSourceAndDataMember()
+ {
+ using Form form = new();
+ using DataGrid dataGrid = new();
+ using DataSet dataSet = CreateDemoDataSet();
+
+ form.Controls.Add(dataGrid);
+ dataGrid.SetDataBinding(dataSet, "Customers");
+
+ Assert.Same(dataSet, dataGrid.DataSource);
+ Assert.Equal("Customers", dataGrid.DataMember);
+ }
+
+ [StaFact]
+ public void DataGrid_SetDataBinding_CurrentRowIndexStaysInSyncWithBindingManager()
+ {
+ using Form form = new();
+ using DataGrid dataGrid = new();
+ using DataSet dataSet = CreateDemoDataSet();
+
+ form.Controls.Add(dataGrid);
+ dataGrid.SetDataBinding(dataSet, "Customers");
+
+ CurrencyManager customersManager = Assert.IsAssignableFrom(form.BindingContext[dataSet, "Customers"]);
+
+ Assert.Equal(0, dataGrid.CurrentRowIndex);
+
+ customersManager.Position = 2;
+
+ Assert.Equal(2, dataGrid.CurrentRowIndex);
+
+ DataRowView customerAtPositionTwo = Assert.IsType(customersManager.Current);
+ Assert.Equal("Customer3", customerAtPositionTwo["CustName"]);
+
+ dataGrid.CurrentRowIndex = 1;
+
+ Assert.Equal(1, customersManager.Position);
+
+ DataRowView currentCustomer = Assert.IsType(customersManager.Current);
+ Assert.Equal("Customer2", currentCustomer["CustName"]);
+ Assert.Equal(5, currentCustomer.Row.GetChildRows("custToOrders").Length);
+ }
+
+ [StaFact]
+ public void DataGrid_SetDataBinding_EmptyTable_CurrentRowIndexIsMinusOne()
+ {
+ using Form form = new();
+ using DataGrid dataGrid = new();
+ using DataSet dataSet = new("EmptySet");
+
+ DataTable emptyTable = new("Items");
+ emptyTable.Columns.Add("ID", typeof(int));
+ emptyTable.Columns.Add("Name", typeof(string));
+ dataSet.Tables.Add(emptyTable);
+
+ form.Controls.Add(dataGrid);
+ dataGrid.SetDataBinding(dataSet, "Items");
+
+ CurrencyManager manager = Assert.IsAssignableFrom(form.BindingContext[dataSet, "Items"]);
+
+ Assert.Equal(0, manager.Count);
+ Assert.Equal(-1, dataGrid.CurrentRowIndex);
+ }
+
+ [StaFact]
+ public void DataGrid_DataSourceChanged_SetWithHandler_CallsDataSourceChanged()
+ {
+ using Form form = new();
+ using DataGrid dataGrid = new();
+ using DataSet dataSet = CreateDemoDataSet();
+
+ form.Controls.Add(dataGrid);
+
+ int callCount = 0;
+ EventHandler handler = (sender, e) =>
+ {
+ Assert.Same(dataGrid, sender);
+ Assert.Same(EventArgs.Empty, e);
+ callCount++;
+ };
+ dataGrid.DataSourceChanged += handler;
+
+ dataGrid.SetDataBinding(dataSet, "Customers");
+
+ Assert.Equal(1, callCount);
+
+ // Set same source.
+ dataGrid.SetDataBinding(dataSet, "Customers");
+
+ Assert.Equal(1, callCount);
+
+ // Remove handler.
+ dataGrid.DataSourceChanged -= handler;
+ dataGrid.SetDataBinding(dataSet, "Orders");
+
+ Assert.Equal(1, callCount);
+ }
+
+ [StaFact]
+ public void DataGrid_TableStyles_AddDemoStyles_MapsCustomersAndOrdersColumns()
+ {
+ using Form form = new();
+ using DataGrid dataGrid = new();
+ using DataSet dataSet = CreateDemoDataSet();
+
+ form.Controls.Add(dataGrid);
+ dataGrid.SetDataBinding(dataSet, "Customers");
+
+ AddDemoTableStyles(form, dataGrid, dataSet);
+
+ Assert.Equal(2, dataGrid.TableStyles.Count);
+
+ DataGridTableStyle customersStyle = Assert.IsType(dataGrid.TableStyles["Customers"]);
+ DataGridTableStyle ordersStyle = Assert.IsType(dataGrid.TableStyles["Orders"]);
+
+ Assert.Equal(Color.LightGray, customersStyle.AlternatingBackColor);
+ Assert.Equal(Color.LightBlue, ordersStyle.AlternatingBackColor);
+
+ DataGridBoolColumn boolColumn = Assert.IsType(customersStyle.GridColumnStyles["Current"]);
+ Assert.Equal("IsCurrent Customer", boolColumn.HeaderText);
+ Assert.Equal(150, boolColumn.Width);
+
+ DataGridTextBoxColumn customerNameColumn = Assert.IsType(customersStyle.GridColumnStyles["custName"]);
+ Assert.Equal("Customer Name", customerNameColumn.HeaderText);
+ Assert.Equal(250, customerNameColumn.Width);
+
+ DataGridTextBoxColumn orderAmountColumn = Assert.IsType(ordersStyle.GridColumnStyles["OrderAmount"]);
+ Assert.Equal("c", orderAmountColumn.Format);
+ Assert.Equal("Total", orderAmountColumn.HeaderText);
+ Assert.Equal(100, orderAmountColumn.Width);
+ }
+
+ [StaFact]
+ public void DataGrid_TableStyles_Clear_RemovesAllStyles()
+ {
+ using Form form = new();
+ using DataGrid dataGrid = new();
+ using DataSet dataSet = CreateDemoDataSet();
+
+ form.Controls.Add(dataGrid);
+ dataGrid.SetDataBinding(dataSet, "Customers");
+
+ AddDemoTableStyles(form, dataGrid, dataSet);
+
+ Assert.Equal(2, dataGrid.TableStyles.Count);
+
+ dataGrid.TableStyles.Clear();
+
+ Assert.Empty(dataGrid.TableStyles);
+ }
+
+ private sealed class SubDataGrid : DataGrid
+ {
+ public new ScrollBar HorizScrollBar => base.HorizScrollBar;
+
+ public new ScrollBar VertScrollBar => base.VertScrollBar;
+ }
+
+ private static DataSet CreateDemoDataSet()
+ {
+ DataSet dataSet = new("myDataSet");
+
+ DataTable customersTable = new("Customers");
+ DataTable ordersTable = new("Orders");
+
+ DataColumn customerIdColumn = new("CustID", typeof(int));
+ DataColumn customerNameColumn = new("CustName", typeof(string));
+ DataColumn currentColumn = new("Current", typeof(bool));
+ customersTable.Columns.Add(customerIdColumn);
+ customersTable.Columns.Add(customerNameColumn);
+ customersTable.Columns.Add(currentColumn);
+
+ DataColumn orderCustomerIdColumn = new("CustID", typeof(int));
+ DataColumn orderDateColumn = new("orderDate", typeof(DateTime));
+ DataColumn orderAmountColumn = new("OrderAmount", typeof(decimal));
+ ordersTable.Columns.Add(orderAmountColumn);
+ ordersTable.Columns.Add(orderCustomerIdColumn);
+ ordersTable.Columns.Add(orderDateColumn);
+
+ dataSet.Tables.Add(customersTable);
+ dataSet.Tables.Add(ordersTable);
+ dataSet.Relations.Add(new DataRelation("custToOrders", customerIdColumn, orderCustomerIdColumn));
+
+ for (int i = 1; i < 4; i++)
+ {
+ DataRow customerRow = customersTable.NewRow();
+ customerRow["CustID"] = i;
+ customerRow["CustName"] = $"Customer{i}";
+ customerRow["Current"] = i < 3;
+ customersTable.Rows.Add(customerRow);
+
+ for (int j = 1; j < 6; j++)
+ {
+ DataRow orderRow = ordersTable.NewRow();
+ orderRow["CustID"] = i;
+ orderRow["orderDate"] = new DateTime(2001, i, j * 2);
+ orderRow["OrderAmount"] = (decimal)(i * 10) + ((decimal)j / 10);
+ ordersTable.Rows.Add(orderRow);
+ }
+ }
+
+ return dataSet;
+ }
+
+ private static void AddDemoTableStyles(Form form, DataGrid dataGrid, DataSet dataSet)
+ {
+ DataGridTableStyle customersStyle = new()
+ {
+ MappingName = "Customers",
+ AlternatingBackColor = Color.LightGray
+ };
+
+ DataGridColumnStyle currentColumn = new DataGridBoolColumn
+ {
+ MappingName = "Current",
+ HeaderText = "IsCurrent Customer",
+ Width = 150
+ };
+ customersStyle.GridColumnStyles.Add(currentColumn);
+
+ DataGridColumnStyle customerNameColumn = new DataGridTextBoxColumn
+ {
+ MappingName = "custName",
+ HeaderText = "Customer Name",
+ Width = 250
+ };
+ customersStyle.GridColumnStyles.Add(customerNameColumn);
+
+ DataGridTableStyle ordersStyle = new()
+ {
+ MappingName = "Orders",
+ AlternatingBackColor = Color.LightBlue
+ };
+
+ DataGridTextBoxColumn orderDateColumn = new()
+ {
+ MappingName = "OrderDate",
+ HeaderText = "Order Date",
+ Width = 100
+ };
+ ordersStyle.GridColumnStyles.Add(orderDateColumn);
+
+ PropertyDescriptorCollection properties = form.BindingContext[dataSet, "Customers.custToOrders"].GetItemProperties();
+ PropertyDescriptor? orderAmountProperty = properties["OrderAmount"];
+ Assert.NotNull(orderAmountProperty);
+
+ DataGridTextBoxColumn orderAmountColumn = new(orderAmountProperty, "c", true)
+ {
+ MappingName = "OrderAmount",
+ HeaderText = "Total",
+ Width = 100
+ };
+ ordersStyle.GridColumnStyles.Add(orderAmountColumn);
+
+ dataGrid.TableStyles.Add(customersStyle);
+ dataGrid.TableStyles.Add(ordersStyle);
+ }
+}
\ No newline at end of file
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/GlobalUsings.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/GlobalUsings.cs
new file mode 100644
index 00000000000..9da52515e59
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/GlobalUsings.cs
@@ -0,0 +1,6 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable disable
+
+global using Xunit;
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/MainMenuTests.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/MainMenuTests.cs
new file mode 100644
index 00000000000..e5a1630526e
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/MainMenuTests.cs
@@ -0,0 +1,143 @@
+namespace System.Windows.Forms.Tests
+{
+ using System.Drawing;
+ using System.Runtime.InteropServices;
+
+ public class MainMenuTests
+ {
+ [StaFact]
+ public void MainMenu_SetAndGet_ReturnsCorrectValue()
+ {
+ // Arrange
+ using Form form = new();
+ MainMenu mainMenu = new();
+
+ // Act
+ form.Menu = mainMenu;
+
+ // Assert
+ Assert.Same(mainMenu, form.Menu);
+ }
+
+ [StaFact]
+ public void MainMenu_AddMenuItem_ReturnsCorrectMenuItem()
+ {
+ // Arrange
+ MainMenu mainMenu = new();
+ MenuItem menuItem = new("Test Item");
+
+ // Act
+ mainMenu.MenuItems.Add(menuItem);
+
+ // Assert
+ Assert.Single(mainMenu.MenuItems);
+ Assert.Same(menuItem, mainMenu.MenuItems[0]);
+ }
+
+ [StaFact]
+ public void MainMenu_FileMenuPopup_AddsMenuItemOnPopup()
+ {
+ // Arrange
+ using var form = new Form();
+ var mainMenu = new MainMenu();
+ var fileMenuItem = new MenuItem("File");
+
+ // Add popup event handler that adds a menu item when fired
+ bool popupEventFired = false;
+ MenuItem? addedMenuItem = null;
+
+ fileMenuItem.Popup += (sender, e) =>
+ {
+ popupEventFired = true;
+ addedMenuItem = new MenuItem("Dynamic Item");
+ fileMenuItem.MenuItems.Add(addedMenuItem);
+ };
+
+ mainMenu.MenuItems.Add(fileMenuItem);
+ form.Menu = mainMenu;
+ form.Size = new Size(400, 300);
+
+ // Initially, the File menu should have no items
+ Assert.Empty(fileMenuItem.MenuItems);
+
+ // Create the handle so we can send Windows messages
+ var handle = form.Handle; // Forces handle creation
+
+ // Act - Simulate WM_INITMENUPOPUP message to trigger popup event
+ // This is what Windows sends when a menu is about to be displayed
+ const uint WM_INITMENUPOPUP = 0x0117;
+
+ // Send the message to trigger the popup event
+ // The wParam contains the handle to the menu, lParam contains position info
+ SendMessage(handle, WM_INITMENUPOPUP, fileMenuItem.Handle, IntPtr.Zero);
+
+ // Assert
+ Assert.True(popupEventFired, "Popup event should have been fired");
+ Assert.Single(fileMenuItem.MenuItems);
+
+ if (addedMenuItem is not null)
+ {
+ Assert.Same(addedMenuItem, fileMenuItem.MenuItems[0]);
+ Assert.Equal("Dynamic Item", fileMenuItem.MenuItems[0].Text);
+ }
+
+ // Clean up
+ form.Dispose();
+ }
+
+ [StaFact]
+ public void MainMenu_FileMenuWithSubmenu_PopupAddsItemToFileMenu()
+ {
+ // Arrange
+ using Form form = new();
+ MainMenu mainMenu = new();
+ MenuItem fileMenuItem = new("File");
+ MenuItem submenu = new("Submenu");
+
+ // Add the submenu to the File menu first
+ fileMenuItem.MenuItems.Add(submenu);
+
+ // Add popup event handler to the File menu (not the submenu)
+ bool popupEventFired = false;
+ MenuItem? addedMenuItem = null;
+
+ fileMenuItem.Popup += (sender, e) =>
+ {
+ popupEventFired = true;
+ addedMenuItem = new MenuItem("Dynamic Item Added to File");
+ fileMenuItem.MenuItems.Add(addedMenuItem);
+ };
+
+ mainMenu.MenuItems.Add(fileMenuItem);
+ form.Menu = mainMenu;
+ form.Size = new Size(400, 300);
+
+ // Initially, the File menu should have 1 item (the submenu)
+ Assert.Single(fileMenuItem.MenuItems);
+ Assert.Same(submenu, fileMenuItem.MenuItems[0]);
+
+ // Create the handle so we can send Windows messages
+ var handle = form.Handle; // Forces handle creation
+
+ // Act - Simulate WM_INITMENUPOPUP message to the File menu
+ const uint WM_INITMENUPOPUP = 0x0117;
+
+ // Send the message to trigger the popup event on the File menu
+ SendMessage(handle, WM_INITMENUPOPUP, fileMenuItem.Handle, IntPtr.Zero);
+
+ // Assert
+ Assert.True(popupEventFired, "Popup event should have been fired");
+ Assert.Equal(2, fileMenuItem.MenuItems.Count);
+ Assert.Same(submenu, fileMenuItem.MenuItems[0]);
+
+ if (addedMenuItem is not null)
+ {
+ Assert.Same(addedMenuItem, fileMenuItem.MenuItems[1]);
+ Assert.Equal("Dynamic Item Added to File", fileMenuItem.MenuItems[1].Text);
+ }
+ }
+
+ [DllImport("user32.dll")]
+ private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
+ }
+}
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/MenuItemTests.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/MenuItemTests.cs
new file mode 100644
index 00000000000..c99c0d30c03
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/MenuItemTests.cs
@@ -0,0 +1,138 @@
+namespace System.Windows.Forms.Tests
+{
+ public class MenuItemTests
+ {
+#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
+ [StaFact]
+ public void MenuItem_OnDrawItem_Invoke_Success()
+ {
+ var menuItem = new SubMenuItem();
+
+ // No handler.
+ menuItem.OnDrawItem(null);
+
+ // Handler.
+ int callCount = 0;
+ DrawItemEventHandler handler = (sender, e) =>
+ {
+ Assert.Same(menuItem, sender);
+ callCount++;
+ };
+
+ menuItem.DrawItem += handler;
+ menuItem.OnDrawItem(null);
+ Assert.Equal(1, callCount);
+
+ // Should not call if the handler is removed.
+ menuItem.DrawItem -= handler;
+ menuItem.OnDrawItem(null);
+ Assert.Equal(1, callCount);
+ }
+
+ [StaFact]
+ public void MenuItem_OnDrawItem_Disposed_ThrowsObjectDisposedException()
+ {
+ var menuItem = new SubMenuItem();
+ menuItem.Dispose();
+ Assert.Throws(() => menuItem.OnDrawItem(null));
+ }
+
+ [StaFact]
+ public void MenuItem_DrawItem_Disposed_ThrowsObjectDisposedException()
+ {
+ var menuItem = new SubMenuItem();
+ menuItem.Dispose();
+ DrawItemEventHandler handler = (sender, e) => { };
+ Assert.Throws(() => menuItem.DrawItem += handler);
+ Assert.Throws(() => menuItem.DrawItem -= handler);
+ }
+
+ [StaFact]
+ public void MenuItem_OnMeasureItem_Invoke_Success()
+ {
+ var menuItem = new SubMenuItem();
+
+ // No handler.
+ menuItem.OnMeasureItem(null);
+
+ // Handler.
+ int callCount = 0;
+ MeasureItemEventHandler handler = (sender, e) =>
+ {
+ Assert.Same(menuItem, sender);
+ callCount++;
+ };
+
+ menuItem.MeasureItem += handler;
+ menuItem.OnMeasureItem(null);
+ Assert.Equal(1, callCount);
+
+ // Should not call if the handler is removed.
+ menuItem.MeasureItem -= handler;
+ menuItem.OnMeasureItem(null);
+ Assert.Equal(1, callCount);
+ }
+
+ [StaFact]
+ public void MenuItem_OnMeasureItem_Disposed_ThrowsObjectDisposedException()
+ {
+ var menuItem = new SubMenuItem();
+ menuItem.Dispose();
+ Assert.Throws(() => menuItem.OnMeasureItem(null));
+ }
+
+ [StaFact]
+ public void MenuItem_MeasureItem_Disposed_ThrowsObjectDisposedException()
+ {
+ var menuItem = new SubMenuItem();
+ menuItem.Dispose();
+ MeasureItemEventHandler handler = (sender, e) => { };
+ Assert.Throws(() => menuItem.MeasureItem += handler);
+ Assert.Throws(() => menuItem.MeasureItem -= handler);
+ }
+#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
+
+ public class SubMenuItem : MenuItem
+ {
+ public SubMenuItem()
+ {
+ }
+
+ public SubMenuItem(string text) : base(text)
+ {
+ }
+
+ public SubMenuItem(string text, EventHandler onClick) : base(text, onClick)
+ {
+ }
+
+ public SubMenuItem(string text, MenuItem[] items) : base(text, items)
+ {
+ }
+
+ public SubMenuItem(string text, EventHandler onClick, Shortcut shortcut) : base(text, onClick, shortcut)
+ {
+ }
+
+ public SubMenuItem(MenuMerge mergeType, int mergeOrder, Shortcut shortcut, string text, EventHandler onClick, EventHandler onPopup, EventHandler onSelect, MenuItem[] items) : base(mergeType, mergeOrder, shortcut, text, onClick, onPopup, onSelect, items)
+ {
+ }
+
+ public new int MenuID => base.MenuID;
+
+ public new void OnClick(EventArgs e) => base.OnClick(e);
+
+ public new void OnDrawItem(DrawItemEventArgs e) => base.OnDrawItem(e);
+
+ public new void OnInitMenuPopup(EventArgs e) => base.OnInitMenuPopup(e);
+
+ public new void OnMeasureItem(MeasureItemEventArgs e) => base.OnMeasureItem(e);
+
+ public new void OnPopup(EventArgs e) => base.OnPopup(e);
+
+ public new void OnSelect(EventArgs e) => base.OnSelect(e);
+
+ public new void CloneMenu(Menu menuSrc) => base.CloneMenu(menuSrc);
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/MenuSizeCalculationTests.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/MenuSizeCalculationTests.cs
new file mode 100644
index 00000000000..129d4098004
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/MenuSizeCalculationTests.cs
@@ -0,0 +1,286 @@
+namespace System.Windows.Forms.Tests
+{
+ using System.Drawing;
+
+ public class MenuSizeCalculationTests
+ {
+ [StaFact]
+ public void Form_WithMenu_HasCorrectWindowSize()
+ {
+ // Arrange
+ using var formWithMenu = new Form();
+ using var formWithoutMenu = new Form();
+
+ var menu = new MainMenu();
+ menu.MenuItems.Add(new MenuItem("File"));
+ formWithMenu.Menu = menu;
+
+ var clientSize = new Size(400, 300);
+
+ // Act
+ formWithMenu.ClientSize = clientSize;
+ formWithoutMenu.ClientSize = clientSize;
+
+ // Assert
+ Assert.Equal(clientSize, formWithMenu.ClientSize);
+ Assert.Equal(clientSize, formWithoutMenu.ClientSize);
+
+ // The key test: form with menu should be taller due to menu bar
+ Assert.True(formWithMenu.Size.Height > formWithoutMenu.Size.Height,
+ "Form with menu should be taller than form without menu (accounts for menu bar height)");
+
+ // Width should be the same (menu doesn't affect width)
+ Assert.Equal(formWithoutMenu.Size.Width, formWithMenu.Size.Width);
+ }
+
+ [StaFact]
+ public void Form_WithEmptyMenu_SameHeightAsFormWithoutMenu()
+ {
+ // Arrange
+ using var formWithEmptyMenu = new Form();
+ using var formWithoutMenu = new Form();
+
+ var emptyMenu = new MainMenu(); // No menu items
+ formWithEmptyMenu.Menu = emptyMenu;
+
+ var clientSize = new Size(400, 300);
+
+ // Act
+ formWithEmptyMenu.ClientSize = clientSize;
+ formWithoutMenu.ClientSize = clientSize;
+
+ // Assert
+ // According to the implementation, empty menus should not affect window height
+ Assert.Equal(formWithoutMenu.Size.Height, formWithEmptyMenu.Size.Height);
+ }
+
+ [StaFact]
+ public void Form_MenuAddedAfterCreation_AdjustsSize()
+ {
+ // Arrange
+ using var form = new Form();
+ var clientSize = new Size(400, 300);
+ form.ClientSize = clientSize;
+
+ var initialHeight = form.Size.Height;
+
+ // Act - Add menu with items
+ var menu = new MainMenu();
+ menu.MenuItems.Add(new MenuItem("File"));
+ form.Menu = menu;
+ form.ClientSize = clientSize; // Trigger recalculation
+
+ // Assert
+ Assert.True(form.Size.Height > initialHeight,
+ "Form height should increase when menu with items is added");
+ Assert.Equal(clientSize, form.ClientSize);
+ }
+
+ [StaFact]
+ public void Form_MenuRemovedAfterCreation_AdjustsSize()
+ {
+ // Arrange
+ using var form = new Form();
+ var menu = new MainMenu();
+ menu.MenuItems.Add(new MenuItem("File"));
+ form.Menu = menu;
+
+ var clientSize = new Size(400, 300);
+ form.ClientSize = clientSize;
+ var heightWithMenu = form.Size.Height;
+
+ // Act - Remove menu
+ form.Menu = null;
+ form.ClientSize = clientSize; // Trigger recalculation
+
+ // Assert
+ Assert.True(form.Size.Height < heightWithMenu,
+ "Form height should decrease when menu is removed");
+ Assert.Equal(clientSize, form.ClientSize);
+ }
+
+ [StaFact]
+ public void Form_NonTopLevel_MenuDoesNotAffectSize()
+ {
+ // Arrange
+ using var parentForm = new Form();
+ using var childForm = new Form();
+
+ var menu = new MainMenu();
+ menu.MenuItems.Add(new MenuItem("File"));
+ childForm.Menu = menu;
+ childForm.TopLevel = false;
+ childForm.Parent = parentForm;
+
+ var clientSize = new Size(200, 150);
+
+ // Create a comparable form without menu but also non-toplevel
+ using var childFormNoMenu = new Form();
+ childFormNoMenu.TopLevel = false;
+ childFormNoMenu.Parent = parentForm;
+
+ // Act
+ childForm.ClientSize = clientSize;
+ childFormNoMenu.ClientSize = clientSize;
+
+ // Assert
+ // Non-top-level forms should not account for menus in sizing
+ Assert.Equal(childFormNoMenu.Size.Height, childForm.Size.Height);
+ }
+
+ [StaFact]
+ public void MDIChild_MenuDoesNotAffectSize()
+ {
+ // Arrange
+ using var parentForm = new Form();
+ parentForm.IsMdiContainer = true;
+
+ using var mdiChild1 = new Form();
+ using var mdiChild2 = new Form();
+
+ var menu = new MainMenu();
+ menu.MenuItems.Add(new MenuItem("File"));
+ mdiChild1.Menu = menu;
+
+ mdiChild1.MdiParent = parentForm;
+ mdiChild2.MdiParent = parentForm;
+
+ var clientSize = new Size(200, 150);
+
+ // Act
+ mdiChild1.ClientSize = clientSize;
+ mdiChild2.ClientSize = clientSize;
+
+ // Assert
+ // MDI children should not account for menus in sizing
+ Assert.Equal(mdiChild2.Size.Height, mdiChild1.Size.Height);
+ }
+
+ [StaFact]
+ public void Form_MenuWithMultipleItems_SameHeightAsMenuWithOneItem()
+ {
+ // Arrange
+ using var formWithOneMenuItem = new Form();
+ using var formWithMultipleMenuItems = new Form();
+
+ var menuOne = new MainMenu();
+ menuOne.MenuItems.Add(new MenuItem("File"));
+ formWithOneMenuItem.Menu = menuOne;
+
+ var menuMultiple = new MainMenu();
+ menuMultiple.MenuItems.Add(new MenuItem("File"));
+ menuMultiple.MenuItems.Add(new MenuItem("Edit"));
+ menuMultiple.MenuItems.Add(new MenuItem("View"));
+ formWithMultipleMenuItems.Menu = menuMultiple;
+
+ var clientSize = new Size(400, 300);
+
+ // Act
+ formWithOneMenuItem.ClientSize = clientSize;
+ formWithMultipleMenuItems.ClientSize = clientSize;
+
+ // Assert
+ // Number of menu items shouldn't affect form height (all in same menu bar)
+ Assert.Equal(formWithOneMenuItem.Size.Height, formWithMultipleMenuItems.Size.Height);
+ }
+
+ [StaFact]
+ public void Form_MenuWithSubmenus_SameHeightAsMenuWithoutSubmenus()
+ {
+ // Arrange
+ using var formWithSubmenu = new Form();
+ using var formWithoutSubmenu = new Form();
+
+ var menuWithSubmenu = new MainMenu();
+ var fileMenu = new MenuItem("File");
+ fileMenu.MenuItems.Add(new MenuItem("New"));
+ fileMenu.MenuItems.Add(new MenuItem("Open"));
+ menuWithSubmenu.MenuItems.Add(fileMenu);
+ formWithSubmenu.Menu = menuWithSubmenu;
+
+ var menuWithoutSubmenu = new MainMenu();
+ menuWithoutSubmenu.MenuItems.Add(new MenuItem("File"));
+ formWithoutSubmenu.Menu = menuWithoutSubmenu;
+
+ var clientSize = new Size(400, 300);
+
+ // Act
+ formWithSubmenu.ClientSize = clientSize;
+ formWithoutSubmenu.ClientSize = clientSize;
+
+ // Assert
+ // Submenus shouldn't affect form height (they're dropdowns)
+ Assert.Equal(formWithoutSubmenu.Size.Height, formWithSubmenu.Size.Height);
+ }
+
+ [StaFact]
+ public void Form_SetClientSizeMultipleTimes_ConsistentBehavior()
+ {
+ // Test that the HasMenu logic is consistently applied
+
+ // Arrange
+ using var form = new Form();
+ var menu = new MainMenu();
+ menu.MenuItems.Add(new MenuItem("File"));
+ form.Menu = menu;
+
+ var clientSize1 = new Size(300, 200);
+ var clientSize2 = new Size(500, 400);
+
+ // Act & Assert
+ form.ClientSize = clientSize1;
+ Assert.Equal(clientSize1, form.ClientSize);
+ var height1 = form.Size.Height;
+
+ form.ClientSize = clientSize2;
+ Assert.Equal(clientSize2, form.ClientSize);
+ var height2 = form.Size.Height;
+
+ // The height difference should be proportional to client size difference
+ var clientHeightDiff = clientSize2.Height - clientSize1.Height;
+ var windowHeightDiff = height2 - height1;
+ Assert.Equal(clientHeightDiff, windowHeightDiff);
+ }
+
+ [StaFact]
+ public void Form_MenuRemovedBeforeHandleCreated_SizeUpdatesImmediately()
+ {
+ // This test verifies the fix for the issue where setting Menu to null
+ // before handle creation didn't update the form size immediately
+
+ // Arrange
+ using var form = new Form();
+
+ // Create menu with items
+ var menu = new MainMenu();
+ menu.MenuItems.Add(new MenuItem("File"));
+ menu.MenuItems.Add(new MenuItem("Edit"));
+
+ // Set menu first
+ form.Menu = menu;
+
+ // Set client size - this should trigger FormStateSetClientSize
+ var targetClientSize = new Size(400, 300);
+ form.ClientSize = targetClientSize;
+
+ // Capture size with menu (before handle is created)
+ var sizeWithMenu = form.Size;
+
+ // Act - Remove menu before handle is created
+ // This should trigger the fix in line 760: if FormStateSetClientSize == 1 && !IsHandleCreated
+ form.Menu = null;
+
+ // Assert
+ var sizeWithoutMenu = form.Size;
+
+ // The form size should be updated immediately when menu is removed
+ Assert.True(sizeWithoutMenu.Height < sizeWithMenu.Height,
+ "Form height should decrease immediately when menu is removed before handle creation");
+
+ Assert.Equal(targetClientSize, form.ClientSize);
+
+ // Width should remain the same
+ Assert.Equal(sizeWithMenu.Width, sizeWithoutMenu.Width);
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/MouseOperations.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/MouseOperations.cs
new file mode 100644
index 00000000000..cfb7c1dac39
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/MouseOperations.cs
@@ -0,0 +1,24 @@
+namespace System.Windows.Forms.Tests
+{
+ using System.Runtime.InteropServices;
+
+ public static class MouseOperations
+ {
+ [Flags]
+ public enum MouseEventFlags
+ {
+ LeftDown = 0x00000002,
+ LeftUp = 0x00000004,
+ RightDown = 0x00000008,
+ RightUp = 0x00000010,
+ }
+
+ [DllImport("user32.dll")]
+ private static extern void mouse_event(int dwFlags, uint dx, uint dy, int dwData, int dwExtraInfo);
+
+ public static void MouseEvent(MouseEventFlags value, uint dx, uint dy)
+ {
+ mouse_event((int)value, dx, dy, 0, 0);
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/StatusBarTests.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/StatusBarTests.cs
new file mode 100644
index 00000000000..af644845561
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/StatusBarTests.cs
@@ -0,0 +1,344 @@
+using System.Drawing;
+
+namespace System.Windows.Forms.Tests;
+
+public class StatusBarTests
+{
+ [StaFact]
+ public void StatusBar_DefaultProperties_MatchExpectedValues()
+ {
+ using StatusBar statusBar = new();
+
+ Assert.Equal(DockStyle.Bottom, statusBar.Dock);
+ Assert.False(statusBar.ShowPanels);
+ Assert.True(statusBar.SizingGrip);
+ Assert.False(statusBar.TabStop);
+ Assert.Equal(string.Empty, statusBar.Text);
+ Assert.Empty(statusBar.Panels);
+ }
+
+ [StaFact]
+ public void StatusBar_Panels_AddRange_SetsPanelCountAndParent()
+ {
+ using StatusBar statusBar = new();
+ StatusBarPanel panel1 = new() { Text = "Panel 1" };
+ StatusBarPanel panel2 = new() { Text = "Panel 2" };
+ StatusBarPanel panel3 = new() { Text = "Panel 3" };
+
+ statusBar.Panels.AddRange([panel1, panel2, panel3]);
+
+ Assert.Equal(3, statusBar.Panels.Count);
+ Assert.Same(panel1, statusBar.Panels[0]);
+ Assert.Same(panel2, statusBar.Panels[1]);
+ Assert.Same(panel3, statusBar.Panels[2]);
+ Assert.Same(statusBar, panel1.Parent);
+ Assert.Same(statusBar, panel2.Parent);
+ Assert.Same(statusBar, panel3.Parent);
+ }
+
+ [StaFact]
+ public void StatusBar_Panels_AddByText_CreatesAndReturnsPanel()
+ {
+ using StatusBar statusBar = new();
+
+ StatusBarPanel panel = statusBar.Panels.Add("Ready");
+
+ Assert.Single(statusBar.Panels);
+ Assert.Equal("Ready", panel.Text);
+ Assert.Same(statusBar, panel.Parent);
+ }
+
+ [StaFact]
+ public void StatusBar_Panels_Insert_PlacesAtCorrectIndex()
+ {
+ using StatusBar statusBar = new();
+ StatusBarPanel first = new() { Name = "first", Text = "First" };
+ StatusBarPanel last = new() { Name = "last", Text = "Last" };
+ StatusBarPanel inserted = new() { Name = "inserted", Text = "Inserted" };
+
+ statusBar.Panels.AddRange([first, last]);
+
+ statusBar.Panels.Insert(1, inserted);
+
+ Assert.Equal(3, statusBar.Panels.Count);
+ Assert.Same(first, statusBar.Panels[0]);
+ Assert.Same(inserted, statusBar.Panels[1]);
+ Assert.Same(last, statusBar.Panels[2]);
+ Assert.Same(statusBar, inserted.Parent);
+ }
+
+ [StaFact]
+ public void StatusBar_Panels_Remove_ClearsParentReference()
+ {
+ using StatusBar statusBar = new();
+ StatusBarPanel panel = new() { Text = "Removable" };
+ statusBar.Panels.Add(panel);
+
+ statusBar.Panels.Remove(panel);
+
+ Assert.Empty(statusBar.Panels);
+ Assert.Null(panel.Parent);
+ }
+
+ [StaFact]
+ public void StatusBar_Panels_RemoveAt_RemovesByIndexAndClearsParent()
+ {
+ using StatusBar statusBar = new();
+ StatusBarPanel keep = new() { Text = "Keep" };
+ StatusBarPanel remove = new() { Text = "Remove" };
+ statusBar.Panels.AddRange([keep, remove]);
+
+ statusBar.Panels.RemoveAt(1);
+
+ Assert.Single(statusBar.Panels);
+ Assert.Same(keep, statusBar.Panels[0]);
+ Assert.Null(remove.Parent);
+ }
+
+ [StaFact]
+ public void StatusBar_Panels_Clear_RemovesAllPanelsAndClearsParents()
+ {
+ using StatusBar statusBar = new();
+ StatusBarPanel panel1 = new() { Text = "A" };
+ StatusBarPanel panel2 = new() { Text = "B" };
+ statusBar.Panels.AddRange([panel1, panel2]);
+
+ statusBar.Panels.Clear();
+
+ Assert.Empty(statusBar.Panels);
+ Assert.Null(panel1.Parent);
+ Assert.Null(panel2.Parent);
+ }
+
+ [StaFact]
+ public void StatusBar_Panels_Contains_ReturnsTrueForAddedPanel_FalseAfterRemoval()
+ {
+ using StatusBar statusBar = new();
+ StatusBarPanel panel = new() { Text = "Test" };
+
+ statusBar.Panels.Add(panel);
+
+ Assert.True(statusBar.Panels.Contains(panel));
+
+ statusBar.Panels.Remove(panel);
+
+ Assert.False(statusBar.Panels.Contains(panel));
+ }
+
+ [StaFact]
+ public void StatusBar_Panels_ContainsKey_FindsByName()
+ {
+ using StatusBar statusBar = new();
+ statusBar.Panels.Add(new StatusBarPanel { Name = "clock", Text = "12:00" });
+
+ Assert.True(statusBar.Panels.ContainsKey("clock"));
+ Assert.False(statusBar.Panels.ContainsKey("missing"));
+ }
+
+ [StaFact]
+ public void StatusBar_Panels_IndexOf_ReturnsCorrectPosition()
+ {
+ using StatusBar statusBar = new();
+ StatusBarPanel panel1 = new() { Name = "mode" };
+ StatusBarPanel panel2 = new() { Name = "detail" };
+ statusBar.Panels.AddRange([panel1, panel2]);
+
+ Assert.Equal(0, statusBar.Panels.IndexOf(panel1));
+ Assert.Equal(1, statusBar.Panels.IndexOf(panel2));
+ Assert.Equal(0, statusBar.Panels.IndexOfKey("mode"));
+ Assert.Equal(1, statusBar.Panels.IndexOfKey("detail"));
+ Assert.Equal(-1, statusBar.Panels.IndexOfKey("missing"));
+ }
+
+ [StaFact]
+ public void StatusBar_ShowPanels_ToggleToFalse_SetsSimpleTextWithoutLosingPanels()
+ {
+ using Form form = new() { ClientSize = new Size(600, 300) };
+ using StatusBar statusBar = new() { ShowPanels = true };
+ StatusBarPanel panel = new() { Text = "Status" };
+ statusBar.Panels.Add(panel);
+ form.Controls.Add(statusBar);
+ _ = form.Handle;
+
+ statusBar.ShowPanels = false;
+ statusBar.Text = "Ready";
+
+ Assert.False(statusBar.ShowPanels);
+ Assert.Equal("Ready", statusBar.Text);
+ Assert.Single(statusBar.Panels);
+ Assert.Same(statusBar, panel.Parent);
+ }
+
+ [StaFact]
+ public void StatusBar_ShowPanels_RestoreToTrue_ReexposesExistingPanels()
+ {
+ using Form form = new() { ClientSize = new Size(600, 300) };
+ using StatusBar statusBar = new() { ShowPanels = true };
+ StatusBarPanel panel = new() { Name = "mode", Text = "Panels Mode" };
+ statusBar.Panels.Add(panel);
+ form.Controls.Add(statusBar);
+ _ = form.Handle;
+
+ statusBar.ShowPanels = false;
+ statusBar.ShowPanels = true;
+ panel.Text = "Panels Mode";
+
+ Assert.True(statusBar.ShowPanels);
+ Assert.Single(statusBar.Panels);
+ Assert.Equal("Panels Mode", statusBar.Panels[0].Text);
+ }
+
+ [StaFact]
+ public void StatusBar_ToString_IncludesPanelCountAndFirstPanelText()
+ {
+ using StatusBar statusBar = new();
+ statusBar.Panels.AddRange([
+ new StatusBarPanel { Text = "Ready" },
+ new StatusBarPanel { Text = "12:00" }
+ ]);
+
+ string result = statusBar.ToString();
+
+ Assert.Contains("Panels.Count: 2", result);
+ Assert.Contains("StatusBarPanel: {Ready}", result);
+ }
+
+ [StaFact]
+ public void StatusBarPanel_Alignment_DefaultIsLeft_CanBeChangedToRightAndCenter()
+ {
+ StatusBarPanel panel = new();
+
+ Assert.Equal(HorizontalAlignment.Left, panel.Alignment);
+
+ panel.Alignment = HorizontalAlignment.Right;
+ Assert.Equal(HorizontalAlignment.Right, panel.Alignment);
+
+ panel.Alignment = HorizontalAlignment.Center;
+ Assert.Equal(HorizontalAlignment.Center, panel.Alignment);
+ }
+
+ [StaFact]
+ public void StatusBarPanel_AutoSize_DefaultIsNone_AllValuesSupported()
+ {
+ StatusBarPanel panel = new();
+
+ Assert.Equal(StatusBarPanelAutoSize.None, panel.AutoSize);
+
+ panel.AutoSize = StatusBarPanelAutoSize.Spring;
+ Assert.Equal(StatusBarPanelAutoSize.Spring, panel.AutoSize);
+
+ panel.AutoSize = StatusBarPanelAutoSize.Contents;
+ Assert.Equal(StatusBarPanelAutoSize.Contents, panel.AutoSize);
+ }
+
+ [StaFact]
+ public void StatusBarPanel_BorderStyle_DefaultIsSunken_AllValuesSupported()
+ {
+ StatusBarPanel panel = new();
+
+ Assert.Equal(StatusBarPanelBorderStyle.Sunken, panel.BorderStyle);
+
+ panel.BorderStyle = StatusBarPanelBorderStyle.Raised;
+ Assert.Equal(StatusBarPanelBorderStyle.Raised, panel.BorderStyle);
+
+ panel.BorderStyle = StatusBarPanelBorderStyle.None;
+ Assert.Equal(StatusBarPanelBorderStyle.None, panel.BorderStyle);
+ }
+
+ [StaFact]
+ public void StatusBarPanel_Style_DefaultIsText_OwnerDrawSupported()
+ {
+ StatusBarPanel panel = new();
+
+ Assert.Equal(StatusBarPanelStyle.Text, panel.Style);
+
+ panel.Style = StatusBarPanelStyle.OwnerDraw;
+
+ Assert.Equal(StatusBarPanelStyle.OwnerDraw, panel.Style);
+ }
+
+ [StaFact]
+ public void StatusBarPanel_Width_ThrowsWhenSetBelowMinWidth()
+ {
+ StatusBarPanel panel = new() { MinWidth = 50 };
+
+ Assert.Throws(() => panel.Width = 30);
+ }
+
+ [StaFact]
+ public void StatusBarPanel_MinWidth_WhenRaisedAboveCurrentWidth_ExpandsWidth()
+ {
+ StatusBarPanel panel = new() { Width = 60, MinWidth = 10 };
+
+ panel.MinWidth = 80;
+
+ Assert.Equal(80, panel.MinWidth);
+ Assert.Equal(80, panel.Width);
+ }
+
+ [StaFact]
+ public void StatusBarPanel_ToolTipText_DefaultIsEmpty_CanBeSet()
+ {
+ StatusBarPanel panel = new();
+
+ Assert.Equal(string.Empty, panel.ToolTipText);
+
+ panel.ToolTipText = "Shows the current connection status.";
+
+ Assert.Equal("Shows the current connection status.", panel.ToolTipText);
+ }
+
+ [StaFact]
+ public void StatusBarPanel_ToString_IncludesText()
+ {
+ StatusBarPanel panel = new() { Text = "Ready" };
+
+ Assert.Equal("StatusBarPanel: {Ready}", panel.ToString());
+ }
+
+ [StaFact]
+ public void StatusBar_PanelClick_Event_FiredThroughOnPanelClick()
+ {
+ using SubStatusBar statusBar = new();
+ StatusBarPanel panel = new() { Text = "Clickable" };
+ statusBar.Panels.Add(panel);
+
+ StatusBarPanelClickEventArgs? received = null;
+ statusBar.PanelClick += (_, e) => received = e;
+
+ StatusBarPanelClickEventArgs args = new(panel, MouseButtons.Left, 1, 10, 5);
+ statusBar.RaisePanelClick(args);
+
+ Assert.NotNull(received);
+ Assert.Same(panel, received.StatusBarPanel);
+ Assert.Equal(MouseButtons.Left, received.Button);
+ Assert.Equal(1, received.Clicks);
+ }
+
+ [StaFact]
+ public void StatusBar_DrawItem_Event_FiredThroughOnDrawItem()
+ {
+ using SubStatusBar statusBar = new();
+ StatusBarPanel panel = new() { Style = StatusBarPanelStyle.OwnerDraw, Width = 100 };
+ statusBar.Panels.Add(panel);
+
+ StatusBarDrawItemEventArgs? received = null;
+ statusBar.DrawItem += (_, e) => received = e;
+
+ using Bitmap bitmap = new(10, 10);
+ using Graphics g = Graphics.FromImage(bitmap);
+ StatusBarDrawItemEventArgs args = new(g, SystemFonts.DefaultFont!, Rectangle.Empty, 0, DrawItemState.None, panel);
+ statusBar.RaiseDrawItem(args);
+
+ Assert.NotNull(received);
+ Assert.Same(panel, received.Panel);
+ Assert.Equal(DrawItemState.None, received.State);
+ }
+
+ private sealed class SubStatusBar : StatusBar
+ {
+ public void RaisePanelClick(StatusBarPanelClickEventArgs e) => OnPanelClick(e);
+
+ public void RaiseDrawItem(StatusBarDrawItemEventArgs e) => OnDrawItem(e);
+ }
+}
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/System.Windows.Forms.Legacy.Tests.csproj b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/System.Windows.Forms.Legacy.Tests.csproj
new file mode 100644
index 00000000000..2d809de49a8
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/System.Windows.Forms.Legacy.Tests.csproj
@@ -0,0 +1,23 @@
+
+
+
+ $(NetCurrent)-windows7.0
+ true
+ true
+ enable
+ enable
+ $(NoWarn);SA1005;SA1028;WFDEV006
+
+ System.Windows.Forms.Tests
+ false
+ true
+ $([System.IO.Path]::GetFullPath('$(RepoRoot)Bin\$(OutDirName)\'))
+
+
+
+
+
+
+
+
+
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/TabControlTests.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/TabControlTests.cs
new file mode 100644
index 00000000000..5050e89093f
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/TabControlTests.cs
@@ -0,0 +1,52 @@
+namespace System.Windows.Forms.Tests
+{
+ public class TabControlTests
+ {
+ [StaFact]
+ public void TabControl_ClearTabsWhileSelected_DoesNotThrowNullReferenceException()
+ {
+ Exception? capturedException = null;
+ string? failureMessage = null;
+ ThreadExceptionEventHandler handler = (_, e) => capturedException = e.Exception;
+
+ Application.ThreadException += handler;
+ try
+ {
+ using Form form = new();
+ using TabControl control = new();
+
+ control.TabPages.Add(new TabPage("Tab 1"));
+ control.TabPages.Add(new TabPage("Tab 2"));
+ control.TabPages.Add(new TabPage("Tab 3"));
+
+ form.Controls.Add(control);
+ form.Show();
+ _ = control.AccessibilityObject;
+
+ control.SelectedIndex = 1;
+ Application.DoEvents();
+
+ control.TabPages.Clear();
+
+ Application.DoEvents();
+ System.Threading.Thread.Sleep(10);
+
+ Assert.Empty(control.TabPages);
+ Assert.Null(control.SelectedTab);
+ }
+ finally
+ {
+ Application.ThreadException -= handler;
+ if (capturedException is not null)
+ {
+ failureMessage = $"Unhandled exception: {capturedException.GetType().Name}\n{capturedException.Message}\n{capturedException.StackTrace}";
+ }
+ }
+
+ if (failureMessage is not null)
+ {
+ throw new Xunit.Sdk.XunitException(failureMessage);
+ }
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/ToolBarTests.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/ToolBarTests.cs
new file mode 100644
index 00000000000..cef6bf352a4
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/ToolBarTests.cs
@@ -0,0 +1,268 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Drawing;
+
+namespace System.Windows.Forms.Tests;
+
+public class ToolBarTests
+{
+ [StaFact]
+ public void ToolBar_DefaultConfiguration_MatchesLegacyBehavior()
+ {
+ using ToolBar toolBar = new();
+
+ Assert.Equal(ToolBarAppearance.Normal, toolBar.Appearance);
+ Assert.Equal(BorderStyle.None, toolBar.BorderStyle);
+ Assert.Equal(DockStyle.Top, toolBar.Dock);
+ Assert.Equal(ToolBarTextAlign.Underneath, toolBar.TextAlign);
+ Assert.True(toolBar.AutoSize);
+ Assert.True(toolBar.Divider);
+ Assert.True(toolBar.DropDownArrows);
+ Assert.True(toolBar.ShowToolTips);
+ Assert.True(toolBar.Wrappable);
+ Assert.False(toolBar.TabStop);
+ Assert.Empty(toolBar.Buttons);
+ Assert.Equal(new Size(39, 36), toolBar.ButtonSize);
+ }
+
+ [StaFact]
+ public void ToolBar_ButtonSize_DefaultTracksTextAlignment()
+ {
+ using ToolBar toolBar = new();
+
+ toolBar.TextAlign = ToolBarTextAlign.Right;
+
+ Assert.Equal(new Size(23, 22), toolBar.ButtonSize);
+ }
+
+ [StaFact]
+ public void ToolBar_Buttons_AddRange_PreservesOrderAndSetsParent()
+ {
+ using ToolBar toolBar = new();
+ ToolBarButton openButton = new() { Name = "open", Text = "Open" };
+ ToolBarButton saveButton = new() { Name = "save", Text = "Save" };
+ ToolBarButton printButton = new() { Name = "print", Text = "Print" };
+
+ toolBar.Buttons.AddRange([openButton, saveButton, printButton]);
+
+ Assert.Equal(3, toolBar.Buttons.Count);
+ Assert.Same(openButton, toolBar.Buttons[0]);
+ Assert.Same(saveButton, toolBar.Buttons[1]);
+ Assert.Same(printButton, toolBar.Buttons[2]);
+ Assert.Same(toolBar, openButton.Parent);
+ Assert.Same(toolBar, saveButton.Parent);
+ Assert.Same(toolBar, printButton.Parent);
+ }
+
+ [StaFact]
+ public void ToolBar_Buttons_Insert_UpdatesOrderAndKeyLookup()
+ {
+ using ToolBar toolBar = new();
+ ToolBarButton firstButton = new() { Name = "first", Text = "First" };
+ ToolBarButton lastButton = new() { Name = "last", Text = "Last" };
+ ToolBarButton insertedButton = new() { Name = "inserted", Text = "Inserted" };
+
+ toolBar.Buttons.AddRange([firstButton, lastButton]);
+
+ toolBar.Buttons.Insert(1, insertedButton);
+
+ Assert.Same(firstButton, toolBar.Buttons[0]);
+ Assert.Same(insertedButton, toolBar.Buttons[1]);
+ Assert.Same(lastButton, toolBar.Buttons[2]);
+ Assert.Equal(1, toolBar.Buttons.IndexOfKey("inserted"));
+ Assert.Same(insertedButton, toolBar.Buttons["INSERTED"]);
+ }
+
+ [StaFact]
+ public void ToolBar_Buttons_SetIndexer_ReplacesButtonAndUpdatesParents()
+ {
+ using ToolBar toolBar = new();
+ ToolBarButton originalButton = new() { Name = "original", Text = "Original" };
+ ToolBarButton replacementButton = new() { Name = "replacement", Text = "Replacement" };
+
+ toolBar.Buttons.Add(originalButton);
+
+ toolBar.Buttons[0] = replacementButton;
+
+ Assert.Null(originalButton.Parent);
+ Assert.Same(replacementButton, toolBar.Buttons[0]);
+ Assert.Same(toolBar, replacementButton.Parent);
+ }
+
+ [StaFact]
+ public void ToolBar_Buttons_RemoveAt_DetachesRemovedButton()
+ {
+ using ToolBar toolBar = new();
+ ToolBarButton keepButton = new() { Name = "keep", Text = "Keep" };
+ ToolBarButton removeButton = new() { Name = "remove", Text = "Remove" };
+
+ toolBar.Buttons.AddRange([keepButton, removeButton]);
+
+ toolBar.Buttons.RemoveAt(1);
+
+ Assert.Single(toolBar.Buttons);
+ Assert.Same(keepButton, toolBar.Buttons[0]);
+ Assert.Null(removeButton.Parent);
+ }
+
+ [StaFact]
+ public void ToolBar_Buttons_Clear_DetachesAllButtons()
+ {
+ using ToolBar toolBar = new();
+ ToolBarButton firstButton = new() { Text = "First" };
+ ToolBarButton secondButton = new() { Text = "Second" };
+
+ toolBar.Buttons.AddRange([firstButton, secondButton]);
+
+ toolBar.Buttons.Clear();
+
+ Assert.Empty(toolBar.Buttons);
+ Assert.Null(firstButton.Parent);
+ Assert.Null(secondButton.Parent);
+ }
+
+ [StaFact]
+ public void ToolBar_Buttons_KeyLookup_IsCaseInsensitive_AndEmptyKeysAreIgnored()
+ {
+ using ToolBar toolBar = new();
+ ToolBarButton saveButton = new() { Name = "Save", Text = "Save" };
+
+ toolBar.Buttons.Add(saveButton);
+
+ Assert.True(toolBar.Buttons.ContainsKey("save"));
+ Assert.Equal(0, toolBar.Buttons.IndexOfKey("SAVE"));
+ Assert.Same(saveButton, toolBar.Buttons["sAvE"]);
+ Assert.False(toolBar.Buttons.ContainsKey(string.Empty));
+ Assert.Equal(-1, toolBar.Buttons.IndexOfKey(string.Empty));
+ Assert.Null(toolBar.Buttons[string.Empty]);
+ Assert.Null(toolBar.Buttons[(string)null!]);
+ }
+
+ [StaFact]
+ public void ToolBar_Buttons_RemoveByKey_MissingKey_IsNoOp()
+ {
+ using ToolBar toolBar = new();
+ ToolBarButton button = new() { Name = "open", Text = "Open" };
+
+ toolBar.Buttons.Add(button);
+
+ toolBar.Buttons.RemoveByKey("missing");
+
+ Assert.Single(toolBar.Buttons);
+ Assert.Same(button, toolBar.Buttons[0]);
+ Assert.Same(toolBar, button.Parent);
+ }
+
+ [StaFact]
+ public void ToolBar_ButtonSize_NegativeDimension_ThrowsArgumentOutOfRangeException()
+ {
+ using ToolBar toolBar = new();
+
+ Assert.Throws(() => toolBar.ButtonSize = new Size(-1, 10));
+ Assert.Throws(() => toolBar.ButtonSize = new Size(10, -1));
+ }
+}
+
+public class ToolBarButtonTests
+{
+ [StaFact]
+ public void ToolBarButton_DefaultState_MatchesToolbarButtonBehavior()
+ {
+ using ToolBarButton button = new();
+
+ Assert.Equal(string.Empty, button.Text);
+ Assert.Equal(string.Empty, button.ToolTipText);
+ Assert.Equal(ToolBarButtonStyle.PushButton, button.Style);
+ Assert.True(button.Enabled);
+ Assert.True(button.Visible);
+ Assert.False(button.Pushed);
+ Assert.False(button.PartialPush);
+ Assert.Equal(Rectangle.Empty, button.Rectangle);
+ Assert.Null(button.Parent);
+ Assert.Null(button.DropDownMenu);
+ Assert.Null(button.Tag);
+ }
+
+ [StaFact]
+ public void ToolBarButton_Text_NormalizesNullAndEmptyToEmptyString()
+ {
+ using ToolBarButton button = new("Initial");
+
+ button.Text = null;
+ Assert.Equal(string.Empty, button.Text);
+
+ button.Text = string.Empty;
+ Assert.Equal(string.Empty, button.Text);
+ }
+
+ [StaFact]
+ public void ToolBarButton_DropDownMenu_RequiresContextMenu()
+ {
+ using ToolBarButton button = new();
+ using ContextMenu contextMenu = new();
+ using MainMenu mainMenu = new();
+
+ button.DropDownMenu = contextMenu;
+
+ Assert.Same(contextMenu, button.DropDownMenu);
+ Assert.Throws(() => button.DropDownMenu = mainMenu);
+ }
+
+ [StaFact]
+ public void ToolBarButton_ImageIndex_LessThanMinusOne_ThrowsArgumentOutOfRangeException()
+ {
+ using ToolBarButton button = new();
+
+ Assert.Throws(() => button.ImageIndex = -2);
+ }
+
+ [StaFact]
+ public void ToolBarButton_StateProperties_RoundTripWithoutParentHandle()
+ {
+ using ToolBarButton button = new();
+ object tag = new();
+
+ button.Enabled = false;
+ button.Visible = false;
+ button.Pushed = true;
+ button.PartialPush = true;
+ button.Style = ToolBarButtonStyle.ToggleButton;
+ button.ToolTipText = "Click to toggle";
+ button.Tag = tag;
+
+ Assert.False(button.Enabled);
+ Assert.False(button.Visible);
+ Assert.True(button.Pushed);
+ Assert.True(button.PartialPush);
+ Assert.Equal(ToolBarButtonStyle.ToggleButton, button.Style);
+ Assert.Equal("Click to toggle", button.ToolTipText);
+ Assert.Same(tag, button.Tag);
+ }
+}
+
+public class ToolBarButtonClickEventArgsTests
+{
+ [StaFact]
+ public void ToolBarButtonClickEventArgs_Constructor_StoresButtonReference()
+ {
+ using ToolBarButton button = new() { Text = "Open" };
+
+ ToolBarButtonClickEventArgs eventArgs = new(button);
+
+ Assert.Same(button, eventArgs.Button);
+ }
+
+ [StaFact]
+ public void ToolBarButtonClickEventArgs_Button_CanBeReassigned()
+ {
+ using ToolBarButton originalButton = new() { Text = "Original" };
+ using ToolBarButton replacementButton = new() { Text = "Replacement" };
+ ToolBarButtonClickEventArgs eventArgs = new(originalButton);
+
+ eventArgs.Button = replacementButton;
+
+ Assert.Same(replacementButton, eventArgs.Button);
+ }
+}
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/ToolBarToolTipTests.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/ToolBarToolTipTests.cs
new file mode 100644
index 00000000000..c05917b81f5
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/ToolBarToolTipTests.cs
@@ -0,0 +1,255 @@
+using System.Runtime.InteropServices;
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.UI.Controls;
+using Windows.Win32.UI.WindowsAndMessaging;
+using DrawingPoint = System.Drawing.Point;
+using DrawingSize = System.Drawing.Size;
+
+namespace System.Windows.Forms.Tests
+{
+ public class ToolBarToolTipTests
+ {
+ // Minimal interop needed for this test (use internal wrappers/constants where available)
+
+ // Hot item flags
+ [Flags]
+ private enum HICF : uint
+ {
+ ENTERING = 0x0010,
+ }
+
+ private const uint TbnHotItemChange = unchecked((uint)-713);
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct NMTBHOTITEM
+ {
+ public NMHDR hdr;
+ public int idOld;
+ public int idNew;
+ public HICF dwFlags;
+ }
+
+ // High DPI thread awareness helpers (User32)
+ [DllImport("user32.dll", EntryPoint = "SetThreadDpiAwarenessContext", ExactSpelling = true)]
+ private static extern IntPtr SetThreadDpiAwarenessContext(IntPtr dpiContext);
+
+ [DllImport("user32.dll", EntryPoint = "GetThreadDpiAwarenessContext", ExactSpelling = true)]
+ private static extern IntPtr GetThreadDpiAwarenessContext();
+
+ // Known DPI_AWARENESS_CONTEXT values (casted from macros):
+ // https://learn.microsoft.com/windows/win32/hidpi/dpi-awareness-context
+ private static readonly IntPtr s_dpiAwarenessContextPerMonitorAwareV2 = new(-4);
+
+ private sealed class DpiAwarenessScope : IDisposable
+ {
+ private readonly IntPtr _old;
+ private readonly bool _enabled;
+
+ public DpiAwarenessScope(IntPtr newContext)
+ {
+ try
+ {
+ _old = GetThreadDpiAwarenessContext();
+ _enabled = SetThreadDpiAwarenessContext(newContext) != IntPtr.Zero;
+ }
+ catch (EntryPointNotFoundException)
+ {
+ _enabled = false; // OS doesn't support per-thread DPI; proceed without changing
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_enabled)
+ {
+ try
+ {
+ SetThreadDpiAwarenessContext(_old);
+ }
+ catch (EntryPointNotFoundException)
+ {
+ /* ignore */
+ }
+ }
+ }
+ }
+
+ [StaFact]
+ public void ToolBar_ToolTip_Show_DoesNotMove_ToolBar()
+ {
+ using var form = new Form
+ {
+ StartPosition = FormStartPosition.Manual,
+ Location = new DrawingPoint(100, 100),
+ Size = new DrawingSize(400, 200)
+ };
+
+ var toolBar = new ToolBar
+ {
+ ShowToolTips = true,
+ Dock = DockStyle.None,
+ Location = new DrawingPoint(0, 0)
+ };
+
+ toolBar.Buttons.Add(new ToolBarButton("Btn") { ToolTipText = "Tip" });
+ form.Controls.Add(toolBar);
+
+ form.Show();
+ Application.DoEvents();
+
+ // Precondition: toolbar starts at 0,0
+ Assert.Equal(new DrawingPoint(0, 0), toolBar.Location);
+
+ // Get the native tooltip HWND created by the toolbar
+ HWND tooltipHwnd = (HWND)PInvokeCore.SendMessage(toolBar, PInvoke.TB_GETTOOLTIPS);
+ Assert.True(tooltipHwnd != HWND.Null, "Expected native tooltip window");
+
+ // Force the tooltip window top-left at (0,0) so TTN_SHOW logic will try to reposition it
+ PInvoke.SetWindowPos(tooltipHwnd, HWND.Null, 0, 0, 0, 0,
+ SET_WINDOW_POS_FLAGS.SWP_NOSIZE | SET_WINDOW_POS_FLAGS.SWP_NOZORDER | SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE);
+
+ // 1) Simulate hot item change so internal hotItem != -1
+ var hot = new NMTBHOTITEM
+ {
+ hdr = new NMHDR { hwndFrom = (HWND)toolBar.Handle, idFrom = 0, code = TbnHotItemChange },
+ idOld = -1,
+ idNew = 0,
+ dwFlags = HICF.ENTERING
+ };
+ PInvokeCore.SendMessage(toolBar, MessageId.WM_REFLECT_NOTIFY, 0, ref hot);
+
+ Application.DoEvents();
+
+ // 2) Simulate TTN_SHOW from the tooltip window
+ var nmhdr = new NMHDR { hwndFrom = tooltipHwnd, idFrom = 0, code = PInvoke.TTN_SHOW };
+ PInvokeCore.SendMessage(toolBar, MessageId.WM_REFLECT_NOTIFY, 0, ref nmhdr);
+
+ Application.DoEvents();
+
+ // Assertion: Showing the tooltip must NOT move the toolbar.
+ // This would fail under the original bug where SetWindowPos targeted the toolbar HWND.
+ Assert.Equal(new DrawingPoint(0, 0), toolBar.Location);
+
+ form.Close();
+ }
+
+ [StaFact]
+ public void ToolBar_ToolTip_Show_Returns_1_When_Tooltip_Is_At_0_0()
+ {
+ using var form = new Form
+ {
+ StartPosition = FormStartPosition.Manual,
+ Location = new DrawingPoint(200, 200),
+ Size = new DrawingSize(400, 200)
+ };
+
+ var toolBar = new ToolBar
+ {
+ ShowToolTips = true,
+ Dock = DockStyle.None,
+ Location = new DrawingPoint(10, 10)
+ };
+
+ toolBar.Buttons.Add(new ToolBarButton("Btn") { ToolTipText = "Tip" });
+ form.Controls.Add(toolBar);
+
+ form.Show();
+ Application.DoEvents();
+
+ // Ensure toolbar is not at 0,0 so wrong GetWindowPlacement handle avoids the reposition branch
+ Assert.Equal(new DrawingPoint(10, 10), toolBar.Location);
+
+ // Acquire native tooltip HWND
+ HWND tooltipHwnd = (HWND)PInvokeCore.SendMessage(toolBar, PInvoke.TB_GETTOOLTIPS);
+ Assert.NotEqual(HWND.Null, tooltipHwnd);
+
+ // Force the tooltip at (0,0) to trigger the reposition path when correct handle is used
+ PInvoke.SetWindowPos(tooltipHwnd, HWND.Null, 0, 0, 0, 0,
+ SET_WINDOW_POS_FLAGS.SWP_NOSIZE | SET_WINDOW_POS_FLAGS.SWP_NOZORDER | SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE);
+
+ // Simulate hot item change so hotItem != -1
+ var hot = new NMTBHOTITEM
+ {
+ hdr = new NMHDR { hwndFrom = (HWND)toolBar.Handle, idFrom = 0, code = TbnHotItemChange },
+ idOld = -1,
+ idNew = 0,
+ dwFlags = HICF.ENTERING
+ };
+ PInvokeCore.SendMessage(toolBar, MessageId.WM_REFLECT_NOTIFY, 0, ref hot);
+
+ Application.DoEvents();
+
+ // Simulate TTN_SHOW and capture the return value from WndProc
+ var nmhdr = new NMHDR { hwndFrom = tooltipHwnd, idFrom = 0, code = PInvoke.TTN_SHOW };
+ nint ret = PInvokeCore.SendMessage(toolBar, MessageId.WM_REFLECT_NOTIFY, 0, ref nmhdr);
+
+ // Expect: when the correct window (tooltip) is queried for placement, TTN_SHOW repositions and returns 1.
+ // With the buggy code that queries the toolbar's placement, the condition won't trigger and ret will be 0.
+ Assert.Equal((nint)1, ret);
+
+ form.Close();
+ }
+
+ [StaFact]
+ public void ToolBar_ToolTip_TTN_SHOW_PerMonitorV2_DoesNotMove_And_Returns_1()
+ {
+ // Enter Per-Monitor V2 DPI context for the thread (best effort; no-op on older OS)
+ using var scope = new DpiAwarenessScope(s_dpiAwarenessContextPerMonitorAwareV2);
+
+ using var form = new Form
+ {
+ StartPosition = FormStartPosition.Manual,
+ Location = new DrawingPoint(300, 300),
+ Size = new DrawingSize(500, 300)
+ };
+
+ var toolBar = new ToolBar
+ {
+ ShowToolTips = true,
+ Dock = DockStyle.None,
+ Location = new DrawingPoint(12, 12)
+ };
+
+ toolBar.Buttons.Add(new ToolBarButton("Btn") { ToolTipText = "Tip" });
+ form.Controls.Add(toolBar);
+
+ form.Show();
+ Application.DoEvents();
+
+ var originalLocation = toolBar.Location;
+
+ // Acquire native tooltip HWND
+ HWND tooltipHwnd = (HWND)PInvokeCore.SendMessage(toolBar, PInvoke.TB_GETTOOLTIPS);
+ Assert.NotEqual(HWND.Null, tooltipHwnd);
+
+ // Force tooltip to (0,0) so TTN_SHOW reposition path is taken
+ PInvoke.SetWindowPos(tooltipHwnd, HWND.Null, 0, 0, 0, 0,
+ SET_WINDOW_POS_FLAGS.SWP_NOSIZE | SET_WINDOW_POS_FLAGS.SWP_NOZORDER | SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE);
+
+ // Simulate hot item change so hotItem != -1
+ var hot = new NMTBHOTITEM
+ {
+ hdr = new NMHDR { hwndFrom = (HWND)toolBar.Handle, idFrom = 0, code = TbnHotItemChange },
+ idOld = -1,
+ idNew = 0,
+ dwFlags = HICF.ENTERING
+ };
+ PInvokeCore.SendMessage(toolBar, MessageId.WM_REFLECT_NOTIFY, 0, ref hot);
+
+ Application.DoEvents();
+
+ // Simulate TTN_SHOW and capture return value
+ var nmhdr = new NMHDR { hwndFrom = tooltipHwnd, idFrom = 0, code = PInvoke.TTN_SHOW };
+ nint ret = PInvokeCore.SendMessage(toolBar, MessageId.WM_REFLECT_NOTIFY, 0, ref nmhdr);
+
+ Application.DoEvents();
+
+ // Assertions: TTN_SHOW is handled (ret==1) and the toolbar itself does not move
+ Assert.Equal((nint)1, ret);
+ Assert.Equal(originalLocation, toolBar.Location);
+
+ form.Close();
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/TreeNodeTests.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/TreeNodeTests.cs
new file mode 100644
index 00000000000..c1f942c25c5
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/TreeNodeTests.cs
@@ -0,0 +1,137 @@
+namespace System.Windows.Forms.Tests
+{
+ public class TreeNodeTests
+ {
+ [StaFact]
+ public void Text_SetAndGet_ReturnsCorrectValue()
+ {
+ // Arrange
+ var treeNode = new TreeNode();
+ var text = "Test Node";
+
+ // Act
+ treeNode.Text = text;
+
+ // Assert
+ Assert.Equal(text, treeNode.Text);
+ }
+
+ [StaFact]
+ public void Nodes_AddAndGet_ReturnsCorrectNodes()
+ {
+ // Arrange
+ var parentNode = new TreeNode();
+ var childNode = new TreeNode("Child Node");
+
+ // Act
+ parentNode.Nodes.Add(childNode);
+
+ // Assert
+ Assert.Single(parentNode.Nodes);
+ Assert.Same(childNode, parentNode.Nodes[0]);
+ }
+
+ [StaFact]
+ public void Parent_SetAndGet_ReturnsCorrectParent()
+ {
+ // Arrange
+ var parentNode = new TreeNode("Parent Node");
+ var childNode = new TreeNode("Child Node");
+
+ // Act
+ parentNode.Nodes.Add(childNode);
+
+ // Assert
+ Assert.Same(parentNode, childNode.Parent);
+ }
+
+ [StaFact]
+ public void Remove_RemovesNodeFromParent()
+ {
+ // Arrange
+ var parentNode = new TreeNode("Parent Node");
+ var childNode = new TreeNode("Child Node");
+ parentNode.Nodes.Add(childNode);
+
+ // Act
+ parentNode.Nodes.Remove(childNode);
+
+ // Assert
+ Assert.Empty(parentNode.Nodes);
+ Assert.Null(childNode.Parent);
+ }
+
+ [StaFact]
+ public void TreeView_SetAndGet_ReturnsCorrectTreeView()
+ {
+ // Arrange
+ var treeView = new TreeView();
+ var treeNode = new TreeNode("Test Node");
+
+ // Act
+ treeView.Nodes.Add(treeNode);
+
+ // Assert
+ Assert.Same(treeView, treeNode.TreeView);
+ }
+
+ // Commenting out this test, as it doesn't work in DAT
+ //[Test]
+ //public void ContextMenu_ShowsOnRightClick()
+ //{
+ // // Arrange
+ // var form = new Form();
+ // var treeView = new TreeView();
+ // var treeNode = new TreeNode("Test Node");
+ // var contextMenu = new ContextMenu();
+ // contextMenu.MenuItems.Add(new MenuItem("Test Item"));
+ // treeNode.ContextMenu = contextMenu;
+ // treeView.Nodes.Add(treeNode);
+ // treeView.Bounds = new Rectangle(10, 10, 200, 200);
+ // form.Controls.Add(treeView);
+
+ // bool contextMenuShown = false;
+ // contextMenu.Popup += (sender, e) => contextMenuShown = true;
+
+ // // Ensure the Form and TreeView are created and visible
+ // form.Load += async (sender, e) =>
+ // {
+ // // Need to wait for the form to be created and visible
+ // await Task.Delay(500);
+
+ // // Get the bounds of the TreeNode
+ // var nodeBounds = treeNode.Bounds;
+ // var clickPointRelativeToTreeView = nodeBounds.Location + new Size(nodeBounds.Width / 2, nodeBounds.Y + nodeBounds.Height / 2);
+ // var clickPointRelativeToScreen = treeView.PointToScreen(clickPointRelativeToTreeView);
+
+ // // Simulate right-click event on the TreeNode
+ // Cursor.Position = clickPointRelativeToScreen;
+ // MouseOperations.MouseEvent(MouseOperations.MouseEventFlags.RightDown, (uint)clickPointRelativeToScreen.X, (uint)clickPointRelativeToScreen.Y);
+ // await Task.Delay(100);
+ // MouseOperations.MouseEvent(MouseOperations.MouseEventFlags.RightUp, (uint)clickPointRelativeToScreen.X, (uint)clickPointRelativeToScreen.Y);
+
+ // // Wait 1 second for the context menu to show
+ // await Task.Delay(1000);
+
+ // var clickPointOutsideContextMenuRelative = new Point(50, 50);
+ // var clickPointOutsideContextMenuAbsolute = treeView.PointToScreen(clickPointOutsideContextMenuRelative);
+
+ // Cursor.Position = clickPointOutsideContextMenuAbsolute;
+ // MouseOperations.MouseEvent(MouseOperations.MouseEventFlags.LeftDown, (uint)clickPointOutsideContextMenuAbsolute.X, (uint)clickPointOutsideContextMenuAbsolute.Y);
+ // await Task.Delay(100);
+ // MouseOperations.MouseEvent(MouseOperations.MouseEventFlags.LeftUp, (uint)clickPointOutsideContextMenuAbsolute.X, (uint)clickPointOutsideContextMenuAbsolute.Y);
+
+ // // Wait 1 second for the context menu to close
+ // await Task.Delay(1000);
+
+ // // Assert
+ // Assert.IsTrue(contextMenuShown);
+ // form.Close();
+ // return;
+ // };
+
+ // // Show the form
+ // form.ShowDialog();
+ //}
+ }
+}
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/TreeViewTests.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/TreeViewTests.cs
new file mode 100644
index 00000000000..f9687b82f29
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/TreeViewTests.cs
@@ -0,0 +1,92 @@
+namespace System.Windows.Forms.Tests
+{
+ public class TreeViewTests
+ {
+ [StaFact]
+ public void ContextMenu_SetAndGet_ReturnsCorrectValue()
+ {
+ // Arrange
+ var treeView = new TreeView();
+ var contextMenu = new ContextMenu();
+
+ // Act
+ treeView.ContextMenu = contextMenu;
+
+ // Assert
+ Assert.Same(contextMenu, treeView.ContextMenu);
+ }
+
+ // Commenting out this test, as it doesn't work in DAT
+ //[Test]
+ //public void ContextMenu_ShowsOnRightClick()
+ //{
+ // // Arrange
+ // var form = new Form();
+ // var treeView = new TreeView();
+ // var contextMenu = new ContextMenu();
+ // contextMenu.MenuItems.Add(new MenuItem("Test Item"));
+ // treeView.ContextMenu = contextMenu;
+ // treeView.Bounds = new Rectangle(10, 10, 200, 200);
+ // form.Controls.Add(treeView);
+
+ // bool contextMenuShown = false;
+ // contextMenu.Popup += (sender, e) => contextMenuShown = true;
+
+ // // Ensure the Form and TreeView are created and visible
+ // form.Load += async (sender, e) =>
+ // {
+ // // Wait for the tree view to be created
+ // await Task.Delay(500);
+
+ // var clickPointRelativeToTreeView = treeView.Bounds.Location + new Size(treeView.Bounds.Width / 2, treeView.Bounds.Y + treeView.Bounds.Height / 2);
+ // var clickPointRelativeToScreen = treeView.PointToScreen(clickPointRelativeToTreeView);
+
+ // // Simulate right-click event on the TreeNode
+ // Cursor.Position = clickPointRelativeToScreen;
+ // MouseOperations.MouseEvent(MouseOperations.MouseEventFlags.RightDown, (uint)clickPointRelativeToScreen.X, (uint)clickPointRelativeToScreen.Y);
+ // await Task.Delay(100);
+ // MouseOperations.MouseEvent(MouseOperations.MouseEventFlags.RightUp, (uint)clickPointRelativeToScreen.X, (uint)clickPointRelativeToScreen.Y);
+
+ // // Wait 1 second for the context menu to show
+ // await Task.Delay(1000);
+
+ // var clickPointOutsideContextMenuRelative = new Point(50, 50);
+ // var clickPointOutsideContextMenuAbsolute = treeView.PointToScreen(clickPointOutsideContextMenuRelative);
+
+ // Cursor.Position = clickPointOutsideContextMenuAbsolute;
+ // MouseOperations.MouseEvent(MouseOperations.MouseEventFlags.LeftDown, (uint)clickPointOutsideContextMenuAbsolute.X, (uint)clickPointOutsideContextMenuAbsolute.Y);
+ // await Task.Delay(100);
+ // MouseOperations.MouseEvent(MouseOperations.MouseEventFlags.LeftUp, (uint)clickPointOutsideContextMenuAbsolute.X, (uint)clickPointOutsideContextMenuAbsolute.Y);
+
+ // // Wait 1 second for the context menu to close
+ // await Task.Delay(1000);
+
+ // // Assert
+ // Assert.IsTrue(contextMenuShown);
+ // form.Close();
+ // return;
+ // };
+
+ // // Show the form
+ // form.ShowDialog();
+ //}
+
+ [StaFact]
+ public void ContextMenu_ContainsExpectedItems()
+ {
+ // Arrange
+ var treeView = new TreeView();
+ var contextMenu = new ContextMenu();
+ var menuItem = new MenuItem("Test Item");
+ contextMenu.MenuItems.Add(menuItem);
+ treeView.ContextMenu = contextMenu;
+
+ // Act
+ var items = treeView.ContextMenu.MenuItems;
+
+ // Assert
+ Assert.Single(items);
+ Assert.Equal("Test Item", items[0].Text);
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Legacy/datagrid-port-plan.md b/src/System.Windows.Forms.Legacy/datagrid-port-plan.md
new file mode 100644
index 00000000000..f0026584655
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/datagrid-port-plan.md
@@ -0,0 +1,198 @@
+# DataGrid Port Plan
+
+## Scope
+
+Source of truth for legacy implementations:
+
+`C:\git\github\WiseTechGlobal\CargoWise.Controls.WinForms.Legacy\src\System.Windows.Forms\src\System\Windows\Forms\`
+
+Target directory:
+
+`src\System.Windows.Forms\System\Windows\Forms\Controls\Unsupported\DataGrid\`
+
+This plan exists to port the legacy `DataGrid` family with the smallest practical amount of translation from the legacy source.
+
+## Working Rules
+
+Apply these rules to every file unless there is a strong reason not to:
+
+1. Start from the legacy implementation, not the current shim.
+2. Convert to file-scoped `namespace System.Windows.Forms;` when the file can be converted cleanly.
+3. Keep `#nullable disable` unless the file is actually nullable-clean.
+4. Keep the .NET Foundation license header.
+5. Keep `[Obsolete(Obsoletions.DataGridMessage, ...)]`, `[EditorBrowsable(EditorBrowsableState.Never)]`, and `[Browsable(false)]` on public unsupported types.
+6. Do not do broad C# 13 modernization in the first pass. Get the file compiling first.
+7. Keep split partial files only where they preserve the legacy structure cleanly.
+
+## Unsupported Compat Rule
+
+When current-repo helpers, interop wrappers, or utility APIs differ from the legacy source, prefer a small local `*Compat.cs` adapter in `Controls/Unsupported/DataGrid/`.
+
+That compat layer should adapt the current repo to the legacy code shape, not rewrite the legacy code to fit modern helpers.
+
+Use a local compat file when:
+
+- the mismatch is mechanical rather than architectural
+- the need is specific to the Unsupported `DataGrid` port
+- a narrow wrapper preserves the legacy flow with minimal translation
+- changing shared infrastructure would broaden impact for little value
+
+Typical cases:
+
+- interop signatures that differ only by wrapper shape
+- helper methods that moved or changed overload form
+- small aliases needed to preserve near-verbatim legacy code
+
+Avoid:
+
+- changing shared interop just to make `DataGrid` compile
+- rewriting large legacy call sites to match modern helper conventions
+- spreading Unsupported-only compatibility concerns into shared layers
+
+Example: if legacy code expects `User32.ScrollWindow(..., ref RECT, ref RECT)` and the current repo exposes a different wrapper shape, add a narrow local compat adapter instead of changing shared interop.
+
+## Current Inventory
+
+Already correct enough to leave alone until the main family compiles:
+
+- `DataGridLineStyle.cs`
+- `DataGridParentRowsLabel.cs`
+
+Not in scope:
+
+- `DataGridView.cs` and all `DataGridView.*.cs` files
+- all `DataGridView*` column, cell, band, and related types
+
+## Port Order
+
+### Step 1: Small replacements
+
+Replace the current shims with the legacy implementations for:
+
+- `DataGridCell.cs`
+- `IDataGridEditingService.cs`
+- `GridTablesFactory.cs`
+
+Acceptance: each file compiles in isolation.
+
+### Step 2: Missing support files
+
+Add:
+
+- `DataGridAddNewRow.cs`
+- `DataGridToolTip.cs`
+- `DataGridState.cs`
+- `DataGridCaption.cs`
+
+`DataGridState.cs` remains `internal`.
+
+Acceptance: all files compile without expanding the intended public API.
+
+### Step 3: Type converter and editor control
+
+Port:
+
+- `DataGridPreferredColumnWidthTypeConverter.cs`
+- `DataGridTextBox.cs`
+
+Acceptance: `DataGridTextBox` is ready for `DataGridTextBoxColumn`.
+
+### Step 4: Column styles
+
+Replace and reconcile:
+
+- `DataGridColumnStyle.cs`
+- `DataGridColumnStyle.CompModSwitches.cs`
+- `DataGridColumnStyle.DataGridColumnHeaderAccessibleObject.cs`
+- `GridColumnStylesCollection.cs`
+
+Acceptance: `DataGridBoolColumn` and `DataGridTextBoxColumn` can consume `DataGridColumnStyle` without compile errors.
+
+### Step 5: Column implementations
+
+Port:
+
+- `DataGridBoolColumn.cs`
+- `DataGridTextBoxColumn.cs`
+
+Acceptance: both compile after Step 4.
+
+### Step 6: Table styles
+
+Port and reconcile:
+
+- `DataGridTableStyle.cs`
+- `GridTableStylesCollection.cs`
+
+Acceptance: `DataGrid.cs` can consume the table-style graph.
+
+### Step 7: Internal row and rendering types
+
+Add:
+
+- `DataGridParentRows.cs`
+- `DataGridRelationshipRow.cs`
+- `DataGridRow.cs`
+
+These remain internal implementation types.
+
+Acceptance: row and layout types compile without changing public API.
+
+### Step 8: Main control
+
+Port `DataGrid.cs`.
+
+Also reconcile `HitTestInfo` and `HitTestType`, which currently exist as split shim files.
+
+Expect first-pass errors around:
+
+- missing `using` directives
+- interop wrapper differences
+- resource or `SRDescription` name drift
+- moved accessibility APIs
+- removed feature switches such as older accessibility-improvement toggles
+
+Acceptance: the project builds cleanly and focused tests pass.
+
+## Files To Restore Or Add
+
+Main public or surface-adjacent files:
+
+- `DataGrid.cs`
+- `DataGridBoolColumn.cs`
+- `DataGridCell.cs`
+- `DataGridColumnStyle.cs`
+- `DataGridPreferredColumnWidthTypeConverter.cs`
+- `DataGridTableStyle.cs`
+- `DataGridTextBox.cs`
+- `DataGridTextBoxColumn.cs`
+- `GridTablesFactory.cs`
+- `IDataGridEditingService.cs`
+
+Missing support files:
+
+- `DataGridAddNewRow.cs`
+- `DataGridCaption.cs`
+- `DataGridColumnCollection.cs`
+- `DataGridParentRows.cs`
+- `DataGridRelationshipRow.cs`
+- `DataGridRow.cs`
+- `DataGridState.cs`
+- `DataGridTableCollection.cs`
+- `DataGridToolTip.cs`
+
+Split files to reconcile as part of the port:
+
+- `DataGridColumnStyle.CompModSwitches.cs`
+- `DataGridColumnStyle.DataGridColumnHeaderAccessibleObject.cs`
+- `GridColumnStylesCollection.cs`
+- `GridTableStylesCollection.cs`
+- `DataGrid.HitTestInfo.cs`
+- `DataGrid.HitTestType.cs`
+
+## Deferred Follow-Up
+
+Do not block the initial port on broader cleanup. Leave these for a later pass unless they directly prevent the build:
+
+- resource and `SRDescription` metadata differences versus the legacy source
+- deeper legacy-alignment issues that need broader review
diff --git a/src/System.Windows.Forms.Legacy/unsupported-controls-support.md b/src/System.Windows.Forms.Legacy/unsupported-controls-support.md
new file mode 100644
index 00000000000..f084323f2ac
--- /dev/null
+++ b/src/System.Windows.Forms.Legacy/unsupported-controls-support.md
@@ -0,0 +1,431 @@
+# Supporting Legacy WinForms Controls
+
+## Purpose
+
+This document explains what would be required to support the old WinForms controls that were removed after .NET Core 3.0 and later reintroduced in this repository as binary-compatibility shims.
+
+The short version is:
+
+- The .NET `release/3.0` branch contained real implementations of these controls.
+- This repository currently contains mostly hollow compatibility types for the same public surface.
+- If the requirement is actual runtime support, the current `Unsupported` area is the wrong abstraction layer to build on incrementally.
+- The lowest-risk path is to restore the 3.0 implementations and then adapt them to the current codebase, rather than trying to evolve the shims into working controls member-by-member.
+
+## Historical Baseline
+
+In the upstream `c:\git\github\dotnet\winforms` checkout on `release/3.0`, the legacy controls were still present as normal runtime implementations under:
+
+`src/System.Windows.Forms/src/System/Windows/Forms/`
+
+Representative examples from that branch include:
+
+- `ContextMenu.cs`
+- `Menu.cs`
+- `MenuItem.cs`
+- `MainMenu.cs`
+- `DataGrid.cs`
+- `DataGridTableStyle.cs`
+- `DataGridTextBoxColumn.cs`
+- `StatusBar.cs`
+- `StatusBarPanel.cs`
+- `ToolBar.cs`
+- `ToolBarButton.cs`
+
+Those files were not stubs. They contained state, rendering behavior, event wiring, message processing, layout logic, interop, and designer-facing metadata.
+
+## Current State In This Repository
+
+The current repository contains the old surface area under:
+
+`src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/`
+
+The families currently present are:
+
+- `ContextMenu/`
+- `DataGrid/`
+- `MainMenu/`
+- `StatusBar/`
+- `ToolBar/`
+
+The implementation model is documented in `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/readme.md`.
+
+That readme is explicit about the intent:
+
+- the types exist for binary compatibility
+- they are not intended to be executed
+- constructors throw `PlatformNotSupportedException`
+- non-void members typically `throw null`
+- void members do nothing
+- inherited members are only reintroduced where metadata requires it
+
+This means the current code is designed to let old assemblies load, not to let old controls work.
+
+## Evidence That The Current Surface Is Hollow
+
+Representative examples in this repository:
+
+- `ContextMenu` throws from both public constructors and exposes no real event or display behavior.
+- `DataGrid` throws from its constructor and exposes a large metadata-shaped surface with inert getters, setters, and events.
+- `MainMenu`, `StatusBar`, and `ToolBar` follow the same pattern.
+- `Obsoletions.cs` marks these APIs as binary compatibility only and points developers to modern replacements.
+
+There are also important host integration gaps:
+
+- `Control.ContextMenu` is currently just a compatibility property/event shell.
+- `Form.Menu` and `Form.MergedMenu` are currently compatibility properties, not real menu plumbing.
+
+That difference matters more than the type definitions themselves. Even if the legacy controls were fully implemented, they would still not behave correctly until the owning `Control` and `Form` integration was restored.
+
+## Gap Versus The 3.0 Implementation
+
+The divergence from the 3.0 implementation is large, not cosmetic.
+
+Two representative comparisons:
+
+- `ContextMenu.cs`: the current shim is roughly a few dozen lines of metadata and throwing members, while the 3.0 implementation contained full popup, collapse, RTL, command-key, and rendering behavior.
+- `DataGrid.cs`: a direct no-index diff against the upstream 3.0 file shows that the current version removes roughly ten thousand lines of implementation logic.
+
+This is the central architectural fact for any recovery plan:
+
+> We are not missing a few handlers or a few constructors. We are missing the actual control implementations.
+
+## Why The Existing Unsupported Folder Should Not Be Evolved In Place
+
+The local `Unsupported/readme.md` says the folder should not be modified. Even without that warning, the current design makes it a poor place to grow real support:
+
+- the code structure is intentionally metadata-first, not behavior-first
+- the nullability mode is intentionally disabled for compatibility
+- members are shaped to preserve reflection and JIT behavior, not runtime semantics
+- the folder encodes an explicit product position: load old binaries, do not support the controls
+
+If the business requirement has changed from compatibility to support, that change should be reflected in the architecture instead of slowly weakening the shim model.
+
+## Recommended Strategy
+
+If actual support is required, use this strategy.
+
+### 1. Decide The Support Contract First
+
+Be explicit about which of these is required:
+
+- binary compatibility only
+- runtime support for legacy applications
+- designer support
+- accessibility parity with current WinForms expectations
+- long-term supported public API, or temporary migration bridge
+
+Without this decision, the implementation will drift between two incompatible goals.
+
+### 2. Treat `release/3.0` As The Behavioral Source Of Truth
+
+Use the upstream `release/3.0` implementation as the starting point for behavior, not the current `Unsupported` folder.
+
+That means importing or porting the 3.0 versions of:
+
+- `ContextMenu`, `Menu`, `MenuItem`, `MainMenu`
+- `DataGrid` and its dependent types
+- `StatusBar` and panel/event types
+- `ToolBar` and button/event types
+
+The current shim files are still useful as a quick inventory of required public API and obsoletion metadata, but they are not a viable base for runtime work.
+
+### 3. Restore Host Integration At The Same Time
+
+Do not port the controls in isolation. The owning framework types must be wired back up in the same effort.
+
+At minimum this includes:
+
+- `Control.ContextMenu`
+- `Control.ContextMenuChanged`
+- `Control.OnContextMenuChanged`
+- `Form.Menu`
+- `Form.MergedMenu`
+- `Form.MenuStart`
+- `Form.MenuComplete`
+- menu handle update logic and message processing paths that interact with legacy menus
+
+The upstream 3.0 branch already shows these members participating in runtime behavior. In this repository they are currently compatibility shells.
+
+### 4. Port By Control Family, Not By Individual File
+
+The safest sequence is:
+
+1. Menu stack: `Menu`, `MenuItem`, `ContextMenu`, `MainMenu`, plus `Control` and `Form` integration.
+2. `StatusBar` family.
+3. `ToolBar` family.
+4. `DataGrid` family.
+
+This order is deliberate:
+
+- the menu stack has the smallest conceptual surface and exposes whether host integration is correct
+- `StatusBar` and `ToolBar` are self-contained compared to `DataGrid`
+- `DataGrid` is the highest-risk port because of its size, layout logic, binding behavior, and accessibility implications
+
+### 5. Preserve Public Shape, Reevaluate Product Semantics
+
+Once a family becomes truly supported, revisit whether these should still:
+
+- throw `PlatformNotSupportedException`
+- remain hidden with `EditorBrowsable(Never)`
+- remain marked with the current `WFDEV006` guidance
+- remain described as binary compatibility only in docs and package metadata
+
+If the runtime behavior is restored but the warnings still tell users not to use the types, the product story becomes internally inconsistent.
+
+## Expected Porting Work
+
+Porting the 3.0 code is not a straight file copy. Expect adaptation work in these areas.
+
+### API And Type System Drift
+
+- current repository coding style is significantly newer than the 3.0 code style
+- nullable reference types have evolved, but the legacy surface intentionally disables nullability in some areas
+- helper APIs, interop wrappers, and internal utility names may have changed
+
+### Rendering And Platform Drift
+
+- current WinForms code has moved on in DPI handling, theming, and message processing
+- any imported rendering path must be checked against the current visual style and DPI model
+- old controls may assume older system metrics or menu handle behavior
+
+### Accessibility
+
+The original reason for removing these controls was not arbitrary. They lagged in accessibility and modern platform expectations.
+
+If support is required, decide whether the target is:
+
+- strict behavioral restoration of 3.0 behavior, or
+- restoration plus modern accessibility fixes
+
+That is a product decision, not just an implementation detail.
+
+### Designer And Serialization
+
+Many of these types carry designer attributes and serializer-facing metadata. Runtime support without designer validation is incomplete for many WinForms applications.
+
+At minimum, validate:
+
+- designer load behavior
+- property grid visibility and serialization
+- event hookup serialization
+- resource serialization
+- form/menu merge scenarios
+
+### DataGrid Complexity
+
+`DataGrid` is the hardest family by far.
+
+Reasons:
+
+- very large implementation footprint
+- custom layout and painting
+- binding manager interactions
+- editing service interactions
+- accessibility subtypes
+- table and column style object graph
+
+If schedule is tight, `DataGrid` should be scoped separately from the menu, `StatusBar`, and `ToolBar` work.
+
+## Suggested Execution Plan
+
+## Execution Checklist
+
+### Phase 1: Menu Stack
+
+- [x] Restore `release/3.0` `Menu.cs` into `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/Menu.cs`.
+- [x] Restore `release/3.0` `MenuItem.cs` into `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/MenuItem.cs`.
+- [x] Restore `release/3.0` `ContextMenu.cs` into `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/ContextMenu.cs`.
+- [x] Restore `release/3.0` `MainMenu.cs` into `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/MainMenu/MainMenu.cs`.
+- [x] Remove the old split `Menu.MenuItemCollection.cs` shim because the real implementation now lives in `Menu.cs`.
+- [x] Rewire `Control.ContextMenu`, `Control.ContextMenuChanged`, `Control.OnContextMenuChanged`, `Control.WmContextMenu`, and `Control.ProcessCmdKey`.
+- [x] Rewire `Form.Menu`, `Form.MergedMenu`, `Form.ProcessCmdKey`, `Form.UpdateMenuHandles`, `WM_INITMENUPOPUP`, `WM_MENUCHAR`, and `WM_UNINITMENUPOPUP`.
+- [ ] Run focused menu-stack behavior tests.
+- [ ] Reconcile product semantics for the restored menu stack: obsolete warnings, IntelliSense visibility, and package messaging.
+
+### Phase 2: StatusBar
+
+- [x] Port `release/3.0` `StatusBar.cs`.
+- [x] Port `release/3.0` `StatusBarDrawItemEventArgs.cs`.
+- [x] Port `release/3.0` `StatusBarDrawItemEventHandler.cs`.
+- [x] Port `release/3.0` `StatusBarPanel.cs`.
+- [x] Port `release/3.0` `StatusBarPanelAutoSize.cs`.
+- [x] Port `release/3.0` `StatusBarPanelBorderStyle.cs`.
+- [x] Port `release/3.0` `StatusBarPanelClickEventArgs.cs`.
+- [x] Port `release/3.0` `StatusBarPanelClickEventHandler.cs`.
+- [x] Port `release/3.0` `StatusBarPanelStyle.cs`.
+- [x] Collapse or replace the current split `StatusBar.StatusBarPanelCollection.cs` if the real collection implementation is restored inside `StatusBar.cs`.
+
+### Phase 3: ToolBar
+
+- [x] Port `release/3.0` `ToolBar.cs`.
+- [x] Port `release/3.0` `ToolBarAppearance.cs`.
+- [x] Port `release/3.0` `ToolBarButton.cs`.
+- [x] Port `release/3.0` `ToolBarButtonClickEventArgs.cs`.
+- [x] Port `release/3.0` `ToolBarButtonClickEventHandler.cs`.
+- [x] Port `release/3.0` `ToolBarButtonStyle.cs`.
+- [x] Port `release/3.0` `ToolBarTextAlign.cs`.
+- [x] Collapse or replace the current split `ToolBar.ToolBarButtonCollection.cs` if the real collection implementation is restored inside `ToolBar.cs`.
+
+### Phase 4: DataGrid
+
+- [ ] Port `release/3.0` `DataGrid.cs`.
+- [ ] Port `release/3.0` `DataGridAddNewRow.cs`.
+- [ ] Port `release/3.0` `DataGridBoolColumn.cs`.
+- [ ] Port `release/3.0` `DataGridCaption.cs`.
+- [ ] Port `release/3.0` `DataGridCell.cs`.
+- [ ] Port `release/3.0` `DataGridColumnCollection.cs`.
+- [ ] Port `release/3.0` `DataGridColumnStyle.cs` and reconcile the current split `DataGridColumnStyle.CompModSwitches.cs` and `DataGridColumnStyle.DataGridColumnHeaderAccessibleObject.cs` layout.
+- [ ] Port `release/3.0` `DataGridDefaultColumnWidthTypeConverter.cs` and reconcile the current `DataGridPreferredColumnWidthTypeConverter.cs` naming difference.
+- [ ] Port `release/3.0` `DataGridLineStyle.cs`.
+- [ ] Port `release/3.0` `DataGridParentRows.cs`.
+- [ ] Port `release/3.0` `DataGridParentRowsLabel.cs`.
+- [ ] Port `release/3.0` `DataGridRelationshipRow.cs`.
+- [ ] Port `release/3.0` `DataGridRow.cs`.
+- [ ] Port `release/3.0` `DataGridState.cs`.
+- [ ] Port `release/3.0` `DataGridTableCollection.cs`.
+- [ ] Port `release/3.0` `DataGridTableStyle.cs`.
+- [ ] Port `release/3.0` `DataGridTextBox.cs`.
+- [ ] Port `release/3.0` `DataGridTextBoxColumn.cs`.
+- [ ] Port `release/3.0` `DataGridToolTip.cs`.
+- [ ] Port `release/3.0` `GridTablesFactory.cs`.
+- [ ] Port `release/3.0` `IDataGridEditingService.cs`.
+- [ ] Add the currently missing supporting files under `Controls/Unsupported/DataGrid/` rather than continuing to expand the shim surface ad hoc.
+
+## File-By-File Port Map
+
+### Menu Stack
+
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/Menu.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/Menu.cs`
+ Status: started
+ Note: the real `MenuItemCollection` now lives inside `Menu.cs`; the old split shim file should stay deleted.
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/MenuItem.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/MenuItem.cs`
+ Status: started
+ Note: this also carries `MenuItemData` and `MdiListUserData` helper types.
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/ContextMenu.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/ContextMenu.cs`
+ Status: started
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/MainMenu.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/MainMenu/MainMenu.cs`
+ Status: started
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/MenuMerge.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/MenuMerge.cs`
+ Status: pending review
+ Note: the local enum file already matches the required public shape closely and may not need replacement.
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/Control.cs` classic menu members -> `src/System.Windows.Forms/System/Windows/Forms/Control.cs`
+ Status: started
+ Scope: `ContextMenu`, `ContextMenuChanged`, `OnContextMenuChanged`, `ProcessCmdKey`, `WmContextMenu`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/Form.cs` classic menu members -> `src/System.Windows.Forms/System/Windows/Forms/Form.cs`
+ Status: started
+ Scope: `Menu`, `MergedMenu`, cached current menu state, `UpdateMenuHandles`, `ProcessCmdKey`, `WM_INITMENUPOPUP`, `WM_MENUCHAR`, `WM_UNINITMENUPOPUP`
+
+### StatusBar
+
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/StatusBar.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/StatusBar/StatusBar.cs`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/StatusBarDrawItemEventArgs.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/StatusBar/StatusBarDrawItemEventArgs.cs`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/StatusBarDrawItemEventHandler.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/StatusBar/StatusBarDrawItemEventHandler.cs`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/StatusBarPanel.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/StatusBar/StatusBarPanel.cs`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/StatusBarPanelAutoSize.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/StatusBar/StatusBarPanelAutoSize.cs`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/StatusBarPanelBorderStyle.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/StatusBar/StatusBarPanelBorderStyle.cs`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/StatusBarPanelClickEventArgs.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/StatusBar/StatusBarPanelClickEventArgs.cs`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/StatusBarPanelClickEventHandler.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/StatusBar/StatusBarPanelClickEventHandler.cs`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/StatusBarPanelStyle.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/StatusBar/StatusBarPanelStyle.cs`
+- `release/3.0` embedded collection implementation in `StatusBar.cs` -> current split `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/StatusBar/StatusBar.StatusBarPanelCollection.cs`
+ Note: likely collapse this split once the real `StatusBar.cs` is ported.
+
+### ToolBar
+
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/ToolBar.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ToolBar/ToolBar.cs`
+ Status: completed
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/ToolBarAppearance.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ToolBar/ToolBarAppearance.cs`
+ Status: completed
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/ToolBarButton.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ToolBar/ToolBarButton.cs`
+ Status: completed
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/ToolBarButtonClickEventArgs.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ToolBar/ToolBarButtonClickEventArgs.cs`
+ Status: completed
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/ToolBarButtonClickEventHandler.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ToolBar/ToolBarButtonClickEventHandler.cs`
+ Status: completed
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/ToolBarButtonStyle.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ToolBar/ToolBarButtonStyle.cs`
+ Status: completed
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/ToolBarTextAlign.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ToolBar/ToolBarTextAlign.cs`
+ Status: completed
+- `release/3.0` embedded collection implementation in `ToolBar.cs` -> current split `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ToolBar/ToolBar.ToolBarButtonCollection.cs`
+ Status: completed
+ Note: the split shim has been removed because the runtime collection implementation now lives inside `ToolBar.cs`.
+
+### DataGrid
+
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/DataGrid.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGrid.cs`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/DataGridAddNewRow.cs` -> add new file under `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/DataGridBoolColumn.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridBoolColumn.cs`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/DataGridCaption.cs` -> add new file under `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/DataGridCell.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridCell.cs`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/DataGridColumnCollection.cs` -> add new file under `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/DataGridColumnStyle.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridColumnStyle.cs`
+- `release/3.0` supporting pieces inside `DataGridColumnStyle.cs` -> current split `DataGridColumnStyle.CompModSwitches.cs` and `DataGridColumnStyle.DataGridColumnHeaderAccessibleObject.cs`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/DataGridDefaultColumnWidthTypeConverter.cs` -> reconcile with `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridPreferredColumnWidthTypeConverter.cs`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/DataGridLineStyle.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridLineStyle.cs`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/DataGridParentRows.cs` -> add new file under `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/DataGridParentRowsLabel.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridParentRowsLabel.cs`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/DataGridRelationshipRow.cs` -> add new file under `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/DataGridRow.cs` -> add new file under `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/DataGridState.cs` -> add new file under `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/DataGridTableCollection.cs` -> add new file under `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/DataGridTableStyle.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridTableStyle.cs`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/DataGridTextBox.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridTextBox.cs`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/DataGridTextBoxColumn.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridTextBoxColumn.cs`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/DataGridToolTip.cs` -> add new file under `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/GridTablesFactory.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/GridTablesFactory.cs`
+- `release/3.0` `src/System.Windows.Forms/src/System/Windows/Forms/IDataGridEditingService.cs` -> `src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/IDataGridEditingService.cs`
+
+### Phase 1: Source Import And Inventory
+
+- map every current shim type to its `release/3.0` implementation file
+- identify supporting internal helpers required by each family
+- decide whether to port by cherry-pick, manual copy, or staged diff application
+
+### Phase 2: Menu Stack Recovery
+
+- restore `Menu`, `MenuItem`, `ContextMenu`, and `MainMenu`
+- restore `Control` and `Form` integration
+- add smoke tests for popup display, merge behavior, and menu events
+
+### Phase 3: StatusBar And ToolBar Recovery
+
+- restore the runtime implementations
+- validate layout, painting, events, image handling, and designer serialization
+
+### Phase 4: DataGrid Recovery
+
+- port the full family together
+- add binding, editing, painting, and accessibility tests before considering the work stable
+
+### Phase 5: Product Cleanup
+
+- revisit `WFDEV006` messaging
+- update package description and docs to match the actual support level
+- decide whether any APIs should remain hidden from IntelliSense
+
+## Test Strategy
+
+Minimum test coverage should include:
+
+- construction and disposal of each restored control family
+- Win32 handle creation and recreation
+- keyboard and mouse interaction
+- menu popup and collapse behavior
+- form menu merge behavior
+- status bar panel rendering and click events
+- toolbar button rendering and click events
+- `DataGrid` data binding, edit commit, and navigation
+- designer serialization round-trip for representative scenarios
+- accessibility smoke tests for each family
+
+## Practical Recommendation
+
+If the requirement is truly to support these old controls, do not continue from the current hollow implementation model.
+
+Instead:
+
+1. Keep the current shim code only as a temporary compatibility layer and API inventory.
+2. Use upstream `release/3.0` as the behavioral baseline.
+3. Restore one control family at a time, together with the owning `Control` and `Form` integration.
+4. Treat `DataGrid` as a separate project with its own risk and test budget.
+
+That is the only approach that aligns the codebase with the actual requirement. Anything smaller will likely produce a half-working surface that compiles, loads, and still fails in real applications.
\ No newline at end of file
diff --git a/src/System.Windows.Forms.PrivateSourceGenerators/src/System.Windows.Forms.PrivateSourceGenerators.csproj b/src/System.Windows.Forms.PrivateSourceGenerators/src/System.Windows.Forms.PrivateSourceGenerators.csproj
index edba9ee5458..08702b3d5bf 100644
--- a/src/System.Windows.Forms.PrivateSourceGenerators/src/System.Windows.Forms.PrivateSourceGenerators.csproj
+++ b/src/System.Windows.Forms.PrivateSourceGenerators/src/System.Windows.Forms.PrivateSourceGenerators.csproj
@@ -5,8 +5,14 @@
Preview
enable
true
+ $(DefaultItemExcludes);bin\**;obj\**
+ $(DefaultItemExcludesInProjectFolder);bin\**;obj\**
+
+
+
+
diff --git a/src/System.Windows.Forms/System.Windows.Forms.csproj b/src/System.Windows.Forms/System.Windows.Forms.csproj
index 7c7a3233561..d2d2da2bd86 100644
--- a/src/System.Windows.Forms/System.Windows.Forms.csproj
+++ b/src/System.Windows.Forms/System.Windows.Forms.csproj
@@ -20,6 +20,24 @@
true
true
ECMA
+
+
+ $([System.IO.Path]::GetFullPath('$(RepoRoot)Bin\$(OutDirName)\'))
+ true
+ true
+ WTG.System.Windows.Forms
+ WiseTech Global forked version of System.Windows.Forms that includes controls that were deprecated in .NET Core 3.1 and .NET 5, like DataGrid, Menu, ToolBar and StatusBar.
+ WiseTech Global
+ © WiseTech Global Limited. All rights reserved.
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Control.cs b/src/System.Windows.Forms/System/Windows/Forms/Control.cs
index 9253cce66ec..5c3e1259832 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Control.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Control.cs
@@ -138,6 +138,7 @@ public unsafe partial class Control :
private static readonly object s_enabledEvent = new();
private static readonly object s_dockEvent = new();
private static readonly object s_cursorEvent = new();
+ private static readonly object s_contextMenuEvent = new();
private static readonly object s_contextMenuStripEvent = new();
private static readonly object s_causesValidationEvent = new();
private static readonly object s_regionChangedEvent = new();
@@ -211,6 +212,7 @@ public unsafe partial class Control :
private static readonly int s_controlVersionInfoProperty = PropertyStore.CreateKey();
private static readonly int s_backgroundImageLayoutProperty = PropertyStore.CreateKey();
+ private static readonly int s_contextMenuProperty = PropertyStore.CreateKey();
private static readonly int s_contextMenuStripProperty = PropertyStore.CreateKey();
private static readonly int s_autoScrollOffsetProperty = PropertyStore.CreateKey();
private static readonly int s_useCompatibleTextRenderingProperty = PropertyStore.CreateKey();
@@ -1687,6 +1689,10 @@ public static Font DefaultFont
///
protected virtual Size DefaultSize => Size.Empty;
+ internal virtual bool HasMenu => false;
+
+ private void DetachContextMenu(object? sender, EventArgs e) => ContextMenu = null;
+
private void DetachContextMenuStrip(object? sender, EventArgs e) => ContextMenuStrip = null;
///
@@ -5405,11 +5411,11 @@ Site is { } site
CreateParams cp = CreateParams;
// We would need to get adornments metrics for both (old and new) Dpi in case application is in PerMonitorV2 mode and Dpi changed.
- AdjustWindowRectExForControlDpi(ref adornmentsAfterDpiChange, (WINDOW_STYLE)cp.Style, bMenu: false, (WINDOW_EX_STYLE)cp.ExStyle);
+ AdjustWindowRectExForControlDpi(ref adornmentsAfterDpiChange, (WINDOW_STYLE)cp.Style, bMenu: HasMenu, (WINDOW_EX_STYLE)cp.ExStyle);
if (OriginalDeviceDpiInternal != DeviceDpiInternal && OsVersion.IsWindows10_1703OrGreater())
{
- AdjustWindowRectExForDpi(ref adornmentsBeforeDpiChange, (WINDOW_STYLE)cp.Style, bMenu: false, (WINDOW_EX_STYLE)cp.ExStyle, OriginalDeviceDpiInternal);
+ AdjustWindowRectExForDpi(ref adornmentsBeforeDpiChange, (WINDOW_STYLE)cp.Style, bMenu: HasMenu, (WINDOW_EX_STYLE)cp.ExStyle, OriginalDeviceDpiInternal);
}
else
{
@@ -8786,8 +8792,18 @@ internal static PreProcessControlState PreProcessControlMessageInternal(Control?
/// key. Modifier keys include the SHIFT, CTRL, and ALT keys.
///
///
- protected virtual bool ProcessCmdKey(ref Message msg, Keys keyData) =>
- _parent?.ProcessCmdKey(ref msg, keyData) ?? false;
+ protected virtual bool ProcessCmdKey(ref Message msg, Keys keyData)
+ {
+#pragma warning disable WFDEV006 // Type or member is obsolete
+ if (Properties.TryGetValue(s_contextMenuProperty, out ContextMenu? contextMenu)
+ && contextMenu.ProcessCmdKey(ref msg, keyData, this))
+ {
+ return true;
+ }
+#pragma warning restore WFDEV006
+
+ return _parent?.ProcessCmdKey(ref msg, keyData) ?? false;
+ }
private unsafe void PrintToMetaFile(HDC hDC, IntPtr lParam)
{
@@ -9803,7 +9819,7 @@ protected virtual void ScaleControl(SizeF factor, BoundsSpecified specified)
{
CreateParams cp = CreateParams;
RECT adornments = default;
- AdjustWindowRectExForControlDpi(ref adornments, (WINDOW_STYLE)cp.Style, false, (WINDOW_EX_STYLE)cp.ExStyle);
+ AdjustWindowRectExForControlDpi(ref adornments, (WINDOW_STYLE)cp.Style, HasMenu, (WINDOW_EX_STYLE)cp.ExStyle);
Size minSize = MinimumSize;
Size maxSize = MaximumSize;
@@ -10248,7 +10264,7 @@ internal Size SizeFromClientSizeInternal(Size size)
{
RECT rect = new(size);
CreateParams cp = CreateParams;
- AdjustWindowRectExForControlDpi(ref rect, (WINDOW_STYLE)cp.Style, false, (WINDOW_EX_STYLE)cp.ExStyle);
+ AdjustWindowRectExForControlDpi(ref rect, (WINDOW_STYLE)cp.Style, HasMenu, (WINDOW_EX_STYLE)cp.ExStyle);
return rect.Size;
}
@@ -11208,6 +11224,28 @@ internal virtual void WmContextMenu(ref Message m)
///
internal void WmContextMenu(ref Message m, Control sourceControl)
{
+ if (Properties.TryGetValue(s_contextMenuProperty, out ContextMenu? contextMenu))
+ {
+ int legacyX = PARAM.SignedLOWORD(m.LParamInternal);
+ int legacyY = PARAM.SignedHIWORD(m.LParamInternal);
+ Point legacyClient = m.LParamInternal == -1
+ ? new Point(Width / 2, Height / 2)
+ : PointToClient(new Point(legacyX, legacyY));
+
+ if (ClientRectangle.Contains(legacyClient))
+ {
+#pragma warning disable WFDEV006 // Type or member is obsolete
+ contextMenu.Show(sourceControl, legacyClient);
+#pragma warning restore WFDEV006
+ }
+ else
+ {
+ DefWndProc(ref m);
+ }
+
+ return;
+ }
+
if (!Properties.TryGetValue(s_contextMenuStripProperty, out ContextMenuStrip? contextMenuStrip))
{
DefWndProc(ref m);
@@ -12958,52 +12996,53 @@ internal ToolStripControlHost? ToolStripControlHost
// Unsupported types don't support nullability.
#nullable disable
- ///
- /// This property is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code.
- ///
- [Obsolete(
- Obsoletions.ContextMenuMessage,
- error: false,
- DiagnosticId = Obsoletions.UnsupportedControlsDiagnosticId,
- UrlFormat = Obsoletions.SharedUrlFormat)]
- [EditorBrowsable(EditorBrowsableState.Never)]
+ [SRCategory(nameof(SR.CatBehavior))]
+ [DefaultValue(null)]
+ [SRDescription(nameof(SR.ControlContextMenuDescr))]
[Browsable(false)]
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public virtual ContextMenu ContextMenu
{
- get;
- set;
- }
+ get => Properties.GetValueOrDefault(s_contextMenuProperty);
+ set
+ {
+ ContextMenu oldValue = Properties.AddOrRemoveValue(s_contextMenuProperty, value);
- ///
- /// This event is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code.
- ///
- [Obsolete(
- Obsoletions.ContextMenuMessage,
- error: false,
- DiagnosticId = Obsoletions.UnsupportedControlsDiagnosticId,
- UrlFormat = Obsoletions.SharedUrlFormat)]
+ if (oldValue == value)
+ {
+ return;
+ }
- [EditorBrowsable(EditorBrowsableState.Never)]
+ if (oldValue is not null)
+ {
+ oldValue.Disposed -= DetachContextMenu;
+ }
+
+ if (value is not null)
+ {
+ value.Disposed += DetachContextMenu;
+ }
+
+ OnContextMenuChanged(EventArgs.Empty);
+ }
+ }
+
+ [SRCategory(nameof(SR.CatPropertyChanged))]
+ [SRDescription(nameof(SR.ControlOnContextMenuChangedDescr))]
[Browsable(false)]
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public event EventHandler ContextMenuChanged
{
- add { }
- remove { }
+ add => Events.AddHandler(s_contextMenuEvent, value);
+ remove => Events.RemoveHandler(s_contextMenuEvent, value);
}
- ///
- /// This method is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code.
- ///
- [Obsolete(
- Obsoletions.ContextMenuMessage,
- error: false,
- DiagnosticId = Obsoletions.UnsupportedControlsDiagnosticId,
- UrlFormat = Obsoletions.SharedUrlFormat)]
- [EditorBrowsable(EditorBrowsableState.Never)]
[Browsable(false)]
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
- protected virtual void OnContextMenuChanged(EventArgs e) { }
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected virtual void OnContextMenuChanged(EventArgs e)
+ {
+ if (Events[s_contextMenuEvent] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
#nullable enable
}
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/ToolStrips/ToolStripTextBox.ToolStripTextBoxControl.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/ToolStrips/ToolStripTextBox.ToolStripTextBoxControl.cs
index 4c378e10e48..0d425d6b440 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/ToolStrips/ToolStripTextBox.ToolStripTextBoxControl.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/ToolStrips/ToolStripTextBox.ToolStripTextBoxControl.cs
@@ -30,7 +30,7 @@ private RECT AbsoluteClientRECT
RECT rect = default;
CreateParams cp = CreateParams;
- AdjustWindowRectExForControlDpi(ref rect, (WINDOW_STYLE)cp.Style, false, (WINDOW_EX_STYLE)cp.ExStyle);
+ AdjustWindowRectExForControlDpi(ref rect, (WINDOW_STYLE)cp.Style, HasMenu, (WINDOW_EX_STYLE)cp.ExStyle);
// the coordinates we get back are negative, we need to translate this back to positive.
int offsetX = -rect.left; // one to get back to 0,0, another to translate
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..45f76766018 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
@@ -49,6 +49,9 @@ public partial class TreeNode : MarshalByRefObject, ICloneable, ISerializable
private TreeNodeImageIndexer? _stateImageIndexer;
private string _toolTipText = string.Empty;
+#nullable disable
+ private ContextMenu _contextMenu;
+#nullable enable
private ContextMenuStrip? _contextMenuStrip;
internal bool _nodesCleared;
@@ -361,6 +364,23 @@ public bool Checked
}
}
+ ///
+ /// The context menu associated with this tree node. The context menu
+ /// will be shown when the user right clicks the mouse on the control.
+ ///
+#pragma warning disable RS0016
+ [SRCategory(nameof(SR.CatBehavior))]
+ [DefaultValue(null)]
+ [SRDescription(nameof(SR.ControlContextMenuDescr))]
+#nullable disable
+ public virtual ContextMenu ContextMenu
+ {
+ get => _contextMenu;
+ set => _contextMenu = value;
+ }
+#nullable enable
+#pragma warning restore RS0016
+
///
/// The associated with this tree node. This menu
/// will be shown when the user right clicks the mouse on the control.
@@ -1353,6 +1373,7 @@ public virtual object Clone()
node.StateImageIndexer.Index = StateImageIndexer.Index;
node.ToolTipText = _toolTipText;
+ node.ContextMenu = _contextMenu;
node.ContextMenuStrip = _contextMenuStrip;
// only set the key if it's set to something useful
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/ContextMenu.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/ContextMenu.cs
index 8c73772752e..ad39d1d806d 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/ContextMenu.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/ContextMenu.cs
@@ -1,60 +1,218 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#pragma warning disable IDE1006, CS8625, CS8618, CS8601, RS0036
+
using System.ComponentModel;
+using System.Runtime.InteropServices;
+using SafeNativeMethods = System.Windows.Forms.LegacyMenuSafeNativeMethods;
-namespace System.Windows.Forms;
-
-#nullable disable
-
-///
-/// This type is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code.
-///
-[DefaultEvent(nameof(Popup))]
-[Obsolete(
- Obsoletions.ContextMenuMessage,
- error: false,
- DiagnosticId = Obsoletions.UnsupportedControlsDiagnosticId,
- UrlFormat = Obsoletions.SharedUrlFormat)]
-[EditorBrowsable(EditorBrowsableState.Never)]
-[Browsable(false)]
-public class ContextMenu : Menu
+namespace System.Windows.Forms
{
- public ContextMenu() : base(items: default) => throw new PlatformNotSupportedException();
+ ///
+ /// This class is used to put context menus on your form and show them for
+ /// controls at runtime. It basically acts like a regular Menu control,
+ /// but can be set for the ContextMenu property that most controls have.
+ ///
+ [DefaultEvent(nameof(Popup))]
+ public class ContextMenu : Menu
+ {
+ private EventHandler onPopup;
+ private EventHandler onCollapse;
+ internal Control sourceControl;
- public ContextMenu(MenuItem[] menuItems) : base(items: menuItems) => throw new PlatformNotSupportedException();
+ private RightToLeft rightToLeft = RightToLeft.Inherit;
- [Localizable(true)]
- [DefaultValue(RightToLeft.No)]
- public virtual RightToLeft RightToLeft
- {
- get => throw null;
- set { }
- }
+ ///
+ /// Creates a new ContextMenu object with no items in it by default.
+ ///
+ public ContextMenu()
+ : base(null)
+ {
+ }
- [Browsable(false)]
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
- public Control SourceControl => throw null;
+ ///
+ /// Creates a ContextMenu object with the given MenuItems.
+ ///
+ public ContextMenu(MenuItem[] menuItems)
+ : base(menuItems)
+ {
+ }
- public event EventHandler Popup
- {
- add { }
- remove { }
- }
+ ///
+ /// The last control that was acted upon that resulted in this context
+ /// menu being displayed.
+ ///
+ [
+ Browsable(false),
+ DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
+ ]
+ public Control SourceControl
+ {
+ get
+ {
+ return sourceControl;
+ }
+ }
- public event EventHandler Collapse
- {
- add { }
- remove { }
- }
+ public event EventHandler Popup
+ {
+ add => onPopup += value;
+ remove => onPopup -= value;
+ }
+
+ ///
+ /// Fires when the context menu collapses.
+ ///
+ public event EventHandler Collapse
+ {
+ add => onCollapse += value;
+ remove => onCollapse -= value;
+ }
+
+ ///
+ /// This is used for international applications where the language
+ /// is written from RightToLeft. When this property is true,
+ /// text alignment and reading order will be from right to left.
+ ///
+ // Add a DefaultValue attribute so that the Reset context menu becomes
+ // available in the Property Grid but the default value remains No.
+ [
+ Localizable(true),
+ DefaultValue(RightToLeft.No)
+ ]
+ public virtual RightToLeft RightToLeft
+ {
+ get
+ {
+ if (rightToLeft == RightToLeft.Inherit)
+ {
+ if (sourceControl is not null)
+ {
+ return ((Control)sourceControl).RightToLeft;
+ }
+ else
+ {
+ return RightToLeft.No;
+ }
+ }
+ else
+ {
+ return rightToLeft;
+ }
+ }
+ set
+ {
+ // valid values are 0x0 to 0x2.
+ if (value is < RightToLeft.No or > RightToLeft.Inherit)
+ {
+ throw new InvalidEnumArgumentException(nameof(RightToLeft), (int)value, typeof(RightToLeft));
+ }
- protected internal virtual void OnCollapse(EventArgs e) { }
+ if (RightToLeft != value)
+ {
+ rightToLeft = value;
+ UpdateRtl((value == RightToLeft.Yes));
+ }
+ }
+ }
- protected internal virtual void OnPopup(EventArgs e) { }
+ internal override bool RenderIsRightToLeft
+ {
+ get
+ {
+ return (rightToLeft == RightToLeft.Yes);
+ }
+ }
- protected internal virtual bool ProcessCmdKey(ref Message msg, Keys keyData, Control control) => throw null;
+ ///
+ /// Fires the popup event
+ ///
+ protected internal virtual void OnPopup(EventArgs e)
+ {
+ onPopup?.Invoke(this, e);
+ }
- public void Show(Control control, Point pos) { }
+ ///
+ /// Fires the collapse event
+ ///
+ protected internal virtual void OnCollapse(EventArgs e)
+ {
+ onCollapse?.Invoke(this, e);
+ }
- public void Show(Control control, Point pos, LeftRightAlignment alignment) { }
+ protected internal virtual bool ProcessCmdKey(ref Message msg, Keys keyData, Control control)
+ {
+ sourceControl = control;
+ return ProcessCmdKey(ref msg, keyData);
+ }
+
+ private void ResetRightToLeft()
+ {
+ RightToLeft = RightToLeft.No;
+ }
+
+ ///
+ /// Returns true if the RightToLeft should be persisted in code gen.
+ ///
+ internal virtual bool ShouldSerializeRightToLeft()
+ {
+ if (rightToLeft == RightToLeft.Inherit)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Displays the context menu at the specified position. This method
+ /// doesn't return until the menu is dismissed.
+ ///
+ public void Show(Control control, Point pos)
+ {
+ Show(control, pos, LegacyMenuNativeMethods.TPM_VERTICAL | LegacyMenuNativeMethods.TPM_RIGHTBUTTON);
+ }
+
+ ///
+ /// Displays the context menu at the specified position. This method
+ /// doesn't return until the menu is dismissed.
+ ///
+ public void Show(Control control, Point pos, LeftRightAlignment alignment)
+ {
+ // This code below looks wrong but it's correct.
+ // WinForms Left alignment means we want the menu to show up left of the point it is invoked from.
+ // We specify TPM_RIGHTALIGN which tells win32 to align the right side of this
+ // menu with the point (which aligns it Left visually)
+ if (alignment == LeftRightAlignment.Left)
+ {
+ Show(control, pos, LegacyMenuNativeMethods.TPM_VERTICAL | LegacyMenuNativeMethods.TPM_RIGHTBUTTON | LegacyMenuNativeMethods.TPM_RIGHTALIGN);
+ }
+ else
+ {
+ Show(control, pos, LegacyMenuNativeMethods.TPM_VERTICAL | LegacyMenuNativeMethods.TPM_RIGHTBUTTON | LegacyMenuNativeMethods.TPM_LEFTALIGN);
+ }
+ }
+
+ private void Show(Control control, Point pos, int flags)
+ {
+ ArgumentNullException.ThrowIfNull(control);
+
+ if (!control.IsHandleCreated || !control.Visible)
+ {
+ throw new ArgumentException("The control must be visible and have a created handle.", nameof(control));
+ }
+
+ sourceControl = control;
+
+ OnPopup(EventArgs.Empty);
+ pos = control.PointToScreen(pos);
+ SafeNativeMethods.TrackPopupMenuEx(new HandleRef(this, Handle),
+ flags,
+ pos.X,
+ pos.Y,
+ new HandleRef(control, control.Handle),
+ null);
+ }
+ }
}
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/LegacyMenuInteropCompat.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/LegacyMenuInteropCompat.cs
new file mode 100644
index 00000000000..d4de9250512
--- /dev/null
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/LegacyMenuInteropCompat.cs
@@ -0,0 +1,135 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma warning disable IDE1006, SA1518
+
+using System.Runtime.InteropServices;
+
+namespace System.Windows.Forms;
+
+internal static class LegacyMenuNativeMethods
+{
+ public const int MNC_EXECUTE = 2;
+ public const int MNC_SELECT = 3;
+ public const int MIIM_STATE = 0x00000001;
+ public const int MIIM_ID = 0x00000002;
+ public const int MIIM_SUBMENU = 0x00000004;
+ public const int MIIM_TYPE = 0x00000010;
+ public const int MIIM_DATA = 0x00000020;
+ public const int MF_BYPOSITION = 0x00000400;
+ public const int MFT_MENUBREAK = 0x00000040;
+ public const int MFT_SEPARATOR = 0x00000800;
+ public const int MFT_RIGHTORDER = 0x00002000;
+ public const int MFT_RIGHTJUSTIFY = 0x00004000;
+ public const int TPM_RIGHTBUTTON = 0x0002;
+ public const int TPM_LEFTALIGN = 0x0000;
+ public const int TPM_RIGHTALIGN = 0x0008;
+ public const int TPM_VERTICAL = 0x0040;
+
+ [StructLayout(LayoutKind.Sequential)]
+ public class DRAWITEMSTRUCT
+ {
+ public int CtlType;
+ public int CtlID;
+ public int itemID;
+ public int itemAction;
+ public int itemState;
+ public IntPtr hwndItem;
+ public IntPtr hDC;
+ public RECT rcItem;
+ public IntPtr itemData;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public class MEASUREITEMSTRUCT
+ {
+ public int CtlType;
+ public int CtlID;
+ public int itemID;
+ public int itemWidth;
+ public int itemHeight;
+ public IntPtr itemData;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public class TPMPARAMS
+ {
+ public int cbSize = Marshal.SizeOf();
+ public int rcExclude_left;
+ public int rcExclude_top;
+ public int rcExclude_right;
+ public int rcExclude_bottom;
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
+ public class MENUITEMINFO_T
+ {
+ public int cbSize = Marshal.SizeOf();
+ public int fMask;
+ public int fType;
+ public int fState;
+ public int wID;
+ public IntPtr hSubMenu;
+ public IntPtr hbmpChecked;
+ public IntPtr hbmpUnchecked;
+ public IntPtr dwItemData;
+ public string? dwTypeData;
+ public int cch;
+ }
+
+ public static class Util
+ {
+ public static int MAKELONG(int low, int high) => (high << 16) | (low & 0xffff);
+
+ public static int LOWORD(nint n) => PARAM.LOWORD(n);
+ }
+}
+
+internal static class LegacyMenuUnsafeNativeMethods
+{
+ [DllImport(Libraries.User32, ExactSpelling = true)]
+ public static extern IntPtr CreatePopupMenu();
+
+ [DllImport(Libraries.User32, ExactSpelling = true)]
+ public static extern IntPtr CreateMenu();
+
+ [DllImport(Libraries.User32, ExactSpelling = true)]
+ public static extern bool DestroyMenu(HandleRef hMenu);
+
+ [DllImport(Libraries.User32, ExactSpelling = true)]
+ public static extern int GetMenuItemCount(HandleRef hMenu);
+
+ [DllImport(Libraries.User32, ExactSpelling = true)]
+ public static extern bool RemoveMenu(HandleRef hMenu, int uPosition, int uFlags);
+
+ [DllImport(Libraries.User32, CharSet = CharSet.Auto)]
+ private static extern bool GetMenuItemInfo(IntPtr hMenu, int uItem, bool fByPosition, [In, Out] LegacyMenuNativeMethods.MENUITEMINFO_T lpmii);
+
+ [DllImport(Libraries.User32, CharSet = CharSet.Auto)]
+ public static extern bool InsertMenuItem(HandleRef hMenu, int uItem, bool fByPosition, LegacyMenuNativeMethods.MENUITEMINFO_T lpmii);
+
+ [DllImport(Libraries.User32, CharSet = CharSet.Auto)]
+ public static extern bool SetMenuItemInfo(HandleRef hMenu, int uItem, bool fByPosition, LegacyMenuNativeMethods.MENUITEMINFO_T lpmii);
+
+ [DllImport(Libraries.User32, ExactSpelling = true)]
+ public static extern bool SetMenuDefaultItem(HandleRef hMenu, int uItem, bool fByPos);
+
+ public static bool GetMenuItemInfo(HandleRef hMenu, int uItem, bool fByPosition, LegacyMenuNativeMethods.MENUITEMINFO_T lpmii)
+ {
+ bool result = GetMenuItemInfo(hMenu.Handle, uItem, fByPosition, lpmii);
+ GC.KeepAlive(hMenu.Wrapper);
+ return result;
+ }
+}
+
+internal static class LegacyMenuSafeNativeMethods
+{
+ [DllImport(Libraries.User32, ExactSpelling = true, CharSet = CharSet.Auto)]
+ public static extern bool TrackPopupMenuEx(HandleRef hmenu, int fuFlags, int x, int y, HandleRef hwnd, LegacyMenuNativeMethods.TPMPARAMS? tpm);
+
+ [DllImport(Libraries.User32, ExactSpelling = true, CharSet = CharSet.Auto)]
+ public static extern bool DrawMenuBar(HandleRef hWnd);
+
+ [DllImport(Libraries.Gdi32, ExactSpelling = true)]
+ public static extern IntPtr SelectPalette(HandleRef hdc, HandleRef hpal, int bForceBkgd);
+}
\ No newline at end of file
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/Menu.MenuItemCollection.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/Menu.MenuItemCollection.cs
deleted file mode 100644
index 25c431d90b3..00000000000
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/Menu.MenuItemCollection.cs
+++ /dev/null
@@ -1,92 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Collections;
-using System.ComponentModel;
-
-namespace System.Windows.Forms;
-
-#nullable disable
-
-public abstract partial class Menu
-{
- ///
- /// This type is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code.
- ///
- [Obsolete(
- Obsoletions.MenuMessage,
- error: false,
- DiagnosticId = Obsoletions.UnsupportedControlsDiagnosticId,
- UrlFormat = Obsoletions.SharedUrlFormat)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- [Browsable(false)]
- [ListBindable(false)]
- public class MenuItemCollection : IList
- {
- public MenuItemCollection(Menu owner) => throw new PlatformNotSupportedException();
-
- public virtual MenuItem this[int index] => throw null;
-
- object IList.this[int index]
- {
- get => throw null;
- set { }
- }
-
- public virtual MenuItem this[string key] => throw null;
-
- public int Count => throw null;
-
- object ICollection.SyncRoot => throw null;
-
- bool ICollection.IsSynchronized => throw null;
-
- bool IList.IsFixedSize => throw null;
-
- public bool IsReadOnly => throw null;
-
- public virtual MenuItem Add(string caption) => throw null;
-
- public virtual MenuItem Add(string caption, EventHandler onClick) => throw null;
-
- public virtual MenuItem Add(string caption, MenuItem[] items) => throw null;
-
- public virtual int Add(MenuItem item) => throw null;
-
- public virtual int Add(int index, MenuItem item) => throw null;
-
- public virtual void AddRange(MenuItem[] items) { }
-
- int IList.Add(object value) => throw null;
-
- public bool Contains(MenuItem value) => throw null;
-
- bool IList.Contains(object value) => throw null;
-
- public virtual bool ContainsKey(string key) => throw null;
-
- public virtual void Clear() { }
-
- public void CopyTo(Array dest, int index) { }
-
- public MenuItem[] Find(string key, bool searchAllChildren) => throw null;
-
- public int IndexOf(MenuItem value) => throw null;
-
- public virtual int IndexOfKey(string key) => throw null;
-
- int IList.IndexOf(object value) => throw null;
-
- void IList.Insert(int index, object value) { }
-
- public IEnumerator GetEnumerator() => throw null;
-
- public virtual void RemoveAt(int index) { }
-
- public virtual void RemoveByKey(string key) { }
-
- public virtual void Remove(MenuItem item) { }
-
- void IList.Remove(object value) { }
- }
-}
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/Menu.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/Menu.cs
index 16a3d6d783e..f46d8bdc5ea 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/Menu.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/Menu.cs
@@ -1,80 +1,1233 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#pragma warning disable IDE1006, CS8769, CS8618, CS8603, CS8625, CS8600, CS8602, CS8604, RS0036, RS0016, CA2020
+
+using System.Collections;
using System.ComponentModel;
+using System.Globalization;
+using System.Runtime.InteropServices;
+using UnsafeNativeMethods = System.Windows.Forms.LegacyMenuUnsafeNativeMethods;
-namespace System.Windows.Forms;
-
-#nullable disable
-
-///
-/// This type is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code.
-///
-[Obsolete(
- Obsoletions.MenuMessage,
- error: false,
- DiagnosticId = Obsoletions.UnsupportedControlsDiagnosticId,
- UrlFormat = Obsoletions.SharedUrlFormat)]
-[EditorBrowsable(EditorBrowsableState.Never)]
-[Browsable(false)]
-[ListBindable(false)]
-[ToolboxItemFilter("System.Windows.Forms")]
-public abstract partial class Menu : Component
+namespace System.Windows.Forms
{
- public const int FindHandle = 0;
- public const int FindShortcut = 1;
+ ///
+ /// This is the base class for all menu components (MainMenu, MenuItem, and ContextMenu).
+ ///
+ [ToolboxItemFilter("System.Windows.Forms")]
+ [ListBindable(false)]
+ public abstract class Menu : Component
+ {
+ internal const int CHANGE_ITEMS = 0; // item(s) added or removed
+ internal const int CHANGE_VISIBLE = 1; // item(s) hidden or shown
+ internal const int CHANGE_MDI = 2; // mdi item changed
+ internal const int CHANGE_MERGE = 3; // mergeType or mergeOrder changed
+ internal const int CHANGE_ITEMADDED = 4; // mergeType or mergeOrder changed
- protected Menu(MenuItem[] items) => throw new PlatformNotSupportedException();
+ ///
+ /// Used by findMenuItem
+ ///
+ public const int FindHandle = 0;
+ ///
+ /// Used by findMenuItem
+ ///
+ public const int FindShortcut = 1;
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Advanced)]
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
- public IntPtr Handle => throw null;
+ private MenuItemCollection itemsCollection;
+ internal MenuItem[] items;
+ private int _itemCount;
+ internal IntPtr handle;
+ internal bool created;
+ private object userData;
+ private string name;
- [Browsable(false)]
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
- public virtual bool IsParent => throw null;
+ ///
+ /// This is an abstract class. Instances cannot be created, so the constructor
+ /// is only called from derived classes.
+ ///
+ protected Menu(MenuItem[] items)
+ {
+ if (items is not null)
+ {
+ MenuItems.AddRange(items);
+ }
+ }
- [Browsable(false)]
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
- public MenuItem MdiListItem => throw null;
+ ///
+ /// The HMENU handle corresponding to this menu.
+ ///
+ [
+ Browsable(false), EditorBrowsable(EditorBrowsableState.Advanced),
+ DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
+ ]
+ public IntPtr Handle
+ {
+ get
+ {
+ if (handle == IntPtr.Zero)
+ {
+ handle = CreateMenuHandle();
+ }
- [Browsable(false)]
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
- public string Name
- {
- get => throw null;
- set { }
- }
+ CreateMenuItems();
+ return handle;
+ }
+ }
- [Browsable(false)]
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
- [MergableProperty(false)]
- public MenuItemCollection MenuItems => throw null;
+ ///
+ /// Specifies whether this menu contains any items.
+ ///
+ [
+ Browsable(false),
+ DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
+ ]
+ public virtual bool IsParent
+ {
+ get
+ {
+ return items is not null && ItemCount > 0;
+ }
+ }
- [Localizable(false)]
- [Bindable(true)]
- [DefaultValue(null)]
- [TypeConverter(typeof(StringConverter))]
- public object Tag
- {
- get => throw null;
- set { }
- }
+ internal int ItemCount
+ {
+ get
+ {
+ return _itemCount;
+ }
+ }
+
+ ///
+ /// The MenuItem that contains the list of MDI child windows.
+ ///
+ [
+ Browsable(false),
+ DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
+ ]
+ public MenuItem MdiListItem
+ {
+ get
+ {
+ for (int i = 0; i < ItemCount; i++)
+ {
+ MenuItem item = items[i];
+ if (item.MdiList)
+ {
+ return item;
+ }
+
+ if (item.IsParent)
+ {
+ item = item.MdiListItem;
+ if (item is not null)
+ {
+ return item;
+ }
+ }
+ }
+
+ return null;
+ }
+ }
+
+ ///
+ /// Name of this control. The designer will set this to the same
+ /// as the programatic Id "(name)" of the control - however this
+ /// property has no bearing on the runtime aspects of this control.
+ ///
+ [
+ DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
+ Browsable(false)
+ ]
+ public string Name
+ {
+ get
+ {
+ return WindowsFormsUtils.GetComponentName(this, name);
+ }
+ set
+ {
+ if (value is null || value.Length == 0)
+ {
+ name = null;
+ }
+ else
+ {
+ name = value;
+ }
+
+ Site?.Name = name;
+ }
+ }
+
+ [
+ Browsable(false),
+ DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
+ MergableProperty(false)
+ ]
+ public MenuItemCollection MenuItems
+ {
+ get
+ {
+ itemsCollection ??= new MenuItemCollection(this);
+ return itemsCollection;
+ }
+ }
+
+ internal virtual bool RenderIsRightToLeft
+ {
+ get
+ {
+ Debug.Assert(true, "Should never get called");
+ return false;
+ }
+ }
+
+ [
+ SRCategory(nameof(SR.CatData)),
+ Localizable(false),
+ Bindable(true),
+ SRDescription(nameof(SR.ControlTagDescr)),
+ DefaultValue(null),
+ TypeConverter(typeof(StringConverter)),
+ ]
+ public object Tag
+ {
+ get
+ {
+ return userData;
+ }
+ set
+ {
+ userData = value;
+ }
+ }
+
+ ///
+ /// Notifies Menu that someone called Windows.DeleteMenu on its handle.
+ ///
+ internal void ClearHandles()
+ {
+ if (handle != IntPtr.Zero)
+ {
+ UnsafeNativeMethods.DestroyMenu(new HandleRef(this, handle));
+ }
+
+ handle = IntPtr.Zero;
+ if (created)
+ {
+ for (int i = 0; i < ItemCount; i++)
+ {
+ items[i].ClearHandles();
+ }
+
+ created = false;
+ }
+ }
+
+ ///
+ /// Sets this menu to be an identical copy of another menu.
+ ///
+ protected internal void CloneMenu(Menu menuSrc)
+ {
+ ArgumentNullException.ThrowIfNull(menuSrc);
+
+ MenuItem[] newItems = null;
+ if (menuSrc.items is not null)
+ {
+ int count = menuSrc.MenuItems.Count;
+ newItems = new MenuItem[count];
+ for (int i = 0; i < count; i++)
+ {
+ newItems[i] = menuSrc.MenuItems[i].CloneMenu();
+ }
+ }
+
+ MenuItems.Clear();
+ if (newItems is not null)
+ {
+ MenuItems.AddRange(newItems);
+ }
+ }
+
+ protected virtual IntPtr CreateMenuHandle()
+ {
+ return UnsafeNativeMethods.CreatePopupMenu();
+ }
+
+ internal void CreateMenuItems()
+ {
+ if (!created)
+ {
+ for (int i = 0; i < ItemCount; i++)
+ {
+ items[i].CreateMenuItem();
+ }
+
+ created = true;
+ }
+ }
+
+ internal void DestroyMenuItems()
+ {
+ if (created)
+ {
+ for (int i = 0; i < ItemCount; i++)
+ {
+ items[i].ClearHandles();
+ }
+
+ while (UnsafeNativeMethods.GetMenuItemCount(new HandleRef(this, handle)) > 0)
+ {
+ UnsafeNativeMethods.RemoveMenu(new HandleRef(this, handle), 0, LegacyMenuNativeMethods.MF_BYPOSITION);
+ }
+
+ created = false;
+ }
+ }
+
+ ///
+ /// Disposes of the component. Call dispose when the component is no longer needed.
+ /// This method removes the component from its container (if the component has a site)
+ /// and triggers the dispose event.
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ while (ItemCount > 0)
+ {
+ MenuItem item = items[--_itemCount];
+
+ // remove the item before we dispose it so it still has valid state
+ // for undo/redo
+ //
+ if (item.Site is not null && item.Site.Container is not null)
+ {
+ item.Site.Container.Remove(item);
+ }
+
+ item.Parent = null;
+ item.Dispose();
+ }
+
+ items = null;
+ }
+
+ if (handle != IntPtr.Zero)
+ {
+ UnsafeNativeMethods.DestroyMenu(new HandleRef(this, handle));
+ handle = IntPtr.Zero;
+ if (disposing)
+ {
+ ClearHandles();
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ public MenuItem FindMenuItem(int type, IntPtr value)
+ {
+ for (int i = 0; i < ItemCount; i++)
+ {
+ MenuItem item = items[i];
+ switch (type)
+ {
+ case FindHandle:
+ if (item.handle == value)
+ {
+ return item;
+ }
+
+ break;
+ case FindShortcut:
+ if (item.Shortcut == (Shortcut)(int)value)
+ {
+ return item;
+ }
+
+ break;
+ }
+
+ item = item.FindMenuItem(type, value);
+ if (item is not null)
+ {
+ return item;
+ }
+ }
+
+ return null;
+ }
+
+ protected int FindMergePosition(int mergeOrder)
+ {
+ int iMin, iLim, iT;
+
+ for (iMin = 0, iLim = ItemCount; iMin < iLim;)
+ {
+ iT = (iMin + iLim) / 2;
+ if (items[iT].MergeOrder <= mergeOrder)
+ {
+ iMin = iT + 1;
+ }
+ else
+ {
+ iLim = iT;
+ }
+ }
+
+ return iMin;
+ }
+
+ // A new method for finding the approximate merge position. The original
+ // method assumed (incorrectly) that the MergeOrder of the target menu would be sequential
+ // as it's guaranteed to be in the MDI imlementation of merging container and child
+ // menus. However, user code can call MergeMenu independently on a source and target
+ // menu whose MergeOrder values are not necessarily pre-sorted.
+ internal int xFindMergePosition(int mergeOrder)
+ {
+ int nPosition = 0;
+
+ // Iterate from beginning to end since we can't assume any sequential ordering to MergeOrder
+ for (int nLoop = 0; nLoop < ItemCount; nLoop++)
+ {
+ if (items[nLoop].MergeOrder > mergeOrder)
+ {
+ // We didn't find what we're looking for, but we've found a stopping point.
+ break;
+ }
+ else if (items[nLoop].MergeOrder < mergeOrder)
+ {
+ // We might have found what we're looking for, but we'll have to come around again
+ // to know.
+ nPosition = nLoop + 1;
+ }
+ else if (mergeOrder == items[nLoop].MergeOrder)
+ {
+ // We've found what we're looking for, so use this value for the merge order
+ nPosition = nLoop;
+ break;
+ }
+ }
+
+ return nPosition;
+ }
+
+ // There's a win32 problem that doesn't allow menus to cascade right to left
+ // unless we explicitely set the bit on the menu the first time it pops up
+ internal void UpdateRtl(bool setRightToLeftBit)
+ {
+ foreach (MenuItem item in MenuItems)
+ {
+ item.UpdateItemRtl(setRightToLeftBit);
+ item.UpdateRtl(setRightToLeftBit);
+ }
+ }
+
+ ///
+ /// Returns the ContextMenu that contains this menu. The ContextMenu
+ /// is at the top of this menu's parent chain.
+ /// Returns null if this menu is not contained in a ContextMenu.
+ /// This can occur if it's contained in a MainMenu or if it isn't
+ /// currently contained in any menu at all.
+ ///
+ public ContextMenu GetContextMenu()
+ {
+ Menu menuT;
+ for (menuT = this; menuT is not ContextMenu;)
+ {
+ if (menuT is not MenuItem)
+ {
+ return null;
+ }
+
+ menuT = ((MenuItem)menuT).Parent;
+ }
+
+ return (ContextMenu)menuT;
+ }
+
+ ///
+ /// Returns the MainMenu item that contains this menu. The MainMenu
+ /// is at the top of this menu's parent chain.
+ /// Returns null if this menu is not contained in a MainMenu.
+ /// This can occur if it's contained in a ContextMenu or if it isn't
+ /// currently contained in any menu at all.
+ ///
+ public MainMenu GetMainMenu()
+ {
+ Menu menuT;
+ for (menuT = this; menuT is not MainMenu;)
+ {
+ if (menuT is not MenuItem)
+ {
+ return null;
+ }
+
+ menuT = ((MenuItem)menuT).Parent;
+ }
+
+ return (MainMenu)menuT;
+ }
+
+ internal virtual void ItemsChanged(int change)
+ {
+ switch (change)
+ {
+ case CHANGE_ITEMS:
+ case CHANGE_VISIBLE:
+ DestroyMenuItems();
+ break;
+ }
+ }
+
+ ///
+ /// Walks the menu item collection, using a caller-supplied delegate to find one
+ /// with a matching access key. Walk starts at specified item index and performs one
+ /// full pass of the entire collection, looping back to the top if necessary.
+ ///
+ /// Return value is intended for return from WM_MENUCHAR message. It includes both
+ /// index of matching item, and action for OS to take (execute or select). Zero is
+ /// used to indicate that no match was found (OS should ignore key and beep).
+ ///
+ private IntPtr MatchKeyToMenuItem(int startItem, char key, MenuItemKeyComparer comparer)
+ {
+ int firstMatch = -1;
+ bool multipleMatches = false;
+
+ for (int i = 0; i < items.Length && !multipleMatches; ++i)
+ {
+ int itemIndex = (startItem + i) % items.Length;
+ MenuItem mi = items[itemIndex];
+ if (mi is not null && comparer(mi, key))
+ {
+ if (firstMatch < 0)
+ {
+ // Using Index doesnt respect hidden items.
+ firstMatch = mi.MenuIndex;
+ }
+ else
+ {
+ multipleMatches = true;
+ }
+ }
+ }
+
+ if (firstMatch < 0)
+ {
+ return IntPtr.Zero;
+ }
+
+ int action = multipleMatches ? LegacyMenuNativeMethods.MNC_SELECT : LegacyMenuNativeMethods.MNC_EXECUTE;
+
+ return (IntPtr)LegacyMenuNativeMethods.Util.MAKELONG(firstMatch, action);
+ }
+
+ /// Delegate type used by MatchKeyToMenuItem
+ private delegate bool MenuItemKeyComparer(MenuItem mi, char key);
+
+ ///
+ /// Merges another menu's items with this one's. Menu items are merged according to their
+ /// mergeType and mergeOrder properties. This function is typically used to
+ /// merge an MDI container's menu with that of its active MDI child.
+ ///
+ public virtual void MergeMenu(Menu menuSrc)
+ {
+ ArgumentNullException.ThrowIfNull(menuSrc);
+ if (menuSrc == this)
+ {
+ throw new ArgumentException("A menu cannot be merged with itself.", nameof(menuSrc));
+ }
+
+ int i, j;
+ MenuItem item;
+ MenuItem itemDst;
+
+ if (menuSrc.items is not null && items is null)
+ {
+ MenuItems.Clear();
+ }
+
+ for (i = 0; i < menuSrc.ItemCount; i++)
+ {
+ item = menuSrc.items[i];
+
+ switch (item.MergeType)
+ {
+ default:
+ continue;
+ case MenuMerge.Add:
+ MenuItems.Add(FindMergePosition(item.MergeOrder), item.MergeMenu());
+ continue;
+ case MenuMerge.Replace:
+ case MenuMerge.MergeItems:
+ break;
+ }
+
+ int mergeOrder = item.MergeOrder;
+ // Can we find a menu item with a matching merge order?
+ // Use new method to find the approximate merge position. The original
+ // method assumed (incorrectly) that the MergeOrder of the target menu would be sequential
+ // as it's guaranteed to be in the MDI imlementation of merging container and child
+ // menus. However, user code can call MergeMenu independently on a source and target
+ // menu whose MergeOrder values are not necessarily pre-sorted.
+ for (j = xFindMergePosition(mergeOrder); ; j++)
+ {
+ if (j >= ItemCount)
+ {
+ // A matching merge position could not be found,
+ // so simply append this menu item to the end.
+ MenuItems.Add(j, item.MergeMenu());
+ break;
+ }
+
+ itemDst = items[j];
+ if (itemDst.MergeOrder != mergeOrder)
+ {
+ MenuItems.Add(j, item.MergeMenu());
+ break;
+ }
+
+ if (itemDst.MergeType != MenuMerge.Add)
+ {
+ if (item.MergeType != MenuMerge.MergeItems
+ || itemDst.MergeType != MenuMerge.MergeItems)
+ {
+ itemDst.Dispose();
+ MenuItems.Add(j, item.MergeMenu());
+ }
+ else
+ {
+ itemDst.MergeMenu(item);
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ internal virtual bool ProcessInitMenuPopup(IntPtr handle)
+ {
+ MenuItem item = FindMenuItem(FindHandle, handle);
+ if (item is not null)
+ {
+ item.OnInitMenuPopup(EventArgs.Empty);
+ item.CreateMenuItems();
+ return true;
+ }
+
+ return false;
+ }
+
+ protected internal virtual bool ProcessCmdKey(ref Message msg, Keys keyData)
+ {
+ MenuItem item = FindMenuItem(FindShortcut, (IntPtr)(int)keyData);
+ return item is not null && item.ShortcutClick();
+ }
+
+ ///
+ /// Returns index of currently selected menu item in
+ /// this menu, or -1 if no item is currently selected.
+ ///
+ internal int SelectedMenuItemIndex
+ {
+ get
+ {
+ for (int i = 0; i < items.Length; ++i)
+ {
+ MenuItem mi = items[i];
+ if (mi is not null && mi.Selected)
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+ }
- protected internal void CloneMenu(Menu menuSrc) { }
+ ///
+ /// Returns a string representation for this control.
+ ///
+ public override string ToString()
+ {
+ string s = base.ToString();
+ return s + ", Items.Count: " + ItemCount.ToString(CultureInfo.CurrentCulture);
+ }
- protected virtual IntPtr CreateMenuHandle() => throw null;
+ ///
+ /// Handles the WM_MENUCHAR message, forwarding it to the intended Menu
+ /// object. All the real work is done inside WmMenuCharInternal().
+ ///
+ internal void WmMenuChar(ref Message m)
+ {
+ Menu menu = (m.LParamInternal == handle) ? this : FindMenuItem(FindHandle, m.LParamInternal);
- public MenuItem FindMenuItem(int type, IntPtr value) => throw null;
+ if (menu is null)
+ {
+ return;
+ }
- protected int FindMergePosition(int mergeOrder) => throw null;
+ char menuKey = char.ToUpper((char)LegacyMenuNativeMethods.Util.LOWORD((nint)m.WParamInternal), CultureInfo.CurrentCulture);
- public ContextMenu GetContextMenu() => throw null;
+ m.ResultInternal = (LRESULT)menu.WmMenuCharInternal(menuKey);
+ }
- public MainMenu GetMainMenu() => throw null;
+ ///
+ /// Handles WM_MENUCHAR to provide access key support for owner-draw menu items (which
+ /// means *all* menu items on a menu when IsImageMarginPresent == true). Attempts to
+ /// simulate the exact behavior that the OS provides for non owner-draw menu items.
+ ///
+ internal IntPtr WmMenuCharInternal(char key)
+ {
+ // Start looking just beyond the current selected item (otherwise just start at the top)
+ int startItem = (SelectedMenuItemIndex + 1) % items.Length;
- public virtual void MergeMenu(Menu menuSrc) { }
+ // First, search for match among owner-draw items with explicitly defined access keys (eg. "S&ave")
+ IntPtr result = MatchKeyToMenuItem(startItem, key, new MenuItemKeyComparer(CheckOwnerDrawItemWithMnemonic));
- protected internal virtual bool ProcessCmdKey(ref Message msg, Keys keyData) => throw null;
+ // Next, search for match among owner-draw items with no access keys (looking at first char of item text)
+ if (result == IntPtr.Zero)
+ {
+ result = MatchKeyToMenuItem(startItem, key, new MenuItemKeyComparer(CheckOwnerDrawItemNoMnemonic));
+ }
+
+ return result;
+ }
+
+ /// MenuItemKeyComparer delegate used by WmMenuCharInternal
+ private bool CheckOwnerDrawItemWithMnemonic(MenuItem mi, char key)
+ {
+ return mi.OwnerDraw &&
+ mi.Mnemonic == key;
+ }
+
+ /// MenuItemKeyComparer delegate used by WmMenuCharInternal
+ private bool CheckOwnerDrawItemNoMnemonic(MenuItem mi, char key)
+ {
+ return mi.OwnerDraw &&
+ mi.Mnemonic == 0 &&
+ mi.Text.Length > 0 &&
+ char.ToUpper(mi.Text[0], CultureInfo.CurrentCulture) == key;
+ }
+
+ [ListBindable(false)]
+ public class MenuItemCollection : IList
+ {
+ private readonly Menu owner;
+
+ /// A caching mechanism for key accessor
+ /// We use an index here rather than control so that we don't have lifetime
+ /// issues by holding on to extra references.
+ private int lastAccessedIndex = -1;
+
+ public MenuItemCollection(Menu owner)
+ {
+ this.owner = owner;
+ }
+
+ public virtual MenuItem this[int index]
+ {
+ get
+ {
+ if (index < 0 || index >= owner.ItemCount)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), index, string.Format(SR.InvalidArgument, nameof(index), index));
+ }
+
+ return owner.items[index];
+ }
+
+ // set not supported
+ }
+
+ object IList.this[int index]
+ {
+ get
+ {
+ return this[index];
+ }
+ set
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ ///
+ /// Retrieves the child control with the specified key.
+ ///
+ public virtual MenuItem this[string key]
+ {
+ get
+ {
+ // We do not support null and empty string as valid keys.
+ if (string.IsNullOrEmpty(key))
+ {
+ return null;
+ }
+
+ // Search for the key in our collection
+ int index = IndexOfKey(key);
+ if (IsValidIndex(index))
+ {
+ return this[index];
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+
+ public int Count
+ {
+ get
+ {
+ return owner.ItemCount;
+ }
+ }
+
+ object ICollection.SyncRoot
+ {
+ get
+ {
+ return this;
+ }
+ }
+
+ bool ICollection.IsSynchronized
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ bool IList.IsFixedSize
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ public bool IsReadOnly
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Adds a new MenuItem to the end of this menu with the specified caption.
+ ///
+ public virtual MenuItem Add(string caption)
+ {
+ MenuItem item = new MenuItem(caption);
+ Add(item);
+ return item;
+ }
+
+ ///
+ /// Adds a new MenuItem to the end of this menu with the specified caption,
+ /// and click handler.
+ ///
+ public virtual MenuItem Add(string caption, EventHandler onClick)
+ {
+ MenuItem item = new MenuItem(caption, onClick);
+ Add(item);
+ return item;
+ }
+
+ ///
+ /// Adds a new MenuItem to the end of this menu with the specified caption,
+ /// click handler, and items.
+ ///
+ public virtual MenuItem Add(string caption, MenuItem[] items)
+ {
+ MenuItem item = new MenuItem(caption, items);
+ Add(item);
+ return item;
+ }
+
+ ///
+ /// Adds a MenuItem to the end of this menu
+ /// MenuItems can only be contained in one menu at a time, and may not be added
+ /// more than once to the same menu.
+ ///
+ public virtual int Add(MenuItem item)
+ {
+ return Add(owner.ItemCount, item);
+ }
+
+ ///
+ /// Adds a MenuItem to this menu at the specified index. The item currently at
+ /// that index, and all items after it, will be moved up one slot.
+ /// MenuItems can only be contained in one menu at a time, and may not be added
+ /// more than once to the same menu.
+ ///
+ public virtual int Add(int index, MenuItem item)
+ {
+ ArgumentNullException.ThrowIfNull(item);
+
+ // MenuItems can only belong to one menu at a time
+ if (item.Parent is not null)
+ {
+ // First check that we're not adding ourself, i.e. walk
+ // the parent chain for equality
+ if (owner is MenuItem parent)
+ {
+ while (parent is not null)
+ {
+ if (parent.Equals(item))
+ {
+ throw new ArgumentException($"The menu item '{item.Text}' already exists in this menu hierarchy.", nameof(item));
+ }
+
+ if (parent.Parent is MenuItem)
+ {
+ parent = (MenuItem)parent.Parent;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ // if we're re-adding an item back to the same collection
+ // the target index needs to be decremented since we're
+ // removing an item from the collection
+ if (item.Parent.Equals(owner) && index > 0)
+ {
+ index--;
+ }
+
+ item.Parent.MenuItems.Remove(item);
+ }
+
+ // Validate our index
+ if (index < 0 || index > owner.ItemCount)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), index, string.Format(SR.InvalidArgument, nameof(index), index));
+ }
+
+ if (owner.items is null || owner.items.Length == owner.ItemCount)
+ {
+ MenuItem[] newItems = new MenuItem[owner.ItemCount < 2 ? 4 : owner.ItemCount * 2];
+ if (owner.ItemCount > 0)
+ {
+ Array.Copy(owner.items, 0, newItems, 0, owner.ItemCount);
+ }
+
+ owner.items = newItems;
+ }
+
+ Array.Copy(owner.items, index, owner.items, index + 1, owner.ItemCount - index);
+ owner.items[index] = item;
+ owner._itemCount++;
+ item.Parent = owner;
+ owner.ItemsChanged(CHANGE_ITEMS);
+ if (owner is MenuItem)
+ {
+ ((MenuItem)owner).ItemsChanged(CHANGE_ITEMADDED, item);
+ }
+
+ return index;
+ }
+
+ public virtual void AddRange(MenuItem[] items)
+ {
+ ArgumentNullException.ThrowIfNull(items);
+ foreach (MenuItem item in items)
+ {
+ Add(item);
+ }
+ }
+
+ int IList.Add(object value)
+ {
+ if (value is MenuItem)
+ {
+ return Add((MenuItem)value);
+ }
+ else
+ {
+ throw new ArgumentException("Value must be a MenuItem.", nameof(value));
+ }
+ }
+
+ public bool Contains(MenuItem value)
+ {
+ return IndexOf(value) != -1;
+ }
+
+ bool IList.Contains(object value)
+ {
+ if (value is MenuItem)
+ {
+ return Contains((MenuItem)value);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Returns true if the collection contains an item with the specified key, false otherwise.
+ ///
+ public virtual bool ContainsKey(string key)
+ {
+ return IsValidIndex(IndexOfKey(key));
+ }
+
+ ///
+ /// Searches for Controls by their Name property, builds up an array
+ /// of all the controls that match.
+ ///
+ public MenuItem[] Find(string key, bool searchAllChildren)
+ {
+ if ((key is null) || (key.Length == 0))
+ {
+ throw new ArgumentNullException(nameof(key), SR.FindKeyMayNotBeEmptyOrNull);
+ }
+
+ ArrayList foundMenuItems = FindInternal(key, searchAllChildren, this, new ArrayList());
+
+ // Make this a stongly typed collection.
+ MenuItem[] stronglyTypedfoundMenuItems = new MenuItem[foundMenuItems.Count];
+ foundMenuItems.CopyTo(stronglyTypedfoundMenuItems, 0);
+
+ return stronglyTypedfoundMenuItems;
+ }
+
+ ///
+ /// Searches for Controls by their Name property, builds up an array list
+ /// of all the controls that match.
+ ///
+ private static ArrayList FindInternal(string key, bool searchAllChildren, MenuItemCollection menuItemsToLookIn, ArrayList foundMenuItems)
+ {
+ if ((menuItemsToLookIn is null) || (foundMenuItems is null))
+ {
+ return null; //
+ }
+
+ // Perform breadth first search - as it's likely people will want controls belonging
+ // to the same parent close to each other.
+
+ for (int i = 0; i < menuItemsToLookIn.Count; i++)
+ {
+ if (menuItemsToLookIn[i] is null)
+ {
+ continue;
+ }
+
+ if (WindowsFormsUtils.SafeCompareStrings(menuItemsToLookIn[i].Name, key, /* ignoreCase = */ true))
+ {
+ foundMenuItems.Add(menuItemsToLookIn[i]);
+ }
+ }
+
+ // Optional recurive search for controls in child collections.
+
+ if (searchAllChildren)
+ {
+ for (int i = 0; i < menuItemsToLookIn.Count; i++)
+ {
+ if (menuItemsToLookIn[i] is null)
+ {
+ continue;
+ }
+
+ if ((menuItemsToLookIn[i].MenuItems is not null) && menuItemsToLookIn[i].MenuItems.Count > 0)
+ {
+ // if it has a valid child collecion, append those results to our collection
+ foundMenuItems = FindInternal(key, searchAllChildren, menuItemsToLookIn[i].MenuItems, foundMenuItems);
+ }
+ }
+ }
+
+ return foundMenuItems;
+ }
+
+ public int IndexOf(MenuItem value)
+ {
+ for (int index = 0; index < Count; ++index)
+ {
+ if (this[index] == value)
+ {
+ return index;
+ }
+ }
+
+ return -1;
+ }
+
+ int IList.IndexOf(object value)
+ {
+ if (value is MenuItem)
+ {
+ return IndexOf((MenuItem)value);
+ }
+ else
+ {
+ return -1;
+ }
+ }
+
+ ///
+ /// The zero-based index of the first occurrence of value within the entire CollectionBase, if found; otherwise, -1.
+ ///
+ public virtual int IndexOfKey(string key)
+ {
+ // Step 0 - Arg validation
+ if (string.IsNullOrEmpty(key))
+ {
+ return -1; // we dont support empty or null keys.
+ }
+
+ // step 1 - check the last cached item
+ if (IsValidIndex(lastAccessedIndex))
+ {
+ if (WindowsFormsUtils.SafeCompareStrings(this[lastAccessedIndex].Name, key, /* ignoreCase = */ true))
+ {
+ return lastAccessedIndex;
+ }
+ }
+
+ // step 2 - search for the item
+ for (int i = 0; i < Count; i++)
+ {
+ if (WindowsFormsUtils.SafeCompareStrings(this[i].Name, key, /* ignoreCase = */ true))
+ {
+ lastAccessedIndex = i;
+ return i;
+ }
+ }
+
+ // step 3 - we didn't find it. Invalidate the last accessed index and return -1.
+ lastAccessedIndex = -1;
+ return -1;
+ }
+
+ void IList.Insert(int index, object value)
+ {
+ if (value is MenuItem)
+ {
+ Add(index, (MenuItem)value);
+ }
+ else
+ {
+ throw new ArgumentException("Value must be a MenuItem.", nameof(value));
+ }
+ }
+
+ ///
+ /// Determines if the index is valid for the collection.
+ ///
+ private bool IsValidIndex(int index)
+ {
+ return ((index >= 0) && (index < Count));
+ }
+
+ ///
+ /// Removes all existing MenuItems from this menu
+ ///
+ public virtual void Clear()
+ {
+ if (owner.ItemCount > 0)
+ {
+ for (int i = 0; i < owner.ItemCount; i++)
+ {
+ owner.items[i].Parent = null;
+ }
+
+ owner._itemCount = 0;
+ owner.items = null;
+
+ owner.ItemsChanged(CHANGE_ITEMS);
+
+ if (owner is MenuItem)
+ {
+ ((MenuItem)(owner)).UpdateMenuItem(true);
+ }
+ }
+ }
+
+ public void CopyTo(Array dest, int index)
+ {
+ if (owner.ItemCount > 0)
+ {
+ Array.Copy(owner.items, 0, dest, index, owner.ItemCount);
+ }
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return new ArraySubsetEnumerator(owner.items, owner.ItemCount);
+ }
+
+ ///
+ /// Removes the item at the specified index in this menu. All subsequent
+ /// items are moved up one slot.
+ ///
+ public virtual void RemoveAt(int index)
+ {
+ if (index < 0 || index >= owner.ItemCount)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), index, string.Format(SR.InvalidArgument, nameof(index), index));
+ }
+
+ MenuItem item = owner.items[index];
+ item.Parent = null;
+ owner._itemCount--;
+ Array.Copy(owner.items, index + 1, owner.items, index, owner.ItemCount - index);
+ owner.items[owner.ItemCount] = null;
+ owner.ItemsChanged(CHANGE_ITEMS);
+
+ // if the last item was removed, clear the collection
+ //
+ if (owner.ItemCount == 0)
+ {
+ Clear();
+ }
+ }
+
+ ///
+ /// Removes the menu iteml with the specified key.
+ ///
+ public virtual void RemoveByKey(string key)
+ {
+ int index = IndexOfKey(key);
+ if (IsValidIndex(index))
+ {
+ RemoveAt(index);
+ }
+ }
+
+ ///
+ /// Removes the specified item from this menu. All subsequent
+ /// items are moved down one slot.
+ ///
+ public virtual void Remove(MenuItem item)
+ {
+ if (item.Parent == owner)
+ {
+ RemoveAt(item.Index);
+ }
+ }
+
+ void IList.Remove(object value)
+ {
+ if (value is MenuItem)
+ {
+ Remove((MenuItem)value);
+ }
+ }
+ }
+ }
}
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/MenuItem.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/MenuItem.cs
index 796345c289e..7d009374f37 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/MenuItem.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/MenuItem.cs
@@ -1,270 +1,1766 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#pragma warning disable IDE1006, CS8625, CS8618, CS8601, CS8600, CS8603, CS8602, CS8604, RS0036, RS0016, IDE0052
+
+using System.Collections;
using System.ComponentModel;
+using System.Drawing;
+using System.Globalization;
+using System.Runtime.InteropServices;
+using SafeNativeMethods = System.Windows.Forms.LegacyMenuSafeNativeMethods;
+using UnsafeNativeMethods = System.Windows.Forms.LegacyMenuUnsafeNativeMethods;
-namespace System.Windows.Forms;
-
-#nullable disable
-
-///
-/// This type is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code.
-///
-[Obsolete(
- Obsoletions.MenuMessage,
- error: false,
- DiagnosticId = Obsoletions.UnsupportedControlsDiagnosticId,
- UrlFormat = Obsoletions.SharedUrlFormat)]
-[EditorBrowsable(EditorBrowsableState.Never)]
-[Browsable(false)]
-[ToolboxItem(false)]
-[DesignTimeVisible(false)]
-[DefaultEvent(nameof(Click))]
-[DefaultProperty(nameof(Text))]
-public class MenuItem : Menu
+namespace System.Windows.Forms
{
- public MenuItem() : this(
- mergeType: MenuMerge.Add,
- mergeOrder: 0,
- shortcut: 0,
- text: null,
- onClick: null,
- onPopup: null,
- onSelect: null,
- items: null) => throw new PlatformNotSupportedException();
-
- public MenuItem(string text) : this(
- mergeType: MenuMerge.Add,
- mergeOrder: 0,
- shortcut: 0,
- text: text,
- onClick: null,
- onPopup: null,
- onSelect: null,
- items: null) => throw new PlatformNotSupportedException();
-
- public MenuItem(string text, EventHandler onClick) : this(
- mergeType: MenuMerge.Add,
- mergeOrder: 0,
- shortcut: 0,
- text: text,
- onClick: onClick,
- onPopup: null,
- onSelect: null,
- items: null) => throw new PlatformNotSupportedException();
-
- public MenuItem(string text, EventHandler onClick, Shortcut shortcut) : this(
- mergeType: MenuMerge.Add,
- mergeOrder: 0,
- shortcut: shortcut,
- text: text,
- onClick: onClick,
- onPopup: null,
- onSelect: null,
- items: null) => throw new PlatformNotSupportedException();
-
- public MenuItem(string text, MenuItem[] items) : this(
- mergeType: MenuMerge.Add,
- mergeOrder: 0,
- shortcut: 0,
- text: text,
- onClick: null,
- onPopup: null,
- onSelect: null,
- items: items) => throw new PlatformNotSupportedException();
-
- public MenuItem(
- MenuMerge mergeType,
- int mergeOrder,
- Shortcut shortcut,
- string text,
- EventHandler onClick,
- EventHandler onPopup,
- EventHandler onSelect,
- MenuItem[] items) : base(items: items) => throw new PlatformNotSupportedException();
-
- [Browsable(false)]
- [DefaultValue(false)]
- public bool BarBreak
+ ///
+ /// Represents an individual item that is displayed within a
+ /// or .
+ ///
+ [ToolboxItem(false)]
+ [DesignTimeVisible(false)]
+ [DefaultEvent(nameof(Click))]
+ [DefaultProperty(nameof(Text))]
+ public class MenuItem : Menu
{
- get => throw null;
- set { }
- }
+ private const int StateBarBreak = 0x00000020;
+ private const int StateBreak = 0x00000040;
+ private const int StateChecked = 0x00000008;
+ private const int StateDefault = 0x00001000;
+ private const int StateDisabled = 0x00000003;
+ private const int StateRadioCheck = 0x00000200;
+ private const int StateHidden = 0x00010000;
+ private const int StateMdiList = 0x00020000;
+ private const int StateCloneMask = 0x0003136B;
+ private const int StateOwnerDraw = 0x00000100;
+ private const int StateInMdiPopup = 0x00000200;
+ private const int StateHiLite = 0x00000080;
- [Browsable(false)]
- [DefaultValue(false)]
- public bool Break
- {
- get => throw null;
- set { }
- }
+ private bool _hasHandle;
+ private MenuItemData _data;
+ private int _dataVersion;
+ private MenuItem _nextLinkedItem; // Next item linked to the same MenuItemData.
- [DefaultValue(false)]
- public bool Checked
- {
- get => throw null;
- set { }
- }
+ // We need to store a table of all created menuitems, so that other objects
+ // such as ContainerControl can get a reference to a particular menuitem,
+ // given a unique ID.
+ private static readonly Hashtable s_allCreatedMenuItems = new Hashtable();
+ private const uint FirstUniqueID = 0xC0000000;
+ private static long s_nextUniqueID = FirstUniqueID;
+ private uint _uniqueID;
+ private IntPtr _msaaMenuInfoPtr = IntPtr.Zero;
+ private bool _menuItemIsCreated;
- [DefaultValue(false)]
- public bool DefaultItem
- {
- get => throw null;
- set { }
- }
+#if DEBUG
+ private string _debugText;
+ private readonly int _creationNumber;
+ private static int CreateCount;
+#endif
- [DefaultValue(false)]
- public bool OwnerDraw
- {
- get => throw null;
- set { }
- }
+ ///
+ /// Initializes a with a blank caption.
+ ///
+ public MenuItem() : this(MenuMerge.Add, 0, 0, null, null, null, null, null)
+ {
+ }
- [Localizable(true)]
- [DefaultValue(true)]
- public bool Enabled
- {
- get => throw null;
- set { }
- }
+ ///
+ /// Initializes a new instance of the class
+ /// with a specified caption for the menu item.
+ ///
+ public MenuItem(string text) : this(MenuMerge.Add, 0, 0, text, null, null, null, null)
+ {
+ }
- [Browsable(false)]
- public int Index
- {
- get => throw null;
- set { }
- }
+ ///
+ /// Initializes a new instance of the class with a specified caption and event handler
+ /// for the menu item.
+ ///
+ public MenuItem(string text, EventHandler onClick) : this(MenuMerge.Add, 0, 0, text, onClick, null, null, null)
+ {
+ }
- [Browsable(false)]
- public override bool IsParent
- {
- get => throw null;
- }
+ ///
+ /// Initializes a new instance of the class with a specified caption, event handler,
+ /// and associated shorcut key for the menu item.
+ ///
+ public MenuItem(string text, EventHandler onClick, Shortcut shortcut) : this(MenuMerge.Add, 0, shortcut, text, onClick, null, null, null)
+ {
+ }
- [DefaultValue(false)]
- public bool MdiList
- {
- get => throw null;
- set { }
- }
+ ///
+ /// Initializes a new instance of the class with a specified caption and an array of
+ /// submenu items defined for the menu item.
+ ///
+ public MenuItem(string text, MenuItem[] items) : this(MenuMerge.Add, 0, 0, text, null, null, null, items)
+ {
+ }
- protected int MenuID
- {
- get => throw null;
- }
+ internal MenuItem(MenuItemData data) : base(null)
+ {
+ data.AddItem(this);
- [DefaultValue(MenuMerge.Add)]
- public MenuMerge MergeType
- {
- get => throw null;
- set { }
- }
+#if DEBUG
+ _debugText = data._caption;
+#endif
+ }
- [DefaultValue(0)]
- public int MergeOrder
- {
- get => throw null;
- set { }
- }
+ ///
+ /// Initializes a new instance of the class with a specified caption, defined
+ /// event-handlers for the Click, Select and Popup events, a shortcut key,
+ /// a merge type, and order specified for the menu item.
+ ///
+ public MenuItem(MenuMerge mergeType, int mergeOrder, Shortcut shortcut,
+ string text, EventHandler onClick, EventHandler onPopup,
+ EventHandler onSelect, MenuItem[] items) : base(items)
+ {
+ new MenuItemData(this, mergeType, mergeOrder, shortcut, true,
+ text, onClick, onPopup, onSelect, null, null);
- [Browsable(false)]
- public char Mnemonic => throw null;
+#if DEBUG
+ _debugText = text;
+ _creationNumber = CreateCount++;
+#endif
+ }
- [Browsable(false)]
- public Menu Parent
- {
- get => throw null;
- }
+ ///
+ /// Gets or sets a value indicating whether the item is placed on a new line (for a
+ /// menu item added to a object) or in a
+ /// new column (for a submenu or menu displayed in a ).
+ ///
+ [Browsable(false)]
+ [DefaultValue(false)]
+ public bool BarBreak
+ {
+ get
+ {
+ CheckIfDisposed();
+ return (_data.State & StateBarBreak) != 0;
+ }
+ set
+ {
+ CheckIfDisposed();
+ _data.SetState(StateBarBreak, value);
+ }
+ }
- [DefaultValue(false)]
- public bool RadioCheck
- {
- get => throw null;
- set { }
- }
+ ///
+ /// Gets or sets a value indicating whether the item is placed on a new line (for a
+ /// menu item added to a object) or in a
+ /// new column (for a submenu or menu displayed in a ).
+ ///
+ [Browsable(false)]
+ [DefaultValue(false)]
+ public bool Break
+ {
+ get
+ {
+ CheckIfDisposed();
+ return (_data.State & StateBreak) != 0;
+ }
+ set
+ {
+ CheckIfDisposed();
+ _data.SetState(StateBreak, value);
+ }
+ }
- [Localizable(true)]
- public string Text
- {
- get => throw null;
- set { }
- }
+ ///
+ /// Gets or sets a value indicating whether a checkmark appears beside the text of
+ /// the menu item.
+ ///
+ [DefaultValue(false)]
+ public bool Checked
+ {
+ get
+ {
+ CheckIfDisposed();
+ return (_data.State & StateChecked) != 0;
+ }
+ set
+ {
+ CheckIfDisposed();
- [Localizable(true)]
- [DefaultValue(Shortcut.None)]
- public Shortcut Shortcut
- {
- get => throw null;
- set { }
- }
+ if (value && (ItemCount != 0 || Parent is MainMenu))
+ {
+ throw new ArgumentException("Checked menu items cannot be parent items or top-level main menu items.", nameof(value));
+ }
- [DefaultValue(true)]
- [Localizable(true)]
- public bool ShowShortcut
- {
- get => throw null;
- set { }
- }
+ _data.SetState(StateChecked, value);
+ }
+ }
- [Localizable(true)]
- [DefaultValue(true)]
- public bool Visible
- {
- get => throw null;
- set { }
- }
+ ///
+ /// Gets or sets a value indicating whether the menu item is the default.
+ ///
+ [DefaultValue(false)]
+ public bool DefaultItem
+ {
+ get
+ {
+ CheckIfDisposed();
+ return (_data.State & StateDefault) != 0;
+ }
+ set
+ {
+ CheckIfDisposed();
+ if (Parent is not null)
+ {
+ if (value)
+ {
+ UnsafeNativeMethods.SetMenuDefaultItem(new HandleRef(Parent, Parent.handle), MenuID, false);
+ }
+ else if (DefaultItem)
+ {
+ UnsafeNativeMethods.SetMenuDefaultItem(new HandleRef(Parent, Parent.handle), -1, false);
+ }
+ }
- public event EventHandler Click
- {
- add { }
- remove { }
- }
+ _data.SetState(StateDefault, value);
+ }
+ }
- public event DrawItemEventHandler DrawItem
- {
- add { }
- remove { }
- }
+ ///
+ /// Gets or sets a value indicating whether code that you provide draws the menu
+ /// item or Windows draws the menu item.
+ ///
+ [SRCategory(nameof(SR.CatBehavior))]
+ [DefaultValue(false)]
+ public bool OwnerDraw
+ {
+ get
+ {
+ CheckIfDisposed();
+ return ((_data.State & StateOwnerDraw) != 0);
+ }
+ set
+ {
+ CheckIfDisposed();
+ _data.SetState(StateOwnerDraw, value);
+ }
+ }
- public event MeasureItemEventHandler MeasureItem
- {
- add { }
- remove { }
- }
+ ///
+ /// Gets or sets a value indicating whether the menu item is enabled.
+ ///
+ [Localizable(true)]
+ [DefaultValue(true)]
+ public bool Enabled
+ {
+ get
+ {
+ CheckIfDisposed();
+ return (_data.State & StateDisabled) == 0;
+ }
+ set
+ {
+ CheckIfDisposed();
+ _data.SetState(StateDisabled, !value);
+ }
+ }
- public event EventHandler Popup
- {
- add { }
- remove { }
- }
+ ///
+ /// Gets or sets the menu item's position in its parent menu.
+ ///
+ [Browsable(false)]
+ public int Index
+ {
+ get
+ {
+ if (Parent is not null)
+ {
+ for (int i = 0; i < Parent.ItemCount; i++)
+ {
+ if (Parent.items[i] == this)
+ {
+ return i;
+ }
+ }
+ }
- public event EventHandler Select
- {
- add { }
- remove { }
- }
+ return -1;
+ }
+ set
+ {
+ int oldIndex = Index;
+ if (oldIndex >= 0)
+ {
+ if (value < 0 || value >= Parent.ItemCount)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), string.Format(SR.InvalidArgument, nameof(Index), value));
+ }
+
+ if (value != oldIndex)
+ {
+ // The menu reverts to null when we're removed, so hold onto it in a
+ // local variable
+ Menu parent = Parent;
+ parent.MenuItems.RemoveAt(oldIndex);
+ parent.MenuItems.Add(value, this);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the menu item contains child menu items.
+ ///
+ [Browsable(false)]
+ public override bool IsParent
+ {
+ get
+ {
+ if (_data is not null && MdiList)
+ {
+ for (int i = 0; i < ItemCount; i++)
+ {
+ if (items[i]._data.UserData is not MdiListUserData)
+ {
+ return true;
+ }
+ }
+
+ if (FindMdiForms().Length > 0)
+ {
+ return true;
+ }
+
+ if (Parent is not null and not MenuItem)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ return base.IsParent;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the menu item will be populated with a
+ /// list of the MDI child windows that are displayed within the associated form.
+ ///
+ [DefaultValue(false)]
+ public bool MdiList
+ {
+ get
+ {
+ CheckIfDisposed();
+ return (_data.State & StateMdiList) != 0;
+ }
+ set
+ {
+ CheckIfDisposed();
+ _data.MdiList = value;
+ CleanListItems(this);
+ }
+ }
+
+ ///
+ /// Gets the Windows identifier for this menu item.
+ ///
+ protected int MenuID
+ {
+ get
+ {
+ CheckIfDisposed();
+ return _data.GetMenuID();
+ }
+ }
+
+ ///
+ /// Is this menu item currently selected (highlighted) by the user?
+ ///
+ internal bool Selected
+ {
+ get
+ {
+ if (Parent is null)
+ {
+ return false;
+ }
+
+ LegacyMenuNativeMethods.MENUITEMINFO_T info = new()
+ {
+ fMask = LegacyMenuNativeMethods.MIIM_STATE
+ };
+ UnsafeNativeMethods.GetMenuItemInfo(new HandleRef(Parent, Parent.handle), MenuID, false, info);
+
+ return (info.fState & StateHiLite) != 0;
+ }
+ }
+
+ ///
+ /// Gets the zero-based index of this menu item in the parent menu, or -1 if this
+ /// menu item is not associated with a parent menu.
+ ///
+ internal int MenuIndex
+ {
+ get
+ {
+ if (Parent is null)
+ {
+ return -1;
+ }
+
+ int count = UnsafeNativeMethods.GetMenuItemCount(new HandleRef(Parent, Parent.Handle));
+ int id = MenuID;
+ LegacyMenuNativeMethods.MENUITEMINFO_T info = new()
+ {
+ fMask = LegacyMenuNativeMethods.MIIM_ID | LegacyMenuNativeMethods.MIIM_SUBMENU
+ };
+
+ for (int i = 0; i < count; i++)
+ {
+ UnsafeNativeMethods.GetMenuItemInfo(new HandleRef(Parent, Parent.handle), i, true, info);
+
+ // For sub menus, the handle is always valid.
+ // For items, however, it is always zero.
+ if ((info.hSubMenu == IntPtr.Zero || info.hSubMenu == Handle) && info.wID == id)
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+ }
+
+ ///
+ /// Gets or sets a value that indicates the behavior of this
+ /// menu item when its menu is merged with another.
+ ///
+ [DefaultValue(MenuMerge.Add)]
+ public MenuMerge MergeType
+ {
+ get
+ {
+ CheckIfDisposed();
+ return _data._mergeType;
+ }
+ set
+ {
+ CheckIfDisposed();
+ if (value is < MenuMerge.Add or > MenuMerge.Remove)
+ {
+ throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(MenuMerge));
+ }
+
+ _data.MergeType = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the relative position the menu item when its
+ /// menu is merged with another.
+ ///
+ [DefaultValue(0)]
+ public int MergeOrder
+ {
+ get
+ {
+ CheckIfDisposed();
+ return _data._mergeOrder;
+ }
+ set
+ {
+ CheckIfDisposed();
+ _data.MergeOrder = value;
+ }
+ }
+
+ ///
+ /// Retrieves the hotkey mnemonic that is associated with this menu item.
+ /// The mnemonic is the first character after an ampersand symbol in the menu's text
+ /// that is not itself an ampersand symbol. If no such mnemonic is defined this
+ /// will return zero.
+ ///
+ [Browsable(false)]
+ public char Mnemonic
+ {
+ get
+ {
+ CheckIfDisposed();
+ return _data.Mnemonic;
+ }
+ }
+
+ ///
+ /// Gets the menu in which this menu item appears.
+ ///
+ [Browsable(false)]
+ public Menu Parent { get; internal set; }
+
+ ///
+ /// Gets or sets a value that indicates whether the menu item, if checked,
+ /// displays a radio-button mark instead of a check mark.
+ ///
+ [DefaultValue(false)]
+ public bool RadioCheck
+ {
+ get
+ {
+ CheckIfDisposed();
+ return (_data.State & StateRadioCheck) != 0;
+ }
+ set
+ {
+ CheckIfDisposed();
+ _data.SetState(StateRadioCheck, value);
+ }
+ }
+
+ internal override bool RenderIsRightToLeft => Parent is not null && Parent.RenderIsRightToLeft;
+
+ ///
+ /// Gets or sets the text of the menu item.
+ ///
+ [Localizable(true)]
+ public string Text
+ {
+ get
+ {
+ CheckIfDisposed();
+ return _data._caption;
+ }
+ set
+ {
+ CheckIfDisposed();
+ _data.SetCaption(value);
+ }
+ }
+
+ ///
+ /// Gets or sets the shortcut key associated with the menu item.
+ ///
+ [Localizable(true)]
+ [DefaultValue(Shortcut.None)]
+ public Shortcut Shortcut
+ {
+ get
+ {
+ CheckIfDisposed();
+ return _data._shortcut;
+ }
+ set
+ {
+ CheckIfDisposed();
+ if (!Enum.IsDefined(typeof(Shortcut), value))
+ {
+ throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(Shortcut));
+ }
+
+ _data._shortcut = value;
+ UpdateMenuItem(force: true);
+ }
+ }
+
+ ///
+ /// Gets or sets a value that indicates whether the shortcut key that is associated
+ /// with the menu item is displayed next to the menu item caption.
+ ///
+ [DefaultValue(true),
+ Localizable(true)]
+ public bool ShowShortcut
+ {
+ get
+ {
+ CheckIfDisposed();
+ return _data._showShortcut;
+ }
+ set
+ {
+ CheckIfDisposed();
+ if (value != _data._showShortcut)
+ {
+ _data._showShortcut = value;
+ UpdateMenuItem(force: true);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets a value that indicates whether the menu item is visible on its
+ /// parent menu.
+ ///
+ [Localizable(true)]
+ [DefaultValue(true)]
+ public bool Visible
+ {
+ get
+ {
+ CheckIfDisposed();
+ return (_data.State & StateHidden) == 0;
+ }
+ set
+ {
+ CheckIfDisposed();
+ _data.Visible = value;
+ }
+ }
+
+ ///
+ /// Occurs when the menu item is clicked or selected using a shortcut key defined
+ /// for the menu item.
+ ///
+ public event EventHandler Click
+ {
+ add
+ {
+ CheckIfDisposed();
+ _data._onClick += value;
+ }
+ remove
+ {
+ CheckIfDisposed();
+ _data._onClick -= value;
+ }
+ }
+
+ ///
+ /// Occurs when when the property of a menu item is set to and
+ /// a request is made to draw the menu item.
+ ///
+ [SRCategory(nameof(SR.CatBehavior)), SRDescription(nameof(SR.drawItemEventDescr))]
+ public event DrawItemEventHandler DrawItem
+ {
+ add
+ {
+ CheckIfDisposed();
+ _data._onDrawItem += value;
+ }
+ remove
+ {
+ CheckIfDisposed();
+ _data._onDrawItem -= value;
+ }
+ }
+
+ ///
+ /// Occurs when when the menu needs to know the size of a menu item before drawing it.
+ ///
+ [SRCategory(nameof(SR.CatBehavior)), SRDescription(nameof(SR.measureItemEventDescr))]
+ public event MeasureItemEventHandler MeasureItem
+ {
+ add
+ {
+ CheckIfDisposed();
+ _data._onMeasureItem += value;
+ }
+ remove
+ {
+ CheckIfDisposed();
+ _data._onMeasureItem -= value;
+ }
+ }
+
+ ///
+ /// Occurs before a menu item's list of menu items is displayed.
+ ///
+ public event EventHandler Popup
+ {
+ add
+ {
+ CheckIfDisposed();
+ _data._onPopup += value;
+ }
+ remove
+ {
+ CheckIfDisposed();
+ _data._onPopup -= value;
+ }
+ }
+
+ ///
+ /// Occurs when the user hovers their mouse over a menu item or selects it with the
+ /// keyboard but has not activated it.
+ ///
+ public event EventHandler Select
+ {
+ add
+ {
+ CheckIfDisposed();
+ _data._onSelect += value;
+ }
+ remove
+ {
+ CheckIfDisposed();
+ _data._onSelect -= value;
+ }
+ }
+
+ private static void CleanListItems(MenuItem senderMenu)
+ {
+ // Remove dynamic items.
+ for (int i = senderMenu.MenuItems.Count - 1; i >= 0; i--)
+ {
+ MenuItem item = senderMenu.MenuItems[i];
+ if (item._data.UserData is MdiListUserData)
+ {
+ item.Dispose();
+ continue;
+ }
+ }
+ }
+
+ ///
+ /// Creates and returns an identical copy of this menu item.
+ ///
+ public virtual MenuItem CloneMenu()
+ {
+ var newItem = new MenuItem();
+ newItem.CloneMenu(this);
+ return newItem;
+ }
+
+ ///
+ /// Creates a copy of the specified menu item.
+ ///
+ protected void CloneMenu(MenuItem itemSrc)
+ {
+ base.CloneMenu(itemSrc);
+ int state = itemSrc._data.State;
+ new MenuItemData(this,
+ itemSrc.MergeType, itemSrc.MergeOrder, itemSrc.Shortcut, itemSrc.ShowShortcut,
+ itemSrc.Text, itemSrc._data._onClick, itemSrc._data._onPopup, itemSrc._data._onSelect,
+ itemSrc._data._onDrawItem, itemSrc._data._onMeasureItem);
+ _data.SetState(state & StateCloneMask, true);
+ }
+
+ internal virtual void CreateMenuItem()
+ {
+ if ((_data.State & StateHidden) == 0)
+ {
+ LegacyMenuNativeMethods.MENUITEMINFO_T info = CreateMenuItemInfo();
+ UnsafeNativeMethods.InsertMenuItem(new HandleRef(Parent, Parent.handle), -1, true, info);
+
+ _hasHandle = info.hSubMenu != IntPtr.Zero;
+ _dataVersion = _data._version;
+
+ _menuItemIsCreated = true;
+ if (RenderIsRightToLeft)
+ {
+ Parent.UpdateRtl(true);
+ }
+
+#if DEBUG
+ LegacyMenuNativeMethods.MENUITEMINFO_T infoVerify = new()
+ {
+ fMask = LegacyMenuNativeMethods.MIIM_ID | LegacyMenuNativeMethods.MIIM_STATE |
+ LegacyMenuNativeMethods.MIIM_SUBMENU | LegacyMenuNativeMethods.MIIM_TYPE
+ };
+ UnsafeNativeMethods.GetMenuItemInfo(new HandleRef(Parent, Parent.handle), MenuID, false, infoVerify);
+#endif
+ }
+ }
+
+ private LegacyMenuNativeMethods.MENUITEMINFO_T CreateMenuItemInfo()
+ {
+ LegacyMenuNativeMethods.MENUITEMINFO_T info = new()
+ {
+ fMask = LegacyMenuNativeMethods.MIIM_ID | LegacyMenuNativeMethods.MIIM_STATE |
+ LegacyMenuNativeMethods.MIIM_SUBMENU | LegacyMenuNativeMethods.MIIM_TYPE | LegacyMenuNativeMethods.MIIM_DATA,
+ fType = _data.State & (StateBarBreak | StateBreak | StateRadioCheck | StateOwnerDraw)
+ };
+
+ // Top level menu items shouldn't have barbreak or break bits set on them.
+ bool isTopLevel = Parent == GetMainMenu();
+
+ if (_data._caption.Equals("-"))
+ {
+ if (isTopLevel)
+ {
+ _data._caption = " ";
+ info.fType |= LegacyMenuNativeMethods.MFT_MENUBREAK;
+ }
+ else
+ {
+ info.fType |= LegacyMenuNativeMethods.MFT_SEPARATOR;
+ }
+ }
+
+ info.fState = _data.State & (StateChecked | StateDefault | StateDisabled);
+
+ info.wID = MenuID;
+ if (IsParent)
+ {
+ info.hSubMenu = Handle;
+ }
+
+ info.hbmpChecked = IntPtr.Zero;
+ info.hbmpUnchecked = IntPtr.Zero;
+
+ // Assign a unique ID to this menu item object.
+ // The ID is stored in the dwItemData of the corresponding Win32 menu item, so
+ // that when we get Win32 messages about the item later, we can delegate to the
+ // original object menu item object. A static hash table is used to map IDs to
+ // menu item objects.
+ if (_uniqueID == 0)
+ {
+ lock (s_allCreatedMenuItems)
+ {
+ _uniqueID = (uint)Interlocked.Increment(ref s_nextUniqueID);
+ Debug.Assert(_uniqueID >= FirstUniqueID); // ...check for ID range exhaustion (unlikely!)
+ // We add a weak ref wrapping a MenuItem to the static hash table, as
+ // supposed to adding the item ref itself, to allow the item to be finalized
+ // in case it is not disposed and no longer referenced anywhere else, hence
+ // preventing leaks.
+ s_allCreatedMenuItems.Add(_uniqueID, new WeakReference(this));
+ }
+ }
+
+ if (IntPtr.Size == 4)
+ {
+ // Store the unique ID in the dwItemData..
+ // For simple menu items, we can just put the unique ID in the dwItemData.
+ // But for owner-draw items, we need to point the dwItemData at an MSAAMENUINFO
+ // structure so that MSAA can get the item text.
+ // To allow us to reliably distinguish between IDs and structure pointers later
+ // on, we keep IDs in the 0xC0000000-0xFFFFFFFF range. This is the top 1Gb of
+ // unmananged process memory, where an app's heap allocations should never come
+ // from. So that we can still get the ID from the dwItemData for an owner-draw
+ // item later on, a copy of the ID is tacked onto the end of the MSAAMENUINFO
+ // structure.
+ if (_data.OwnerDraw)
+ {
+ info.dwItemData = AllocMsaaMenuInfo();
+ }
+ else
+ {
+ info.dwItemData = unchecked((IntPtr)(int)_uniqueID);
+ }
+ }
+ else
+ {
+ // On Win64, there are no reserved address ranges we can use for menu item IDs. So instead we will
+ // have to allocate an MSAMENUINFO heap structure for all menu items, not just owner-drawn ones.
+ info.dwItemData = AllocMsaaMenuInfo();
+ }
+
+ // We won't render the shortcut if: 1) it's not set, 2) we're a parent, 3) we're toplevel
+ if (_data._showShortcut && _data._shortcut != 0 && !IsParent && !isTopLevel)
+ {
+ info.dwTypeData = _data._caption + "\t" + TypeDescriptor.GetConverter(typeof(Keys)).ConvertToString((Keys)(int)_data._shortcut);
+ }
+ else
+ {
+ // Windows issue: Items with empty captions sometimes block keyboard
+ // access to other items in same menu.
+ info.dwTypeData = (_data._caption.Length == 0 ? " " : _data._caption);
+ }
+
+ info.cch = 0;
+
+ return info;
+ }
+
+ ///
+ /// Disposes the .
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ Parent?.MenuItems.Remove(this);
+ _data?.RemoveItem(this);
+ lock (s_allCreatedMenuItems)
+ {
+ s_allCreatedMenuItems.Remove(_uniqueID);
+ }
+
+ _uniqueID = 0;
+ }
+
+ FreeMsaaMenuInfo();
+ base.Dispose(disposing);
+ }
+
+ ///
+ /// Given a unique menu item ID, find the corresponding MenuItem
+ /// object, using the master lookup table of all created MenuItems.
+ ///
+ internal static MenuItem GetMenuItemFromUniqueID(uint uniqueID)
+ {
+ WeakReference weakRef = (WeakReference)s_allCreatedMenuItems[uniqueID];
+ if (weakRef is not null && weakRef.IsAlive)
+ {
+ return (MenuItem)weakRef.Target;
+ }
- public virtual MenuItem CloneMenu() => throw null;
+ Debug.Fail("Weakref for menu item has expired or has been removed! Who is trying to access this ID?");
+ return null;
+ }
- protected void CloneMenu(MenuItem itemSrc) { }
+ ///
+ /// Given the "item data" value of a Win32 menu item, find the corresponding MenuItem object (using
+ /// the master lookup table of all created MenuItems). The item data may be either the unique menu
+ /// item ID, or a pointer to an MSAAMENUINFO structure with a copy of the unique ID tacked to the end.
+ /// To reliably tell IDs and structure addresses apart, IDs live in the 0xC0000000-0xFFFFFFFF range.
+ /// This is the top 1Gb of unmananged process memory, where an app's heap allocations should never be.
+ ///
+ internal static MenuItem GetMenuItemFromItemData(IntPtr itemData)
+ {
+ uint uniqueID = (uint)(ulong)itemData;
+ if (uniqueID == 0)
+ {
+ return null;
+ }
- public virtual MenuItem MergeMenu() => throw null;
+ if (IntPtr.Size == 4)
+ {
+ if (uniqueID < FirstUniqueID)
+ {
+ MsaaMenuInfoWithId msaaMenuInfo = Marshal.PtrToStructure(itemData);
+ uniqueID = msaaMenuInfo._uniqueID;
+ }
+ }
+ else
+ {
+ // Its always a pointer on Win64 (see CreateMenuItemInfo)
+ MsaaMenuInfoWithId msaaMenuInfo = Marshal.PtrToStructure(itemData);
+ uniqueID = msaaMenuInfo._uniqueID;
+ }
- public void MergeMenu(MenuItem itemSrc) { }
+ return GetMenuItemFromUniqueID(uniqueID);
+ }
- protected virtual void OnClick(EventArgs e) { }
+ ///
+ /// MsaaMenuInfo is a small compatibility structure that carries menu text for accessibility.
+ ///
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ private struct MsaaMenuInfo
+ {
+ public readonly uint _signature;
+ public readonly uint _textLength;
- protected virtual void OnDrawItem(DrawItemEventArgs e) { }
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public readonly string _text;
- protected virtual void OnInitMenuPopup(EventArgs e) { }
+ public MsaaMenuInfo(string text)
+ {
+ _signature = 0;
+ _text = text;
+ _textLength = (uint)(text?.Length ?? 0);
+ }
+ }
- protected virtual void OnMeasureItem(MeasureItemEventArgs e) { }
+ ///
+ /// MsaaMenuInfoWithId is an MSAAMENUINFO structure with a menu item ID field tacked onto the
+ /// end. This allows us to pass the data we need to Win32 / MSAA, and still be able to get the ID
+ /// out again later on, so we can delegate Win32 menu messages back to the correct MenuItem object.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ private struct MsaaMenuInfoWithId
+ {
+ public readonly MsaaMenuInfo _msaaMenuInfo;
+ public readonly uint _uniqueID;
- protected virtual void OnPopup(EventArgs e) { }
+ public MsaaMenuInfoWithId(string text, uint uniqueID)
+ {
+ _msaaMenuInfo = new MsaaMenuInfo(text);
+ _uniqueID = uniqueID;
+ }
+ }
- protected virtual void OnSelect(EventArgs e) { }
+ ///
+ /// Creates an MSAAMENUINFO structure (in the unmanaged heap) based on the current state
+ /// of this MenuItem object. Address of this structure is cached in the object so we can
+ /// free it later on using FreeMsaaMenuInfo(). If structure has already been allocated,
+ /// it is destroyed and a new one created.
+ ///
+ private IntPtr AllocMsaaMenuInfo()
+ {
+ FreeMsaaMenuInfo();
+ _msaaMenuInfoPtr = Marshal.AllocHGlobal(Marshal.SizeOf());
- public void PerformClick() { }
+ if (IntPtr.Size == 4)
+ {
+ // We only check this on Win32, irrelevant on Win64 (see CreateMenuItemInfo)
+ // Check for incursion into menu item ID range (unlikely!)
+ Debug.Assert(((uint)(ulong)_msaaMenuInfoPtr) < FirstUniqueID);
+ }
- public virtual void PerformSelect() { }
+ MsaaMenuInfoWithId msaaMenuInfoStruct = new MsaaMenuInfoWithId(_data._caption, _uniqueID);
+ Marshal.StructureToPtr(msaaMenuInfoStruct, _msaaMenuInfoPtr, false);
+ Debug.Assert(_msaaMenuInfoPtr != IntPtr.Zero);
+ return _msaaMenuInfoPtr;
+ }
+
+ ///
+ /// Frees the MSAAMENUINFO structure (in the unmanaged heap) for the current MenuObject
+ /// object, if one has previously been allocated. Takes care to free sub-structures too,
+ /// to avoid leaks!
+ ///
+ private void FreeMsaaMenuInfo()
+ {
+ if (_msaaMenuInfoPtr != IntPtr.Zero)
+ {
+ Marshal.DestroyStructure(_msaaMenuInfoPtr, typeof(MsaaMenuInfoWithId));
+ Marshal.FreeHGlobal(_msaaMenuInfoPtr);
+ _msaaMenuInfoPtr = IntPtr.Zero;
+ }
+ }
+
+ internal override void ItemsChanged(int change)
+ {
+ base.ItemsChanged(change);
+
+ if (change == CHANGE_ITEMS)
+ {
+ // when the menu collection changes deal with it locally
+ Debug.Assert(!created, "base.ItemsChanged should have wiped out our handles");
+ if (Parent is not null && Parent.created)
+ {
+ UpdateMenuItem(force: true);
+ CreateMenuItems();
+ }
+ }
+ else
+ {
+ if (!_hasHandle && IsParent)
+ {
+ UpdateMenuItem(force: true);
+ }
+
+ MainMenu main = GetMainMenu();
+ if (main is not null && ((_data.State & StateInMdiPopup) == 0))
+ {
+ main.ItemsChanged(change, this);
+ }
+ }
+ }
+
+ internal void ItemsChanged(int change, MenuItem item)
+ {
+ if (change == CHANGE_ITEMADDED &&
+ _data is not null &&
+ _data.baseItem is not null &&
+ _data.baseItem.MenuItems.Contains(item))
+ {
+ if (Parent is not null && Parent.created)
+ {
+ UpdateMenuItem(force: true);
+ CreateMenuItems();
+ }
+ else if (_data is not null)
+ {
+ MenuItem currentMenuItem = _data.firstItem;
+ while (currentMenuItem is not null)
+ {
+ if (currentMenuItem.created)
+ {
+ MenuItem newItem = item.CloneMenu();
+ item._data.AddItem(newItem);
+ currentMenuItem.MenuItems.Add(newItem);
+ break;
+ }
+
+ currentMenuItem = currentMenuItem._nextLinkedItem;
+ }
+ }
+ }
+ }
+
+ internal Form[] FindMdiForms()
+ {
+ Form[] forms = null;
+ MainMenu main = GetMainMenu();
+ Form menuForm = null;
+ if (main is not null)
+ {
+ menuForm = main.GetFormUnsafe();
+ }
+
+ if (menuForm is not null)
+ {
+ forms = menuForm.MdiChildren;
+ }
+
+ forms ??= [];
+
+ return forms;
+ }
+
+ ///
+ /// See the similar code in MdiWindowListStrip.PopulateItems, which is
+ /// unfortunately just different enough in its working environment that we
+ /// can't readily combine the two. But if you're fixing something here, chances
+ /// are that the same issue will need scrutiny over there.
+ ///
+// "-" is OK
+ private void PopulateMdiList()
+ {
+ MenuItem senderMenu = this;
+ _data.SetState(StateInMdiPopup, true);
+ try
+ {
+ CleanListItems(this);
+
+ // Add new items
+ Form[] forms = FindMdiForms();
+ if (forms is not null && forms.Length > 0)
+ {
+ Form activeMdiChild = GetMainMenu().GetFormUnsafe().ActiveMdiChild;
+
+ if (senderMenu.MenuItems.Count > 0)
+ {
+ MenuItem sep = (MenuItem)Activator.CreateInstance(GetType());
+ sep._data.UserData = new MdiListUserData();
+ sep.Text = "-";
+ senderMenu.MenuItems.Add(sep);
+ }
+
+ // Build a list of child windows to be displayed in
+ // the MDIList menu item...
+ // Show the first maxMenuForms visible elements of forms[] as Window menu items, except:
+ // Always show the active form, even if it's not in the first maxMenuForms visible elements of forms[].
+ // If the active form isn't in the first maxMenuForms forms, then show the first maxMenuForms-1 elements
+ // in forms[], and make the active form the last one on the menu.
+ // Don't count nonvisible forms against the limit on Window menu items.
+
+ const int MaxMenuForms = 9; // Max number of Window menu items for forms
+ int visibleChildren = 0; // the number of visible child forms (so we know to show More Windows...)
+ int accel = 1; // prefix the form name with this digit, underlined, as an accelerator
+ int formsAddedToMenu = 0;
+ bool activeFormAdded = false;
+ for (int i = 0; i < forms.Length; i++)
+ {
+ if (forms[i].Visible)
+ {
+ visibleChildren++;
+ if ((activeFormAdded && (formsAddedToMenu < MaxMenuForms)) || // don't exceed max
+ ((!activeFormAdded && (formsAddedToMenu < (MaxMenuForms - 1))) || // save room for active if it's not in yet
+ (forms[i].Equals(activeMdiChild))))
+ {
+ // there's always room for activeMdiChild
+ MenuItem windowItem = (MenuItem)Activator.CreateInstance(GetType());
+ windowItem._data.UserData = new MdiListFormData(this, i);
+
+ if (forms[i].Equals(activeMdiChild))
+ {
+ windowItem.Checked = true;
+ activeFormAdded = true;
+ }
+
+ windowItem.Text = string.Format(CultureInfo.CurrentUICulture, "&{0} {1}", accel, forms[i].Text);
+ accel++;
+ formsAddedToMenu++;
+ senderMenu.MenuItems.Add(windowItem);
+ }
+ }
+ }
+
+ // Display the More Windows menu option when there are more than 9 MDI
+ // Child menu items to be displayed. This is necessary because we're managing our own
+ // MDI lists, rather than letting Windows do this for us.
+ if (visibleChildren > MaxMenuForms)
+ {
+ MenuItem moreWindows = (MenuItem)Activator.CreateInstance(GetType());
+ moreWindows._data.UserData = new MdiListMoreWindowsData(this);
+ moreWindows.Text = SR.MDIMenuMoreWindows;
+ senderMenu.MenuItems.Add(moreWindows);
+ }
+ }
+ }
+ finally
+ {
+ _data.SetState(StateInMdiPopup, false);
+ }
+ }
+
+ ///
+ /// Merges this menu item with another menu item and returns the resulting merged
+ /// .
+ ///
+ public virtual MenuItem MergeMenu()
+ {
+ CheckIfDisposed();
+
+ MenuItem newItem = (MenuItem)Activator.CreateInstance(GetType());
+ _data.AddItem(newItem);
+ newItem.MergeMenu(this);
+ return newItem;
+ }
+
+ ///
+ /// Merges another menu item with this menu item.
+ ///
+ public void MergeMenu(MenuItem itemSrc)
+ {
+ base.MergeMenu(itemSrc);
+ itemSrc._data.AddItem(this);
+ }
+
+ ///
+ /// Raises the event.
+ ///
+ protected virtual void OnClick(EventArgs e)
+ {
+ CheckIfDisposed();
+
+ if (_data.UserData is MdiListUserData)
+ {
+ ((MdiListUserData)_data.UserData).OnClick(e);
+ }
+ else if (_data.baseItem != this)
+ {
+ _data.baseItem.OnClick(e);
+ }
+ else
+ {
+ _data._onClick?.Invoke(this, e);
+ }
+ }
+
+ ///
+ /// Raises the event.
+ ///
+ protected virtual void OnDrawItem(DrawItemEventArgs e)
+ {
+ CheckIfDisposed();
+
+ if (_data.baseItem != this)
+ {
+ _data.baseItem.OnDrawItem(e);
+ }
+ else
+ {
+ _data._onDrawItem?.Invoke(this, e);
+ }
+ }
+
+ ///
+ /// Raises the event.
+ ///
+ protected virtual void OnMeasureItem(MeasureItemEventArgs e)
+ {
+ CheckIfDisposed();
+
+ if (_data.baseItem != this)
+ {
+ _data.baseItem.OnMeasureItem(e);
+ }
+ else
+ {
+ _data._onMeasureItem?.Invoke(this, e);
+ }
+ }
+
+ ///
+ /// Raises the event.
+ ///
+ protected virtual void OnPopup(EventArgs e)
+ {
+ CheckIfDisposed();
+
+ bool recreate = false;
+ for (int i = 0; i < ItemCount; i++)
+ {
+ if (items[i].MdiList)
+ {
+ recreate = true;
+ items[i].UpdateMenuItem(force: true);
+ }
+ }
+
+ if (recreate || (_hasHandle && !IsParent))
+ {
+ UpdateMenuItem(force: true);
+ }
+
+ if (_data.baseItem != this)
+ {
+ _data.baseItem.OnPopup(e);
+ }
+ else
+ {
+ _data._onPopup?.Invoke(this, e);
+ }
+
+ // Update any subitem states that got changed in the event
+ for (int i = 0; i < ItemCount; i++)
+ {
+ MenuItem item = items[i];
+ if (item._dataVersion != item._data._version)
+ {
+ item.UpdateMenuItem(force: true);
+ }
+ }
+
+ if (MdiList)
+ {
+ PopulateMdiList();
+ }
+ }
+
+ ///
+ /// Raises the event.
+ ///
+ protected virtual void OnSelect(EventArgs e)
+ {
+ CheckIfDisposed();
+
+ if (_data.baseItem != this)
+ {
+ _data.baseItem.OnSelect(e);
+ }
+ else
+ {
+ _data._onSelect?.Invoke(this, e);
+ }
+ }
+
+ protected internal virtual void OnInitMenuPopup(EventArgs e) => OnPopup(e);
+
+ ///
+ /// Generates a event for the MenuItem,
+ /// simulating a click by a user.
+ ///
+ public void PerformClick() => OnClick(EventArgs.Empty);
+
+ ///
+ /// Raises the event for this menu item.
+ ///
+ public virtual void PerformSelect() => OnSelect(EventArgs.Empty);
+
+ internal virtual bool ShortcutClick()
+ {
+ if (Parent is MenuItem parent)
+ {
+ if (!parent.ShortcutClick() || Parent != parent)
+ {
+ return false;
+ }
+ }
+
+ if ((_data.State & StateDisabled) != 0)
+ {
+ return false;
+ }
+
+ if (ItemCount > 0)
+ {
+ OnPopup(EventArgs.Empty);
+ }
+ else
+ {
+ OnClick(EventArgs.Empty);
+ }
+
+ return true;
+ }
+
+ public override string ToString()
+ {
+ string s = base.ToString();
+ string menuItemText = _data?._caption ?? string.Empty;
+ ;
+ return s + ", Text: " + menuItemText;
+ }
+
+ internal void UpdateItemRtl(bool setRightToLeftBit)
+ {
+ if (!_menuItemIsCreated)
+ {
+ return;
+ }
+
+ LegacyMenuNativeMethods.MENUITEMINFO_T info = new()
+ {
+ fMask = LegacyMenuNativeMethods.MIIM_TYPE | LegacyMenuNativeMethods.MIIM_STATE | LegacyMenuNativeMethods.MIIM_SUBMENU,
+ dwTypeData = new string('\0', Text.Length + 2)
+ };
+ info.cch = info.dwTypeData.Length - 1;
+ UnsafeNativeMethods.GetMenuItemInfo(new HandleRef(Parent, Parent.handle), MenuID, false, info);
+ if (setRightToLeftBit)
+ {
+ info.fType |= LegacyMenuNativeMethods.MFT_RIGHTJUSTIFY | LegacyMenuNativeMethods.MFT_RIGHTORDER;
+ }
+ else
+ {
+ info.fType &= ~(LegacyMenuNativeMethods.MFT_RIGHTJUSTIFY | LegacyMenuNativeMethods.MFT_RIGHTORDER);
+ }
+
+ UnsafeNativeMethods.SetMenuItemInfo(new HandleRef(Parent, Parent.handle), MenuID, false, info);
+ }
+
+ internal void UpdateMenuItem(bool force)
+ {
+ if (Parent is null || !Parent.created)
+ {
+ return;
+ }
+
+ if (force || Parent is MainMenu || Parent is ContextMenu)
+ {
+ LegacyMenuNativeMethods.MENUITEMINFO_T info = CreateMenuItemInfo();
+ UnsafeNativeMethods.SetMenuItemInfo(new HandleRef(Parent, Parent.handle), MenuID, false, info);
+#if DEBUG
+ LegacyMenuNativeMethods.MENUITEMINFO_T infoVerify = new()
+ {
+ fMask = LegacyMenuNativeMethods.MIIM_ID | LegacyMenuNativeMethods.MIIM_STATE |
+ LegacyMenuNativeMethods.MIIM_SUBMENU | LegacyMenuNativeMethods.MIIM_TYPE
+ };
+ UnsafeNativeMethods.GetMenuItemInfo(new HandleRef(Parent, Parent.handle), MenuID, false, infoVerify);
+#endif
+
+ if (_hasHandle && info.hSubMenu == IntPtr.Zero)
+ {
+ ClearHandles();
+ }
+
+ _hasHandle = info.hSubMenu != IntPtr.Zero;
+ _dataVersion = _data._version;
+ if (Parent is MainMenu mainMenu)
+ {
+ Form f = mainMenu.GetFormUnsafe();
+ if (f is not null)
+ {
+ SafeNativeMethods.DrawMenuBar(new HandleRef(f, f.Handle));
+ }
+ }
+ }
+ }
+
+ internal void WmDrawItem(ref Message m)
+ {
+ // Handles the OnDrawItem message sent from ContainerControl
+ LegacyMenuNativeMethods.DRAWITEMSTRUCT dis = (LegacyMenuNativeMethods.DRAWITEMSTRUCT)m.GetLParam(typeof(LegacyMenuNativeMethods.DRAWITEMSTRUCT));
+ using Graphics g = Graphics.FromHdcInternal(dis.hDC);
+
+ OnDrawItem(new DrawItemEventArgs(g, SystemInformation.MenuFont, Rectangle.FromLTRB(dis.rcItem.left, dis.rcItem.top, dis.rcItem.right, dis.rcItem.bottom), Index, (DrawItemState)dis.itemState));
+
+ m.ResultInternal = (LRESULT)1;
+ }
+
+ internal void WmMeasureItem(ref Message m)
+ {
+ // Handles the OnMeasureItem message sent from ContainerControl
+
+ // Obtain the measure item struct
+ LegacyMenuNativeMethods.MEASUREITEMSTRUCT mis = (LegacyMenuNativeMethods.MEASUREITEMSTRUCT)m.GetLParam(typeof(LegacyMenuNativeMethods.MEASUREITEMSTRUCT));
+
+ // The OnMeasureItem handler now determines the height and width of the item
+ using var screen = GdiCache.GetScreenDCGraphics();
+ MeasureItemEventArgs mie = new MeasureItemEventArgs(screen.Graphics, Index);
+
+ OnMeasureItem(mie);
+
+ // Update the measure item struct with the new width and height
+ mis.itemHeight = mie.ItemHeight;
+ mis.itemWidth = mie.ItemWidth;
+ Marshal.StructureToPtr(mis, m.LParamInternal, false);
+
+ m.ResultInternal = (LRESULT)1;
+ }
+
+ private void CheckIfDisposed()
+ {
+ ObjectDisposedException.ThrowIf(_data is null, this);
+ }
+
+ internal class MenuItemData : ICommandExecutor
+ {
+ internal MenuItem baseItem;
+ internal MenuItem firstItem;
+
+ private int _state;
+ internal int _version;
+ internal MenuMerge _mergeType;
+ internal int _mergeOrder;
+ internal string _caption;
+ internal short _mnemonic;
+ internal Shortcut _shortcut;
+ internal bool _showShortcut;
+ internal EventHandler _onClick;
+ internal EventHandler _onPopup;
+ internal EventHandler _onSelect;
+ internal DrawItemEventHandler _onDrawItem;
+ internal MeasureItemEventHandler _onMeasureItem;
+
+ private Command _cmd;
+
+ internal MenuItemData(MenuItem baseItem, MenuMerge mergeType, int mergeOrder, Shortcut shortcut, bool showShortcut,
+ string caption, EventHandler onClick, EventHandler onPopup, EventHandler onSelect,
+ DrawItemEventHandler onDrawItem, MeasureItemEventHandler onMeasureItem)
+ {
+ AddItem(baseItem);
+ _mergeType = mergeType;
+ _mergeOrder = mergeOrder;
+ _shortcut = shortcut;
+ _showShortcut = showShortcut;
+ _caption = caption ?? string.Empty;
+ _onClick = onClick;
+ _onPopup = onPopup;
+ _onSelect = onSelect;
+ _onDrawItem = onDrawItem;
+ _onMeasureItem = onMeasureItem;
+ _version = 1;
+ _mnemonic = -1;
+ }
+
+ internal bool OwnerDraw
+ {
+ get => ((State & StateOwnerDraw) != 0);
+ set => SetState(StateOwnerDraw, value);
+ }
+
+ internal bool MdiList
+ {
+ get => ((State & StateMdiList) == StateMdiList);
+ set
+ {
+ if (((_state & StateMdiList) != 0) != value)
+ {
+ SetState(StateMdiList, value);
+ for (MenuItem item = firstItem; item is not null; item = item._nextLinkedItem)
+ {
+ item.ItemsChanged(CHANGE_MDI);
+ }
+ }
+ }
+ }
+
+ internal MenuMerge MergeType
+ {
+ get => _mergeType;
+ set
+ {
+ if (_mergeType != value)
+ {
+ _mergeType = value;
+ ItemsChanged(CHANGE_MERGE);
+ }
+ }
+ }
+
+ internal int MergeOrder
+ {
+ get => _mergeOrder;
+ set
+ {
+ if (_mergeOrder != value)
+ {
+ _mergeOrder = value;
+ ItemsChanged(CHANGE_MERGE);
+ }
+ }
+ }
+
+ internal char Mnemonic
+ {
+ get
+ {
+ if (_mnemonic == -1)
+ {
+ _mnemonic = (short)WindowsFormsUtils.GetMnemonic(_caption, true);
+ }
+
+ return (char)_mnemonic;
+ }
+ }
+
+ internal int State => _state;
+
+ internal bool Visible
+ {
+ get => (_state & StateHidden) == 0;
+ set
+ {
+ if (((_state & StateHidden) == 0) != value)
+ {
+ _state = value ? _state & ~StateHidden : _state | StateHidden;
+ ItemsChanged(CHANGE_VISIBLE);
+ }
+ }
+ }
+
+ internal object UserData { get; set; }
+
+ internal void AddItem(MenuItem item)
+ {
+ if (item._data != this)
+ {
+ item._data?.RemoveItem(item);
+
+ item._nextLinkedItem = firstItem;
+ firstItem = item;
+ baseItem ??= item;
+
+ item._data = this;
+ item._dataVersion = 0;
+ item.UpdateMenuItem(false);
+ }
+ }
+
+ public void Execute()
+ {
+ baseItem?.OnClick(EventArgs.Empty);
+ }
+
+ internal int GetMenuID()
+ {
+ _cmd ??= new Command(this);
+
+ return _cmd.ID;
+ }
+
+ internal void ItemsChanged(int change)
+ {
+ for (MenuItem item = firstItem; item is not null; item = item._nextLinkedItem)
+ {
+ item.Parent?.ItemsChanged(change);
+ }
+ }
+
+ internal void RemoveItem(MenuItem item)
+ {
+ Debug.Assert(item._data == this, "bad item passed to MenuItemData.removeItem");
+
+ if (item == firstItem)
+ {
+ firstItem = item._nextLinkedItem;
+ }
+ else
+ {
+ MenuItem itemT;
+ for (itemT = firstItem; item != itemT._nextLinkedItem;)
+ {
+ itemT = itemT._nextLinkedItem;
+ }
+
+ itemT._nextLinkedItem = item._nextLinkedItem;
+ }
+
+ item._nextLinkedItem = null;
+ item._data = null;
+ item._dataVersion = 0;
+
+ if (item == baseItem)
+ {
+ baseItem = firstItem;
+ }
+
+ if (firstItem is null)
+ {
+ // No longer needed. Toss all references and the Command object.
+ Debug.Assert(baseItem is null, "why isn't baseItem null?");
+ _onClick = null;
+ _onPopup = null;
+ _onSelect = null;
+ _onDrawItem = null;
+ _onMeasureItem = null;
+ _cmd?.Dispose();
+ _cmd = null;
+ }
+ }
+
+ internal void SetCaption(string value)
+ {
+ value ??= string.Empty;
+
+ if (!_caption.Equals(value))
+ {
+ _caption = value;
+ UpdateMenuItems();
+ }
+
+#if DEBUG
+ if (value.Length > 0)
+ {
+ baseItem._debugText = value;
+ }
+#endif
+ }
+
+ internal void SetState(int flag, bool value)
+ {
+ if (((_state & flag) != 0) != value)
+ {
+ _state = value ? _state | flag : _state & ~flag;
+ UpdateMenuItems();
+ }
+ }
+
+ internal void UpdateMenuItems()
+ {
+ _version++;
+ for (MenuItem item = firstItem; item is not null; item = item._nextLinkedItem)
+ {
+ item.UpdateMenuItem(force: true);
+ }
+ }
+ }
+
+ private class MdiListUserData
+ {
+ public virtual void OnClick(EventArgs e)
+ {
+ }
+ }
+
+ private class MdiListFormData : MdiListUserData
+ {
+ private readonly MenuItem _parent;
+ private readonly int _boundIndex;
+
+ public MdiListFormData(MenuItem parentItem, int boundFormIndex)
+ {
+ _boundIndex = boundFormIndex;
+ _parent = parentItem;
+ }
+
+ public override void OnClick(EventArgs e)
+ {
+ if (_boundIndex != -1)
+ {
+ Form[] forms = _parent.FindMdiForms();
+ Debug.Assert(forms is not null, "Didn't get a list of the MDI Forms.");
+
+ if (forms is not null && forms.Length > _boundIndex)
+ {
+ Form boundForm = forms[_boundIndex];
+ boundForm.Activate();
+ if (boundForm.ActiveControl is not null && !boundForm.ActiveControl.Focused)
+ {
+ boundForm.ActiveControl.Focus();
+ }
+ }
+ }
+ }
+ }
+
+ private class MdiListMoreWindowsData : MdiListUserData
+ {
+ private readonly MenuItem _parent;
+
+ public MdiListMoreWindowsData(MenuItem parent)
+ {
+ _parent = parent;
+ }
+
+ public override void OnClick(EventArgs e)
+ {
+ Form[] forms = _parent.FindMdiForms();
+ Debug.Assert(forms is not null, "Didn't get a list of the MDI Forms.");
+ Form active = _parent.GetMainMenu().GetFormUnsafe().ActiveMdiChild;
+ Debug.Assert(active is not null, "Didn't get the active MDI child");
+ if (forms is not null && forms.Length > 0 && active is not null)
+ {
+ using var dialog = new MdiWindowDialog();
+ dialog.SetItems(active, forms);
+ DialogResult result = dialog.ShowDialog();
+ if (result == DialogResult.OK)
+ {
+ dialog.ActiveChildForm.Activate();
+ if (dialog.ActiveChildForm.ActiveControl is not null && !dialog.ActiveChildForm.ActiveControl.Focused)
+ {
+ dialog.ActiveChildForm.ActiveControl.Focus();
+ }
+ }
+ }
+ }
+ }
+ }
}
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/MenuMerge.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/MenuMerge.cs
index 213491c8c79..f98608e335b 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/MenuMerge.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/MenuMerge.cs
@@ -1,20 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.ComponentModel;
-
namespace System.Windows.Forms;
-///
-/// This type is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code.
-///
-[Obsolete(
- Obsoletions.MenuMessage,
- error: false,
- DiagnosticId = Obsoletions.UnsupportedControlsDiagnosticId,
- UrlFormat = Obsoletions.SharedUrlFormat)]
-[EditorBrowsable(EditorBrowsableState.Never)]
-[Browsable(false)]
public enum MenuMerge
{
Add = 0,
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/CompModSwitches.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/CompModSwitches.cs
new file mode 100644
index 00000000000..6fe49e985f7
--- /dev/null
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/CompModSwitches.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma warning disable IDE1006, SA1518
+
+namespace System.Windows.Forms;
+
+internal static class CompModSwitches
+{
+ public static TraceSwitch DataGridCursor { get; } = new("DataGridCursor", "DataGrid cursor tracing");
+ public static TraceSwitch DataGridEditing { get; } = new("DataGridEditing", "DataGrid editing tracing");
+ public static TraceSwitch DataGridKeys { get; } = new("DataGridKeys", "DataGrid keyboard tracing");
+ public static TraceSwitch DataGridLayout { get; } = new("DataGridLayout", "DataGrid layout tracing");
+ public static TraceSwitch DataGridPainting { get; } = new("DataGridPainting", "DataGrid painting tracing");
+ public static TraceSwitch DataGridParents { get; } = new("DataGridParents", "DataGrid parent rows tracing");
+ public static TraceSwitch DataGridScrolling { get; } = new("DataGridScrolling", "DataGrid scrolling tracing");
+ public static TraceSwitch DataGridSelection { get; } = new("DataGridSelection", "DataGrid selection tracing");
+ public static TraceSwitch DGCaptionPaint { get; } = new("DGCaptionPaint", "DataGrid caption painting tracing");
+ public static TraceSwitch DGEditColumnEditing { get; } = new("DGEditColumnEditing", "DataGrid edit column tracing");
+ public static TraceSwitch DGRelationShpRowLayout { get; } = new("DGRelationShpRowLayout", "DataGrid relationship row layout tracing");
+ public static TraceSwitch DGRelationShpRowPaint { get; } = new("DGRelationShpRowPaint", "DataGrid relationship row painting tracing");
+ public static TraceSwitch DGRowPaint { get; } = new("DGRowPaint", "DataGrid row painting tracing");
+}
\ No newline at end of file
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGrid.HitTestInfo.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGrid.HitTestInfo.cs
deleted file mode 100644
index 83dc85c3308..00000000000
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGrid.HitTestInfo.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.ComponentModel;
-
-namespace System.Windows.Forms;
-
-#nullable disable
-
-public partial class DataGrid
-{
- ///
- /// This type is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code.
- ///
- [Obsolete(
- Obsoletions.DataGridMessage,
- error: false,
- DiagnosticId = Obsoletions.UnsupportedControlsDiagnosticId,
- UrlFormat = Obsoletions.SharedUrlFormat)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- [Browsable(false)]
- public sealed class HitTestInfo
- {
- internal HitTestInfo() => throw new PlatformNotSupportedException();
-
- public static readonly HitTestInfo Nowhere;
-
- public int Column => throw null;
-
- public int Row => throw null;
-
- public HitTestType Type => throw null;
- }
-}
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGrid.HitTestType.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGrid.HitTestType.cs
deleted file mode 100644
index 6ef140092a7..00000000000
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGrid.HitTestType.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.ComponentModel;
-
-namespace System.Windows.Forms;
-
-public partial class DataGrid
-{
- ///
- /// This type is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code.
- ///
- [Obsolete(
- Obsoletions.DataGridMessage,
- error: false,
- DiagnosticId = Obsoletions.UnsupportedControlsDiagnosticId,
- UrlFormat = Obsoletions.SharedUrlFormat)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- [Browsable(false)]
- [Flags]
- public enum HitTestType
- {
- None = 0x00000000,
- Cell = 0x00000001,
- ColumnHeader = 0x00000002,
- RowHeader = 0x00000004,
- ColumnResize = 0x00000008,
- RowResize = 0x00000010,
- Caption = 0x00000020,
- ParentRows = 0x00000040
- }
-}
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGrid.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGrid.cs
index e3f18cd7017..b8449129eaf 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGrid.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGrid.cs
@@ -1,599 +1,10706 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#pragma warning disable IDE0040, IDE1006, SA1005, SA1206, SA1400, WFDEV006, RS0016, IDE0301, IDE0300, IDE0036, SA1507, IDE0078, IDE0075, IDE0059, IDE0051, IDE0031, SA1129, IDE0063, IDE0060, CA1823, CA1822
+
+#nullable disable
+
+using System.Collections;
using System.ComponentModel;
+using System.ComponentModel.Design;
using System.Drawing;
-using System.Drawing.Design;
+using System.Globalization;
using System.Runtime.InteropServices;
+using System.Text;
+using static System.Windows.Forms.LegacyDataGridInteropCompat;
+using static System.Windows.Forms.Triangle;
+using DSR = System.Windows.Forms.DataGridStrings;
namespace System.Windows.Forms;
-
-#nullable disable
-
-///
-/// This type is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code.
-///
-[Obsolete(
- Obsoletions.DataGridMessage,
- error: false,
- DiagnosticId = Obsoletions.UnsupportedControlsDiagnosticId,
- UrlFormat = Obsoletions.SharedUrlFormat)]
-[EditorBrowsable(EditorBrowsableState.Never)]
-[Browsable(false)]
-[ComVisible(true)]
-[ClassInterface(ClassInterfaceType.AutoDispatch)]
-[Designer($"System.Windows.Forms.Design.DataGridDesigner, {Assemblies.SystemDesign}")]
-[DefaultProperty(nameof(DataSource))]
-[DefaultEvent(nameof(Navigate))]
-[ComplexBindingProperties(nameof(DataSource), nameof(DataMember))]
-public partial class DataGrid : Control, ISupportInitialize, IDataGridEditingService
-{
- // Implement the default constructor explicitly to ensure that class can't be constructed.
- public DataGrid() => throw new PlatformNotSupportedException();
-
- [DefaultValue(true)]
- public bool AllowNavigation
- {
- get => throw null;
- set { }
- }
-
- [DefaultValue(true)]
- public bool AllowSorting
- {
- get => throw null;
- set { }
- }
-
- public Color AlternatingBackColor
- {
- get => throw null;
- set { }
- }
-
- public Color BackgroundColor
- {
- get => throw null;
- set { }
- }
-
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public override Image BackgroundImage
- {
- get => throw null;
- set { }
- }
-
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public override ImageLayout BackgroundImageLayout
- {
- get => throw null;
- set { }
- }
-
- [DefaultValue(BorderStyle.Fixed3D)]
- [DispId(-504)]
- public BorderStyle BorderStyle
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler BorderStyleChanged
- {
- add { }
- remove { }
- }
-
- public Color CaptionBackColor
- {
- get => throw null;
- set { }
- }
-
- public Color CaptionForeColor
- {
- get => throw null;
- set { }
- }
-
- [Localizable(true)]
- [AmbientValue(null)]
- public Font CaptionFont
- {
- get => throw null;
- set { }
- }
-
- [DefaultValue("")]
- [Localizable(true)]
- public string CaptionText
- {
- get => throw null;
- set { }
- }
-
- [DefaultValue(true)]
- public bool CaptionVisible
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler CaptionVisibleChanged
- {
- add { }
- remove { }
- }
-
- [DefaultValue(true)]
- public bool ColumnHeadersVisible
- {
- get => throw null;
- set { }
- }
-
- [Browsable(false)]
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
- public DataGridCell CurrentCell
- {
- get => throw null;
- set { }
- }
-
- [Browsable(false)]
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
- public int CurrentRowIndex
- {
- get => throw null;
- set { }
- }
-
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public override Cursor Cursor
- {
- get => throw null;
- set { }
- }
-
- [DefaultValue(null)]
- [RefreshProperties(RefreshProperties.Repaint)]
- [AttributeProvider(typeof(IListSource))]
- public object DataSource
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler DataSourceChanged
- {
- add { }
- remove { }
- }
-
- [DefaultValue(null)]
- [Editor($"System.Windows.Forms.Design.DataMemberListEditor, {Assemblies.SystemDesign}", typeof(UITypeEditor))]
- public string DataMember
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler CurrentCellChanged
- {
- add { }
- remove { }
- }
-
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public new event EventHandler CursorChanged
- {
- add { }
- remove { }
- }
-
- public Color SelectionBackColor
- {
- get => throw null;
- set { }
- }
-
- public Color SelectionForeColor
- {
- get => throw null;
- set { }
- }
-
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
- [Localizable(true)]
- public GridTableStylesCollection TableStyles => throw null;
-
- public Color GridLineColor
- {
- get => throw null;
- set { }
- }
-
- [DefaultValue(DataGridLineStyle.Solid)]
- public DataGridLineStyle GridLineStyle
- {
- get => throw null;
- set { }
- }
-
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
- [DefaultValue(DataGridParentRowsLabelStyle.Both)]
- public DataGridParentRowsLabelStyle ParentRowsLabelStyle
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler ParentRowsLabelStyleChanged
- {
- add { }
- remove { }
- }
-
- [Browsable(false)]
- public int FirstVisibleColumn => throw null;
-
- [DefaultValue(false)]
- public bool FlatMode
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler FlatModeChanged
- {
- add { }
- remove { }
- }
-
- public Color HeaderBackColor
- {
- get => throw null;
- set { }
- }
-
- public Font HeaderFont
- {
- get => throw null;
- set { }
- }
-
- public Color HeaderForeColor
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler BackgroundColorChanged
- {
- add { }
- remove { }
- }
-
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public new event EventHandler BackgroundImageChanged
- {
- add { }
- remove { }
- }
-
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public new event EventHandler BackgroundImageLayoutChanged
- {
- add { }
- remove { }
- }
-
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Advanced)]
- protected internal CurrencyManager ListManager
+ ///
+ /// Displays ADO.NET data in a scrollable grid.
+ ///
+ [
+ ComVisible(true),
+ ClassInterface(ClassInterfaceType.AutoDispatch),
+ Designer("System.Windows.Forms.Design.DataGridDesigner, " + Assemblies.SystemDesign),
+ DefaultProperty(nameof(DataSource)),
+ DefaultEvent(nameof(Navigate)),
+ ComplexBindingProperties(nameof(DataSource), nameof(DataMember)),
+ ]
+ public class DataGrid : Control, ISupportInitialize, IDataGridEditingService
{
- get => throw null;
- set { }
- }
-
- public void ResetAlternatingBackColor() { }
-
- public void ResetGridLineColor() { }
-
- public void ResetHeaderBackColor() { }
-
- public void ResetHeaderFont() { }
-
- public void ResetSelectionBackColor() { }
-
- public void ResetSelectionForeColor() { }
-
- protected bool ShouldSerializeSelectionBackColor() => throw null;
-
- protected virtual bool ShouldSerializeSelectionForeColor() => throw null;
-
- public void SetDataBinding(object dataSource, string dataMember) { }
-
- protected virtual bool ShouldSerializeGridLineColor() => throw null;
+#if DEBUG
+ internal TraceSwitch DataGridAcc = new TraceSwitch("DataGridAcc", "Trace Windows Forms DataGrid Accessibility");
+#else
+ internal TraceSwitch DataGridAcc = null;
+#endif
- protected virtual bool ShouldSerializeCaptionBackColor() => throw null;
+ private const int GRIDSTATE_allowSorting = 0x00000001;
+ private const int GRIDSTATE_columnHeadersVisible = 0x00000002;
+ private const int GRIDSTATE_rowHeadersVisible = 0x00000004;
+ private const int GRIDSTATE_trackColResize = 0x00000008;
+ private const int GRIDSTATE_trackRowResize = 0x00000010;
+ private const int GRIDSTATE_isLedgerStyle = 0x00000020;
+ private const int GRIDSTATE_isFlatMode = 0x00000040;
+ private const int GRIDSTATE_listHasErrors = 0x00000080;
+ private const int GRIDSTATE_dragging = 0x00000100;
+ private const int GRIDSTATE_inListAddNew = 0x00000200;
+ private const int GRIDSTATE_inDeleteRow = 0x00000400;
+ private const int GRIDSTATE_canFocus = 0x00000800;
+ private const int GRIDSTATE_readOnlyMode = 0x00001000;
+ private const int GRIDSTATE_allowNavigation = 0x00002000;
+ private const int GRIDSTATE_isNavigating = 0x00004000;
+ private const int GRIDSTATE_isEditing = 0x00008000;
+ private const int GRIDSTATE_editControlChanging = 0x00010000;
+ private const int GRIDSTATE_isScrolling = 0x00020000;
+ private const int GRIDSTATE_overCaption = 0x00040000;
+ private const int GRIDSTATE_childLinkFocused = 0x00080000;
+ private const int GRIDSTATE_inAddNewRow = 0x00100000;
+ private const int GRIDSTATE_inSetListManager = 0x00200000;
+ private const int GRIDSTATE_metaDataChanged = 0x00400000;
+ private const int GRIDSTATE_exceptionInPaint = 0x00800000;
+ private const int GRIDSTATE_layoutSuspended = 0x01000000;
- protected virtual bool ShouldSerializeAlternatingBackColor() => throw null;
+ // PERF: take all the bools and put them into a state variable
+ private Collections.Specialized.BitVector32 gridState; // see GRIDSTATE_ consts above
- protected virtual bool ShouldSerializeCaptionForeColor() => throw null;
+ // for column widths
+ private const int NumRowsForAutoResize = 10;
- protected virtual bool ShouldSerializeHeaderBackColor() => throw null;
+ private const int errorRowBitmapWidth = 15;
+
+ private const DataGridParentRowsLabelStyle defaultParentRowsLabelStyle = DataGridParentRowsLabelStyle.Both;
- protected virtual bool ShouldSerializeBackgroundColor() => throw null;
+ private const BorderStyle defaultBorderStyle = BorderStyle.Fixed3D;
- protected bool ShouldSerializeHeaderFont() => throw null;
+ private const bool defaultCaptionVisible = true;
- protected virtual bool ShouldSerializeHeaderForeColor() => throw null;
+ private const bool defaultParentRowsVisible = true;
- public void ResetHeaderForeColor() { }
+ private readonly DataGridTableStyle defaultTableStyle = new DataGridTableStyle(true);
- protected ScrollBar HorizScrollBar => throw null;
-
- public Color LinkColor
- {
- get => throw null;
- set { }
- }
-
- public void ResetLinkColor() { }
-
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public Color LinkHoverColor
- {
- get => throw null;
- set { }
- }
-
- protected virtual bool ShouldSerializeLinkHoverColor() => throw null;
-
- public void ResetLinkHoverColor() { }
-
- public event EventHandler AllowNavigationChanged
- {
- add { }
- remove { }
- }
-
- public Color ParentRowsBackColor
- {
- get => throw null;
- set { }
- }
+ // private bool allowSorting = true;
- protected virtual bool ShouldSerializeParentRowsBackColor() => throw null;
+ private SolidBrush alternatingBackBrush = DefaultAlternatingBackBrush;
- public Color ParentRowsForeColor
- {
- get => throw null;
- set { }
- }
+ // private bool columnHeadersVisible = true;
- protected virtual bool ShouldSerializeParentRowsForeColor() => throw null;
+ private SolidBrush gridLineBrush = DefaultGridLineBrush;
- [DefaultValue(75)]
- [TypeConverter(typeof(DataGridPreferredColumnWidthTypeConverter))]
- public int PreferredColumnWidth
- {
- get => throw null;
- set { }
- }
+ private const DataGridLineStyle defaultGridLineStyle = DataGridLineStyle.Solid;
+ private DataGridLineStyle gridLineStyle = defaultGridLineStyle;
- public int PreferredRowHeight
- {
- get => throw null;
- set { }
- }
+ private SolidBrush headerBackBrush = DefaultHeaderBackBrush;
- protected bool ShouldSerializePreferredRowHeight() => throw null;
+ private Font headerFont; // this is ambient property to Font value
- [DefaultValue(false)]
- public bool ReadOnly
- {
- get => throw null;
- set { }
- }
+ private SolidBrush headerForeBrush = DefaultHeaderForeBrush;
+ private Pen headerForePen = DefaultHeaderForePen;
- public event EventHandler ReadOnlyChanged
- {
- add { }
- remove { }
- }
+ private SolidBrush linkBrush = DefaultLinkBrush;
- [DefaultValue(true)]
- public bool ParentRowsVisible
- {
- get => throw null;
- set { }
- }
+ private const int defaultPreferredColumnWidth = 75;
+ private int preferredColumnWidth = defaultPreferredColumnWidth;
- public event EventHandler ParentRowsVisibleChanged
- {
- add { }
- remove { }
- }
+ private static readonly int defaultFontHeight = Control.DefaultFont.Height;
+ private int preferredRowHeight = defaultFontHeight + 3;
+
+ // private bool rowHeadersVisible = true;
+ private const int defaultRowHeaderWidth = 35;
+ private int rowHeaderWidth = defaultRowHeaderWidth;
+ private int minRowHeaderWidth;
- [DefaultValue(true)]
- public bool RowHeadersVisible
- {
- get => throw null;
- set { }
- }
+ private SolidBrush selectionBackBrush = DefaultSelectionBackBrush;
+ private SolidBrush selectionForeBrush = DefaultSelectionForeBrush;
+
+ // parent rows
+ //
+ private readonly DataGridParentRows parentRows;
+ // Set_ListManager uses the originalState to determine
+ // if the grid should disconnect from all the MetaDataChangedEvents
+ // keep "originalState is not null" when navigating back and forth in the grid
+ // and use Add/RemoveMetaDataChanged methods.
+ private DataGridState originalState;
+
+ // ui state
+ //
+ // Don't use dataGridRows, use the accessor!!!
+ private DataGridRow[] dataGridRows = Array.Empty();
+ private int dataGridRowsLength;
+
+ // for toolTip
+ private int toolTipId;
+ private DataGridToolTip toolTipProvider;
+
+ private DataGridAddNewRow addNewRow;
+ private LayoutData layout = new LayoutData();
+ private RECT[] cachedScrollableRegion;
+
+ // header namespace goo
+ //
+
+ // these are actually get/set by ColumnBehavior
+ internal bool allowColumnResize = true;
+
+ internal bool allowRowResize = true;
- [DefaultValue(35)]
- public int RowHeaderWidth
- {
- get => throw null;
- set { }
- }
+ internal DataGridParentRowsLabelStyle parentRowsLabels = defaultParentRowsLabelStyle;
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
- [Bindable(false)]
- public override string Text
- {
- get => throw null;
- set { }
- }
+ // information for col/row resizing
+ // private bool trackColResize = false;
+ private int trackColAnchor;
+ private int trackColumn;
+ // private bool trackRowResize = false;
+ private int trackRowAnchor;
+ private int trackRow;
+ private PropertyDescriptor trackColumnHeader;
+ private MouseEventArgs lastSplitBar;
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Advanced)]
- protected ScrollBar VertScrollBar => throw null;
+ // private bool isLedgerStyle = true;
+ // private bool isFlatMode = false;
+ private Font linkFont;
- [Browsable(false)]
- public int VisibleColumnCount => throw null;
+ private SolidBrush backBrush = DefaultBackBrush;
+ private SolidBrush foreBrush = DefaultForeBrush;
+ private SolidBrush backgroundBrush = DefaultBackgroundBrush;
- [Browsable(false)]
- public int VisibleRowCount => throw null;
+ // font cacheing info
+ private int fontHeight = -1;
+ private int linkFontHeight = -1;
+ private int captionFontHeight = -1;
+ private int headerFontHeight = -1;
- public object this[int rowIndex, int columnIndex]
- {
- get => throw null;
- set { }
- }
+ // the preffered height of the row.
- public object this[DataGridCell cell]
- {
- get => throw null;
- set { }
+ // if the list has items with errors
+
+ // private bool listHasErrors = false;
+
+ // caption
+ private readonly DataGridCaption caption;
+
+ // Border
+ //
+ private BorderStyle borderStyle;
+
+ // data binding
+ //
+ private object dataSource;
+ private string dataMember = string.Empty;
+ private CurrencyManager listManager;
+
+ // currently focused control
+ // we want to unparent it either when rebinding the grid or when the grid is disposed
+ Control toBeDisposedEditingControl;
+
+ // persistent data state
+ //
+ internal GridTableStylesCollection dataGridTables;
+ // SET myGridTable in SetDataGridTable ONLY
+ internal DataGridTableStyle myGridTable;
+ internal bool checkHierarchy = true;
+ internal bool inInit;
+
+ // Selection
+ internal int currentRow;
+ internal int currentCol;
+ private int numSelectedRows;
+ private int lastRowSelected = -1;
+
+ // dragging:
+ // private bool dragging = false;
+
+ // addNewRow
+ // private bool inAddNewRow = false;
+ // delete Row
+ // private bool inDeleteRow = false;
+
+ // when we leave, we call CommitEdit
+ // if we leave, then do not focus the dataGrid.
+ // so we can't focus the grid at the following moments:
+ // 1. while processing the OnLayout event
+ // 2. while processing the OnLeave event
+ // private bool canFocus = true;
+
+ // for CurrentCell
+#if DEBUG
+ private bool inDataSource_PositionChanged;
+#endif // DEBUG
+
+ // Policy
+ // private bool readOnlyMode = false;
+ private readonly Policy policy = new Policy();
+ // private bool allowNavigation = true;
+
+ // editing
+ // private bool isNavigating = false;
+ // private bool isEditing = false;
+ // private bool editControlChanging = false;
+ private DataGridColumnStyle editColumn;
+ private DataGridRow editRow;
+
+ // scrolling
+ //
+ private readonly HScrollBar horizScrollBar = new();
+ private readonly VScrollBar vertScrollBar = new();
+
+ // the sum of the widths of the columns preceding the firstVisibleColumn
+ //
+ private int horizontalOffset;
+
+ // the number of pixels of the firstVisibleColumn which are not visible
+ //
+ private int negOffset;
+
+ private int wheelDelta;
+ // private bool isScrolling = false;
+
+ // Visibility
+ //
+ internal int firstVisibleRow;
+ internal int firstVisibleCol;
+ private int numVisibleRows;
+ // the number of columns which are visible
+ private int numVisibleCols;
+ private int numTotallyVisibleRows;
+ // lastTotallyVisibleCol == -1 means that the data grid does not show any column in its entirety
+ private int lastTotallyVisibleCol;
+
+ // mouse move hot-tracking
+ //
+ private int oldRow = -1;
+ // private bool overCaption = true;
+
+ // child relationships focused
+ //
+ // private bool childLinkFocused = false;
+
+ // private static readonly object EVENT_COLUMNHEADERCLICK = new object();
+ private static readonly object EVENT_CURRENTCELLCHANGED = new object();
+ // private static readonly object EVENT_COLUMNRESIZE = new object();
+ // private static readonly object EVENT_LINKCLICKED = new object();
+ private static readonly object EVENT_NODECLICKED = new object();
+ // private static readonly object EVENT_ROWRESIZE = new object();
+ private static readonly object EVENT_SCROLL = new object();
+ private static readonly object EVENT_BACKBUTTONCLICK = new object();
+ private static readonly object EVENT_DOWNBUTTONCLICK = new object();
+
+ // event handlers
+ //
+ private readonly ItemChangedEventHandler itemChangedHandler;
+ private readonly EventHandler positionChangedHandler;
+ private readonly EventHandler currentChangedHandler;
+ private readonly EventHandler metaDataChangedHandler;
+
+ // we have to know when the collection of dataGridTableStyles changes
+ private readonly CollectionChangeEventHandler dataGridTableStylesCollectionChanged;
+
+ private readonly EventHandler backButtonHandler;
+ private readonly EventHandler downButtonHandler;
+
+ private NavigateEventHandler onNavigate;
+
+ private EventHandler onRowHeaderClick;
+
+ // forDebug
+ //
+ // private int forDebug = 0;
+
+ // =-----------------------------------------------------------------
+
+ ///
+ /// Initializes a new instance of the
+ /// class.
+ ///
+ public DataGrid() : base()
+ {
+ SetStyle(ControlStyles.UserPaint, true);
+ SetStyle(ControlStyles.Opaque, false);
+ SetStyle(ControlStyles.SupportsTransparentBackColor, false);
+ SetStyle(ControlStyles.UserMouse, true);
+ gridState = new Collections.Specialized.BitVector32(0x00042827);
+
+ dataGridTables = new GridTableStylesCollection(this);
+ layout = CreateInitialLayoutState();
+ parentRows = new DataGridParentRows(this);
+
+ horizScrollBar.Top = ClientRectangle.Height - horizScrollBar.Height;
+ horizScrollBar.Left = 0;
+ horizScrollBar.Visible = false;
+ horizScrollBar.Scroll += new ScrollEventHandler(GridHScrolled);
+ Controls.Add(horizScrollBar);
+
+ vertScrollBar.Top = 0;
+ vertScrollBar.Left = ClientRectangle.Width - vertScrollBar.Width;
+ vertScrollBar.Visible = false;
+ vertScrollBar.Scroll += new ScrollEventHandler(GridVScrolled);
+ Controls.Add(vertScrollBar);
+
+ BackColor = DefaultBackBrush.Color;
+ ForeColor = DefaultForeBrush.Color;
+ borderStyle = defaultBorderStyle;
+
+ // create the event handlers
+ //
+ currentChangedHandler = new EventHandler(DataSource_RowChanged);
+ positionChangedHandler = new EventHandler(DataSource_PositionChanged);
+ itemChangedHandler = new ItemChangedEventHandler(DataSource_ItemChanged);
+ metaDataChangedHandler = new EventHandler(DataSource_MetaDataChanged);
+ dataGridTableStylesCollectionChanged = new CollectionChangeEventHandler(TableStylesCollectionChanged);
+ dataGridTables.CollectionChanged += dataGridTableStylesCollectionChanged;
+
+ SetDataGridTable(defaultTableStyle, true);
+
+ backButtonHandler = new EventHandler(OnBackButtonClicked);
+ downButtonHandler = new EventHandler(OnShowParentDetailsButtonClicked);
+
+ caption = new DataGridCaption(this);
+ caption.BackwardClicked += backButtonHandler;
+ caption.DownClicked += downButtonHandler;
+
+ RecalculateFonts();
+ Size = new Size(130, 80);
+ Invalidate();
+ PerformLayout();
+ }
+
+ // =------------------------------------------------------------------
+ // = Properties
+ // =------------------------------------------------------------------
+
+ ///
+ /// Gets or sets a value indicating whether the grid can be resorted by clicking on
+ /// a column header.
+ ///
+ [
+ SRCategory(nameof(SR.CatBehavior)),
+ DefaultValue(true),
+ ]
+ public bool AllowSorting
+ {
+ get
+ {
+ return gridState[GRIDSTATE_allowSorting];
+ }
+ set
+ {
+ if (AllowSorting != value)
+ {
+ gridState[GRIDSTATE_allowSorting] = value;
+ if (!value && listManager is not null)
+ {
+ IList list = listManager.List;
+ if (list is IBindingList)
+ {
+ ((IBindingList)list).RemoveSort();
+ }
+ }
+ }
+ }
+ }
+
+ [
+ SRCategory(nameof(SR.CatColors)),
+ ]
+ public Color AlternatingBackColor
+ {
+ get
+ {
+ return alternatingBackBrush.Color;
+ }
+ set
+ {
+ if (value.IsEmpty)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridEmptyColor,
+ "AlternatingBackColor"));
+ }
+
+ if (IsTransparentColor(value))
+ {
+ throw new ArgumentException(DSR.DataGridTransparentAlternatingBackColorNotAllowed);
+ }
+
+ if (!alternatingBackBrush.Color.Equals(value))
+ {
+ alternatingBackBrush = new SolidBrush(value);
+ InvalidateInside();
+ }
+ }
+ }
+
+ public void ResetAlternatingBackColor()
+ {
+ if (ShouldSerializeAlternatingBackColor())
+ {
+ AlternatingBackColor = DefaultAlternatingBackBrush.Color;
+ InvalidateInside();
+ }
+ }
+
+ protected virtual bool ShouldSerializeAlternatingBackColor()
+ {
+ return !AlternatingBackBrush.Equals(DefaultAlternatingBackBrush);
+ }
+
+ internal Brush AlternatingBackBrush
+ {
+ get
+ {
+ return alternatingBackBrush;
+ }
+ }
+
+ // overrode those properties just to move the BackColor and the ForeColor
+ // from the Appearance group onto the Color Group
+ ///
+ /// Gets or sets the background color of the grid.
+ ///
+ [
+ SRCategory(nameof(SR.CatColors)),
+ SRDescription(nameof(SR.ControlBackColorDescr))
+ ]
+ public override Color BackColor
+ {
+ // overrode those properties just to move the BackColor and the ForeColor
+ // from the Appearance group onto the Color Group
+ get
+ {
+ return base.BackColor;
+ }
+ set
+ {
+ if (IsTransparentColor(value))
+ {
+ throw new ArgumentException(DSR.DataGridTransparentBackColorNotAllowed);
+ }
+
+ base.BackColor = value;
+ }
+ }
+
+ public override void ResetBackColor()
+ {
+ if (!BackColor.Equals(DefaultBackBrush.Color))
+ {
+ BackColor = DefaultBackBrush.Color;
+ }
+ }
+
+ [
+ SRCategory(nameof(SR.CatColors)),
+ SRDescription(nameof(SR.ControlForeColorDescr))
+ ]
+ public override Color ForeColor
+ {
+ get
+ {
+ return base.ForeColor;
+ }
+ set
+ {
+ base.ForeColor = value;
+ }
+ }
+
+ public override void ResetForeColor()
+ {
+ if (!ForeColor.Equals(DefaultForeBrush.Color))
+ {
+ ForeColor = DefaultForeBrush.Color;
+ }
+ }
+
+ ///
+ /// Gets a value
+ /// indicating whether the property should be
+ /// persisted.
+ ///
+ internal SolidBrush BackBrush
+ {
+ get
+ {
+ return backBrush;
+ }
+ }
+
+ internal SolidBrush ForeBrush
+ {
+ get
+ {
+ return foreBrush;
+ }
+ }
+
+ ///
+ /// Gets or sets the border style.
+ ///
+ [SRCategory(nameof(SR.CatAppearance))]
+ [DefaultValue(defaultBorderStyle)]
+ [DispId(-504)]
+ public BorderStyle BorderStyle
+ {
+ get => borderStyle;
+ set
+ {
+ SourceGenerated.EnumValidator.Validate(value);
+
+ if (borderStyle != value)
+ {
+ borderStyle = value;
+ PerformLayout();
+ Invalidate();
+ OnBorderStyleChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ private static readonly object EVENT_BORDERSTYLECHANGED = new object();
+
+ [SRCategory(nameof(SR.CatPropertyChanged))]
+ public event EventHandler BorderStyleChanged
+ {
+ add => Events.AddHandler(EVENT_BORDERSTYLECHANGED, value);
+ remove => Events.RemoveHandler(EVENT_BORDERSTYLECHANGED, value);
+ }
+
+ private int BorderWidth
+ {
+ get
+ {
+ if (BorderStyle == BorderStyle.Fixed3D)
+ {
+ return SystemInformation.Border3DSize.Width;
+ }
+ else if (BorderStyle == BorderStyle.FixedSingle)
+ {
+ return 2;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ }
+
+ protected override Size DefaultSize
+ {
+ get
+ {
+ return new Size(130, 80);
+ }
+ }
+
+ private static SolidBrush DefaultSelectionBackBrush
+ {
+ get
+ {
+ return (SolidBrush)SystemBrushes.ActiveCaption;
+ }
+ }
+
+ private static SolidBrush DefaultSelectionForeBrush
+ {
+ get
+ {
+ return (SolidBrush)SystemBrushes.ActiveCaptionText;
+ }
+ }
+
+ internal static SolidBrush DefaultBackBrush
+ {
+ get
+ {
+ return (SolidBrush)SystemBrushes.Window;
+ }
+ }
+
+ internal static SolidBrush DefaultForeBrush
+ {
+ get
+ {
+ return (SolidBrush)SystemBrushes.WindowText;
+ }
+ }
+
+ private static SolidBrush DefaultBackgroundBrush
+ {
+ get
+ {
+ return (SolidBrush)SystemBrushes.AppWorkspace;
+ }
+ }
+
+ internal static SolidBrush DefaultParentRowsForeBrush
+ {
+ get
+ {
+ return (SolidBrush)SystemBrushes.WindowText;
+ }
+ }
+
+ internal static SolidBrush DefaultParentRowsBackBrush
+ {
+ get
+ {
+ return (SolidBrush)SystemBrushes.Control;
+ }
+ }
+
+ internal static SolidBrush DefaultAlternatingBackBrush
+ {
+ get
+ {
+ return (SolidBrush)SystemBrushes.Window;
+ }
+ }
+
+ private static SolidBrush DefaultGridLineBrush
+ {
+ get
+ {
+ return (SolidBrush)SystemBrushes.Control;
+ }
+ }
+
+ private static SolidBrush DefaultHeaderBackBrush
+ {
+ get
+ {
+ return (SolidBrush)SystemBrushes.Control;
+ }
+ }
+
+ private static SolidBrush DefaultHeaderForeBrush
+ {
+ get
+ {
+ return (SolidBrush)SystemBrushes.ControlText;
+ }
+ }
+
+ private static Pen DefaultHeaderForePen
+ {
+ get
+ {
+ return new Pen(SystemColors.ControlText);
+ }
+ }
+
+ private static SolidBrush DefaultLinkBrush
+ {
+ get
+ {
+ return (SolidBrush)SystemBrushes.HotTrack;
+ }
+ }
+
+ private bool ListHasErrors
+ {
+ get
+ {
+ return gridState[GRIDSTATE_listHasErrors];
+ }
+ set
+ {
+ if (ListHasErrors != value)
+ {
+ gridState[GRIDSTATE_listHasErrors] = value;
+ ComputeMinimumRowHeaderWidth();
+ if (!layout.RowHeadersVisible)
+ {
+ return;
+ }
+
+ if (value)
+ {
+ if (myGridTable.IsDefault)
+ {
+ RowHeaderWidth += errorRowBitmapWidth;
+ }
+ else
+ {
+ myGridTable.RowHeaderWidth += errorRowBitmapWidth;
+ }
+ }
+ else
+ {
+ if (myGridTable.IsDefault)
+ {
+ RowHeaderWidth -= errorRowBitmapWidth;
+ }
+ else
+ {
+ myGridTable.RowHeaderWidth -= errorRowBitmapWidth;
+ }
+ }
+ }
+ }
+ }
+
+ private bool Bound
+ {
+ get
+ {
+ return !(listManager is null || myGridTable is null);
+ }
+ }
+
+ internal DataGridCaption Caption
+ {
+ get
+ {
+ return caption;
+ }
+ }
+
+ ///
+ /// Gets or sets the background color of the caption area.
+ ///
+ [
+ SRCategory(nameof(SR.CatColors)),
+ ]
+ public Color CaptionBackColor
+ {
+ get
+ {
+ return Caption.BackColor;
+ }
+ set
+ {
+ if (IsTransparentColor(value))
+ {
+ throw new ArgumentException(DSR.DataGridTransparentCaptionBackColorNotAllowed);
+ }
+
+ Caption.BackColor = value;
+ }
+ }
+
+ private void ResetCaptionBackColor()
+ {
+ Caption.ResetBackColor();
+ }
+
+ ///
+ /// Gets a value
+ /// indicating whether the property should be
+ /// persisted.
+ ///
+ protected virtual bool ShouldSerializeCaptionBackColor()
+ {
+ return Caption.ShouldSerializeBackColor();
+ }
+
+ ///
+ /// Gets
+ /// or sets the foreground color
+ /// of the caption area.
+ ///
+ [
+ SRCategory(nameof(SR.CatColors)),
+ ]
+ public Color CaptionForeColor
+ {
+ get
+ {
+ return Caption.ForeColor;
+ }
+ set
+ {
+ Caption.ForeColor = value;
+ }
+ }
+
+ private void ResetCaptionForeColor()
+ {
+ Caption.ResetForeColor();
+ }
+
+ ///
+ /// Gets a value
+ /// indicating whether the property should be
+ /// persisted.
+ ///
+ protected virtual bool ShouldSerializeCaptionForeColor()
+ {
+ return Caption.ShouldSerializeForeColor();
+ }
+
+ ///
+ /// Gets or sets the font of the grid's caption.
+ ///
+ [
+ SRCategory(nameof(SR.CatAppearance)),
+ Localizable(true),
+ AmbientValue(null),
+ ]
+ public Font CaptionFont
+ {
+ get
+ {
+ return Caption.Font;
+ }
+ set
+ {
+ Caption.Font = value;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the
+ /// caption's font is persisted.
+ ///
+ private bool ShouldSerializeCaptionFont()
+ {
+ return Caption.ShouldSerializeFont();
+ }
+
+ private void ResetCaptionFont()
+ {
+ Caption.ResetFont();
+ }
+
+ ///
+ /// Gets or sets the text of the grid's caption.
+ ///
+ [
+ SRCategory(nameof(SR.CatAppearance)),
+ DefaultValue(""),
+ Localizable(true),
+ ]
+ public string CaptionText
+ {
+ get
+ {
+ return Caption.Text;
+ }
+ set
+ {
+ Caption.Text = value;
+ }
+ }
+
+ ///
+ /// Gets or sets a value that indicates
+ /// whether the grid's caption is visible.
+ ///
+ [
+ DefaultValue(true),
+ SRCategory(nameof(SR.CatDisplay)),
+ ]
+ public bool CaptionVisible
+ {
+ get
+ {
+ return layout.CaptionVisible;
+ }
+ set
+ {
+ if (layout.CaptionVisible != value)
+ {
+ layout.CaptionVisible = value;
+ PerformLayout();
+ Invalidate();
+ OnCaptionVisibleChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ private static readonly object EVENT_CAPTIONVISIBLECHANGED = new object();
+
+ [SRCategory(nameof(SR.CatPropertyChanged))]
+ public event EventHandler CaptionVisibleChanged
+ {
+ add => Events.AddHandler(EVENT_CAPTIONVISIBLECHANGED, value);
+ remove => Events.RemoveHandler(EVENT_CAPTIONVISIBLECHANGED, value);
+ }
+
+ ///
+ /// Gets or sets which cell has the focus. Not available at design time.
+ ///
+ [
+ Browsable(false),
+ DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
+ ]
+ public DataGridCell CurrentCell
+ {
+ get
+ {
+ return new DataGridCell(currentRow, currentCol);
+ }
+ set
+ {
+ // if the OnLayout event was not set in the grid, then we can't
+ // reliably set the currentCell on the grid.
+ if (layout.dirty)
+ {
+ throw new ArgumentException(DSR.DataGridSettingCurrentCellNotGood);
+ }
+
+ if (value.RowNumber == currentRow && value.ColumnNumber == currentCol)
+ {
+ return;
+ }
+
+ // should we throw an exception, maybe?
+ if (DataGridRowsLength == 0 || myGridTable.GridColumnStyles is null || myGridTable.GridColumnStyles.Count == 0)
+ {
+ return;
+ }
+
+ EnsureBound();
+
+ int currentRowSaved = currentRow;
+ int currentColSaved = currentCol;
+ bool wasEditing = gridState[GRIDSTATE_isEditing];
+ bool cellChanged = false;
+
+ // if the position in the listManager changed under the DataGrid,
+ // then do not edit after setting the current cell
+ bool doNotEdit = false;
+
+ int newCol = value.ColumnNumber;
+ int newRow = value.RowNumber;
+
+ string errorMessage = null;
+
+ try
+ {
+ int columnCount = myGridTable.GridColumnStyles.Count;
+ if (newCol < 0)
+ {
+ newCol = 0;
+ }
+
+ if (newCol >= columnCount)
+ {
+ newCol = columnCount - 1;
+ }
+
+ int localGridRowsLength = DataGridRowsLength;
+ DataGridRow[] localGridRows = DataGridRows;
+
+ if (newRow < 0)
+ {
+ newRow = 0;
+ }
+
+ if (newRow >= localGridRowsLength)
+ {
+ newRow = localGridRowsLength - 1;
+ }
+
+ // Current Column changing
+ //
+ if (currentCol != newCol)
+ {
+ cellChanged = true;
+ int currentListManagerPosition = ListManager.Position;
+ int currentListManagerCount = ListManager.List.Count;
+
+ EndEdit();
+
+ if (ListManager.Position != currentListManagerPosition ||
+ currentListManagerCount != ListManager.List.Count)
+ {
+ // EndEdit changed the list.
+ // Reset the data grid rows and the current row inside the datagrid.
+ // And then exit the method.
+ RecreateDataGridRows();
+ if (ListManager.List.Count > 0)
+ {
+ currentRow = ListManager.Position;
+ Edit();
+ }
+ else
+ {
+ currentRow = -1;
+ }
+
+ return;
+ }
+
+ currentCol = newCol;
+ InvalidateRow(currentRow);
+ }
+
+ // Current Row changing
+ //
+ if (currentRow != newRow)
+ {
+ cellChanged = true;
+ int currentListManagerPosition = ListManager.Position;
+ int currentListManagerCount = ListManager.List.Count;
+
+ EndEdit();
+
+ if (ListManager.Position != currentListManagerPosition ||
+ currentListManagerCount != ListManager.List.Count)
+ {
+ // EndEdit changed the list.
+ // Reset the data grid rows and the current row inside the datagrid.
+ // And then exit the method.
+ RecreateDataGridRows();
+ if (ListManager.List.Count > 0)
+ {
+ currentRow = ListManager.Position;
+ Edit();
+ }
+ else
+ {
+ currentRow = -1;
+ }
+
+ return;
+ }
+
+ if (currentRow < localGridRowsLength)
+ {
+ localGridRows[currentRow].OnRowLeave();
+ }
+
+ localGridRows[newRow].OnRowEnter();
+ currentRow = newRow;
+ if (currentRowSaved < localGridRowsLength)
+ {
+ InvalidateRow(currentRowSaved);
+ }
+
+ InvalidateRow(currentRow);
+
+ if (currentRowSaved != listManager.Position)
+ {
+ // not in sync
+#if DEBUG
+ Debug.Assert(inDataSource_PositionChanged, "currentRow and listManager.Position can be out of sync only when the listManager changes its position under the DataGrid or when navigating back");
+ Debug.Assert(ListManager.Position == currentRow || listManager.Position == -1, "DataSource_PositionChanged changes the position in the grid to the position in the listManager");
+#endif //DEBUG
+ doNotEdit = true;
+ if (gridState[GRIDSTATE_isEditing])
+ {
+ AbortEdit();
+ }
+ }
+ else if (gridState[GRIDSTATE_inAddNewRow])
+ {
+#if DEBUG
+ int currentRowCount = DataGridRowsLength;
+#endif // debug
+ // cancelCurrentEdit will change the position in the list
+ // to the last element in the list. and the grid will get an on position changed
+ // event, and will set the current cell to the last element in the dataSource.
+ // so unhook the PositionChanged event from the listManager;
+ ListManager.PositionChanged -= positionChangedHandler;
+ ListManager.CancelCurrentEdit();
+ ListManager.Position = currentRow;
+ ListManager.PositionChanged += positionChangedHandler;
+#if DEBUG
+
+ Debug.Assert(currentRowSaved > currentRow, "we can only go up when we are inAddNewRow");
+ Debug.Assert(currentRowCount == DataGridRowsLength, "the number of rows in the dataGrid should not change");
+ Debug.Assert(currentRowCount == ListManager.Count + 1, "the listManager should have one less record");
+#endif // debug
+ localGridRows[DataGridRowsLength - 1] = new DataGridAddNewRow(this, myGridTable, DataGridRowsLength - 1);
+ SetDataGridRows(localGridRows, DataGridRowsLength);
+ gridState[GRIDSTATE_inAddNewRow] = false;
+ }
+ else
+ {
+ ListManager.EndCurrentEdit();
+ // some special care must be given when setting the
+ // position in the listManager.
+ // if EndCurrentEdit() deleted the current row
+ // ( because of some filtering problem, say )
+ // then we cannot go over the last row
+ //
+ if (localGridRowsLength != DataGridRowsLength)
+ {
+ Debug.Assert(localGridRowsLength == DataGridRowsLength + 1, "this is the only change that could have happened");
+ currentRow = (currentRow == localGridRowsLength - 1) ? DataGridRowsLength - 1 : currentRow;
+ }
+
+ if (currentRow == dataGridRowsLength - 1 && policy.AllowAdd)
+ {
+ // it may be case ( see previous comment )
+ // that listManager.EndCurrentEdit changed the number of rows
+ // in the grid. in this case, we should not be using the old
+ // localGridRows in our assertion, cause they are outdated now
+ //
+ Debug.Assert(DataGridRows[currentRow] is DataGridAddNewRow, "the last row is the DataGridAddNewRow");
+ AddNewRow();
+ Debug.Assert(ListManager.Position == currentRow || listManager.Position == -1, "the listManager should be positioned at the last row");
+ }
+ else
+ {
+ ListManager.Position = currentRow;
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ errorMessage = e.Message;
+ }
+
+ if (errorMessage is not null)
+ {
+ DialogResult result = RTLAwareMessageBox.Show(null,
+ string.Format(DSR.DataGridPushedIncorrectValueIntoColumn, errorMessage),
+ DSR.DataGridErrorMessageBoxCaption, MessageBoxButtons.YesNo,
+ MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, 0);
+
+ if (result == DialogResult.Yes)
+ {
+ currentRow = currentRowSaved;
+ currentCol = currentColSaved;
+ Debug.Assert(currentRow == ListManager.Position || listManager.Position == -1, "the position in the list manager (" + ListManager.Position + ") is out of sync with the currentRow (" + currentRow + ")" + " and the exception is '" + errorMessage + "'");
+ // this will make sure the newRow will not paint the
+ // row selector.
+ InvalidateRowHeader(newRow);
+ // also, make sure that we get the row selector on the currentrow, too
+ InvalidateRowHeader(currentRow);
+ if (wasEditing)
+ {
+ Edit();
+ }
+ }
+ else
+ {
+ // if the user committed a row that used to be addNewRow and the backEnd rejects it,
+ // and then it tries to navigate down then we should stay in the addNewRow
+ // in this particular scenario, CancelCurrentEdit will cause the last row to be deleted,
+ // and this will ultimately call InvalidateRow w/ a row number larger than the number of rows
+ // so set the currentRow here:
+ if (currentRow == DataGridRowsLength - 1 && currentRowSaved == DataGridRowsLength - 2 && DataGridRows[currentRow] is DataGridAddNewRow)
+ {
+ newRow = currentRowSaved;
+ }
+
+ currentRow = newRow;
+ Debug.Assert(result == DialogResult.No, "we only put cancel and ok on the error message box");
+ listManager.PositionChanged -= positionChangedHandler;
+ listManager.CancelCurrentEdit();
+ listManager.Position = newRow;
+ listManager.PositionChanged += positionChangedHandler;
+ currentRow = newRow;
+ currentCol = newCol;
+ if (wasEditing)
+ {
+ Edit();
+ }
+ }
+ }
+
+ if (cellChanged)
+ {
+ EnsureVisible(currentRow, currentCol);
+ OnCurrentCellChanged(EventArgs.Empty);
+
+ // if the user changed the current cell using the UI, edit the new cell
+ // but if the user changed the current cell by changing the position in the
+ // listManager, then do not continue the edit
+ //
+ if (!doNotEdit)
+ {
+#if DEBUG
+ Debug.Assert(!inDataSource_PositionChanged, "if the user changed the current cell using the UI, then do not edit");
+#endif // debug
+ Edit();
+ }
+
+ AccessibilityNotifyClients(AccessibleEvents.Focus, CurrentCellAccIndex);
+ AccessibilityNotifyClients(AccessibleEvents.Selection, CurrentCellAccIndex);
+ }
+
+ Debug.Assert(currentRow == ListManager.Position || listManager.Position == -1, "the position in the list manager is out of sync with the currentRow");
+ }
+ }
+
+ internal int CurrentCellAccIndex
+ {
+ get
+ {
+ int currentCellAccIndex = 0;
+ currentCellAccIndex++; // ParentRowsAccessibleObject
+ currentCellAccIndex += myGridTable.GridColumnStyles.Count; // ColumnHeaderAccessibleObject
+ currentCellAccIndex += DataGridRows.Length; // DataGridRowAccessibleObject
+ if (horizScrollBar.Visible) // Horizontal Scroll Bar Accessible Object
+ {
+ currentCellAccIndex++;
+ }
+
+ if (vertScrollBar.Visible) // Vertical Scroll Bar Accessible Object
+ {
+ currentCellAccIndex++;
+ }
+
+ currentCellAccIndex += (currentRow * myGridTable.GridColumnStyles.Count) + currentCol;
+ return currentCellAccIndex;
+ }
+ }
+
+ [SRCategory(nameof(SR.CatPropertyChanged))]
+ public event EventHandler CurrentCellChanged
+ {
+ add => Events.AddHandler(EVENT_CURRENTCELLCHANGED, value);
+ remove => Events.RemoveHandler(EVENT_CURRENTCELLCHANGED, value);
+ }
+
+ private int CurrentColumn
+ {
+ get
+ {
+ return CurrentCell.ColumnNumber;
+ }
+ set
+ {
+ CurrentCell = new DataGridCell(currentRow, value);
+ }
+ }
+
+ private int CurrentRow
+ {
+ get
+ {
+ return CurrentCell.RowNumber;
+ }
+ set
+ {
+ CurrentCell = new DataGridCell(value, currentCol);
+ }
+ }
+
+ [
+ SRCategory(nameof(SR.CatColors)),
+ ]
+ public Color SelectionBackColor
+ {
+ get
+ {
+ return selectionBackBrush.Color;
+ }
+ set
+ {
+ if (value.IsEmpty)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridEmptyColor, "SelectionBackColor"));
+ }
+
+ if (IsTransparentColor(value))
+ {
+ throw new ArgumentException(DSR.DataGridTransparentSelectionBackColorNotAllowed);
+ }
+
+ if (!value.Equals(selectionBackBrush.Color))
+ {
+ selectionBackBrush = new SolidBrush(value);
+
+ InvalidateInside();
+ }
+ }
+ }
+
+ internal SolidBrush SelectionBackBrush
+ {
+ get
+ {
+ return selectionBackBrush;
+ }
+ }
+
+ internal SolidBrush SelectionForeBrush
+ {
+ get
+ {
+ return selectionForeBrush;
+ }
+ }
+
+ protected bool ShouldSerializeSelectionBackColor()
+ {
+ return !DefaultSelectionBackBrush.Equals(selectionBackBrush);
+ }
+
+ public void ResetSelectionBackColor()
+ {
+ if (ShouldSerializeSelectionBackColor())
+ {
+ SelectionBackColor = DefaultSelectionBackBrush.Color;
+ }
+ }
+
+ [
+ SRCategory(nameof(SR.CatColors)),
+ ]
+ public Color SelectionForeColor
+ {
+ get
+ {
+ return selectionForeBrush.Color;
+ }
+ set
+ {
+ if (value.IsEmpty)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridEmptyColor, "SelectionForeColor"));
+ }
+
+ if (!value.Equals(selectionForeBrush.Color))
+ {
+ selectionForeBrush = new SolidBrush(value);
+
+ InvalidateInside();
+ }
+ }
+ }
+
+ protected virtual bool ShouldSerializeSelectionForeColor()
+ {
+ return !SelectionForeBrush.Equals(DefaultSelectionForeBrush);
+ }
+
+ public void ResetSelectionForeColor()
+ {
+ if (ShouldSerializeSelectionForeColor())
+ {
+ SelectionForeColor = DefaultSelectionForeBrush.Color;
+ }
+ }
+
+ internal override bool ShouldSerializeForeColor()
+ {
+ return !DefaultForeBrush.Color.Equals(ForeColor);
+ }
+
+ ///
+ /// Indicates whether the property should be
+ /// persisted.
+ ///
+ internal override bool ShouldSerializeBackColor()
+ {
+ return !DefaultBackBrush.Color.Equals(BackColor);
+ }
+
+ // Don't use dataGridRows, use the accessor!!!
+ internal DataGridRow[] DataGridRows
+ {
+ get
+ {
+ if (dataGridRows is null)
+ {
+ CreateDataGridRows();
+ }
+
+ return dataGridRows;
+ }
+ }
+
+ // ToolTipping
+ internal DataGridToolTip ToolTipProvider
+ {
+ get
+ {
+ return toolTipProvider;
+ }
+ }
+
+ internal int ToolTipId
+ {
+ get
+ {
+ return toolTipId;
+ }
+ set
+ {
+ toolTipId = value;
+ }
+ }
+
+ private void ResetToolTip()
+ {
+ // remove all the tool tips which are stored
+ for (int i = 0; i < ToolTipId; i++)
+ {
+ ToolTipProvider.RemoveToolTip(new IntPtr(i));
+ }
+
+ // add toolTips for the backButton and
+ // details button on the caption.
+ if (!parentRows.IsEmpty())
+ {
+ bool alignRight = isRightToLeft();
+ int detailsButtonWidth = Caption.GetDetailsButtonWidth();
+ Rectangle backButton = Caption.GetBackButtonRect(layout.Caption, alignRight, detailsButtonWidth);
+ Rectangle detailsButton = Caption.GetDetailsButtonRect(layout.Caption, alignRight);
+
+ // mirror the buttons wrt RTL property
+ backButton.X = MirrorRectangle(backButton, layout.Inside, isRightToLeft());
+ detailsButton.X = MirrorRectangle(detailsButton, layout.Inside, isRightToLeft());
+
+ ToolTipProvider.AddToolTip(DSR.DataGridCaptionBackButtonToolTip, new IntPtr(0), backButton);
+ ToolTipProvider.AddToolTip(DSR.DataGridCaptionDetailsButtonToolTip, new IntPtr(1), detailsButton);
+ ToolTipId = 2;
+ }
+ else
+ {
+ ToolTipId = 0;
+ }
+ }
+
+ ///
+ /// Given a cursor, this will Create the right DataGridRows
+ ///
+ private void CreateDataGridRows()
+ {
+ CurrencyManager listManager = ListManager;
+ DataGridTableStyle dgt = myGridTable;
+ InitializeColumnWidths();
+
+ if (listManager is null)
+ {
+ SetDataGridRows(Array.Empty(), 0);
+ return;
+ }
+
+ int nDataGridRows = listManager.Count;
+ if (policy.AllowAdd)
+ {
+ nDataGridRows++;
+ }
+
+ DataGridRow[] rows = new DataGridRow[nDataGridRows];
+ for (int r = 0; r < listManager.Count; r++)
+ {
+ rows[r] = new DataGridRelationshipRow(this, dgt, r);
+ }
+
+ if (policy.AllowAdd)
+ {
+ addNewRow = new DataGridAddNewRow(this, dgt, nDataGridRows - 1);
+ rows[nDataGridRows - 1] = addNewRow;
+ }
+ else
+ {
+ addNewRow = null;
+ }
+
+ // SetDataGridRows(rows, rows.Length);
+ SetDataGridRows(rows, nDataGridRows);
+ }
+
+ private void RecreateDataGridRows()
+ {
+ int nDataGridRows = 0;
+ CurrencyManager listManager = ListManager;
+
+ if (listManager is not null)
+ {
+ nDataGridRows = listManager.Count;
+ if (policy.AllowAdd)
+ {
+ nDataGridRows++;
+ }
+ }
+
+ SetDataGridRows(null, nDataGridRows);
+ }
+
+ ///
+ /// Sets the array of DataGridRow objects used for
+ /// all row-related logic in the DataGrid.
+ ///
+ internal void SetDataGridRows(DataGridRow[] newRows, int newRowsLength)
+ {
+ dataGridRows = newRows;
+ dataGridRowsLength = newRowsLength;
+
+ // update the vertical scroll bar
+ vertScrollBar.Maximum = Math.Max(0, DataGridRowsLength - 1);
+ if (firstVisibleRow > newRowsLength)
+ {
+ vertScrollBar.Value = 0;
+ firstVisibleRow = 0;
+ }
+
+ ResetUIState();
+#if DEBUG
+ // sanity check: all the rows should have the same
+ // dataGridTable
+ if (newRows is not null && newRowsLength > 0)
+ {
+ DataGridTableStyle dgTable = newRows[0].DataGridTableStyle;
+ for (int i = 0; i < newRowsLength; i++)
+ {
+ Debug.Assert(dgTable == newRows[i].DataGridTableStyle, "how can two rows have different tableStyles?");
+ }
+ }
+#endif // DEBUG
+ Debug.WriteLineIf(CompModSwitches.DataGridCursor.TraceVerbose, "DataGridCursor: There are now " + DataGridRowsLength.ToString(CultureInfo.InvariantCulture) + " rows.");
+ }
+
+ internal int DataGridRowsLength
+ {
+ get
+ {
+ return dataGridRowsLength;
+ }
+ }
+
+ ///
+ /// Gets or sets the data source that the grid is displaying data for.
+ ///
+ [
+ DefaultValue(null),
+ SRCategory(nameof(SR.CatData)),
+ RefreshProperties(RefreshProperties.Repaint),
+ AttributeProvider(typeof(IListSource)),
+ ]
+ public object DataSource
+ {
+ get
+ {
+ return dataSource;
+ }
+
+ set
+ {
+ if (value is not null && !(value is IList || value is IListSource))
+ {
+ throw new ArgumentException(SR.BadDataSourceForComplexBinding);
+ }
+
+ if (dataSource is not null && dataSource.Equals(value))
+ {
+ return;
+ }
+
+ // when the designer resets the dataSource to null, set the dataMember to null, too
+ if ((value is null || value == Convert.DBNull) && DataMember is not null && DataMember.Length != 0)
+ {
+ dataSource = null;
+ DataMember = string.Empty;
+ return;
+ }
+
+ // if we are setting the dataSource and the dataMember is not a part
+ // of the properties in the dataSource, then set the dataMember to ""
+ //
+ if (value is not null)
+ {
+ EnforceValidDataMember(value);
+ }
+
+ Debug.WriteLineIf(CompModSwitches.DataGridCursor.TraceVerbose, "DataGridCursor: DataSource being set to " + ((value is null) ? "null" : value.ToString()));
+
+ // when we change the dataSource, we need to clear the parent rows.
+ // the same goes for all the caption UI: reset it when the datasource changes.
+ //
+ ResetParentRows();
+ Set_ListManager(value, DataMember, false);
+ }
+ }
+
+ private static readonly object EVENT_DATASOURCECHANGED = new object();
+
+ [SRCategory(nameof(SR.CatPropertyChanged))]
+ public event EventHandler DataSourceChanged
+ {
+ add => Events.AddHandler(EVENT_DATASOURCECHANGED, value);
+ remove => Events.RemoveHandler(EVENT_DATASOURCECHANGED, value);
+ }
+
+ ///
+ /// Gets or sets the specific table in a DataSource for the control.
+ ///
+ [
+ DefaultValue(null),
+ SRCategory(nameof(SR.CatData)),
+ Editor("System.Windows.Forms.Design.DataMemberListEditor, " + Assemblies.SystemDesign, typeof(Drawing.Design.UITypeEditor)),
+ ]
+ public string DataMember
+ {
+ get
+ {
+ return dataMember;
+ }
+ set
+ {
+ if (dataMember is not null && dataMember.Equals(value))
+ {
+ return;
+ }
+
+ Debug.WriteLineIf(CompModSwitches.DataGridCursor.TraceVerbose, "DataGridCursor: DataSource being set to " + ((value is null) ? "null" : value.ToString()));
+ // when we change the dataMember, we need to clear the parent rows.
+ // the same goes for all the caption UI: reset it when the datamember changes.
+ //
+ ResetParentRows();
+ Set_ListManager(DataSource, value, false);
+ }
+ }
+
+ public void SetDataBinding(object dataSource, string dataMember)
+ {
+ parentRows.Clear();
+ originalState = null;
+ caption.BackButtonActive = caption.DownButtonActive = caption.BackButtonVisible = false;
+ caption.SetDownButtonDirection(!layout.ParentRowsVisible);
+
+ Set_ListManager(dataSource, dataMember, false);
+ }
+
+ [
+ Browsable(false), EditorBrowsable(EditorBrowsableState.Advanced),
+ ]
+ internal protected CurrencyManager ListManager
+ {
+ get
+ {
+ //try to return something useful:
+ if (listManager is null && BindingContext is not null && DataSource is not null)
+ {
+ return (CurrencyManager)BindingContext[DataSource, DataMember];
+ }
+ else
+ {
+ return listManager;
+ }
+ }
+ set
+ {
+ throw new NotSupportedException(DSR.DataGridSetListManager);
+ }
+ }
+
+ internal void Set_ListManager(object newDataSource, string newDataMember, bool force)
+ {
+ Set_ListManager(newDataSource, newDataMember, force, true); // true for forcing column creation
+ }
+
+ //
+ // prerequisite: the dataMember and the dataSource should be set to the new values
+ //
+ // will do the following:
+ // call EndEdit on the current listManager, will unWire the listManager events, will set the listManager to the new
+ // reality, will wire the new listManager, will update the policy, will set the dataGridTable, will reset the ui state.
+ //
+ internal void Set_ListManager(object newDataSource, string newDataMember, bool force, bool forceColumnCreation)
+ {
+ bool dataSourceChanged = DataSource != newDataSource;
+ bool dataMemberChanged = DataMember != newDataMember;
+
+ // if nothing happened, then why do any work?
+ if (!force && !dataSourceChanged && !dataMemberChanged && gridState[GRIDSTATE_inSetListManager])
+ {
+ return;
+ }
+
+ gridState[GRIDSTATE_inSetListManager] = true;
+ if (toBeDisposedEditingControl is not null)
+ {
+ Debug.Assert(Controls.Contains(toBeDisposedEditingControl));
+ Controls.Remove(toBeDisposedEditingControl);
+ toBeDisposedEditingControl = null;
+ }
+
+ bool beginUpdateInternal = true;
+ try
+ {
+ // will endEdit on the current listManager
+ UpdateListManager();
+
+ // unwire the events:
+ if (listManager is not null)
+ {
+ UnWireDataSource();
+ }
+
+ CurrencyManager oldListManager = listManager;
+ bool listManagerChanged = false;
+ // set up the new listManager
+ // CAUTION: we need to set up the listManager in the grid before setting the dataSource/dataMember props
+ // in the grid. the reason is that if the BindingContext was not yet requested, and it is created in the BindingContext prop
+ // then the grid will call Set_ListManager again, and eventually that means that the dataGrid::listManager will
+ // be hooked up twice to all the events (PositionChanged, ItemChanged, CurrentChanged)
+ if (newDataSource is not null && BindingContext is not null && !(newDataSource == Convert.DBNull))
+ {
+ listManager = (CurrencyManager)BindingContext[newDataSource, newDataMember];
+ }
+ else
+ {
+ listManager = null;
+ }
+
+ // update the dataSource and the dateMember
+ dataSource = newDataSource;
+ dataMember = newDataMember ?? "";
+
+ listManagerChanged = (listManager != oldListManager);
+
+ // wire the events
+ if (listManager is not null)
+ {
+ WireDataSource();
+ // update the policy
+ policy.UpdatePolicy(listManager, ReadOnly);
+ }
+
+ if (!Initializing)
+ {
+ if (listManager is null)
+ {
+ if (ContainsFocus && ParentInternal is null)
+ {
+ Debug.Assert(toBeDisposedEditingControl is null, "we should have removed the toBeDisposedEditingControl already");
+ // if we unparent the active control then the form won't close
+ for (int i = 0; i < Controls.Count; i++)
+ {
+ if (Controls[i].Focused)
+ {
+ toBeDisposedEditingControl = Controls[i];
+ break;
+ }
+ }
+
+ if (toBeDisposedEditingControl == horizScrollBar || toBeDisposedEditingControl == vertScrollBar)
+ {
+ toBeDisposedEditingControl = null;
+ }
+
+#if DEBUG
+ else
+ {
+ Debug.Assert(toBeDisposedEditingControl is not null, "if the grid contains the focus, then the active control should be in the children of data grid control");
+ Debug.Assert(editColumn is not null, "if we have an editing control should be a control in the data grid column");
+ if (editColumn is DataGridTextBoxColumn)
+ {
+ Debug.Assert(((DataGridTextBoxColumn)editColumn).TextBox == toBeDisposedEditingControl, "if we have an editing control should be a control in the data grid column");
+ }
+ }
+#endif // debug;
+
+ }
+
+ SetDataGridRows(null, 0);
+ defaultTableStyle.GridColumnStyles.Clear();
+ SetDataGridTable(defaultTableStyle, forceColumnCreation);
+
+ if (toBeDisposedEditingControl is not null)
+ {
+ Controls.Add(toBeDisposedEditingControl);
+ }
+ }
+ }
+
+ // PERF: if the listManager did not change, then do not:
+ // 1. create new rows
+ // 2. create new columns
+ // 3. compute the errors in the list
+ //
+ // when the metaDataChanges, we need to recreate
+ // the rows and the columns
+ //
+ if (listManagerChanged || gridState[GRIDSTATE_metaDataChanged])
+ {
+ if (Visible)
+ {
+ BeginUpdateInternal();
+ }
+
+ if (listManager is not null)
+ {
+ // get rid of the old gridColumns
+ // we need to clear the old column collection even when navigating to
+ // a list that has a table style associated w/ it. Why? because the
+ // old column collection will be used by the parent rows to paint
+ defaultTableStyle.GridColumnStyles.ResetDefaultColumnCollection();
+
+ DataGridTableStyle newGridTable = dataGridTables[listManager.GetListName()];
+ if (newGridTable is null)
+ {
+ SetDataGridTable(defaultTableStyle, forceColumnCreation);
+ }
+ else
+ {
+ SetDataGridTable(newGridTable, forceColumnCreation);
+ }
+
+ // set the currentRow in ssync w/ the position in the listManager
+ currentRow = listManager.Position == -1 ? 0 : listManager.Position;
+ }
+
+ // when we create the rows we need to use the current dataGridTable
+ //
+ RecreateDataGridRows();
+ if (Visible)
+ {
+ EndUpdateInternal();
+ }
+
+ beginUpdateInternal = false;
+
+ ComputeMinimumRowHeaderWidth();
+ if (myGridTable.IsDefault)
+ {
+ RowHeaderWidth = Math.Max(minRowHeaderWidth, RowHeaderWidth);
+ }
+ else
+ {
+ myGridTable.RowHeaderWidth = Math.Max(minRowHeaderWidth, RowHeaderWidth);
+ }
+
+ ListHasErrors = DataGridSourceHasErrors();
+
+ // build the list of columns and relationships
+ // wipe out the now invalid states
+ //ResetMouseState();
+
+ ResetUIState();
+
+ //layout.CaptionVisible = dataCursor is null ? false : true;
+
+ OnDataSourceChanged(EventArgs.Empty);
+ }
+ }
+ finally
+ {
+ gridState[GRIDSTATE_inSetListManager] = false;
+ // start painting again
+ if (beginUpdateInternal && Visible)
+ {
+ EndUpdateInternal();
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets index of the selected row.
+ ///
+ // will set the position in the ListManager
+ //
+ [
+ DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
+ Browsable(false),
+ ]
+ public int CurrentRowIndex
+ {
+ get
+ {
+ if (originalState is null)
+ {
+ return listManager is null ? -1 : listManager.Position;
+ }
+ else
+ {
+ if (BindingContext is null)
+ {
+ return -1;
+ }
+
+ CurrencyManager originalListManager = (CurrencyManager)BindingContext[originalState.DataSource, originalState.DataMember];
+ return originalListManager.Position;
+ }
+ }
+ set
+ {
+ if (listManager is null)
+ {
+ throw new InvalidOperationException(DSR.DataGridSetSelectIndex);
+ }
+
+ if (originalState is null)
+ {
+ listManager.Position = value;
+ currentRow = value;
+ return;
+ }
+
+ // if we have a this.ListManager, then this.BindingManager cannot be null
+ //
+ CurrencyManager originalListManager = (CurrencyManager)BindingContext[originalState.DataSource, originalState.DataMember];
+ originalListManager.Position = value;
+
+ // this is for parent rows
+ originalState.LinkingRow = originalState.DataGridRows[value];
+
+ // Invalidate everything
+ Invalidate();
+ }
+ }
+
+ ///
+ /// Gets the collection of tables for the grid.
+ ///
+ [
+ SRCategory(nameof(SR.CatData)),
+ DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
+ Localizable(true),
+ ]
+ public GridTableStylesCollection TableStyles
+ {
+ get
+ {
+ return dataGridTables;
+ }
+ }
+
+ internal new int FontHeight
+ {
+ get
+ {
+ return fontHeight;
+ }
+ }
+
+ internal AccessibleObject ParentRowsAccessibleObject
+ {
+ get
+ {
+ return parentRows.AccessibleObject;
+ }
+ }
+
+ internal Rectangle ParentRowsBounds
+ {
+ get
+ {
+ return layout.ParentRows;
+ }
+ }
+
+ ///
+ /// Gets or sets the color of the grid lines.
+ ///
+ [
+ SRCategory(nameof(SR.CatColors)),
+ ]
+ public Color GridLineColor
+ {
+ get
+ {
+ return gridLineBrush.Color;
+ }
+ set
+ {
+ if (gridLineBrush.Color != value)
+ {
+ if (value.IsEmpty)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridEmptyColor, "GridLineColor"));
+ }
+
+ gridLineBrush = new SolidBrush(value);
+
+ Invalidate(layout.Data);
+ }
+ }
+ }
+
+ protected virtual bool ShouldSerializeGridLineColor()
+ {
+ return !GridLineBrush.Equals(DefaultGridLineBrush);
+ }
+
+ public void ResetGridLineColor()
+ {
+ if (ShouldSerializeGridLineColor())
+ {
+ GridLineColor = DefaultGridLineBrush.Color;
+ }
+ }
+
+ internal SolidBrush GridLineBrush
+ {
+ get
+ {
+ return gridLineBrush;
+ }
+ }
+
+ ///
+ /// Gets or sets the line style of the grid.
+ ///
+ [
+ SRCategory(nameof(SR.CatAppearance)),
+ DefaultValue(defaultGridLineStyle),
+ ]
+ public DataGridLineStyle GridLineStyle
+ {
+ get
+ {
+ return gridLineStyle;
+ }
+ set
+ {
+ if (value is < DataGridLineStyle.None or > DataGridLineStyle.Solid)
+ {
+ throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(DataGridLineStyle));
+ }
+
+ if (gridLineStyle != value)
+ {
+ gridLineStyle = value;
+ myGridTable.ResetRelationsUI();
+ Invalidate(layout.Data);
+ }
+ }
+ }
+
+ internal int GridLineWidth
+ {
+ get
+ {
+ Debug.Assert(GridLineStyle == DataGridLineStyle.Solid || GridLineStyle == DataGridLineStyle.None, "are there any other styles?");
+ return GridLineStyle == DataGridLineStyle.Solid ? 1 : 0;
+ }
+ }
+
+ ///
+ /// Gets or
+ /// sets the
+ /// way parent row labels are displayed.
+ ///
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
+ DefaultValue(defaultParentRowsLabelStyle),
+ SRCategory(nameof(SR.CatDisplay)),
+ ]
+ public DataGridParentRowsLabelStyle ParentRowsLabelStyle
+ {
+ get
+ {
+ return parentRowsLabels;
+ }
+
+ set
+ {
+ if (value is < DataGridParentRowsLabelStyle.None or > DataGridParentRowsLabelStyle.Both)
+ {
+ throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(DataGridParentRowsLabelStyle));
+ }
+
+ if (parentRowsLabels != value)
+ {
+ parentRowsLabels = value;
+ Invalidate(layout.ParentRows);
+ OnParentRowsLabelStyleChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ private static readonly object EVENT_PARENTROWSLABELSTYLECHANGED = new object();
+
+ [SRCategory(nameof(SR.CatPropertyChanged))]
+ public event EventHandler ParentRowsLabelStyleChanged
+ {
+ add => Events.AddHandler(EVENT_PARENTROWSLABELSTYLECHANGED, value);
+ remove => Events.RemoveHandler(EVENT_PARENTROWSLABELSTYLECHANGED, value);
+ }
+
+ internal bool Initializing
+ {
+ get
+ {
+ return inInit;
+ }
+ }
+
+ ///
+ /// Gets the index of the first visible column in a grid.
+ ///
+ [
+ Browsable(false),
+ ]
+ public int FirstVisibleColumn
+ {
+ get
+ {
+ return firstVisibleCol;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the grid displays in flat mode.
+ ///
+ [
+ DefaultValue(false),
+ SRCategory(nameof(SR.CatAppearance)),
+ ]
+ public bool FlatMode
+ {
+ get
+ {
+ return gridState[GRIDSTATE_isFlatMode];
+ }
+ set
+ {
+ if (value != FlatMode)
+ {
+ gridState[GRIDSTATE_isFlatMode] = value;
+ Invalidate(layout.Inside);
+ OnFlatModeChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ private static readonly object EVENT_FLATMODECHANGED = new object();
+
+ [SRCategory(nameof(SR.CatPropertyChanged))]
+ public event EventHandler FlatModeChanged
+ {
+ add => Events.AddHandler(EVENT_FLATMODECHANGED, value);
+ remove => Events.RemoveHandler(EVENT_FLATMODECHANGED, value);
+ }
+
+ ///
+ /// Gets or
+ /// sets the background color of all row and column headers.
+ ///
+ [
+ SRCategory(nameof(SR.CatColors)),
+ ]
+ public Color HeaderBackColor
+ {
+ get
+ {
+ return headerBackBrush.Color;
+ }
+ set
+ {
+ if (value.IsEmpty)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridEmptyColor, "HeaderBackColor"));
+ }
+
+ if (IsTransparentColor(value))
+ {
+ throw new ArgumentException(DSR.DataGridTransparentHeaderBackColorNotAllowed);
+ }
+
+ if (!value.Equals(headerBackBrush.Color))
+ {
+ headerBackBrush = new SolidBrush(value);
+
+ if (layout.RowHeadersVisible)
+ {
+ Invalidate(layout.RowHeaders);
+ }
+
+ if (layout.ColumnHeadersVisible)
+ {
+ Invalidate(layout.ColumnHeaders);
+ }
+
+ Invalidate(layout.TopLeftHeader);
+ }
+ }
+ }
+
+ internal SolidBrush HeaderBackBrush
+ {
+ get
+ {
+ return headerBackBrush;
+ }
+ }
+
+ protected virtual bool ShouldSerializeHeaderBackColor()
+ {
+ return !HeaderBackBrush.Equals(DefaultHeaderBackBrush);
+ }
+
+ public void ResetHeaderBackColor()
+ {
+ if (ShouldSerializeHeaderBackColor())
+ {
+ HeaderBackColor = DefaultHeaderBackBrush.Color;
+ }
+ }
+
+ internal SolidBrush BackgroundBrush
+ {
+ get
+ {
+ return backgroundBrush;
+ }
+ }
+
+ private void ResetBackgroundColor()
+ {
+ if (backgroundBrush is not null && BackgroundBrush != DefaultBackgroundBrush)
+ {
+ backgroundBrush.Dispose();
+ backgroundBrush = null;
+ }
+
+ backgroundBrush = DefaultBackgroundBrush;
+ }
+
+ protected virtual bool ShouldSerializeBackgroundColor()
+ {
+ return !BackgroundBrush.Equals(DefaultBackgroundBrush);
+ }
+
+ // using this property, the user can set the backGround color
+ ///
+ /// Gets or sets the background color of the grid.
+ ///
+ [
+ SRCategory(nameof(SR.CatColors)),
+ ]
+ public Color BackgroundColor
+ {
+ get
+ {
+ return backgroundBrush.Color;
+ }
+ set
+ {
+ if (value.IsEmpty)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridEmptyColor, "BackgroundColor"));
+ }
+
+ if (!value.Equals(backgroundBrush.Color))
+ {
+ if (backgroundBrush is not null && BackgroundBrush != DefaultBackgroundBrush)
+ {
+ backgroundBrush.Dispose();
+ backgroundBrush = null;
+ }
+
+ backgroundBrush = new SolidBrush(value);
+
+ Invalidate(layout.Inside);
+ OnBackgroundColorChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ private static readonly object EVENT_BACKGROUNDCOLORCHANGED = new object();
+
+ [SRCategory(nameof(SR.CatPropertyChanged))]
+ public event EventHandler BackgroundColorChanged
+ {
+ add => Events.AddHandler(EVENT_BACKGROUNDCOLORCHANGED, value);
+ remove => Events.RemoveHandler(EVENT_BACKGROUNDCOLORCHANGED, value);
+ }
+
+ ///
+ /// Indicates whether the property should be persisted.
+ ///
+ [
+ SRCategory(nameof(SR.CatAppearance)),
+ ]
+ public Font HeaderFont
+ {
+ get
+ {
+ return (headerFont ?? Font);
+ }
+ set
+ {
+ ArgumentNullException.ThrowIfNull(value);
+
+ if (!value.Equals(headerFont))
+ {
+ headerFont = value;
+ RecalculateFonts();
+ PerformLayout();
+ Invalidate(layout.Inside);
+ }
+ }
+ }
+
+ protected bool ShouldSerializeHeaderFont()
+ {
+ return (headerFont is not null);
+ }
+
+ public void ResetHeaderFont()
+ {
+ if (headerFont is not null)
+ {
+ headerFont = null;
+ RecalculateFonts();
+ PerformLayout();
+ Invalidate(layout.Inside);
+ }
+ }
+
+ ///
+ /// Resets the property to its default value.
+ ///
+ ///
+ /// Gets or sets the foreground color of the grid's headers.
+ ///
+ [
+ SRCategory(nameof(SR.CatColors)),
+ ]
+ public Color HeaderForeColor
+ {
+ get
+ {
+ return headerForePen.Color;
+ }
+ set
+ {
+ if (value.IsEmpty)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridEmptyColor, "HeaderForeColor"));
+ }
+
+ if (!value.Equals(headerForePen.Color))
+ {
+ headerForePen = new Pen(value);
+ headerForeBrush = new SolidBrush(value);
+
+ if (layout.RowHeadersVisible)
+ {
+ Invalidate(layout.RowHeaders);
+ }
+
+ if (layout.ColumnHeadersVisible)
+ {
+ Invalidate(layout.ColumnHeaders);
+ }
+
+ Invalidate(layout.TopLeftHeader);
+ }
+ }
+ }
+
+ protected virtual bool ShouldSerializeHeaderForeColor()
+ {
+ return !HeaderForePen.Equals(DefaultHeaderForePen);
+ }
+
+ public void ResetHeaderForeColor()
+ {
+ if (ShouldSerializeHeaderForeColor())
+ {
+ HeaderForeColor = DefaultHeaderForeBrush.Color;
+ }
+ }
+
+ internal SolidBrush HeaderForeBrush
+ {
+ get
+ {
+ return headerForeBrush;
+ }
+ }
+
+ internal Pen HeaderForePen
+ {
+ get
+ {
+ return headerForePen;
+ }
+ }
+
+ private void ResetHorizontalOffset()
+ {
+ horizontalOffset = 0;
+ negOffset = 0;
+ firstVisibleCol = 0;
+ numVisibleCols = 0;
+ lastTotallyVisibleCol = -1;
+ }
+
+ internal int HorizontalOffset
+ {
+ get
+ {
+ return horizontalOffset;
+ }
+ set
+ {
+ //if (CompModSwitches.DataGridScrolling.TraceVerbose) Debug.WriteLine("DataGridScrolling: Set_HorizontalOffset, value = " + value.ToString());
+ if (value < 0)
+ {
+ value = 0;
+ }
+
+ //
+ // if the dataGrid is not bound ( listManager is null || gridTable is null)
+ // then use ResetHorizontalOffset();
+ //
+
+ int totalWidth = GetColumnWidthSum();
+ int widthNotVisible = totalWidth - layout.Data.Width;
+ if (value > widthNotVisible && widthNotVisible > 0)
+ {
+ value = widthNotVisible;
+ }
+
+ if (value == horizontalOffset)
+ {
+ return;
+ }
+
+ int change = horizontalOffset - value;
+ horizScrollBar.Value = value;
+ Rectangle scroll = layout.Data;
+ if (layout.ColumnHeadersVisible)
+ {
+ scroll = Rectangle.Union(scroll, layout.ColumnHeaders);
+ }
+
+ horizontalOffset = value;
+
+ firstVisibleCol = ComputeFirstVisibleColumn();
+ // update the lastTotallyVisibleCol
+ ComputeVisibleColumns();
+
+ if (gridState[GRIDSTATE_isScrolling])
+ {
+ // if the user did not click on the grid yet, then do not put the edit
+ // control when scrolling
+ if (currentCol >= firstVisibleCol && currentCol < firstVisibleCol + numVisibleCols - 1 && (gridState[GRIDSTATE_isEditing] || gridState[GRIDSTATE_isNavigating]))
+ {
+ Edit();
+ }
+ else
+ {
+ EndEdit();
+ }
+
+ // isScrolling is set to TRUE when the user scrolls.
+ // once we move the edit box, we finished processing the scroll event, so set isScrolling to FALSE
+ // to set isScrolling to TRUE, we need another scroll event.
+ gridState[GRIDSTATE_isScrolling] = false;
+ }
+ else
+ {
+ EndEdit();
+ }
+
+ RECT[] rects = CreateScrollableRegion(scroll);
+ ScrollRectangles(rects, change);
+ OnScroll(EventArgs.Empty);
+ }
+ }
+
+ private void ScrollRectangles(RECT[] rects, int change)
+ {
+ if (rects is not null)
+ {
+ RECT scroll;
+ if (isRightToLeft())
+ {
+ change = -change;
+ }
+
+ for (int r = 0; r < rects.Length; r++)
+ {
+ scroll = rects[r];
+ User32.ScrollWindow(this,
+ change,
+ 0,
+ ref scroll,
+ ref scroll);
+ }
+ }
+ }
+
+ protected ScrollBar HorizScrollBar
+ {
+ get
+ {
+ return horizScrollBar;
+ }
+ }
+
+ ///
+ /// Retrieves a value indicating whether odd and even
+ /// rows are painted using a different background color.
+ ///
+ // Cleanup eventually to be static.
+ internal bool LedgerStyle
+ {
+ get
+ {
+ return gridState[GRIDSTATE_isLedgerStyle];
+ }
+
+ /*
+ set {
+ if (isLedgerStyle != value) {
+ isLedgerStyle = value;
+ InvalidateInside();
+ }
+ }
+ */
+ }
+
+ ///
+ /// Indicates whether the property should be persisted.
+ ///
+ [
+ SRCategory(nameof(SR.CatColors)),
+ ]
+ public Color LinkColor
+ {
+ get
+ {
+ return linkBrush.Color;
+ }
+ set
+ {
+ if (value.IsEmpty)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridEmptyColor, "LinkColor"));
+ }
+
+ if (!linkBrush.Color.Equals(value))
+ {
+ linkBrush = new SolidBrush(value);
+ Invalidate(layout.Data);
+ }
+ }
+ }
+
+ internal virtual bool ShouldSerializeLinkColor()
+ {
+ return !LinkBrush.Equals(DefaultLinkBrush);
+ }
+
+ public void ResetLinkColor()
+ {
+ if (ShouldSerializeLinkColor())
+ {
+ LinkColor = DefaultLinkBrush.Color;
+ }
+ }
+
+ internal Brush LinkBrush
+ {
+ get
+ {
+ return linkBrush;
+ }
+ }
+
+ ///
+ /// Gets
+ /// or sets the color a link changes to when
+ /// the mouse pointer moves over it.
+ ///
+ [
+ SRCategory(nameof(SR.CatColors)),
+ Browsable(false),
+ EditorBrowsable(EditorBrowsableState.Never)
+
+ ]
+ public Color LinkHoverColor
+ {
+ get
+ {
+ return LinkColor;
+ }
+ set
+ {
+ }
+ }
+
+ protected virtual bool ShouldSerializeLinkHoverColor()
+ {
+ return false;
+ // return !LinkHoverBrush.Equals(defaultLinkHoverBrush);
+ }
+
+ public void ResetLinkHoverColor()
+ {
+ /*
+ if (ShouldSerializeLinkHoverColor())
+ LinkHoverColor = defaultLinkHoverBrush.Color;*/
+ }
+
+ ///
+ /// Indicates whether the property should be
+ /// persisted.
+ ///
+ internal Font LinkFont
+ {
+ get
+ {
+ return linkFont;
+ }
+ }
+
+ internal int LinkFontHeight
+ {
+ get
+ {
+ return linkFontHeight;
+ }
+ }
+
+ ///
+ /// Gets or sets a value
+ /// that specifies which links are shown and in what context.
+ ///
+ [
+ DefaultValue(true),
+ SRCategory(nameof(SR.CatBehavior))
+ ]
+ public bool AllowNavigation
+ {
+ get
+ {
+ return gridState[GRIDSTATE_allowNavigation];
+ }
+ set
+ {
+ if (AllowNavigation != value)
+ {
+ gridState[GRIDSTATE_allowNavigation] = value;
+ // let the Caption know about this:
+ Caption.BackButtonActive = !parentRows.IsEmpty() && (value);
+ Caption.BackButtonVisible = Caption.BackButtonActive;
+ RecreateDataGridRows();
+
+ OnAllowNavigationChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ private static readonly object EVENT_ALLOWNAVIGATIONCHANGED = new object();
+
+ [SRCategory(nameof(SR.CatPropertyChanged))]
+ public event EventHandler AllowNavigationChanged
+ {
+ add => Events.AddHandler(EVENT_ALLOWNAVIGATIONCHANGED, value);
+ remove => Events.RemoveHandler(EVENT_ALLOWNAVIGATIONCHANGED, value);
+ }
+
+ [
+ Browsable(false), EditorBrowsable(EditorBrowsableState.Never)
+ ]
+ public override Cursor Cursor
+ {
+ // get the cursor out of the propertyGrid.
+ get
+ {
+ return base.Cursor;
+ }
+
+ set
+ {
+ base.Cursor = value;
+ }
+ }
+
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
+ new public event EventHandler CursorChanged
+ {
+ add => base.CursorChanged += value;
+ remove => base.CursorChanged -= value;
+ }
+
+ [
+ Browsable(false), EditorBrowsable(EditorBrowsableState.Never)
+ ]
+ public override Image BackgroundImage
+ {
+ // get the BackgroundImage out of the propertyGrid.
+ get
+ {
+ return base.BackgroundImage;
+ }
+
+ set
+ {
+ base.BackgroundImage = value;
+ }
+ }
+
+ [
+ Browsable(false), EditorBrowsable(EditorBrowsableState.Never)
+ ]
+ public override ImageLayout BackgroundImageLayout
+ {
+ // get the BackgroundImage out of the propertyGrid.
+ get
+ {
+ return base.BackgroundImageLayout;
+ }
+
+ set
+ {
+ base.BackgroundImageLayout = value;
+ }
+ }
+
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
+ new public event EventHandler BackgroundImageChanged
+ {
+ add => base.BackgroundImageChanged += value;
+ remove => base.BackgroundImageChanged -= value;
+ }
+
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
+ new public event EventHandler BackgroundImageLayoutChanged
+ {
+ add => base.BackgroundImageLayoutChanged += value;
+ remove => base.BackgroundImageLayoutChanged -= value;
+ }
+
+ ///
+ /// Gets or sets the background color of parent rows.
+ ///
+ [
+ SRCategory(nameof(SR.CatColors)),
+ ]
+ public Color ParentRowsBackColor
+ {
+ get
+ {
+ return parentRows.BackColor;
+ }
+ set
+ {
+ if (IsTransparentColor(value))
+ {
+ throw new ArgumentException(DSR.DataGridTransparentParentRowsBackColorNotAllowed);
+ }
+
+ parentRows.BackColor = value;
+ }
+ }
+
+ internal SolidBrush ParentRowsBackBrush
+ {
+ get
+ {
+ return parentRows.BackBrush;
+ }
+ }
+
+ ///
+ /// Indicates whether the property should be
+ /// persisted.
+ ///
+ protected virtual bool ShouldSerializeParentRowsBackColor()
+ {
+ return !ParentRowsBackBrush.Equals(DefaultParentRowsBackBrush);
+ }
+
+ private void ResetParentRowsBackColor()
+ {
+ if (ShouldSerializeParentRowsBackColor())
+ {
+ parentRows.BackBrush = DefaultParentRowsBackBrush;
+ }
+ }
+
+ ///
+ /// Gets or sets the foreground color of parent rows.
+ ///
+ [
+ SRCategory(nameof(SR.CatColors)),
+ ]
+ public Color ParentRowsForeColor
+ {
+ get
+ {
+ return parentRows.ForeColor;
+ }
+ set
+ {
+ parentRows.ForeColor = value;
+ }
+ }
+
+ internal SolidBrush ParentRowsForeBrush
+ {
+ get
+ {
+ return parentRows.ForeBrush;
+ }
+ }
+
+ ///
+ /// Indicates whether the property should be
+ /// persisted.
+ ///
+ protected virtual bool ShouldSerializeParentRowsForeColor()
+ {
+ return !ParentRowsForeBrush.Equals(DefaultParentRowsForeBrush);
+ }
+
+ private void ResetParentRowsForeColor()
+ {
+ if (ShouldSerializeParentRowsForeColor())
+ {
+ parentRows.ForeBrush = DefaultParentRowsForeBrush;
+ }
+ }
+
+ ///
+ /// Gets
+ /// or sets the default width of the grid columns in
+ /// pixels.
+ ///
+ [
+ DefaultValue(defaultPreferredColumnWidth),
+ SRCategory(nameof(SR.CatLayout)),
+ TypeConverter(typeof(DataGridPreferredColumnWidthTypeConverter))
+ ]
+ public int PreferredColumnWidth
+ {
+ get
+ {
+ return preferredColumnWidth;
+ }
+ set
+ {
+ if (value < 0)
+ {
+ throw new ArgumentException(DSR.DataGridColumnWidth, nameof(PreferredColumnWidth));
+ }
+
+ if (preferredColumnWidth != value)
+ {
+ preferredColumnWidth = value;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the preferred row height for the control.
+ ///
+ [
+ SRCategory(nameof(SR.CatLayout)),
+ ]
+ public int PreferredRowHeight
+ {
+ get
+ {
+ return preferredRowHeight;
+ }
+ set
+ {
+ if (value < 0)
+ {
+ throw new ArgumentException(DSR.DataGridRowRowHeight);
+ }
+
+ preferredRowHeight = value;
+ }
+ }
+
+ private void ResetPreferredRowHeight()
+ {
+ preferredRowHeight = defaultFontHeight + 3;
+ }
+
+ protected bool ShouldSerializePreferredRowHeight()
+ {
+ return preferredRowHeight != defaultFontHeight + 3;
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the grid
+ /// is in read-only mode.
+ ///
+ [
+ DefaultValue(false),
+ SRCategory(nameof(SR.CatBehavior)),
+ ]
+ public bool ReadOnly
+ {
+ get
+ {
+ return gridState[GRIDSTATE_readOnlyMode];
+ }
+ set
+ {
+ if (ReadOnly != value)
+ {
+ bool recreateRows = false;
+ if (value)
+ {
+ // AllowAdd happens to have the same boolean value as whether we need to recreate rows.
+ recreateRows = policy.AllowAdd;
+
+ policy.AllowRemove = false;
+ policy.AllowEdit = false;
+ policy.AllowAdd = false;
+ }
+ else
+ {
+ recreateRows |= policy.UpdatePolicy(listManager, value);
+ }
+
+ gridState[GRIDSTATE_readOnlyMode] = value;
+ DataGridRow[] dataGridRows = DataGridRows;
+ if (recreateRows)
+ {
+ RecreateDataGridRows();
+
+ // keep the selected rows
+ DataGridRow[] currentDataGridRows = DataGridRows;
+ int rowCount = Math.Min(currentDataGridRows.Length, dataGridRows.Length);
+ for (int i = 0; i < rowCount; i++)
+ {
+ if (dataGridRows[i].Selected)
+ {
+ currentDataGridRows[i].Selected = true;
+ }
+ }
+ }
+
+ // the addnew row needs to be updated.
+ PerformLayout();
+ InvalidateInside();
+ OnReadOnlyChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ private static readonly object EVENT_READONLYCHANGED = new object();
+
+ [SRCategory(nameof(SR.CatPropertyChanged))]
+ public event EventHandler ReadOnlyChanged
+ {
+ add => Events.AddHandler(EVENT_READONLYCHANGED, value);
+ remove => Events.RemoveHandler(EVENT_READONLYCHANGED, value);
+ }
+
+ ///
+ /// Gets
+ /// or sets a value indicating if the grid's column headers are visible.
+ ///
+ [
+ SRCategory(nameof(SR.CatDisplay)),
+ DefaultValue(true),
+ ]
+ public bool ColumnHeadersVisible
+ {
+ get
+ {
+ return gridState[GRIDSTATE_columnHeadersVisible];
+ }
+ set
+ {
+ if (ColumnHeadersVisible != value)
+ {
+ gridState[GRIDSTATE_columnHeadersVisible] = value;
+ layout.ColumnHeadersVisible = value;
+ PerformLayout();
+ InvalidateInside();
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the parent rows of a table are
+ /// visible.
+ ///
+ [
+ SRCategory(nameof(SR.CatDisplay)),
+ DefaultValue(true),
+ ]
+ public bool ParentRowsVisible
+ {
+ get
+ {
+ return layout.ParentRowsVisible;
+ }
+ set
+ {
+ if (layout.ParentRowsVisible != value)
+ {
+ SetParentRowsVisibility(value);
+
+ // update the caption: parentDownVisible == false corresponds to DownButtonDown == true;
+ //
+ caption.SetDownButtonDirection(!value);
+
+ OnParentRowsVisibleChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ private static readonly object EVENT_PARENTROWSVISIBLECHANGED = new object();
+
+ [SRCategory(nameof(SR.CatPropertyChanged))]
+ public event EventHandler ParentRowsVisibleChanged
+ {
+ add => Events.AddHandler(EVENT_PARENTROWSVISIBLECHANGED, value);
+ remove => Events.RemoveHandler(EVENT_PARENTROWSVISIBLECHANGED, value);
+ }
+
+ internal bool ParentRowsIsEmpty()
+ {
+ return parentRows.IsEmpty();
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the data grid's row headers are
+ /// visible.
+ ///
+ [
+ SRCategory(nameof(SR.CatDisplay)),
+ DefaultValue(true),
+ ]
+ public bool RowHeadersVisible
+ {
+ get
+ {
+ return gridState[GRIDSTATE_rowHeadersVisible];
+ }
+ set
+ {
+ if (RowHeadersVisible != value)
+ {
+ gridState[GRIDSTATE_rowHeadersVisible] = value;
+ PerformLayout();
+ InvalidateInside();
+ }
+ }
+ }
+
+ [
+ SRCategory(nameof(SR.CatLayout)),
+ DefaultValue(defaultRowHeaderWidth),
+ ]
+ public int RowHeaderWidth
+ {
+ get
+ {
+ return rowHeaderWidth;
+ }
+ set
+ {
+ value = Math.Max(minRowHeaderWidth, value);
+ if (rowHeaderWidth != value)
+ {
+ rowHeaderWidth = value;
+ if (layout.RowHeadersVisible)
+ {
+ PerformLayout();
+ InvalidateInside();
+ }
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the width of headers.
+ ///
+ [
+ Browsable(false), EditorBrowsable(EditorBrowsableState.Never),
+ DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
+ Bindable(false)
+ ]
+ public override string Text
+ {
+ get
+ {
+ return base.Text;
+ }
+ set
+ {
+ base.Text = value;
+ }
+ }
+
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
+ new public event EventHandler TextChanged
+ {
+ add => base.TextChanged += value;
+ remove => base.TextChanged -= value;
+ }
+
+ ///
+ /// Gets the vertical scroll bar of the control.
+ ///
+ [
+ Browsable(false), EditorBrowsable(EditorBrowsableState.Advanced),
+ ]
+ protected ScrollBar VertScrollBar
+ {
+ get
+ {
+ return vertScrollBar;
+ }
+ }
+
+ ///
+ /// Gets the number of visible columns.
+ ///
+ [
+ Browsable(false),
+ ]
+ public int VisibleColumnCount
+ {
+ get
+ {
+ return Math.Min(numVisibleCols, myGridTable is null ? 0 : myGridTable.GridColumnStyles.Count);
+ }
+ }
+
+ ///
+ /// Gets the number of rows visible.
+ ///
+ [
+ Browsable(false),
+ ]
+ public int VisibleRowCount
+ {
+ get
+ {
+ return numVisibleRows;
+ }
+ }
+
+ ///
+ /// Gets or sets the value of the cell at
+ /// the specified the row and column.
+ ///
+ public object this[int rowIndex, int columnIndex]
+ {
+ get
+ {
+ EnsureBound();
+ if (rowIndex < 0 || rowIndex >= DataGridRowsLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(rowIndex));
+ }
+
+ if (columnIndex < 0 || columnIndex >= myGridTable.GridColumnStyles.Count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(columnIndex));
+ }
+
+ CurrencyManager listManager = this.listManager;
+ DataGridColumnStyle column = myGridTable.GridColumnStyles[columnIndex];
+ return column.GetColumnValueAtRow(listManager, rowIndex);
+ }
+ set
+ {
+ EnsureBound();
+ if (rowIndex < 0 || rowIndex >= DataGridRowsLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(rowIndex));
+ }
+
+ if (columnIndex < 0 || columnIndex >= myGridTable.GridColumnStyles.Count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(columnIndex));
+ }
+
+ CurrencyManager listManager = this.listManager;
+ if (listManager.Position != rowIndex)
+ {
+ listManager.Position = rowIndex;
+ }
+
+ DataGridColumnStyle column = myGridTable.GridColumnStyles[columnIndex];
+ column.SetColumnValueAtRow(listManager, rowIndex, value);
+
+ // invalidate the bounds of the cell only if the cell is visible
+ if (columnIndex >= firstVisibleCol && columnIndex <= firstVisibleCol + numVisibleCols - 1 &&
+ rowIndex >= firstVisibleRow && rowIndex <= firstVisibleRow + numVisibleRows)
+ {
+ Rectangle bounds = GetCellBounds(rowIndex, columnIndex);
+ Invalidate(bounds);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the value of a specified .
+ ///
+ public object this[DataGridCell cell]
+ {
+ get
+ {
+ return this[cell.RowNumber, cell.ColumnNumber];
+ }
+ set
+ {
+ this[cell.RowNumber, cell.ColumnNumber] = value;
+ }
+ }
+
+ private void WireTableStylePropChanged(DataGridTableStyle gridTable)
+ {
+ gridTable.GridLineColorChanged += new EventHandler(GridLineColorChanged);
+ gridTable.GridLineStyleChanged += new EventHandler(GridLineStyleChanged);
+ gridTable.HeaderBackColorChanged += new EventHandler(HeaderBackColorChanged);
+ gridTable.HeaderFontChanged += new EventHandler(HeaderFontChanged);
+ gridTable.HeaderForeColorChanged += new EventHandler(HeaderForeColorChanged);
+ gridTable.LinkColorChanged += new EventHandler(LinkColorChanged);
+ gridTable.LinkHoverColorChanged += new EventHandler(LinkHoverColorChanged);
+ gridTable.PreferredColumnWidthChanged += new EventHandler(PreferredColumnWidthChanged);
+ gridTable.RowHeadersVisibleChanged += new EventHandler(RowHeadersVisibleChanged);
+ gridTable.ColumnHeadersVisibleChanged += new EventHandler(ColumnHeadersVisibleChanged);
+ gridTable.RowHeaderWidthChanged += new EventHandler(RowHeaderWidthChanged);
+ gridTable.AllowSortingChanged += new EventHandler(AllowSortingChanged);
+ }
+
+ private void UnWireTableStylePropChanged(DataGridTableStyle gridTable)
+ {
+ gridTable.GridLineColorChanged -= new EventHandler(GridLineColorChanged);
+ gridTable.GridLineStyleChanged -= new EventHandler(GridLineStyleChanged);
+ gridTable.HeaderBackColorChanged -= new EventHandler(HeaderBackColorChanged);
+ gridTable.HeaderFontChanged -= new EventHandler(HeaderFontChanged);
+ gridTable.HeaderForeColorChanged -= new EventHandler(HeaderForeColorChanged);
+ gridTable.LinkColorChanged -= new EventHandler(LinkColorChanged);
+ gridTable.LinkHoverColorChanged -= new EventHandler(LinkHoverColorChanged);
+ gridTable.PreferredColumnWidthChanged -= new EventHandler(PreferredColumnWidthChanged);
+ gridTable.RowHeadersVisibleChanged -= new EventHandler(RowHeadersVisibleChanged);
+ gridTable.ColumnHeadersVisibleChanged -= new EventHandler(ColumnHeadersVisibleChanged);
+ gridTable.RowHeaderWidthChanged -= new EventHandler(RowHeaderWidthChanged);
+ gridTable.AllowSortingChanged -= new EventHandler(AllowSortingChanged);
+ }
+
+ ///
+ /// DataSource events are handled
+ ///
+ private void WireDataSource()
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridCursor.TraceVerbose, "DataGridCursor: WireDataSource");
+ Debug.Assert(listManager is not null, "Can't wire up to a null DataSource");
+ listManager.CurrentChanged += currentChangedHandler;
+ listManager.PositionChanged += positionChangedHandler;
+ listManager.ItemChanged += itemChangedHandler;
+ listManager.MetaDataChanged += metaDataChangedHandler;
+ }
+
+ private void UnWireDataSource()
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridCursor.TraceVerbose, "DataGridCursor: UnWireDataSource");
+ Debug.Assert(listManager is not null, "Can't un wire from a null DataSource");
+ listManager.CurrentChanged -= currentChangedHandler;
+ listManager.PositionChanged -= positionChangedHandler;
+ listManager.ItemChanged -= itemChangedHandler;
+ listManager.MetaDataChanged -= metaDataChangedHandler;
+ }
+
+ // This is called after a row has been added. And I think whenever
+ // a row gets deleted, etc.
+ // We recreate our datagrid rows at this point.
+ private void DataSource_Changed(object sender, EventArgs ea)
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridCursor.TraceVerbose, "DataGridCursor: DataSource_Changed");
+
+ // the grid will receive the dataSource_Changed event when
+ // allowAdd changes on the dataView.
+ policy.UpdatePolicy(ListManager, ReadOnly);
+ if (gridState[GRIDSTATE_inListAddNew])
+ {
+ // we are adding a new row
+ // keep the old rows, w/ their height, expanded/collapsed information
+ //
+ Debug.Assert(policy.AllowAdd, "how can we add a new row if the policy does not allow this?");
+ Debug.Assert(DataGridRowsLength == DataGridRows.Length, "how can this fail?");
+
+ DataGridRow[] gridRows = DataGridRows;
+ int currentRowCount = DataGridRowsLength;
+ // put the added row:
+ //
+ gridRows[currentRowCount - 1] = new DataGridRelationshipRow(this, myGridTable, currentRowCount - 1);
+ SetDataGridRows(gridRows, currentRowCount);
+ }
+ else if (gridState[GRIDSTATE_inAddNewRow] && !gridState[GRIDSTATE_inDeleteRow])
+ {
+ // when the backEnd adds a row and we are still inAddNewRow
+ listManager.CancelCurrentEdit();
+ gridState[GRIDSTATE_inAddNewRow] = false;
+ RecreateDataGridRows();
+ }
+ else if (!gridState[GRIDSTATE_inDeleteRow])
+ {
+ RecreateDataGridRows();
+ currentRow = Math.Min(currentRow, listManager.Count);
+ }
+
+ bool oldListHasErrors = ListHasErrors;
+ ListHasErrors = DataGridSourceHasErrors();
+ // if we changed the ListHasErrors, then the grid is already invalidated
+ if (oldListHasErrors == ListHasErrors)
+ {
+ InvalidateInside();
+ }
+ }
+
+ private void GridLineColorChanged(object sender, EventArgs e)
+ {
+ Invalidate(layout.Data);
+ }
+
+ private void GridLineStyleChanged(object sender, EventArgs e)
+ {
+ myGridTable.ResetRelationsUI();
+ Invalidate(layout.Data);
+ }
+
+ private void HeaderBackColorChanged(object sender, EventArgs e)
+ {
+ if (layout.RowHeadersVisible)
+ {
+ Invalidate(layout.RowHeaders);
+ }
+
+ if (layout.ColumnHeadersVisible)
+ {
+ Invalidate(layout.ColumnHeaders);
+ }
+
+ Invalidate(layout.TopLeftHeader);
+ }
+
+ private void HeaderFontChanged(object sender, EventArgs e)
+ {
+ RecalculateFonts();
+ PerformLayout();
+ Invalidate(layout.Inside);
+ }
+
+ private void HeaderForeColorChanged(object sender, EventArgs e)
+ {
+ if (layout.RowHeadersVisible)
+ {
+ Invalidate(layout.RowHeaders);
+ }
+
+ if (layout.ColumnHeadersVisible)
+ {
+ Invalidate(layout.ColumnHeaders);
+ }
+
+ Invalidate(layout.TopLeftHeader);
+ }
+
+ private void LinkColorChanged(object sender, EventArgs e)
+ {
+ Invalidate(layout.Data);
+ }
+
+ private void LinkHoverColorChanged(object sender, EventArgs e)
+ {
+ Invalidate(layout.Data);
+ }
+
+ private void PreferredColumnWidthChanged(object sender, EventArgs e)
+ {
+ // reset the dataGridRows
+ SetDataGridRows(null, DataGridRowsLength);
+ // layout the horizontal scroll bar
+ PerformLayout();
+ // invalidate everything
+ Invalidate();
+ }
+
+ private void RowHeadersVisibleChanged(object sender, EventArgs e)
+ {
+ layout.RowHeadersVisible = myGridTable is null ? false : myGridTable.RowHeadersVisible;
+ PerformLayout();
+ InvalidateInside();
+ }
+
+ private void ColumnHeadersVisibleChanged(object sender, EventArgs e)
+ {
+ layout.ColumnHeadersVisible = myGridTable is null ? false : myGridTable.ColumnHeadersVisible;
+ PerformLayout();
+ InvalidateInside();
+ }
+
+ private void RowHeaderWidthChanged(object sender, EventArgs e)
+ {
+ if (layout.RowHeadersVisible)
+ {
+ PerformLayout();
+ InvalidateInside();
+ }
+ }
+
+ private void AllowSortingChanged(object sender, EventArgs e)
+ {
+ if (!myGridTable.AllowSorting && listManager is not null)
+ {
+ IList list = listManager.List;
+ if (list is IBindingList)
+ {
+ ((IBindingList)list).RemoveSort();
+ }
+ }
+ }
+
+ private void DataSource_RowChanged(object sender, EventArgs ea)
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridCursor.TraceVerbose, "DataGridCursor: DataSource_RowChanged");
+ // it may be the case that our cache was not updated
+ // to the latest changes in the list : CurrentChanged is fired before
+ // ListChanged.
+ // So invalidate the row if there is something to invalidate
+ DataGridRow[] rows = DataGridRows;
+ if (currentRow < DataGridRowsLength)
+ {
+ InvalidateRow(currentRow);
+ }
+ }
+
+ ///
+ /// Fired by the DataSource when row position moves.
+ ///
+ private void DataSource_PositionChanged(object sender, EventArgs ea)
+ {
+#if DEBUG
+ inDataSource_PositionChanged = true;
+#endif
+ Debug.WriteLineIf(CompModSwitches.DataGridCursor.TraceVerbose, "DataGridCursor: DataSource_PositionChanged to " + listManager.Position.ToString(CultureInfo.InvariantCulture));
+ // the grid will get the PositionChanged event
+ // before the OnItemChanged event when a row will be deleted in the backEnd;
+ // we still want to keep the old rows when the user deletes the rows using the grid
+ // and we do not want to do the same work twice when the user adds a row via the grid
+ if (DataGridRowsLength > listManager.Count + (policy.AllowAdd ? 1 : 0) && !gridState[GRIDSTATE_inDeleteRow])
+ {
+ Debug.Assert(!gridState[GRIDSTATE_inAddNewRow] && !gridState[GRIDSTATE_inListAddNew], "how can the list decrease when we are adding a row?");
+ RecreateDataGridRows();
+ }
+
+ if (ListManager.Position != currentRow)
+ {
+ CurrentCell = new DataGridCell(listManager.Position, currentCol);
+ }
+#if DEBUG
+ inDataSource_PositionChanged = false;
+#endif
+ }
+
+ internal void DataSource_MetaDataChanged(object sender, EventArgs e)
+ {
+ MetaDataChanged();
+ }
+
+ private bool DataGridSourceHasErrors()
+ {
+ if (listManager is null)
+ {
+ return false;
+ }
+
+ for (int i = 0; i < listManager.Count; i++)
+ {
+ object errObj = listManager[i];
+ if (errObj is IDataErrorInfo)
+ {
+ string errString = ((IDataErrorInfo)errObj).Error;
+ if (errString is not null && errString.Length != 0)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private void TableStylesCollectionChanged(object sender, CollectionChangeEventArgs ccea)
+ {
+ // if the users changed the collection of tableStyles
+ if (sender != dataGridTables)
+ {
+ return;
+ }
+
+ if (listManager is null)
+ {
+ return;
+ }
+
+ if (ccea.Action == CollectionChangeAction.Add)
+ {
+ DataGridTableStyle tableStyle = (DataGridTableStyle)ccea.Element;
+ if (listManager.GetListName().Equals(tableStyle.MappingName))
+ {
+ Debug.Assert(myGridTable.IsDefault, "if the table is not default, then it had a name. how can one add another table to the collection w/ the same name and not throw an exception");
+ SetDataGridTable(tableStyle, true); // true for forcing column creation
+ SetDataGridRows(null, 0);
+ }
+ }
+ else if (ccea.Action == CollectionChangeAction.Remove)
+ {
+ DataGridTableStyle tableStyle = (DataGridTableStyle)ccea.Element;
+ if (myGridTable.MappingName.Equals(tableStyle.MappingName))
+ {
+ Debug.Assert(myGridTable.IsDefault, "if the table is not default, then it had a name. how can one add another table to the collection w/ the same name and not throw an exception");
+ defaultTableStyle.GridColumnStyles.ResetDefaultColumnCollection();
+ SetDataGridTable(defaultTableStyle, true); // true for forcing column creation
+ SetDataGridRows(null, 0);
+ }
+ }
+ else
+ {
+ Debug.Assert(ccea.Action == CollectionChangeAction.Refresh, "what else is possible?");
+ // we have to search to see if the collection of table styles contains one
+ // w/ the same name as the list in the dataGrid
+
+ DataGridTableStyle newGridTable = dataGridTables[listManager.GetListName()];
+ if (newGridTable is null)
+ {
+ if (!myGridTable.IsDefault)
+ {
+ // get rid of the old gridColumns
+ defaultTableStyle.GridColumnStyles.ResetDefaultColumnCollection();
+ SetDataGridTable(defaultTableStyle, true); // true for forcing column creation
+ SetDataGridRows(null, 0);
+ }
+ }
+ else
+ {
+ SetDataGridTable(newGridTable, true); // true for forcing column creation
+ SetDataGridRows(null, 0);
+ }
+ }
+ }
+
+ private void DataSource_ItemChanged(object sender, ItemChangedEventArgs ea)
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridCursor.TraceVerbose, "DataGridCursor: DataSource_ItemChanged at index " + ea.Index.ToString(CultureInfo.InvariantCulture));
+
+ // if ea.Index == -1, then we invalidate all rows.
+ if (ea.Index == -1)
+ {
+ DataSource_Changed(sender, EventArgs.Empty);
+ /*
+ // if there are rows which are invisible, it is more efficient to invalidata layout.Data
+ if (numVisibleRows <= dataGridRowsLength)
+ Invalidate(layout.Data);
+ else
+ {
+ Debug.Assert(firstVisibleRow == 0, "if all rows are visible, then how come that first row is not visible?");
+ for (int i = 0; i < numVisibleRows; i++)
+ InvalidateRow(firstVisibleRow + numVisibleRows);
+ }
+ */
+ }
+ else
+ {
+ // let's see how we are doing w/ the errors
+ object errObj = listManager[ea.Index];
+ bool oldListHasErrors = ListHasErrors;
+ if (errObj is IDataErrorInfo)
+ {
+ if (((IDataErrorInfo)errObj).Error.Length != 0)
+ {
+ ListHasErrors = true;
+ }
+ else if (ListHasErrors)
+ {
+ // maybe there was an error that now is fixed
+ ListHasErrors = DataGridSourceHasErrors();
+ }
+ }
+
+ // Invalidate the row only if we did not change the ListHasErrors
+ if (oldListHasErrors == ListHasErrors)
+ {
+ InvalidateRow(ea.Index);
+ }
+
+ // we need to update the edit box:
+ // we update the text in the edit box only when the currentRow
+ // equals the ea.Index
+ if (editColumn is not null && ea.Index == currentRow)
+ {
+ editColumn.UpdateUI(ListManager, ea.Index, null);
+ }
+ }
+ }
+
+ protected virtual void OnBorderStyleChanged(EventArgs e)
+ {
+ if (Events[EVENT_BORDERSTYLECHANGED] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnCaptionVisibleChanged(EventArgs e)
+ {
+ if (Events[EVENT_CAPTIONVISIBLECHANGED] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnCurrentCellChanged(EventArgs e)
+ {
+ if (Events[EVENT_CURRENTCELLCHANGED] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnFlatModeChanged(EventArgs e)
+ {
+ if (Events[EVENT_FLATMODECHANGED] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnBackgroundColorChanged(EventArgs e)
+ {
+ if (Events[EVENT_BACKGROUNDCOLORCHANGED] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnAllowNavigationChanged(EventArgs e)
+ {
+ if (Events[EVENT_ALLOWNAVIGATIONCHANGED] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnParentRowsVisibleChanged(EventArgs e)
+ {
+ if (Events[EVENT_PARENTROWSVISIBLECHANGED] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnParentRowsLabelStyleChanged(EventArgs e)
+ {
+ if (Events[EVENT_PARENTROWSLABELSTYLECHANGED] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnReadOnlyChanged(EventArgs e)
+ {
+ if (Events[EVENT_READONLYCHANGED] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ ///
+ /// Raises the
+ /// event.
+ ///
+ protected void OnNavigate(NavigateEventArgs e)
+ {
+ onNavigate?.Invoke(this, e);
+ }
+
+ /*
+ ///
+ /// Raises the event.
+ ///
+ protected void OnColumnResize(EventArgs e) {
+ RaiseEvent(EVENT_COLUMNRESIZE, e);
+ }
+
+ internal void OnLinkClick(EventArgs e) {
+ RaiseEvent(EVENT_LINKCLICKED, e);
+ }
+ */
+
+ internal void OnNodeClick(EventArgs e)
+ {
+ // if we expanded/collapsed the RelationshipRow
+ // then we need to layout the vertical scroll bar
+ //
+ PerformLayout();
+
+ // also, we need to let the hosted edit control that its
+ // boundaries possibly changed. do this with a call to Edit()
+ // do this only if the firstVisibleColumn is the editColumn
+ //
+ GridColumnStylesCollection columns = myGridTable.GridColumnStyles;
+ if (firstVisibleCol > -1 && firstVisibleCol < columns.Count && columns[firstVisibleCol] == editColumn)
+ {
+ Edit();
+ }
+
+ // Raise the event for the event listeners
+ ((EventHandler)Events[EVENT_NODECLICKED])?.Invoke(this, e);
+ }
+
+ ///
+ /// Raises the event.
+ ///
+ protected void OnRowHeaderClick(EventArgs e)
+ {
+ onRowHeaderClick?.Invoke(this, e);
+ }
+
+ ///
+ /// Raises the event.
+ ///
+ protected void OnScroll(EventArgs e)
+ {
+ // reset the toolTip information
+ if (ToolTipProvider is not null)
+ {
+ ResetToolTip();
+ }
+
+((EventHandler)Events[EVENT_SCROLL])?.Invoke(this, e);
+ }
+
+ ///
+ /// Listens
+ /// for the horizontal scrollbar's scroll
+ /// event.
+ ///
+ protected virtual void GridHScrolled(object sender, ScrollEventArgs se)
+ {
+ if (!Enabled)
+ {
+ return;
+ }
+
+ if (DataSource is null)
+ {
+ Debug.Fail("Horizontal Scrollbar should be disabled without a DataSource.");
+ return;
+ }
+
+ gridState[GRIDSTATE_isScrolling] = true;
+
+#if DEBUG
+
+ Debug.WriteLineIf(CompModSwitches.DataGridScrolling.TraceVerbose, "DataGridScrolling: in GridHScrolled: the scroll event type:");
+ switch (se.Type)
+ {
+ case ScrollEventType.SmallIncrement:
+ Debug.WriteLineIf(CompModSwitches.DataGridScrolling.TraceVerbose, "small increment");
+ break;
+ case ScrollEventType.SmallDecrement:
+ Debug.WriteLineIf(CompModSwitches.DataGridScrolling.TraceVerbose, "small decrement");
+ break;
+ case ScrollEventType.LargeIncrement:
+ Debug.WriteLineIf(CompModSwitches.DataGridScrolling.TraceVerbose, "Large decrement");
+ break;
+ case ScrollEventType.LargeDecrement:
+ Debug.WriteLineIf(CompModSwitches.DataGridScrolling.TraceVerbose, "small decrement");
+ break;
+ case ScrollEventType.ThumbPosition:
+ Debug.WriteLineIf(CompModSwitches.DataGridScrolling.TraceVerbose, "Thumb Position");
+ break;
+ case ScrollEventType.ThumbTrack:
+ Debug.WriteLineIf(CompModSwitches.DataGridScrolling.TraceVerbose, "Thumb Track");
+ break;
+ case ScrollEventType.First:
+ Debug.WriteLineIf(CompModSwitches.DataGridScrolling.TraceVerbose, "First");
+ break;
+ case ScrollEventType.Last:
+ Debug.WriteLineIf(CompModSwitches.DataGridScrolling.TraceVerbose, "Last");
+ break;
+ case ScrollEventType.EndScroll:
+ Debug.WriteLineIf(CompModSwitches.DataGridScrolling.TraceVerbose, "EndScroll");
+ break;
+ }
+
+#endif // DEBUG
+
+ if (se.Type == ScrollEventType.SmallIncrement ||
+ se.Type == ScrollEventType.SmallDecrement)
+ {
+ int dCols = (se.Type == ScrollEventType.SmallIncrement) ? 1 : -1;
+ if (se.Type == ScrollEventType.SmallDecrement && negOffset == 0)
+ {
+ GridColumnStylesCollection cols = myGridTable.GridColumnStyles;
+ // if the column before the first visible column has width == 0 then skip it
+ for (int i = firstVisibleCol - 1; i >= 0 && cols[i].Width == 0; i--)
+ {
+ dCols--;
+ }
+ }
+
+ if (se.Type == ScrollEventType.SmallIncrement && negOffset == 0)
+ {
+ GridColumnStylesCollection cols = myGridTable.GridColumnStyles;
+ for (int i = firstVisibleCol; i > -1 && i < cols.Count && cols[i].Width == 0; i++)
+ {
+ dCols++;
+ }
+ }
+
+ ScrollRight(dCols);
+ se.NewValue = HorizontalOffset;
+ }
+ else if (se.Type != ScrollEventType.EndScroll)
+ {
+ HorizontalOffset = se.NewValue;
+ }
+
+ gridState[GRIDSTATE_isScrolling] = false;
+ }
+
+ ///
+ /// Listens
+ /// for the vertical scrollbar's scroll event.
+ ///
+ protected virtual void GridVScrolled(object sender, ScrollEventArgs se)
+ {
+ if (!Enabled)
+ {
+ return;
+ }
+
+ if (DataSource is null)
+ {
+ Debug.Fail("Vertical Scrollbar should be disabled without a DataSource.");
+ return;
+ }
+
+ gridState[GRIDSTATE_isScrolling] = true;
+
+ try
+ {
+ se.NewValue = Math.Min(se.NewValue, DataGridRowsLength - numTotallyVisibleRows);
+ int dRows = se.NewValue - firstVisibleRow;
+ ScrollDown(dRows);
+ }
+ finally
+ {
+ gridState[GRIDSTATE_isScrolling] = false;
+ }
+ }
+
+ private void HandleEndCurrentEdit()
+ {
+ int currentRowSaved = currentRow;
+ int currentColSaved = currentCol;
+
+ string errorMessage = null;
+
+ try
+ {
+ listManager.EndCurrentEdit();
+ }
+ catch (Exception e)
+ {
+ errorMessage = e.Message;
+ }
+
+ if (errorMessage is not null)
+ {
+ DialogResult result = RTLAwareMessageBox.Show(null, string.Format(DSR.DataGridPushedIncorrectValueIntoColumn,
+ errorMessage), DSR.DataGridErrorMessageBoxCaption, MessageBoxButtons.YesNo,
+ MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, 0);
+
+ if (result == DialogResult.Yes)
+ {
+ currentRow = currentRowSaved;
+ currentCol = currentColSaved;
+ Debug.Assert(currentRow == ListManager.Position || listManager.Position == -1, "the position in the list manager (" + ListManager.Position + ") is out of sync with the currentRow (" + currentRow + ")" + " and the exception is '" + errorMessage + "'");
+ // also, make sure that we get the row selector on the currentrow, too
+ InvalidateRowHeader(currentRow);
+ Edit();
+ }
+ else
+ {
+ // if the user committed a row that used to be addNewRow and the backEnd rejects it,
+ // and then it tries to navigate down then we should stay in the addNewRow
+ // in this particular scenario, CancelCurrentEdit will cause the last row to be deleted,
+ // and this will ultimately call InvalidateRow w/ a row number larger than the number of rows
+ // so set the currentRow here:
+ Debug.Assert(result == DialogResult.No, "we only put cancel and ok on the error message box");
+ listManager.PositionChanged -= positionChangedHandler;
+ listManager.CancelCurrentEdit();
+ listManager.Position = currentRow;
+ listManager.PositionChanged += positionChangedHandler;
+ }
+ }
+ }
+
+ ///
+ /// Listens
+ /// for the caption's back button clicked event.
+ ///
+ protected void OnBackButtonClicked(object sender, EventArgs e)
+ {
+ NavigateBack();
+
+ ((EventHandler)Events[EVENT_BACKBUTTONCLICK])?.Invoke(this, e);
+ }
+
+ protected override void OnBackColorChanged(EventArgs e)
+ {
+ backBrush = new SolidBrush(BackColor);
+ Invalidate();
+
+ base.OnBackColorChanged(e);
+ }
+
+ protected override void OnBindingContextChanged(EventArgs e)
+ {
+ if (DataSource is not null && !gridState[GRIDSTATE_inSetListManager])
+ {
+ try
+ {
+ Set_ListManager(DataSource, DataMember, true, false); // we do not want to create columns
+ // if the columns are already created
+ // the grid should not rely on OnBindingContextChanged
+ // to create columns.
+ }
+ catch
+ {
+ // at runtime we will rethrow the exception
+ if (Site is null || !Site.DesignMode)
+ {
+ throw;
+ }
+
+ RTLAwareMessageBox.Show(null, DSR.DataGridExceptionInPaint, null,
+ MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, 0);
+
+ if (Visible)
+ {
+ BeginUpdateInternal();
+ }
+
+ ResetParentRows();
+
+ Set_ListManager(null, string.Empty, true);
+ if (Visible)
+ {
+ EndUpdateInternal();
+ }
+ }
+ }
+
+ base.OnBindingContextChanged(e);
+ }
+
+ protected virtual void OnDataSourceChanged(EventArgs e)
+ {
+ if (Events[EVENT_DATASOURCECHANGED] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ ///
+ /// Listens for
+ /// the caption's down button clicked event.
+ ///
+ protected void OnShowParentDetailsButtonClicked(object sender, EventArgs e)
+ {
+ // we need to fire the ParentRowsVisibleChanged event
+ // and the ParentRowsVisible property just calls SetParentRowsVisibility and
+ // then fires the event.
+ ParentRowsVisible = !caption.ToggleDownButtonDirection();
+
+ ((EventHandler)Events[EVENT_DOWNBUTTONCLICK])?.Invoke(this, e);
+ }
+
+ protected override void OnForeColorChanged(EventArgs e)
+ {
+ foreBrush = new SolidBrush(ForeColor);
+ Invalidate();
+
+ base.OnForeColorChanged(e);
+ }
+
+ protected override void OnFontChanged(EventArgs e)
+ {
+ // let the caption know about the event changed
+ //
+ Caption.OnGridFontChanged();
+ RecalculateFonts();
+ RecreateDataGridRows();
+ // get all the rows in the parentRows stack, and modify their height
+ if (originalState is not null)
+ {
+ Debug.Assert(!parentRows.IsEmpty(), "if the originalState is not null, then parentRows contains at least one row");
+ Stack parentStack = new Stack();
+ // this is a huge performance hit:
+ // everytime we get/put something from/to
+ // the parentRows, the buttons in the dataGridCaption
+ // are invalidated
+ while (!parentRows.IsEmpty())
+ {
+ DataGridState dgs = parentRows.PopTop();
+ int rowCount = dgs.DataGridRowsLength;
+
+ for (int i = 0; i < rowCount; i++)
+ {
+ // performance hit: this will cause to invalidate a bunch of
+ // stuff
+
+ dgs.DataGridRows[i].Height = dgs.DataGridRows[i].MinimumRowHeight(dgs.GridColumnStyles);
+ }
+
+ parentStack.Push(dgs);
+ }
+
+ while (parentStack.Count != 0)
+ {
+ parentRows.AddParent((DataGridState)parentStack.Pop());
+ }
+ }
+
+ base.OnFontChanged(e);
+ }
+
+#pragma warning disable CS0419 // Ambiguous reference in cref attribute
+ ///
+ /// Raises the
+ /// event.
+ ///
+ protected override void OnPaintBackground(PaintEventArgs pevent)
+#pragma warning restore CS0419 // Ambiguous reference in cref attribute
+ {
+ // null body
+ }
+
+ ///
+ /// Raises the event which
+ /// repositions controls
+ /// and updates scroll bars.
+ ///
+ protected override void OnLayout(LayoutEventArgs levent)
+ {
+ // if we get a OnLayout event while the editControl changes, then just
+ // ignore it
+ //
+ if (gridState[GRIDSTATE_editControlChanging])
+ {
+ return;
+ }
+
+ Debug.WriteLineIf(CompModSwitches.DataGridLayout.TraceVerbose, "DataGridLayout: OnLayout");
+ base.OnLayout(levent);
+
+ gridState[GRIDSTATE_canFocus] = false;
+ try
+ {
+ if (IsHandleCreated)
+ {
+ if (layout.ParentRowsVisible)
+ {
+ parentRows.OnLayout();
+ }
+
+ // reset the toolTip information
+ if (ToolTipProvider is not null)
+ {
+ ResetToolTip();
+ }
+
+ ComputeLayout();
+ }
+ }
+ finally
+ {
+ gridState[GRIDSTATE_canFocus] = true;
+ }
+ }
+
+ ///
+ /// Raises the
+ /// event.
+ ///
+ protected override void OnHandleCreated(EventArgs e)
+ {
+ base.OnHandleCreated(e);
+
+ // toolTipping
+ toolTipProvider = new DataGridToolTip(this);
+ toolTipProvider.CreateToolTipHandle();
+ toolTipId = 0;
+
+ PerformLayout();
+ }
+
+ ///
+ /// Raises the
+ /// event.
+ ///
+ protected override void OnHandleDestroyed(EventArgs e)
+ {
+ base.OnHandleDestroyed(e);
+
+ // toolTipping
+ if (toolTipProvider is not null)
+ {
+ toolTipProvider.Destroy();
+ toolTipProvider = null;
+ }
+
+ toolTipId = 0;
+ }
+
+ ///
+ /// Raises the
+ /// event.
+ ///
+ protected internal override void OnEnter(EventArgs e)
+ {
+ if (gridState[GRIDSTATE_canFocus] && !gridState[GRIDSTATE_editControlChanging])
+ {
+ if (Bound)
+ {
+ Edit();
+ }
+
+ base.OnEnter(e);
+ }
+ }
+
+ ///
+ /// Raises the
+ /// event.
+ ///
+ protected internal override void OnLeave(EventArgs e)
+ {
+ OnLeave_Grid();
+ base.OnLeave(e);
+ }
+
+ private void OnLeave_Grid()
+ {
+ gridState[GRIDSTATE_canFocus] = false;
+ try
+ {
+ EndEdit();
+ if (listManager is not null && !gridState[GRIDSTATE_editControlChanging])
+ {
+ if (gridState[GRIDSTATE_inAddNewRow])
+ {
+ // if the user did not type anything
+ // in the addNewRow, then cancel the currentedit
+ listManager.CancelCurrentEdit();
+ // set the addNewRow back
+ DataGridRow[] localGridRows = DataGridRows;
+ localGridRows[DataGridRowsLength - 1] = new DataGridAddNewRow(this, myGridTable, DataGridRowsLength - 1);
+ SetDataGridRows(localGridRows, DataGridRowsLength);
+ }
+ else
+ {
+ // this.listManager.EndCurrentEdit();
+ HandleEndCurrentEdit();
+ }
+ }
+ }
+ finally
+ {
+ gridState[GRIDSTATE_canFocus] = true;
+ // inAddNewRow should be set to false if the control was
+ // not changing
+ if (!gridState[GRIDSTATE_editControlChanging])
+ {
+ gridState[GRIDSTATE_inAddNewRow] = false;
+ }
+ }
+ }
+
+ ///
+ /// Raises the
+ /// event.
+ ///
+ protected override void OnKeyDown(KeyEventArgs e)
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridKeys.TraceVerbose, "DataGridKeys: OnKeyDown ");
+ base.OnKeyDown(e);
+ ProcessGridKey(e);
+ }
+
+ ///
+ /// Raises the event.
+ ///
+ protected override void OnKeyPress(KeyPressEventArgs e)
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridKeys.TraceVerbose, "DataGridKeys: OnKeyPress " + TypeDescriptor.GetConverter(typeof(Keys)).ConvertToString(e.KeyChar));
+
+ base.OnKeyPress(e);
+ GridColumnStylesCollection coll = myGridTable.GridColumnStyles;
+ if (coll is not null && currentCol > 0 && currentCol < coll.Count)
+ {
+ if (!coll[currentCol].ReadOnly)
+ {
+ if (e.KeyChar > 32)
+ {
+ Edit(new string(new char[] { e.KeyChar }));
+ }
+ }
+ }
+ }
+
+ ///
+ /// Raises the event.
+ ///
+ protected override void OnMouseDown(MouseEventArgs e)
+ {
+ base.OnMouseDown(e);
+
+ gridState[GRIDSTATE_childLinkFocused] = false;
+ gridState[GRIDSTATE_dragging] = false;
+ if (listManager is null)
+ {
+ return;
+ }
+
+ HitTestInfo location = HitTest(e.X, e.Y);
+ Keys nModifier = ModifierKeys;
+ bool isControlDown = (nModifier & Keys.Control) == Keys.Control && (nModifier & Keys.Alt) == 0;
+ bool isShiftDown = (nModifier & Keys.Shift) == Keys.Shift;
+
+ // Only left clicks for now
+ if (e.Button != MouseButtons.Left)
+ {
+ return;
+ }
+
+ // Check column resize
+ if (location.type == HitTestType.ColumnResize)
+ {
+ if (e.Clicks > 1)
+ {
+ ColAutoResize(location.col);
+ }
+ else
+ {
+ ColResizeBegin(e, location.col);
+ }
+
+ return;
+ }
+
+ // Check row resize
+ if (location.type == HitTestType.RowResize)
+ {
+ if (e.Clicks > 1)
+ {
+ RowAutoResize(location.row);
+ }
+ else
+ {
+ RowResizeBegin(e, location.row);
+ }
+
+ return;
+ }
+
+ // Check column headers
+ if (location.type == HitTestType.ColumnHeader)
+ {
+ trackColumnHeader = myGridTable.GridColumnStyles[location.col].PropertyDescriptor;
+ return;
+ }
+
+ if (location.type == HitTestType.Caption)
+ {
+ Rectangle captionRect = layout.Caption;
+ caption.MouseDown(e.X - captionRect.X, e.Y - captionRect.Y);
+ return;
+ }
+
+ if (layout.Data.Contains(e.X, e.Y) || layout.RowHeaders.Contains(e.X, e.Y))
+ {
+ // Check with the row underneath the mouse
+ int row = GetRowFromY(e.Y);
+ if (row > -1)
+ {
+ Point p = NormalizeToRow(e.X, e.Y, row);
+ DataGridRow[] localGridRows = DataGridRows;
+ if (localGridRows[row].OnMouseDown(p.X, p.Y,
+ layout.RowHeaders,
+ isRightToLeft()))
+ {
+ CommitEdit();
+
+ // possibly this was the last row, so then the link may not
+ // be visible. make it visible, by making the row visible.
+ // how can we be sure that the user did not click
+ // on a relationship and navigated to the child rows?
+ // check if the row is expanded: if the row is expanded, then the user clicked
+ // on the node. when we navigate to child rows the rows are recreated
+ // and are initially collapsed
+ localGridRows = DataGridRows;
+ if (row < DataGridRowsLength && (localGridRows[row] is DataGridRelationshipRow) && ((DataGridRelationshipRow)localGridRows[row]).Expanded)
+ {
+ EnsureVisible(row, 0);
+ }
+
+ // show the edit box
+ //
+ Edit();
+ return;
+ }
+ }
+ }
+
+ // Check row headers
+ //
+ if (location.type == HitTestType.RowHeader)
+ {
+ EndEdit();
+ if (!(DataGridRows[location.row] is DataGridAddNewRow))
+ {
+ int savedCurrentRow = currentRow;
+ CurrentCell = new DataGridCell(location.row, currentCol);
+ if (location.row != savedCurrentRow &&
+ currentRow != location.row &&
+ currentRow == savedCurrentRow)
+ {
+ // The data grid was not able to move away from its previous current row.
+ // Be defensive and don't select the row.
+ return;
+ }
+ }
+
+ if (isControlDown)
+ {
+ if (IsSelected(location.row))
+ {
+ UnSelect(location.row);
+ }
+ else
+ {
+ Select(location.row);
+ }
+ }
+ else
+ {
+ if (lastRowSelected == -1 || !isShiftDown)
+ {
+ ResetSelection();
+ Select(location.row);
+ }
+ else
+ {
+ int lowerRow = Math.Min(lastRowSelected, location.row);
+ int upperRow = Math.Max(lastRowSelected, location.row);
+
+ // we need to reset the old SelectedRows.
+ // ResetSelection() will also reset lastRowSelected, so we
+ // need to save it
+ int saveLastRowSelected = lastRowSelected;
+ ResetSelection();
+ lastRowSelected = saveLastRowSelected;
+
+ DataGridRow[] rows = DataGridRows;
+ for (int i = lowerRow; i <= upperRow; i++)
+ {
+ rows[i].Selected = true;
+ numSelectedRows++;
+ }
+
+ // hide the edit box:
+ //
+ EndEdit();
+ return;
+ }
+ }
+
+ lastRowSelected = location.row;
+ // OnRowHeaderClick(EventArgs.Empty);
+ return;
+ }
+
+ // Check parentRows
+ //
+ if (location.type == HitTestType.ParentRows)
+ {
+ EndEdit();
+ parentRows.OnMouseDown(e.X, e.Y, isRightToLeft());
+ }
+
+ // Check cell clicks
+ //
+ if (location.type == HitTestType.Cell)
+ {
+ if (myGridTable.GridColumnStyles[location.col].MouseDown(location.row, e.X, e.Y))
+ {
+ return;
+ }
+
+ DataGridCell target = new DataGridCell(location.row, location.col);
+ if (policy.AllowEdit && CurrentCell.Equals(target))
+ {
+ ResetSelection();
+ //
+ // what if only a part of the current cell is visible?
+ //
+ EnsureVisible(currentRow, currentCol);
+ Edit();
+ }
+ else
+ {
+ ResetSelection();
+ CurrentCell = target;
+ }
+ }
+ }
+
+ ///
+ /// Creates the
+ /// event.
+ ///
+ protected override void OnMouseLeave(EventArgs e)
+ {
+ base.OnMouseLeave(e);
+ if (oldRow != -1)
+ {
+ DataGridRow[] localGridRows = DataGridRows;
+ localGridRows[oldRow].OnMouseLeft(layout.RowHeaders, isRightToLeft());
+ }
+
+ if (gridState[GRIDSTATE_overCaption])
+ {
+ caption.MouseLeft();
+ }
+
+ // when the mouse leaves the grid control, reset the cursor to arrow
+ Cursor = null;
+ }
+
+ internal void TextBoxOnMouseWheel(MouseEventArgs e)
+ {
+ OnMouseWheel(e);
+ }
+
+ ///
+ /// Raises the
+ /// event.
+ ///
+ protected override void OnMouseMove(MouseEventArgs e)
+ {
+ base.OnMouseMove(e);
+ if (listManager is null)
+ {
+ return;
+ }
+
+ HitTestInfo location = HitTest(e.X, e.Y);
+
+ bool alignToRight = isRightToLeft();
+
+ // We need to give UI feedback when the user is resizing a column
+ if (gridState[GRIDSTATE_trackColResize])
+ {
+ ColResizeMove(e);
+ }
+
+ if (gridState[GRIDSTATE_trackRowResize])
+ {
+ RowResizeMove(e);
+ }
+
+ if (gridState[GRIDSTATE_trackColResize] || location.type == HitTestType.ColumnResize)
+ {
+ Cursor = Cursors.SizeWE;
+ return;
+ }
+ else if (gridState[GRIDSTATE_trackRowResize] || location.type == HitTestType.RowResize)
+ {
+ Cursor = Cursors.SizeNS;
+ return;
+ }
+ else
+ {
+ Cursor = null;
+ }
+
+ if ((layout.Data.Contains(e.X, e.Y)
+ || (layout.RowHeadersVisible && layout.RowHeaders.Contains(e.X, e.Y))))
+ {
+ // && (isNavigating || isEditing)) {
+ DataGridRow[] localGridRows = DataGridRows;
+ // If we are over a row, let it know about the mouse move.
+ int rowOver = GetRowFromY(e.Y);
+ // set the dragging bit:
+ if (lastRowSelected != -1 && !gridState[GRIDSTATE_dragging])
+ {
+ int topRow = GetRowTop(lastRowSelected);
+ int bottomRow = topRow + localGridRows[lastRowSelected].Height;
+ int dragHeight = SystemInformation.DragSize.Height;
+ gridState[GRIDSTATE_dragging] = ((e.Y - topRow < dragHeight && topRow - e.Y < dragHeight) || (e.Y - bottomRow < dragHeight && bottomRow - e.Y < dragHeight));
+ }
+
+ if (rowOver > -1)
+ {
+ Point p = NormalizeToRow(e.X, e.Y, rowOver);
+ if (!localGridRows[rowOver].OnMouseMove(p.X, p.Y, layout.RowHeaders, alignToRight) && gridState[GRIDSTATE_dragging])
+ {
+ // if the row did not use this, see if selection can use it
+ MouseButtons mouse = MouseButtons;
+ if (lastRowSelected != -1 && (mouse & MouseButtons.Left) == MouseButtons.Left
+ && !(((Control.ModifierKeys & Keys.Control) == Keys.Control) && (Control.ModifierKeys & Keys.Alt) == 0))
+ {
+ // ResetSelection() will reset the lastRowSelected too
+ //
+ int saveLastRowSelected = lastRowSelected;
+ ResetSelection();
+ lastRowSelected = saveLastRowSelected;
+
+ int lowerRow = Math.Min(lastRowSelected, rowOver);
+ int upperRow = Math.Max(lastRowSelected, rowOver);
+
+ DataGridRow[] rows = DataGridRows;
+ for (int i = lowerRow; i <= upperRow; i++)
+ {
+ rows[i].Selected = true;
+ numSelectedRows++;
+ }
+ }
+ }
+ }
+
+ if (oldRow != rowOver && oldRow != -1)
+ {
+ localGridRows[oldRow].OnMouseLeft(layout.RowHeaders, alignToRight);
+ }
+
+ oldRow = rowOver;
+ }
+
+ // check parentRows
+ //
+ if (location.type == HitTestType.ParentRows)
+ {
+ if (parentRows is not null)
+ {
+ parentRows.OnMouseMove(e.X, e.Y);
+ }
+ }
+
+ if (location.type == HitTestType.Caption)
+ {
+ gridState[GRIDSTATE_overCaption] = true;
+ Rectangle captionRect = layout.Caption;
+ caption.MouseOver(e.X - captionRect.X, e.Y - captionRect.Y);
+ return;
+ }
+ else
+ {
+ if (gridState[GRIDSTATE_overCaption])
+ {
+ gridState[GRIDSTATE_overCaption] = false;
+ caption.MouseLeft();
+ }
+ }
+ }
+
+ ///
+ /// Raises the event.
+ ///
+ protected override void OnMouseUp(MouseEventArgs e)
+ {
+ base.OnMouseUp(e);
+ gridState[GRIDSTATE_dragging] = false;
+ if (listManager is null || myGridTable is null)
+ {
+ return;
+ }
+
+ if (gridState[GRIDSTATE_trackColResize])
+ {
+ ColResizeEnd(e);
+ }
+
+ if (gridState[GRIDSTATE_trackRowResize])
+ {
+ RowResizeEnd(e);
+ }
+
+ gridState[GRIDSTATE_trackColResize] = false;
+ gridState[GRIDSTATE_trackRowResize] = false;
+
+ HitTestInfo ci = HitTest(e.X, e.Y);
+ if ((ci.type & HitTestType.Caption) == HitTestType.Caption)
+ {
+ caption.MouseUp(e.X, e.Y);
+ }
+
+ // Check column headers
+ if (ci.type == HitTestType.ColumnHeader)
+ {
+ PropertyDescriptor prop = myGridTable.GridColumnStyles[ci.col].PropertyDescriptor;
+ if (prop == trackColumnHeader)
+ {
+ ColumnHeaderClicked(trackColumnHeader);
+ }
+ }
+
+ trackColumnHeader = null;
+ }
+
+ ///
+ /// Raises the event.
+ ///
+ protected override void OnMouseWheel(MouseEventArgs e)
+ {
+ base.OnMouseWheel(e);
+
+ if (e is HandledMouseEventArgs)
+ {
+ if (((HandledMouseEventArgs)e).Handled)
+ {
+ // The application event handler handled the scrolling - don't do anything more.
+ return;
+ }
+
+ ((HandledMouseEventArgs)e).Handled = true;
+ }
+
+ bool wheelingDown = true;
+ if ((ModifierKeys & Keys.Control) != 0)
+ {
+ wheelingDown = false;
+ }
+
+ if (listManager is null || myGridTable is null)
+ {
+ return;
+ }
+
+ ScrollBar sb = wheelingDown ? vertScrollBar : horizScrollBar;
+ if (!sb.Visible)
+ {
+ return;
+ }
+
+ // so we scroll. we have to know this, cause otherwise we will call EndEdit
+ // and that would be wrong.
+ gridState[GRIDSTATE_isScrolling] = true;
+ wheelDelta += e.Delta;
+ float movePerc = (float)wheelDelta / (float)PInvoke.WHEEL_DELTA;
+ int move = (int)((float)SystemInformation.MouseWheelScrollLines * movePerc);
+ if (move != 0)
+ {
+ wheelDelta = 0;
+ Debug.WriteLineIf(CompModSwitches.DataGridLayout.TraceVerbose, "DataGridLayout: OnMouseWheel move=" + move.ToString(CultureInfo.InvariantCulture));
+ if (wheelingDown)
+ {
+ int newRow = firstVisibleRow - move;
+ newRow = Math.Max(0, Math.Min(newRow, DataGridRowsLength - numTotallyVisibleRows));
+ ScrollDown(newRow - firstVisibleRow);
+ }
+ else
+ {
+ int newValue = horizScrollBar.Value + (move < 0 ? 1 : -1) * horizScrollBar.LargeChange;
+ HorizontalOffset = newValue;
+ }
+ }
+
+ gridState[GRIDSTATE_isScrolling] = false;
+ }
+
+ ///
+ /// Raises the
+ /// event.
+ ///
+ protected override void OnPaint(PaintEventArgs e)
+ {
+ try
+ {
+ CheckHierarchyState();
+
+ if (layout.dirty)
+ {
+ ComputeLayout();
+ }
+
+ Graphics g = e.Graphics;
+
+ Region clipRegion = g.Clip;
+ if (layout.CaptionVisible)
+ {
+ caption.Paint(g, layout.Caption, isRightToLeft());
+ }
+
+ if (layout.ParentRowsVisible)
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridParents.TraceVerbose, "DataGridParents: Painting ParentRows " + layout.ParentRows.ToString());
+ g.FillRectangle(SystemBrushes.AppWorkspace, layout.ParentRows);
+ parentRows.Paint(g, layout.ParentRows, isRightToLeft());
+ }
+
+ Rectangle gridRect = layout.Data;
+ if (layout.RowHeadersVisible)
+ {
+ gridRect = Rectangle.Union(gridRect, layout.RowHeaders);
+ }
+
+ if (layout.ColumnHeadersVisible)
+ {
+ gridRect = Rectangle.Union(gridRect, layout.ColumnHeaders);
+ }
+
+ g.SetClip(gridRect);
+ PaintGrid(g, gridRect);
+ g.Clip = clipRegion;
+ clipRegion.Dispose();
+ PaintBorder(g, layout.ClientRectangle);
+
+ g.FillRectangle(DefaultHeaderBackBrush, layout.ResizeBoxRect);
+
+ base.OnPaint(e); // raise paint event
+ }
+ catch
+ {
+ // at runtime we will rethrow the exception
+ if (Site is null || !Site.DesignMode)
+ {
+ throw;
+ }
+
+ gridState[GRIDSTATE_exceptionInPaint] = true;
+ try
+ {
+ RTLAwareMessageBox.Show(null, DSR.DataGridExceptionInPaint, null, MessageBoxButtons.OK,
+ MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, 0);
+
+ if (Visible)
+ {
+ BeginUpdateInternal();
+ }
+
+ ResetParentRows();
+
+ Set_ListManager(null, string.Empty, true);
+ }
+ finally
+ {
+ gridState[GRIDSTATE_exceptionInPaint] = false;
+ if (Visible)
+ {
+ EndUpdateInternal();
+ }
+ }
+ }
+ }
+
+ // Since Win32 only invalidates the area that gets uncovered,
+ // we have to manually invalidate the old border area
+ ///
+ /// Raises the event.
+ ///
+ protected override void OnResize(EventArgs e)
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridLayout.TraceVerbose, "DataGridLayout: OnResize");
+
+ if (layout.CaptionVisible)
+ {
+ Invalidate(layout.Caption);
+ }
+
+ if (layout.ParentRowsVisible)
+ {
+ parentRows.OnResize(layout.ParentRows);
+ }
+
+ int borderWidth = BorderWidth;
+ Rectangle right;
+ Rectangle bottom;
+ Rectangle oldClientRectangle = layout.ClientRectangle;
+
+ right = new Rectangle(oldClientRectangle.X + oldClientRectangle.Width - borderWidth,
+ oldClientRectangle.Y,
+ borderWidth,
+ oldClientRectangle.Height);
+ bottom = new Rectangle(oldClientRectangle.X,
+ oldClientRectangle.Y + oldClientRectangle.Height - borderWidth,
+ oldClientRectangle.Width,
+ borderWidth);
+
+ Rectangle newClientRectangle = ClientRectangle;
+ if (newClientRectangle.Width != oldClientRectangle.Width)
+ {
+ Invalidate(right);
+ right = new Rectangle(newClientRectangle.X + newClientRectangle.Width - borderWidth,
+ newClientRectangle.Y,
+ borderWidth,
+ newClientRectangle.Height);
+ Invalidate(right);
+ }
+
+ if (newClientRectangle.Height != oldClientRectangle.Height)
+ {
+ Invalidate(bottom);
+ bottom = new Rectangle(newClientRectangle.X,
+ newClientRectangle.Y + newClientRectangle.Height - borderWidth,
+ newClientRectangle.Width,
+ borderWidth);
+ Invalidate(bottom);
+ }
+
+ //also, invalidate the ResizeBoxRect
+ if (!layout.ResizeBoxRect.IsEmpty)
+ {
+ Invalidate(layout.ResizeBoxRect);
+ }
+
+ layout.ClientRectangle = newClientRectangle;
+
+ int oldFirstVisibleRow = firstVisibleRow;
+ base.OnResize(e);
+ if (isRightToLeft() || oldFirstVisibleRow != firstVisibleRow)
+ {
+ Invalidate();
+ }
+ }
+
+ internal void OnRowHeightChanged(DataGridRow row)
+ {
+ ClearRegionCache();
+ int cy = GetRowTop(row.RowNumber);
+ if (cy > 0)
+ {
+ Rectangle refresh = new Rectangle
+ {
+ Y = cy,
+ X = layout.Inside.X,
+ Width = layout.Inside.Width,
+ Height = layout.Inside.Bottom - cy
+ };
+ Invalidate(refresh);
+ }
+ }
+
+ internal void ParentRowsDataChanged()
+ {
+ Debug.Assert(originalState is not null, "how can we get a list changed event from another listmanager/list while not navigating");
+
+ // do the reset work that is done in SetDataBindings, set_DataSource, set_DataMember;
+ parentRows.Clear();
+ caption.BackButtonActive = caption.DownButtonActive = caption.BackButtonVisible = false;
+ caption.SetDownButtonDirection(!layout.ParentRowsVisible);
+ object dSource = originalState.DataSource;
+ string dMember = originalState.DataMember;
+ // we don't need to set the GRIDSTATE_metaDataChanged bit, cause
+ // the listManager from the originalState should be different from the current listManager
+ //
+ // set the originalState to null so that Set_ListManager knows that
+ // it has to unhook the MetaDataChanged events
+ originalState = null;
+ Set_ListManager(dSource, dMember, true);
+ }
+
+ // =------------------------------------------------------------------
+ // = Methods
+ // =------------------------------------------------------------------
+
+ private void AbortEdit()
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridEditing.TraceVerbose, "DataGridEditing: \t! AbortEdit");
+
+ // the same rules from editColumn.OnEdit
+ // while changing the editControl's visibility, do not
+ // PerformLayout on the entire DataGrid
+ gridState[GRIDSTATE_editControlChanging] = true;
+
+ editColumn.Abort(editRow.RowNumber);
+
+ // reset the editControl flag:
+ gridState[GRIDSTATE_editControlChanging] = false;
+
+ gridState[GRIDSTATE_isEditing] = false;
+ editRow = null;
+ editColumn = null;
+ }
+
+ ///
+ /// Occurs when the user navigates to a new table.
+ ///
+ [SRCategory(nameof(SR.CatAction))]
+ public event NavigateEventHandler Navigate
+ {
+ add => onNavigate += value;
+ remove => onNavigate -= value;
+ }
+
+ ///
+ /// Occurs when a row header is clicked.
+ ///
+ protected event EventHandler RowHeaderClick
+ {
+ add => onRowHeaderClick += value;
+ remove => onRowHeaderClick -= value;
+ }
+
+ ///
+ /// Adds an event handler for the 'System.Windows.Forms.DataGrid.OnNodeClick'
+ /// event.
+ ///
+ [SRCategory(nameof(SR.CatAction))]
+ internal event EventHandler NodeClick
+ {
+ add => Events.AddHandler(EVENT_NODECLICKED, value);
+ remove => Events.RemoveHandler(EVENT_NODECLICKED, value);
+ }
+
+ ///
+ /// Occurs when the user scrolls the control.
+ ///
+ [SRCategory(nameof(SR.CatAction))]
+ public event EventHandler Scroll
+ {
+ add => Events.AddHandler(EVENT_SCROLL, value);
+ remove => Events.RemoveHandler(EVENT_SCROLL, value);
+ }
+
+ public override ISite Site
+ {
+ get
+ {
+ return base.Site;
+ }
+ set
+ {
+ ISite temp = Site;
+ base.Site = value;
+ if (value != temp && !Disposing)
+ {
+ // we should site the tables and the columns
+ // only when our site changes
+ SubObjectsSiteChange(false);
+ SubObjectsSiteChange(true);
+ }
+ }
+ }
+
+ internal void AddNewRow()
+ {
+ EnsureBound();
+ ResetSelection();
+ // EndEdit();
+ UpdateListManager();
+ gridState[GRIDSTATE_inListAddNew] = true;
+ gridState[GRIDSTATE_inAddNewRow] = true;
+ try
+ {
+ ListManager.AddNew();
+ }
+ catch
+ {
+ gridState[GRIDSTATE_inListAddNew] = false;
+ gridState[GRIDSTATE_inAddNewRow] = false;
+ PerformLayout();
+ InvalidateInside();
+ throw;
+ }
+
+ gridState[GRIDSTATE_inListAddNew] = false;
+ }
+
+ ///
+ /// Attempts to
+ /// put the grid into a state where editing is
+ /// allowed.
+ ///
+ public bool BeginEdit(DataGridColumnStyle gridColumn, int rowNumber)
+ {
+ if (DataSource is null || myGridTable is null)
+ {
+ return false;
+ }
+
+ // We deny edit requests if we are already editing a cell.
+ if (gridState[GRIDSTATE_isEditing])
+ {
+ return false;
+ }
+ else
+ {
+ int col = -1;
+ if ((col = myGridTable.GridColumnStyles.IndexOf(gridColumn)) < 0)
+ {
+ return false;
+ }
+
+ CurrentCell = new DataGridCell(rowNumber, col);
+ ResetSelection();
+ Edit();
+ return true;
+ }
+ }
+
+ ///
+ /// Specifies the beginning of the initialization code.
+ ///
+ public void BeginInit()
+ {
+ if (inInit)
+ {
+ throw new InvalidOperationException(DSR.DataGridBeginInit);
+ }
+
+ inInit = true;
+ }
+
+ private Rectangle CalcRowResizeFeedbackRect(MouseEventArgs e)
+ {
+ Rectangle inside = layout.Data;
+ Rectangle r = new Rectangle(inside.X, e.Y, inside.Width, 3);
+ r.Y = Math.Min(inside.Bottom - 3, r.Y);
+ r.Y = Math.Max(r.Y, 0);
+ return r;
+ }
+
+ private Rectangle CalcColResizeFeedbackRect(MouseEventArgs e)
+ {
+ Rectangle inside = layout.Data;
+ Rectangle r = new Rectangle(e.X, inside.Y, 3, inside.Height);
+ r.X = Math.Min(inside.Right - 3, r.X);
+ r.X = Math.Max(r.X, 0);
+ return r;
+ }
+
+ private void CancelCursorUpdate()
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridCursor.TraceVerbose, "DataGridCursor: Requesting CancelEdit()");
+ if (listManager is not null)
+ {
+ EndEdit();
+ listManager.CancelCurrentEdit();
+ }
+ }
+
+ private void CheckHierarchyState()
+ {
+ if (checkHierarchy && listManager is not null && myGridTable is not null)
+ {
+ if (myGridTable is null)
+ {
+ // there was nothing to check
+ return;
+ }
+
+ for (int j = 0; j < myGridTable.GridColumnStyles.Count; j++)
+ {
+ DataGridColumnStyle gridColumn = myGridTable.GridColumnStyles[j];
+ }
+
+ checkHierarchy = false;
+ }
+ }
+
+ ///
+ /// The DataGrid caches an array of rectangular areas
+ /// which represent the area which scrolls left to right.
+ /// This method is invoked whenever the DataGrid's
+ /// scrollable regions change in such a way as to require
+ /// a re-recalculation.
+ ///
+ private void ClearRegionCache()
+ {
+ cachedScrollableRegion = null;
+ }
+
+ ///
+ /// Determines the best fit size for the given column.
+ ///
+ private void ColAutoResize(int col)
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridLayout.TraceVerbose, "DataGridLayout: ColAutoResize");
+ EndEdit();
+ CurrencyManager listManager = this.listManager;
+ if (listManager is null)
+ {
+ return;
+ }
+
+ int size;
+ Graphics g = CreateGraphicsInternal();
+ try
+ {
+ DataGridColumnStyle column = myGridTable.GridColumnStyles[col];
+ string columnName = column.HeaderText;
+
+ Font headerFont;
+ if (myGridTable.IsDefault)
+ {
+ headerFont = HeaderFont;
+ }
+ else
+ {
+ headerFont = myGridTable.HeaderFont;
+ }
+
+ size = (int)g.MeasureString(columnName, headerFont).Width + layout.ColumnHeaders.Height + 1; // The sort triangle's width is equal to it's height
+ int rowCount = listManager.Count;
+ for (int row = 0; row < rowCount; ++row)
+ {
+ object value = column.GetColumnValueAtRow(listManager, row);
+ int width = column.GetPreferredSize(g, value).Width;
+ if (width > size)
+ {
+ size = width;
+ }
+ }
+
+ if (column.Width != size)
+ {
+ column._width = size;
+
+ ComputeVisibleColumns();
+
+ bool lastColumnIsLastTotallyVisibleCol = true;
+ if (lastTotallyVisibleCol != -1)
+ {
+ for (int i = lastTotallyVisibleCol + 1; i < myGridTable.GridColumnStyles.Count; i++)
+ {
+ if (myGridTable.GridColumnStyles[i].PropertyDescriptor is not null)
+ {
+ lastColumnIsLastTotallyVisibleCol = false;
+ break;
+ }
+ }
+ }
+ else
+ {
+ lastColumnIsLastTotallyVisibleCol = false;
+ }
+
+ // if the column shrank and the last totally visible column was the last column
+ // then we need to recompute the horizontalOffset, firstVisibleCol, negOffset.
+ // lastTotallyVisibleCol remains the last column
+ if (lastColumnIsLastTotallyVisibleCol &&
+ (negOffset != 0 || horizontalOffset != 0))
+ {
+ // update the column width
+ column._width = size;
+
+ int cx = 0;
+ int colCount = myGridTable.GridColumnStyles.Count;
+ int visibleWidth = layout.Data.Width;
+ GridColumnStylesCollection cols = myGridTable.GridColumnStyles;
+
+ // assume everything fits
+ negOffset = 0;
+ horizontalOffset = 0;
+ firstVisibleCol = 0;
+
+ for (int i = colCount - 1; i >= 0; i--)
+ {
+ if (cols[i].PropertyDescriptor is null)
+ {
+ continue;
+ }
+
+ cx += cols[i].Width;
+ if (cx > visibleWidth)
+ {
+ if (negOffset == 0)
+ {
+ firstVisibleCol = i;
+ negOffset = cx - visibleWidth;
+ horizontalOffset = negOffset;
+ numVisibleCols++;
+ }
+ else
+ {
+ horizontalOffset += cols[i].Width;
+ }
+ }
+ else
+ {
+ numVisibleCols++;
+ }
+ }
+
+ // refresh the horizontal scrollbar
+ PerformLayout();
+
+ // we need to invalidate the layout.Data and layout.ColumnHeaders
+ Invalidate(Rectangle.Union(layout.Data, layout.ColumnHeaders));
+ }
+ else
+ {
+ // need to refresh the scroll bar
+ PerformLayout();
+
+ Rectangle rightArea = layout.Data;
+ if (layout.ColumnHeadersVisible)
+ {
+ rightArea = Rectangle.Union(rightArea, layout.ColumnHeaders);
+ }
+
+ int left = GetColBeg(col);
+
+ if (!isRightToLeft())
+ {
+ rightArea.Width -= left - rightArea.X;
+ rightArea.X = left;
+ }
+ else
+ {
+ rightArea.Width -= left;
+ }
+
+ Invalidate(rightArea);
+ }
+ }
+ }
+ finally
+ {
+ g.Dispose();
+ }
+
+ if (horizScrollBar.Visible)
+ {
+ horizScrollBar.Value = HorizontalOffset;
+ }
+
+ // OnColumnResize(EventArgs.Empty);
+ }
+
+ ///
+ /// Collapses child relations, if any exist for all rows, or for a
+ /// specified row.
+ ///
+ public void Collapse(int row)
+ {
+ SetRowExpansionState(row, false);
+ }
+
+ private void ColResizeBegin(MouseEventArgs e, int col)
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridLayout.TraceVerbose, "DataGridLayout: ColResizeBegin");
+ Debug.Assert(myGridTable is not null, "Column resizing operations can't be called when myGridTable is null.");
+
+ int x = e.X;
+ EndEdit();
+ Rectangle clip = Rectangle.Union(layout.ColumnHeaders, layout.Data);
+ if (isRightToLeft())
+ {
+ clip.Width = GetColBeg(col) - layout.Data.X - 2;
+ }
+ else
+ {
+ int leftEdge = GetColBeg(col);
+ clip.X = leftEdge + 3;
+ clip.Width = layout.Data.X + layout.Data.Width - leftEdge - 2;
+ }
+
+ Capture = true;
+ Cursor.Clip = RectangleToScreen(clip);
+ gridState[GRIDSTATE_trackColResize] = true;
+ trackColAnchor = x;
+ trackColumn = col;
+
+ DrawColSplitBar(e);
+ lastSplitBar = e;
+ }
+
+ private void ColResizeMove(MouseEventArgs e)
+ {
+ if (lastSplitBar is not null)
+ {
+ DrawColSplitBar(lastSplitBar);
+ lastSplitBar = e;
+ }
+
+ DrawColSplitBar(e);
+ }
+
+ private void ColResizeEnd(MouseEventArgs e)
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridLayout.TraceVerbose, "DataGridLayout: ColResizeEnd");
+ Debug.Assert(myGridTable is not null, "Column resizing operations can't be called when myGridTable is null.");
+
+ gridState[GRIDSTATE_layoutSuspended] = true;
+ try
+ {
+ if (lastSplitBar is not null)
+ {
+ DrawColSplitBar(lastSplitBar);
+ lastSplitBar = null;
+ }
+
+ bool rightToLeft = isRightToLeft();
+
+ int x = rightToLeft ? Math.Max(e.X, layout.Data.X) : Math.Min(e.X, layout.Data.Right + 1);
+ int delta = x - GetColEnd(trackColumn);
+ if (rightToLeft)
+ {
+ delta = -delta;
+ }
+
+ if (trackColAnchor != x && delta != 0)
+ {
+ DataGridColumnStyle column = myGridTable.GridColumnStyles[trackColumn];
+ int proposed = column.Width + delta;
+ proposed = Math.Max(proposed, 3);
+ column.Width = proposed;
+
+ // refresh scrolling data: horizontalOffset, negOffset, firstVisibleCol, numVisibleCols, lastTotallyVisibleCol
+ ComputeVisibleColumns();
+
+ bool lastColumnIsLastTotallyVisibleCol = true;
+ for (int i = lastTotallyVisibleCol + 1; i < myGridTable.GridColumnStyles.Count; i++)
+ {
+ if (myGridTable.GridColumnStyles[i].PropertyDescriptor is not null)
+ {
+ lastColumnIsLastTotallyVisibleCol = false;
+ break;
+ }
+ }
+
+ if (lastColumnIsLastTotallyVisibleCol &&
+ (negOffset != 0 || horizontalOffset != 0))
+ {
+ int cx = 0;
+ int colCount = myGridTable.GridColumnStyles.Count;
+ int visibleWidth = layout.Data.Width;
+ GridColumnStylesCollection cols = myGridTable.GridColumnStyles;
+
+ // assume everything fits
+ negOffset = 0;
+ horizontalOffset = 0;
+ firstVisibleCol = 0;
+
+ for (int i = colCount - 1; i > -1; i--)
+ {
+ if (cols[i].PropertyDescriptor is null)
+ {
+ continue;
+ }
+
+ cx += cols[i].Width;
+
+ if (cx > visibleWidth)
+ {
+ if (negOffset == 0)
+ {
+ negOffset = cx - visibleWidth;
+ firstVisibleCol = i;
+ horizontalOffset = negOffset;
+ numVisibleCols++;
+ }
+ else
+ {
+ horizontalOffset += cols[i].Width;
+ }
+ }
+ else
+ {
+ numVisibleCols++;
+ }
+ }
+
+ // and invalidate pretty much everything
+ Invalidate(Rectangle.Union(layout.Data, layout.ColumnHeaders));
+ }
+ else
+ {
+ Rectangle rightArea = Rectangle.Union(layout.ColumnHeaders, layout.Data);
+ int left = GetColBeg(trackColumn);
+ rightArea.Width -= rightToLeft ? rightArea.Right - left : left - rightArea.X;
+ rightArea.X = rightToLeft ? layout.Data.X : left;
+ Invalidate(rightArea);
+ }
+ }
+ }
+ finally
+ {
+ Cursor.Clip = Rectangle.Empty;
+ Capture = false;
+ gridState[GRIDSTATE_layoutSuspended] = false;
+ }
+
+ PerformLayout();
+
+ if (horizScrollBar.Visible)
+ {
+ horizScrollBar.Value = HorizontalOffset;
+ }
+
+ // OnColumnResize(EventArgs.Empty);
+ }
+
+ private void MetaDataChanged()
+ {
+ // when we reset the Binding in the grid, we need to clear the parent rows.
+ // the same goes for all the caption UI: reset it when the datasource changes.
+ //
+ parentRows.Clear();
+ caption.BackButtonActive = caption.DownButtonActive = caption.BackButtonVisible = false;
+ caption.SetDownButtonDirection(!layout.ParentRowsVisible);
+
+ gridState[GRIDSTATE_metaDataChanged] = true;
+ try
+ {
+ if (originalState is not null)
+ {
+ // set the originalState to null so that Set_ListManager knows that
+ // it has to unhook the MetaDataChanged events
+ Set_ListManager(originalState.DataSource, originalState.DataMember, true);
+ originalState = null;
+ }
+ else
+ {
+ Set_ListManager(DataSource, DataMember, true);
+ }
+ }
+ finally
+ {
+ gridState[GRIDSTATE_metaDataChanged] = false;
+ }
+ }
+
+ // =------------------------------------------------------------------
+ // = Functions to resize rows
+ // =------------------------------------------------------------------
+
+ // will autoResize "row"
+ private void RowAutoResize(int row)
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridLayout.TraceVerbose, "DataGridLayout: RowAutoResize");
+
+ EndEdit();
+ CurrencyManager listManager = ListManager;
+ if (listManager is null)
+ {
+ return;
+ }
+
+ Graphics g = CreateGraphicsInternal();
+ try
+ {
+ GridColumnStylesCollection columns = myGridTable.GridColumnStyles;
+ DataGridRow resizeRow = DataGridRows[row];
+ int rowCount = listManager.Count;
+ int resizeHeight = 0;
+
+ // compute the height that we should resize to:
+ int columnsCount = columns.Count;
+ for (int col = 0; col < columnsCount; col++)
+ {
+ object value = columns[col].GetColumnValueAtRow(listManager, row);
+ resizeHeight = Math.Max(resizeHeight, columns[col].GetPreferredHeight(g, value));
+ }
+
+ if (resizeRow.Height != resizeHeight)
+ {
+ resizeRow.Height = resizeHeight;
+
+ // needed to refresh scrollbar properties
+ PerformLayout();
+
+ Rectangle rightArea = layout.Data;
+ if (layout.RowHeadersVisible)
+ {
+ rightArea = Rectangle.Union(rightArea, layout.RowHeaders);
+ }
+
+ int top = GetRowTop(row);
+ rightArea.Height -= rightArea.Y - top;
+ rightArea.Y = top;
+ Invalidate(rightArea);
+ }
+ }
+ finally
+ {
+ g.Dispose();
+ }
+
+ // OnRowResize(EventArgs.Empty);
+ return;
+ }
+
+ private void RowResizeBegin(MouseEventArgs e, int row)
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridLayout.TraceVerbose, "DataGridLayout: RowResizeBegin");
+ Debug.Assert(myGridTable is not null, "Row resizing operations can't be called when myGridTable is null.");
+
+ int y = e.Y;
+ EndEdit();
+ Rectangle clip = Rectangle.Union(layout.RowHeaders, layout.Data);
+ int topEdge = GetRowTop(row);
+ clip.Y = topEdge + 3;
+ clip.Height = layout.Data.Y + layout.Data.Height - topEdge - 2;
+
+ Capture = true;
+ Cursor.Clip = RectangleToScreen(clip);
+ gridState[GRIDSTATE_trackRowResize] = true;
+ trackRowAnchor = y;
+ trackRow = row;
+
+ DrawRowSplitBar(e);
+ lastSplitBar = e;
+ }
+
+ private void RowResizeMove(MouseEventArgs e)
+ {
+ if (lastSplitBar is not null)
+ {
+ DrawRowSplitBar(lastSplitBar);
+ lastSplitBar = e;
+ }
+
+ DrawRowSplitBar(e);
+ }
+
+ private void RowResizeEnd(MouseEventArgs e)
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridLayout.TraceVerbose, "DataGridLayout: RowResizeEnd");
+ Debug.Assert(myGridTable is not null, "Row resizing operations can't be called when myGridTable is null.");
+
+ try
+ {
+ if (lastSplitBar is not null)
+ {
+ DrawRowSplitBar(lastSplitBar);
+ lastSplitBar = null;
+ }
+
+ int y = Math.Min(e.Y, layout.Data.Y + layout.Data.Height + 1);
+ int delta = y - GetRowBottom(trackRow);
+ if (trackRowAnchor != y && delta != 0)
+ {
+ DataGridRow row = DataGridRows[trackRow];
+ int proposed = row.Height + delta;
+ proposed = Math.Max(proposed, 3);
+ row.Height = proposed;
+
+ // needed to refresh scrollbar properties
+ PerformLayout();
+
+ Rectangle rightArea = Rectangle.Union(layout.RowHeaders, layout.Data);
+ int top = GetRowTop(trackRow);
+ rightArea.Height -= rightArea.Y - top;
+ rightArea.Y = top;
+ Invalidate(rightArea);
+ }
+ }
+ finally
+ {
+ Cursor.Clip = Rectangle.Empty;
+ Capture = false;
+ }
+
+ // OnRowResize(EventArgs.Empty);
+ }
+
+ ///
+ /// Fires the ColumnHeaderClicked event and handles column
+ /// sorting.
+ ///
+ private void ColumnHeaderClicked(PropertyDescriptor prop)
+ {
+ if (!CommitEdit())
+ {
+ return;
+ }
+
+ // OnColumnHeaderClick(EventArgs.Empty);
+ bool allowSorting;
+ if (myGridTable.IsDefault)
+ {
+ allowSorting = AllowSorting;
+ }
+ else
+ {
+ allowSorting = myGridTable.AllowSorting;
+ }
+
+ if (!allowSorting)
+ {
+ return;
+ }
+
+ // if (CompModSwitches.DataGridCursor.OutputVerbose) Debug.WriteLine("DataGridCursor: We are about to sort column " + col.ToString());
+ ListSortDirection direction = ListManager.GetSortDirection();
+ PropertyDescriptor sortColumn = ListManager.GetSortProperty();
+ if (sortColumn is not null && sortColumn.Equals(prop))
+ {
+ direction = (direction == ListSortDirection.Ascending) ? ListSortDirection.Descending : ListSortDirection.Ascending;
+ }
+ else
+ {
+ // defaultSortDirection : ascending
+ direction = ListSortDirection.Ascending;
+ }
+
+ if (listManager.Count == 0)
+ {
+ return;
+ }
+
+ ListManager.SetSort(prop, direction);
+ ResetSelection();
+
+ InvalidateInside();
+ }
+
+ ///
+ /// Attempts to commit editing if a cell is being edited.
+ /// Return true if successfully commited editing.
+ /// Return false if editing can not be completed and the gird must
+ /// remain in our current Edit state.
+ ///
+ private bool CommitEdit()
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridEditing.TraceVerbose, "DataGridEditing: \t CommitEdit " + (editRow is null ? "" : editRow.RowNumber.ToString(CultureInfo.InvariantCulture)));
+
+ // we want to commit the editing if
+ // 1. the user was editing or navigating around the data grid and
+ // 2. this is not the result of moving focus inside the data grid and
+ // 3. if the user was scrolling
+#pragma warning disable SA1408 // Conditional expressions should declare precedence
+ if (!gridState[GRIDSTATE_isEditing] && !gridState[GRIDSTATE_isNavigating] || (gridState[GRIDSTATE_editControlChanging] && !gridState[GRIDSTATE_isScrolling]))
+#pragma warning restore SA1408 // Conditional expressions should declare precedence
+ {
+ return true;
+ }
+
+ // the same rules from editColumn.OnEdit
+ // flag that we are editing the Edit control, so if we get a OnLayout on the
+ // datagrid side of things while the edit control changes its visibility and bounds
+ // the datagrid does not perform a layout
+ gridState[GRIDSTATE_editControlChanging] = true;
+
+ if ((editColumn is not null && editColumn.ReadOnly) || gridState[GRIDSTATE_inAddNewRow])
+ {
+ bool focusTheGrid = false;
+ if (ContainsFocus)
+ {
+ focusTheGrid = true;
+ }
+
+ if (focusTheGrid && gridState[GRIDSTATE_canFocus])
+ {
+ Focus();
+ }
+
+ editColumn.ConcedeFocus();
+
+ // set the focus back to the grid
+ if (focusTheGrid && gridState[GRIDSTATE_canFocus] && CanFocus && !Focused)
+ {
+ Focus();
+ }
+
+ // reset the editControl flag
+ gridState[GRIDSTATE_editControlChanging] = false;
+ return true;
+ }
+
+ bool retVal = editColumn?.Commit(ListManager, currentRow) ?? true;
+
+ // reset the editControl flag
+ gridState[GRIDSTATE_editControlChanging] = false;
+
+ if (retVal)
+ {
+ gridState[GRIDSTATE_isEditing] = false;
+ }
+
+ return retVal;
+ }
+
+ ///
+ /// Figure out how many rows we need to scroll down
+ /// to move targetRow into visibility.
+ ///
+ private int ComputeDeltaRows(int targetRow)
+ {
+ //Debug.WriteLineIf(CompModSwitches.DataGridScrolling.TraceVerbose, "DataGridScrolling: ComputeDeltaRows, firstVisibleRow = "
+ // + firstVisibleRow.ToString() + ", "
+ // + "targetRow = " + targetRow.ToString());
+
+ if (firstVisibleRow == targetRow)
+ {
+ return 0;
+ }
+
+ int dRows = 0;
+ int firstVisibleRowLogicalTop = -1;
+ int targetRowLogicalTop = -1;
+ int nRows = DataGridRowsLength;
+ int cy = 0;
+ DataGridRow[] localGridRows = DataGridRows;
+
+ for (int row = 0; row < nRows; ++row)
+ {
+ if (row == firstVisibleRow)
+ {
+ firstVisibleRowLogicalTop = cy;
+ }
+
+ if (row == targetRow)
+ {
+ targetRowLogicalTop = cy;
+ }
+
+ if (targetRowLogicalTop != -1 && firstVisibleRowLogicalTop != -1)
+ {
+ break;
+ }
+
+ cy += localGridRows[row].Height;
+ }
+
+ int targetRowLogicalBottom = targetRowLogicalTop + localGridRows[targetRow].Height;
+ int dataLogicalBottom = layout.Data.Height + firstVisibleRowLogicalTop;
+ if (targetRowLogicalBottom > dataLogicalBottom)
+ {
+ // we need to move down.
+ int downDelta = targetRowLogicalBottom - dataLogicalBottom;
+ firstVisibleRowLogicalTop += downDelta;
+ }
+ else if (firstVisibleRowLogicalTop < targetRowLogicalTop)
+ {
+ // we don't need to move
+ return 0;
+ }
+ else
+ {
+ // we need to move up.
+ int upDelta = firstVisibleRowLogicalTop - targetRowLogicalTop;
+ firstVisibleRowLogicalTop -= upDelta;
+ }
+
+ int newFirstRow = ComputeFirstVisibleRow(firstVisibleRowLogicalTop);
+ dRows = (newFirstRow - firstVisibleRow);
+ //Debug.WriteLineIf(CompModSwitches.DataGridScrolling.TraceVerbose, "DataGridScrolling: ComputeDeltaRows returning " + dRows.ToString());
+ return dRows;
+ }
+
+ ///
+ /// Given the a logical vertical offset, figure out
+ /// which row number should be the first fully visible
+ /// row on or after the offset.
+ ///
+ private int ComputeFirstVisibleRow(int firstVisibleRowLogicalTop)
+ {
+ int first;
+ int nRows = DataGridRowsLength;
+ int cy = 0;
+ DataGridRow[] localGridRows = DataGridRows;
+ for (first = 0; first < nRows; ++first)
+ {
+ if (cy >= firstVisibleRowLogicalTop)
+ {
+ break;
+ }
+
+ cy += localGridRows[first].Height;
+ }
+
+ return first;
+ }
+
+ ///
+ /// Constructs an updated Layout object.
+ ///
+ private void ComputeLayout()
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridLayout.TraceVerbose, "DataGridLayout: ComputeLayout");
+
+ bool alignLeft = !isRightToLeft();
+ Rectangle oldResizeRect = layout.ResizeBoxRect;
+
+ // hide the EditBox
+ EndEdit();
+
+ ClearRegionCache();
+
+ // NOTE : Since Rectangles are structs, then assignment is a
+ // : copy. Therefore, after saying "Rectangle inside = newLayout.Inside",
+ // : we must always assign back to newLayout.Inside.
+ //
+
+ // Important since all of the visibility flags will move
+ // to the new layout here.
+ LayoutData newLayout = new LayoutData(layout)
+ {
+ // Inside
+ Inside = ClientRectangle
+ };
+ Rectangle inside = newLayout.Inside;
+ int borderWidth = BorderWidth;
+ inside.Inflate(-borderWidth, -borderWidth);
+
+ Rectangle insideLeft = inside;
+
+ // Caption
+ if (layout.CaptionVisible)
+ {
+ int captionHeight = captionFontHeight + 6;
+ Rectangle cap = newLayout.Caption;
+ cap = insideLeft;
+ cap.Height = captionHeight;
+ insideLeft.Y += captionHeight;
+ insideLeft.Height -= captionHeight;
+
+ newLayout.Caption = cap;
+ }
+ else
+ {
+ newLayout.Caption = Rectangle.Empty;
+ }
+
+ // Parent Rows
+ if (layout.ParentRowsVisible)
+ {
+ Rectangle parents = newLayout.ParentRows;
+ int parentHeight = parentRows.Height;
+ parents = insideLeft;
+ parents.Height = parentHeight;
+ insideLeft.Y += parentHeight;
+ insideLeft.Height -= parentHeight;
+
+ newLayout.ParentRows = parents;
+ }
+ else
+ {
+ newLayout.ParentRows = Rectangle.Empty;
+ }
+
+ // Headers
+ //
+ int columnHeaderHeight = headerFontHeight + 6;
+ if (layout.ColumnHeadersVisible)
+ {
+ Rectangle colHeaders = newLayout.ColumnHeaders;
+ colHeaders = insideLeft;
+ colHeaders.Height = columnHeaderHeight;
+ insideLeft.Y += columnHeaderHeight;
+ insideLeft.Height -= columnHeaderHeight;
+
+ newLayout.ColumnHeaders = colHeaders;
+ }
+ else
+ {
+ newLayout.ColumnHeaders = Rectangle.Empty;
+ }
+
+ bool newRowHeadersVisible = myGridTable.IsDefault ? RowHeadersVisible : myGridTable.RowHeadersVisible;
+ int newRowHeaderWidth = myGridTable.IsDefault ? RowHeaderWidth : myGridTable.RowHeaderWidth;
+ newLayout.RowHeadersVisible = newRowHeadersVisible;
+ if (myGridTable is not null && newRowHeadersVisible)
+ {
+ Rectangle rowHeaders = newLayout.RowHeaders;
+ if (alignLeft)
+ {
+ rowHeaders = insideLeft;
+ rowHeaders.Width = newRowHeaderWidth;
+ insideLeft.X += newRowHeaderWidth;
+ insideLeft.Width -= newRowHeaderWidth;
+ }
+ else
+ {
+ rowHeaders = insideLeft;
+ rowHeaders.Width = newRowHeaderWidth;
+ rowHeaders.X = insideLeft.Right - newRowHeaderWidth;
+ insideLeft.Width -= newRowHeaderWidth;
+ }
+
+ newLayout.RowHeaders = rowHeaders;
+
+ if (layout.ColumnHeadersVisible)
+ {
+ Rectangle topLeft = newLayout.TopLeftHeader;
+ Rectangle colHeaders = newLayout.ColumnHeaders;
+ if (alignLeft)
+ {
+ topLeft = colHeaders;
+ topLeft.Width = newRowHeaderWidth;
+ colHeaders.Width -= newRowHeaderWidth;
+ colHeaders.X += newRowHeaderWidth;
+ }
+ else
+ {
+ topLeft = colHeaders;
+ topLeft.Width = newRowHeaderWidth;
+ topLeft.X = colHeaders.Right - newRowHeaderWidth;
+ colHeaders.Width -= newRowHeaderWidth;
+ }
+
+ newLayout.TopLeftHeader = topLeft;
+ newLayout.ColumnHeaders = colHeaders;
+ }
+ else
+ {
+ newLayout.TopLeftHeader = Rectangle.Empty;
+ }
+ }
+ else
+ {
+ newLayout.RowHeaders = Rectangle.Empty;
+ newLayout.TopLeftHeader = Rectangle.Empty;
+ }
+
+ // The Data region
+ newLayout.Data = insideLeft;
+ newLayout.Inside = inside;
+
+ layout = newLayout;
+
+ LayoutScrollBars();
+
+ // if the user shrank the grid client area, then OnResize invalidated the old
+ // resize area. however, we need to invalidate the left upper corner in the new ResizeArea
+ // note that we can't take the Invalidate call from the OnResize method, because if the
+ // user enlarges the form then the old area will not be invalidated.
+ //
+ if (!oldResizeRect.Equals(layout.ResizeBoxRect) && !layout.ResizeBoxRect.IsEmpty)
+ {
+ Invalidate(layout.ResizeBoxRect);
+ }
+
+ layout.dirty = false;
+ Debug.WriteLineIf(CompModSwitches.DataGridLayout.TraceVerbose, "DataGridLayout: " + layout.ToString());
+ }
+
+ ///
+ /// Computes the number of pixels to scroll to scroll from one
+ /// row to another.
+ ///
+ private int ComputeRowDelta(int from, int to)
+ {
+ int first = from;
+ int last = to;
+ int sign = -1;
+ if (first > last)
+ {
+ first = to;
+ last = from;
+ sign = 1;
+ }
+
+ DataGridRow[] localGridRows = DataGridRows;
+ int delta = 0;
+ for (int row = first; row < last; ++row)
+ {
+ delta += localGridRows[row].Height;
+ }
+
+ return sign * delta;
+ }
+
+ internal int MinimumRowHeaderWidth()
+ {
+ return minRowHeaderWidth;
+ }
+
+ internal void ComputeMinimumRowHeaderWidth()
+ {
+ minRowHeaderWidth = errorRowBitmapWidth; // the size of the pencil, star and row selector images are the same as the image for the error bitmap
+ if (ListHasErrors)
+ {
+ minRowHeaderWidth += errorRowBitmapWidth;
+ }
+
+ if (myGridTable is not null && myGridTable.RelationsList.Count != 0)
+ {
+ minRowHeaderWidth += 15; // the size of the plus/minus glyph and spacing around it
+ }
+ }
+
+ ///
+ /// Updates the internal variables with the number of columns visible
+ /// inside the Grid's client rectangle.
+ ///
+ private void ComputeVisibleColumns()
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridLayout.TraceVerbose, "DataGridLayout: ComputeVisibleColumns");
+ EnsureBound();
+
+ GridColumnStylesCollection columns = myGridTable.GridColumnStyles;
+
+ int nGridCols = columns.Count;
+ int cx = -negOffset;
+ int visibleColumns = 0;
+ int visibleWidth = layout.Data.Width;
+ int curCol = firstVisibleCol;
+
+ // the same problem with negative numbers:
+ // if the width passed in is negative, then return 0
+ //
+ // added the check for the columns.Count == 0
+ //
+ if (visibleWidth < 0 || columns.Count == 0)
+ {
+ numVisibleCols = firstVisibleCol = 0;
+ lastTotallyVisibleCol = -1;
+ return;
+ }
+
+ while (cx < visibleWidth && curCol < nGridCols)
+ {
+ // if (columns.Visible && columns.PropertyDescriptor is not null)
+ if (columns[curCol].PropertyDescriptor is not null)
+ {
+ cx += columns[curCol].Width;
+ }
+
+ curCol++;
+ visibleColumns++;
+ }
+
+ numVisibleCols = visibleColumns;
+
+ // if we inflate the data area
+ // then we paint columns to the left of firstVisibleColumn
+ if (cx < visibleWidth)
+ {
+ for (int i = firstVisibleCol - 1; i > 0; i--)
+ {
+ if (cx + columns[i].Width > visibleWidth)
+ {
+ break;
+ }
+
+ // if (columns.Visible && columns.PropertyDescriptor is not null)
+ if (columns[i].PropertyDescriptor is not null)
+ {
+ cx += columns[i].Width;
+ }
+
+ visibleColumns++;
+ firstVisibleCol--;
+ }
+
+ if (numVisibleCols != visibleColumns)
+ {
+ Debug.Assert(numVisibleCols < visibleColumns, "the number of visible columns can only grow");
+ // is there space for more columns than were visible?
+ // if so, then we need to repaint Data and ColumnHeaders
+ Invalidate(layout.Data);
+ Invalidate(layout.ColumnHeaders);
+
+ // update the number of visible columns to the new reality
+ numVisibleCols = visibleColumns;
+ }
+ }
+
+ lastTotallyVisibleCol = firstVisibleCol + numVisibleCols - 1;
+ if (cx > visibleWidth)
+ {
+ if (numVisibleCols <= 1 || (numVisibleCols == 2 && negOffset != 0))
+ {
+ // no column is entirely visible
+ lastTotallyVisibleCol = -1;
+ }
+ else
+ {
+ lastTotallyVisibleCol--;
+ }
+ }
+ }
+
+ ///
+ /// Determines which column is the first visible given
+ /// the object's horizontalOffset.
+ ///
+ private int ComputeFirstVisibleColumn()
+ {
+ int first = 0;
+ if (horizontalOffset == 0)
+ {
+ negOffset = 0;
+ return 0;
+ }
+
+ // we will check to see if myGridTables.GridColumns.Count != 0
+ // because when we reset the dataGridTable, we don't have any columns, and we still
+ // call HorizontalOffset = 0, and that will call ComputeFirstVisibleColumn with an empty collection of columns.
+ if (myGridTable is not null && myGridTable.GridColumnStyles is not null && myGridTable.GridColumnStyles.Count != 0)
+ {
+ GridColumnStylesCollection columns = myGridTable.GridColumnStyles;
+ int cx = 0;
+ int nGridCols = columns.Count;
+
+ if (columns[0].Width == -1)
+ {
+ // the columns are not initialized yet
+ //
+#if DEBUG
+ for (int i = 0; i < nGridCols; i++)
+ {
+ Debug.Assert(columns[i].Width == -1, "the columns' widths should not be initialized");
+ }
+#endif // DEBUG
+ negOffset = 0;
+ return 0;
+ }
+
+ for (first = 0; first < nGridCols; first++)
+ {
+ // if (columns[first].Visible && columns[first].PropertyDescriptor is not null);
+ if (columns[first].PropertyDescriptor is not null)
+ {
+ cx += columns[first].Width;
+ }
+
+ if (cx > horizontalOffset)
+ {
+ break;
+ }
+ }
+
+ // first may actually be the number of columns
+ // in that case all the columns fit in the layout data
+ if (first == nGridCols)
+ {
+ Debug.Assert(cx <= horizontalOffset, "look at the for loop before: we only exit that loop early if the cx is over the horizontal offset");
+ negOffset = 0;
+ return 0;
+ }
+
+ negOffset = columns[first].Width - (cx - horizontalOffset);
+ //Debug.WriteLineIf(CompModSwitches.DataGridScrolling.TraceVerbose, "DataGridScrolling: ComputeFirstVisibleColumn, ret = " + first.ToString() + ", negOffset = " + negOffset.ToString());
+ }
+
+ return first;
+ }
+
+ ///
+ /// Updates the internal variables with the number of rows visible
+ /// in a given DataGrid Layout.
+ ///
+ private void ComputeVisibleRows()
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridLayout.TraceVerbose, "DataGridLayout: ComputeVisibleRows");
+ EnsureBound();
+
+ Rectangle data = layout.Data;
+ int visibleHeight = data.Height;
+ int cy = 0;
+ int visibleRows = 0;
+ DataGridRow[] localGridRows = DataGridRows;
+ int numRows = DataGridRowsLength;
+
+ // when minimizing the dataGrid window, we will get negative values for the
+ // layout.Data.Width and layout.Data.Height ( is this a
+
+ if (visibleHeight < 0)
+ {
+ numVisibleRows = numTotallyVisibleRows = 0;
+ return;
+ }
+
+ for (int i = firstVisibleRow; i < numRows; ++i)
+ {
+ if (cy > visibleHeight)
+ {
+ break;
+ }
+
+ cy += localGridRows[i].Height;
+ visibleRows++;
+ }
+
+ if (cy < visibleHeight)
+ {
+ for (int i = firstVisibleRow - 1; i >= 0; i--)
+ {
+ int height = localGridRows[i].Height;
+ if (cy + height > visibleHeight)
+ {
+ break;
+ }
+
+ cy += height;
+ firstVisibleRow--;
+ visibleRows++;
+ }
+ }
+
+ numVisibleRows = numTotallyVisibleRows = visibleRows;
+ if (cy > visibleHeight)
+ {
+ numTotallyVisibleRows--;
+ }
+
+ Debug.Assert(numVisibleRows >= 0, "the number of visible rows can't be negative");
+ Debug.Assert(numTotallyVisibleRows >= 0, "the number of totally visible rows can't be negative");
+ }
+
+ ///
+ /// Constructs the new instance of the accessibility object for this control. Subclasses
+ /// should not call base.CreateAccessibilityObject.
+ ///
+ protected override AccessibleObject CreateAccessibilityInstance()
+ {
+ return new DataGridAccessibleObject(this);
+ }
+
+ ///
+ /// Creates a DataGridState representing the child table retrieved
+ /// from the passed DataRelation.
+ ///
+ private DataGridState CreateChildState(string relationName, DataGridRow source)
+ {
+ DataGridState dgs = new DataGridState();
+
+ string newDataMember;
+ if (string.IsNullOrEmpty(DataMember))
+ {
+ newDataMember = relationName;
+ }
+ else
+ {
+ newDataMember = DataMember + "." + relationName;
+ }
+
+ CurrencyManager childLM = (CurrencyManager)BindingContext[DataSource, newDataMember];
+
+ dgs.DataSource = DataSource;
+ dgs.DataMember = newDataMember;
+ dgs.ListManager = childLM;
+
+ dgs.DataGridRows = null;
+ dgs.DataGridRowsLength = childLM.Count + (policy.AllowAdd ? 1 : 0);
+
+ return dgs;
+ }
+
+ ///
+ /// Constructs a Layout object containing the state
+ /// for a newly constructed DataGrid.
+ ///
+ private LayoutData CreateInitialLayoutState()
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridLayout.TraceVerbose, "DataGridLayout: CreateInitialLayoutState");
+ LayoutData newLayout = new LayoutData
+ {
+ Inside = new Rectangle(),
+ TopLeftHeader = new Rectangle(),
+ ColumnHeaders = new Rectangle(),
+ RowHeaders = new Rectangle(),
+ Data = new Rectangle(),
+ Caption = new Rectangle(),
+ ParentRows = new Rectangle(),
+ ResizeBoxRect = new Rectangle(),
+ ColumnHeadersVisible = true,
+ RowHeadersVisible = true,
+ CaptionVisible = defaultCaptionVisible,
+ ParentRowsVisible = defaultParentRowsVisible,
+ ClientRectangle = ClientRectangle
+ };
+ return newLayout;
+ }
+
+ ///
+ /// The DataGrid caches an array of rectangular areas
+ /// which represent the area which scrolls left to right.
+ /// This method is invoked whenever the DataGrid needs
+ /// this scrollable region.
+ ///
+ private RECT[] CreateScrollableRegion(Rectangle scroll)
+ {
+ if (cachedScrollableRegion is not null)
+ {
+ return cachedScrollableRegion;
+ }
+
+ bool alignToRight = isRightToLeft();
+
+ using (Region region = new Region(scroll))
+ {
+ int nRows = numVisibleRows;
+ int cy = layout.Data.Y;
+ int cx = layout.Data.X;
+ DataGridRow[] localGridRows = DataGridRows;
+ for (int r = firstVisibleRow; r < nRows; r++)
+ {
+ int rowHeight = localGridRows[r].Height;
+ Rectangle rowExclude = localGridRows[r].GetNonScrollableArea();
+ rowExclude.X += cx;
+ rowExclude.X = MirrorRectangle(rowExclude, layout.Data, alignToRight);
+ if (!rowExclude.IsEmpty)
+ {
+ region.Exclude(new Rectangle(rowExclude.X,
+ rowExclude.Y + cy,
+ rowExclude.Width,
+ rowExclude.Height));
+ }
+
+ cy += rowHeight;
+ }
+
+ using (Graphics graphics = CreateGraphicsInternal())
+ {
+ HRGN hrgn = (HRGN)region.GetHrgn(graphics);
+
+ if (!hrgn.IsNull)
+ {
+ cachedScrollableRegion = hrgn.GetRegionRects();
+ region.ReleaseHrgn((IntPtr)hrgn);
+ }
+ }
+ }
+
+ return cachedScrollableRegion;
+ }
+
+ ///
+ /// Disposes of the resources (other than memory) used
+ /// by the .
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (vertScrollBar is not null)
+ {
+ vertScrollBar.Dispose();
+ }
+
+ if (horizScrollBar is not null)
+ {
+ horizScrollBar.Dispose();
+ }
+
+ if (toBeDisposedEditingControl is not null)
+ {
+ toBeDisposedEditingControl.Dispose();
+ toBeDisposedEditingControl = null;
+ }
+
+ GridTableStylesCollection tables = TableStyles;
+ if (tables is not null)
+ {
+#if DEBUG
+ Debug.Assert(myGridTable is null || myGridTable.IsDefault || tables.Contains(myGridTable), "how come that the currentTable is not in the list of tables?");
+#endif // DEBUG
+ for (int i = 0; i < tables.Count; i++)
+ {
+ tables[i].Dispose();
+ }
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ ///
+ /// Draws an XOR region to give UI feedback for Column Resizing.
+ /// This looks just like the Splitter control's UI when resizing.
+ ///
+ private void DrawColSplitBar(MouseEventArgs e)
+ {
+ Rectangle r = CalcColResizeFeedbackRect(e);
+ DrawSplitBar(r);
+ }
+
+ ///
+ /// Draws an XOR region to give UI feedback for Row Resizing.
+ /// This looks just like the Splitter control's UI when resizing.
+ ///
+ private void DrawRowSplitBar(MouseEventArgs e)
+ {
+ Rectangle r = CalcRowResizeFeedbackRect(e);
+ DrawSplitBar(r);
+ }
+
+ ///
+ /// Draws an XOR region to give UI feedback for Column/Row Resizing.
+ /// This looks just like the Splitter control's UI when resizing.
+ ///
+ private void DrawSplitBar(Rectangle r)
+ {
+ using GetDcScope dc = new(HWND, HRGN.Null, GET_DCX_FLAGS.DCX_CACHE | GET_DCX_FLAGS.DCX_LOCKWINDOWUPDATE);
+ HBRUSH halftone = ControlPaint.CreateHalftoneHBRUSH();
+ HGDIOBJ saveBrush = PInvokeCore.SelectObject(dc, halftone);
+ PInvoke.PatBlt(dc, r.X, r.Y, r.Width, r.Height, ROP_CODE.PATINVERT);
+ PInvokeCore.SelectObject(dc, saveBrush);
+ PInvokeCore.DeleteObject(halftone);
+ }
+
+ ///
+ /// Begin in-place editing of a cell. Any editing is commited
+ /// before the new edit takes place.
+ ///
+ /// This will always edit the currentCell
+ /// If you want to edit another cell than the current one, just reset CurrentCell
+ ///
+ private void Edit()
+ {
+ Edit(null);
+ }
+
+ private void Edit(string displayText)
+ {
+ EnsureBound();
+
+ // we want to be able to edit a cell which is not visible, as in the case with editing and scrolling
+ // at the same time. So do not call Ensure Visible
+ //
+ // EnsureVisible(currentRow, currentCol);
+
+ bool cellIsVisible = true;
+
+ // whoever needs to call ResetSelection should not rely on
+ // Edit() to call it;
+ //
+ // ResetSelection();
+
+ EndEdit();
+
+ Debug.WriteLineIf(CompModSwitches.DataGridEditing.TraceVerbose, "DataGridEditing: Edit, currentRow = " + currentRow.ToString(CultureInfo.InvariantCulture) +
+ ", currentCol = " + currentCol.ToString(CultureInfo.InvariantCulture) + (displayText is not null ? ", displayText= " + displayText : ""));
+
+ /* allow navigation even if the policy does not allow editing
+ if (!policy.AllowEdit)
+ return;
+ */
+
+ DataGridRow[] localGridRows = DataGridRows;
+
+ // what do you want to edit when there are no rows?
+ if (DataGridRowsLength == 0)
+ {
+ return;
+ }
+
+ localGridRows[currentRow].OnEdit();
+ editRow = localGridRows[currentRow];
+
+ // if the list has no columns, then what good is an edit?
+ if (myGridTable.GridColumnStyles.Count == 0)
+ {
+ return;
+ }
+
+ // what if the currentCol does not have a propDesc?
+ editColumn = myGridTable.GridColumnStyles[currentCol];
+ if (editColumn.PropertyDescriptor is null)
+ {
+ return;
+ }
+
+ Rectangle cellBounds = Rectangle.Empty;
+ if (currentRow < firstVisibleRow || currentRow > firstVisibleRow + numVisibleRows ||
+ currentCol < firstVisibleCol || currentCol > firstVisibleCol + numVisibleCols - 1 ||
+ (currentCol == firstVisibleCol && negOffset != 0))
+ {
+ cellIsVisible = false;
+ }
+ else
+ {
+ cellBounds = GetCellBounds(currentRow, currentCol);
+ }
+
+ gridState[GRIDSTATE_isNavigating] = true;
+ gridState[GRIDSTATE_isEditing] = false;
+
+ // once we call editColumn.Edit on a DataGridTextBoxColumn
+ // the edit control will become visible, and its bounds will get set.
+ // both actions cause Edit.Parent.OnLayout
+ // so we flag this change, cause we don't want to PerformLayout on the entire DataGrid
+ // everytime the edit column gets edited
+ gridState[GRIDSTATE_editControlChanging] = true;
+
+ editColumn.Edit(ListManager,
+ currentRow,
+ cellBounds,
+ myGridTable.ReadOnly || ReadOnly || !policy.AllowEdit,
+ displayText,
+ cellIsVisible);
+
+ // reset the editControlChanging to false
+ gridState[GRIDSTATE_editControlChanging] = false;
+ }
+
+ ///
+ /// Requests an end to an edit operation taking place on the
+ ///
+ /// control.
+ ///
+ public bool EndEdit(DataGridColumnStyle gridColumn, int rowNumber, bool shouldAbort)
+ {
+ bool ret = false;
+ if (gridState[GRIDSTATE_isEditing])
+ {
+ if (gridColumn != editColumn)
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridEditing.TraceVerbose, "DataGridEditing: EndEdit requested on a column we are not editing.");
+ }
+
+ if (rowNumber != editRow.RowNumber)
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridEditing.TraceVerbose, "DataGridEditing: EndEdit requested on a row we are not editing.");
+ }
+
+ if (shouldAbort)
+ {
+ AbortEdit();
+ ret = true;
+ }
+ else
+ {
+ ret = CommitEdit();
+ }
+ }
+
+ return ret;
+ }
+
+ ///
+ /// Ends any editing in progress by attempting to commit and then
+ /// aborting if not possible.
+ ///
+ private void EndEdit()
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridEditing.TraceVerbose, "DataGridEditing: EndEdit");
+
+ if (!gridState[GRIDSTATE_isEditing] && !gridState[GRIDSTATE_isNavigating])
+ {
+ return;
+ }
+
+ if (!CommitEdit())
+ {
+ AbortEdit();
+ }
+ }
+
+ // PERF: we attempt to create a ListManager for the DataSource/DateMember combination
+ // we do this in order to check for a valid DataMember
+ // if the check succeeds, then it means that we actully put the listManager in the BindingContext's
+ // list of BindingManagers. this is fine, cause if the check succeds, then Set_ListManager
+ // will be called, and this will get the listManager from the bindingManagerBase hashTable kept in the BindingContext
+ //
+ // this will work if the dataMember does not contain any dots ('.')
+ // if the dataMember contains dots, then it will be more complicated: maybe part of the binding path
+ // is valid w/ the new dataSource
+ // but we can leave w/ this, cause in the designer the dataMember will be only a column name. and the DataSource/DataMember
+ // properties are for use w/ the designer.
+ //
+ private void EnforceValidDataMember(object value)
+ {
+ Debug.Assert(value is not null, "we should not have a null dataSource when we want to check for a valid dataMember");
+ if (DataMember is null || DataMember.Length == 0)
+ {
+ return;
+ }
+
+ if (BindingContext is null)
+ {
+ return;
+ }
+
+ //
+ try
+ {
+ BindingManagerBase bm = BindingContext[value, dataMember];
+ }
+ catch
+ {
+ dataMember = string.Empty;
+ }
+ }
+
+ // will be used by the columns to tell the grid that
+ // editing is taken place (ie, the grid is no longer in the
+ // editOrNavigateMode)
+ //
+ // also, tell the current row to lose child focus
+ //
+ internal protected virtual void ColumnStartedEditing(Rectangle bounds)
+ {
+ Debug.Assert(currentRow >= firstVisibleRow && currentRow <= firstVisibleRow + numVisibleRows, "how can one edit a row which is invisible?");
+ DataGridRow[] localGridRows = DataGridRows;
+
+ if (bounds.IsEmpty && editColumn is DataGridTextBoxColumn && currentRow != -1 && currentCol != -1)
+ {
+ // set the bounds on the control
+ // this will only work w/ our DataGridTexBox control
+ DataGridTextBoxColumn col = editColumn as DataGridTextBoxColumn;
+ Rectangle editBounds = GetCellBounds(currentRow, currentCol);
+
+ gridState[GRIDSTATE_editControlChanging] = true;
+ try
+ {
+ col.TextBox.Bounds = editBounds;
+ }
+ finally
+ {
+ gridState[GRIDSTATE_editControlChanging] = false;
+ }
+ }
+
+ if (gridState[GRIDSTATE_inAddNewRow])
+ {
+ int currentRowCount = DataGridRowsLength;
+ DataGridRow[] newDataGridRows = new DataGridRow[currentRowCount + 1];
+ for (int i = 0; i < currentRowCount; i++)
+ {
+ newDataGridRows[i] = localGridRows[i];
+ }
+
+ // put the AddNewRow
+ newDataGridRows[currentRowCount] = new DataGridAddNewRow(this, myGridTable, currentRowCount);
+ SetDataGridRows(newDataGridRows, currentRowCount + 1);
+
+ Edit();
+ // put this after the call to edit so that
+ // CommitEdit knows that the inAddNewRow is true;
+ gridState[GRIDSTATE_inAddNewRow] = false;
+ gridState[GRIDSTATE_isEditing] = true;
+ gridState[GRIDSTATE_isNavigating] = false;
+ return;
+ }
+
+ gridState[GRIDSTATE_isEditing] = true;
+ gridState[GRIDSTATE_isNavigating] = false;
+ InvalidateRowHeader(currentRow);
+
+ // tell the current row to lose the childFocuse
+ if (currentRow < localGridRows.Length)
+ {
+ localGridRows[currentRow].LoseChildFocus(layout.RowHeaders, isRightToLeft());
+ }
+ }
+
+ internal protected virtual void ColumnStartedEditing(Control editingControl)
+ {
+ if (editingControl is null)
+ {
+ return;
+ }
+
+ ColumnStartedEditing(editingControl.Bounds);
+ }
+
+ ///
+ /// Displays child relations, if any exist, for all rows or a
+ /// specific row.
+ ///
+ public void Expand(int row)
+ {
+ SetRowExpansionState(row, true);
+ }
+
+ ///
+ /// Creates a using the specified .
+ ///
+ // protected and virtual because the SimpleDropdownDataGrid will override this
+ protected virtual DataGridColumnStyle CreateGridColumn(PropertyDescriptor prop, bool isDefault)
+ {
+ return myGridTable?.CreateGridColumn(prop, isDefault);
+ }
+
+ protected virtual DataGridColumnStyle CreateGridColumn(PropertyDescriptor prop)
+ {
+ return myGridTable?.CreateGridColumn(prop);
+ }
+
+#if PARENT_LINKS
+
+ private ListManager ListManagerForChildColumn(ListManager childListManager, PropertyDescriptor prop)
+ {
+ /*
+ DataKey key;
+ RelationsCollection relCollection = dataColumn.Table.ParentRelations;
+ */
+
+ // this will give us the list of properties of the child
+ PropertyDescriptorCollection propCollection = childListManager.GetItemProperties();
+
+ int relCount = propCollection.Count;
+ for (int i=0;i
+ /// Specifies the end of the initialization code.
+ ///
+ public void EndInit()
+ {
+ inInit = false;
+ if (myGridTable is null && ListManager is not null)
+ {
+ SetDataGridTable(TableStyles[ListManager.GetListName()], true); // true for forcing column creation
+ }
+
+ if (myGridTable is not null)
+ {
+ myGridTable.DataGrid = this;
+ }
+ }
+
+ ///
+ /// Given a x coordinate, returns the column it is over.
+ ///
+ private int GetColFromX(int x)
+ {
+ if (myGridTable is null)
+ {
+ return -1;
+ }
+
+ Rectangle inside = layout.Data;
+ Debug.Assert(x >= inside.X && x < inside.Right, "x must be inside the horizontal bounds of layout.Data");
+
+ x = MirrorPoint(x, inside, isRightToLeft());
+
+ GridColumnStylesCollection columns = myGridTable.GridColumnStyles;
+ int columnCount = columns.Count;
+
+ int cx = inside.X - negOffset;
+ int col = firstVisibleCol;
+ while (cx < inside.Width + inside.X && col < columnCount)
+ {
+ // if (columns[col].Visible && columns[col].PropertyDescriptor is not null)
+ if (columns[col].PropertyDescriptor is not null)
+ {
+ cx += columns[col].Width;
+ }
+
+ if (cx > x)
+ {
+ return col;
+ }
+
+ ++col;
+ }
+
+ return -1;
+ }
+
+ ///
+ /// Returns the coordinate of the left edge of the given column
+ /// Bi-Di: if the grid has the RightToLeft property set to RightToLeft.Yes, this will
+ /// return what appears as the right edge of the column
+ ///
+ internal int GetColBeg(int col)
+ {
+ Debug.Assert(myGridTable is not null, "GetColBeg can't be called when myGridTable is null.");
+
+ int offset = layout.Data.X - negOffset;
+ GridColumnStylesCollection columns = myGridTable.GridColumnStyles;
+
+ int lastCol = Math.Min(col, columns.Count);
+ for (int i = firstVisibleCol; i < lastCol; ++i)
+ {
+ // if (columns[i].Visible && columns[i].PropertyDescriptor is not null)
+ if (columns[i].PropertyDescriptor is not null)
+ {
+ offset += columns[i].Width;
+ }
+ }
+
+ return MirrorPoint(offset, layout.Data, isRightToLeft());
+ }
+
+ ///
+ /// Returns the coordinate of the right edge of the given column
+ /// Bi-Di: if the grid has the RightToLeft property set to RightToLeft.Yes, this will
+ /// return what appears as the left edge of the column
+ ///
+ internal int GetColEnd(int col)
+ {
+ // return MirrorPoint(GetColBeg(col) + myGridTable.GridColumnStyles[col].Width, layout.Data, isRightToLeft());
+ int colBeg = GetColBeg(col);
+ Debug.Assert(myGridTable.GridColumnStyles[col].PropertyDescriptor is not null, "why would we need the coordinate of a column that is not visible?");
+ int width = myGridTable.GridColumnStyles[col].Width;
+ return isRightToLeft() ? colBeg - width : colBeg + width;
+ }
+
+ private int GetColumnWidthSum()
+ {
+ int sum = 0;
+ if (myGridTable is not null && myGridTable.GridColumnStyles is not null)
+ {
+ GridColumnStylesCollection columns = myGridTable.GridColumnStyles;
+
+ int columnsCount = columns.Count;
+ for (int i = 0; i < columnsCount; i++)
+ {
+ // if (columns[i].Visible && columns[i].PropertyDescriptor is not null)
+ if (columns[i].PropertyDescriptor is not null)
+ {
+ sum += columns[i].Width;
+ }
+ }
+ }
+
+ return sum;
+ }
+
+ ///
+ /// Not all rows in the DataGrid are expandable,
+ /// this computes which ones are and returns an array
+ /// of references to them.
+ ///
+ private DataGridRelationshipRow[] GetExpandableRows()
+ {
+ int nExpandableRows = DataGridRowsLength;
+ DataGridRow[] localGridRows = DataGridRows;
+ if (policy.AllowAdd)
+ {
+ nExpandableRows = Math.Max(nExpandableRows - 1, 0);
+ }
+
+ DataGridRelationshipRow[] expandableRows = new DataGridRelationshipRow[nExpandableRows];
+ for (int i = 0; i < nExpandableRows; i++)
+ {
+ expandableRows[i] = (DataGridRelationshipRow)localGridRows[i];
+ }
+
+ return expandableRows;
+ }
+
+ ///
+ /// Returns the row number underneath the given y coordinate.
+ ///
+ private int GetRowFromY(int y)
+ {
+ Rectangle inside = layout.Data;
+ Debug.Assert(y >= inside.Y && y < inside.Bottom, "y must be inside the vertical bounds of the data");
+
+ int cy = inside.Y;
+ int row = firstVisibleRow;
+ int rowCount = DataGridRowsLength;
+ DataGridRow[] localGridRows = DataGridRows;
+ int bottom = inside.Bottom;
+ while (cy < bottom && row < rowCount)
+ {
+ cy += localGridRows[row].Height;
+ if (cy > y)
+ {
+ return row;
+ }
+
+ ++row;
+ }
+
+ return -1;
+ }
+
+ internal Rectangle GetRowHeaderRect()
+ {
+ return layout.RowHeaders;
+ }
+
+ internal Rectangle GetColumnHeadersRect()
+ {
+ return layout.ColumnHeaders;
+ }
+
+ ///
+ /// Determines where on the control's ClientRectangle a given row is
+ /// painting to.
+ ///
+ private Rectangle GetRowRect(int rowNumber)
+ {
+ Rectangle inside = layout.Data;
+ int cy = inside.Y;
+ DataGridRow[] localGridRows = DataGridRows;
+ for (int row = firstVisibleRow; row <= rowNumber; ++row)
+ {
+ if (cy > inside.Bottom)
+ {
+ break;
+ }
+
+ if (row == rowNumber)
+ {
+ Rectangle rowRect = new Rectangle(inside.X,
+ cy,
+ inside.Width,
+ localGridRows[row].Height);
+ if (layout.RowHeadersVisible)
+ {
+ rowRect.Width += layout.RowHeaders.Width;
+ rowRect.X -= isRightToLeft() ? 0 : layout.RowHeaders.Width;
+ }
+
+ return rowRect;
+ }
+
+ cy += localGridRows[row].Height;
+ }
+
+ return Rectangle.Empty;
+ }
+
+ ///
+ /// Returns the coordinate of the top edge of the given row
+ ///
+ private int GetRowTop(int row)
+ {
+ DataGridRow[] localGridRows = DataGridRows;
+ int offset = layout.Data.Y;
+ int lastRow = Math.Min(row, DataGridRowsLength);
+ for (int i = firstVisibleRow; i < lastRow; ++i)
+ {
+ offset += localGridRows[i].Height;
+ }
+
+ for (int i = firstVisibleRow; i > lastRow; i--)
+ {
+ offset -= localGridRows[i].Height;
+ }
+
+ return offset;
+ }
+
+ ///
+ /// Returns the coordinate of the bottom edge of the given row
+ ///
+ private int GetRowBottom(int row)
+ {
+ DataGridRow[] localGridRows = DataGridRows;
+
+ return GetRowTop(row) + localGridRows[row].Height;
+ }
+
+ ///
+ /// This method is called on methods that need the grid
+ /// to be bound to a DataTable to work.
+ ///
+ private void EnsureBound()
+ {
+ if (!Bound)
+ {
+ throw new InvalidOperationException(DSR.DataGridUnbound);
+ }
+ }
+
+ private void EnsureVisible(int row, int col)
+ {
+ if (row < firstVisibleRow
+ || row >= firstVisibleRow + numTotallyVisibleRows)
+ {
+ int dRows = ComputeDeltaRows(row);
+ ScrollDown(dRows);
+ }
+
+ if (firstVisibleCol == 0 && numVisibleCols == 0 && lastTotallyVisibleCol == -1)
+ {
+ // no columns are displayed whatsoever
+ // some sanity checks
+ Debug.Assert(negOffset == 0, " no columns are displayed so the negative offset should be 0");
+ return;
+ }
+
+ int previousFirstVisibleCol = firstVisibleCol;
+ int previousNegOffset = negOffset;
+ int previousLastTotallyVisibleCol = lastTotallyVisibleCol;
+
+ while (col < firstVisibleCol
+#pragma warning disable SA1408 // Conditional expressions should declare precedence
+ || col == firstVisibleCol && negOffset != 0
+ || lastTotallyVisibleCol == -1 && col > firstVisibleCol
+ || lastTotallyVisibleCol > -1 && col > lastTotallyVisibleCol)
+#pragma warning restore SA1408 // Conditional expressions should declare precedence
+ {
+ ScrollToColumn(col);
+
+ if (previousFirstVisibleCol == firstVisibleCol &&
+ previousNegOffset == negOffset &&
+ previousLastTotallyVisibleCol == lastTotallyVisibleCol)
+ {
+ // nothing changed since the last iteration
+ // don't get into an infinite loop
+ break;
+ }
+
+ previousFirstVisibleCol = firstVisibleCol;
+ previousNegOffset = negOffset;
+ previousLastTotallyVisibleCol = lastTotallyVisibleCol;
+
+ // continue to scroll to the right until the scrollTo column is the totally last visible column or it is the first visible column
+ }
+ }
+
+ ///
+ /// Gets a
+ /// that specifies the four corners of the selected cell.
+ ///
+ public Rectangle GetCurrentCellBounds()
+ {
+ DataGridCell current = CurrentCell;
+ return GetCellBounds(current.RowNumber, current.ColumnNumber);
+ }
+
+ ///
+ /// Gets the of the cell specified by row and column number.
+ ///
+ public Rectangle GetCellBounds(int row, int col)
+ {
+ DataGridRow[] localGridRows = DataGridRows;
+ Rectangle cellBounds = localGridRows[row].GetCellBounds(col);
+ cellBounds.Y += GetRowTop(row);
+ cellBounds.X += layout.Data.X - negOffset;
+ cellBounds.X = MirrorRectangle(cellBounds, layout.Data, isRightToLeft());
+ return cellBounds;
+ }
+
+ ///
+ /// Gets the of the cell specified by .
+ ///
+ public Rectangle GetCellBounds(DataGridCell dgc)
+ {
+ return GetCellBounds(dgc.RowNumber, dgc.ColumnNumber);
+ }
+
+ //
+
+ internal Rectangle GetRowBounds(DataGridRow row)
+ {
+ Rectangle rowBounds = new Rectangle
+ {
+ Y = GetRowTop(row.RowNumber),
+ X = layout.Data.X,
+ Height = row.Height,
+ Width = layout.Data.Width
+ };
+ return rowBounds;
+ }
+
+ ///
+ /// Gets information, such as row and column number of a
+ /// clicked point on
+ /// the grid,
+ /// using the x
+ /// and y coordinate passed to the method.
+ ///
+ public HitTestInfo HitTest(int x, int y)
+ {
+ int topOfData = layout.Data.Y;
+ HitTestInfo ci = new HitTestInfo();
+
+ if (layout.CaptionVisible && layout.Caption.Contains(x, y))
+ {
+ ci.type = HitTestType.Caption;
+ return ci;
+ }
+
+ if (layout.ParentRowsVisible && layout.ParentRows.Contains(x, y))
+ {
+ ci.type = HitTestType.ParentRows;
+ return ci;
+ }
+
+ if (!layout.Inside.Contains(x, y))
+ {
+ return ci;
+ }
+
+ if (layout.TopLeftHeader.Contains(x, y))
+ {
+ return ci;
+ }
+
+ // check for column resize
+ if (layout.ColumnHeaders.Contains(x, y))
+ {
+ ci.type = HitTestType.ColumnHeader;
+ ci.col = GetColFromX(x);
+ if (ci.col < 0)
+ {
+ return HitTestInfo.Nowhere;
+ }
+
+ int right = GetColBeg(ci.col + 1);
+ bool rightToLeft = isRightToLeft();
+ if ((rightToLeft && x - right < 8) || (!rightToLeft && right - x < 8))
+ {
+ ci.type = HitTestType.ColumnResize;
+ }
+
+ return (allowColumnResize ? ci : HitTestInfo.Nowhere);
+ }
+
+ //check for RowResize:
+ if (layout.RowHeaders.Contains(x, y))
+ {
+ ci.type = HitTestType.RowHeader;
+ ci.row = GetRowFromY(y);
+ if (ci.row < 0)
+ {
+ return HitTestInfo.Nowhere;
+ }
+
+ // find out if the click was a RowResize click
+ DataGridRow[] localGridRows = DataGridRows;
+ int bottomBorder = GetRowTop(ci.row) + localGridRows[ci.row].Height;
+ if (bottomBorder - y - BorderWidth < 2 && !(localGridRows[ci.row] is DataGridAddNewRow))
+ {
+ ci.type = HitTestType.RowResize;
+ }
+
+ return (allowRowResize ? ci : HitTestInfo.Nowhere);
+ }
+
+ if (layout.Data.Contains(x, y))
+ {
+ ci.type = HitTestType.Cell;
+ ci.col = GetColFromX(x);
+ ci.row = GetRowFromY(y);
+ if (ci.col < 0 || ci.row < 0)
+ {
+ return HitTestInfo.Nowhere;
+ }
+
+ return ci;
+ }
+
+ return ci;
+ }
+
+ ///
+ /// Gets information, such as row and column number of a
+ /// clicked point on the grid, about the
+ /// grid using a specific
+ /// .
+ ///
+ public HitTestInfo HitTest(Point position)
+ {
+ return HitTest(position.X, position.Y);
+ }
+
+ ///
+ /// Initializes the values for column widths in the table.
+ ///
+ private void InitializeColumnWidths()
+ {
+ if (myGridTable is null)
+ {
+ return;
+ }
+
+ GridColumnStylesCollection columns = myGridTable.GridColumnStyles;
+ int numCols = columns.Count;
+
+ // Resize the columns to a approximation of a best fit.
+ // We find the best fit width of NumRowsForAutoResize rows
+ // and use it for each column.
+ int preferredColumnWidth = myGridTable.IsDefault ? PreferredColumnWidth : myGridTable.PreferredColumnWidth;
+ // if we set the PreferredColumnWidth to something else than AutoColumnSize
+ // then use that value
+ //
+ for (int col = 0; col < numCols; col++)
+ {
+ // if the column width is not -1, then this column was initialized already
+ if (columns[col]._width != -1)
+ {
+ continue;
+ }
+
+ columns[col]._width = preferredColumnWidth;
+ }
+ }
+
+ ///
+ /// Invalidates the scrollable area of the DataGrid.
+ ///
+ internal void InvalidateInside()
+ {
+ Invalidate(layout.Inside);
+ }
+
+ ///
+ /// Invalidates the caption area of the DataGrid.
+ ///
+ internal void InvalidateCaption()
+ {
+ if (layout.CaptionVisible)
+ {
+ Invalidate(layout.Caption);
+ }
+ }
+
+ ///
+ /// Invalidates a rectangle normalized to the caption's
+ /// visual bounds.
+ ///
+ internal void InvalidateCaptionRect(Rectangle r)
+ {
+ if (layout.CaptionVisible)
+ {
+ Invalidate(r);
+ }
+ }
+
+ ///
+ /// Invalidates the display region of a given DataGridColumn.
+ ///
+ internal void InvalidateColumn(int column)
+ {
+ GridColumnStylesCollection gridColumns = myGridTable.GridColumnStyles;
+ if (column < 0 || gridColumns is null || gridColumns.Count <= column)
+ {
+ return;
+ }
+
+ Debug.Assert(gridColumns[column].PropertyDescriptor is not null, "how can we invalidate a column that is invisible?");
+ // bail if the column is not visible.
+ if (column < firstVisibleCol || column > firstVisibleCol + numVisibleCols - 1)
+ {
+ return;
+ }
+
+ Rectangle columnArea = new Rectangle
+ {
+ Height = layout.Data.Height,
+ Width = gridColumns[column].Width,
+ Y = layout.Data.Y
+ };
+
+ int x = layout.Data.X - negOffset;
+ int gridColumnsCount = gridColumns.Count;
+ for (int i = firstVisibleCol; i < gridColumnsCount; ++i)
+ {
+ if (i == column)
+ {
+ break;
+ }
+
+ x += gridColumns[i].Width;
+ }
+
+ columnArea.X = x;
+ columnArea.X = MirrorRectangle(columnArea, layout.Data, isRightToLeft());
+ Invalidate(columnArea);
+ }
+
+ ///
+ /// Invalidates the parent rows area of the DataGrid
+ ///
+ internal void InvalidateParentRows()
+ {
+ if (layout.ParentRowsVisible)
+ {
+ Invalidate(layout.ParentRows);
+ }
+ }
+
+ ///
+ /// Invalidates a rectangle normalized to the parent
+ /// rows area's visual bounds.
+ ///
+ internal void InvalidateParentRowsRect(Rectangle r)
+ {
+ Rectangle parentRowsRect = layout.ParentRows;
+ Invalidate(r);
+ if (!parentRowsRect.IsEmpty)
+ {
+ //Invalidate(new Rectangle(parentRowsRect.X + r.X, parentRowsRect.Y + r.Y,
+ // r.Width, r.Height));
+ }
+ }
+
+ ///
+ /// Invalidate the painting region for the row specified.
+ ///
+ internal void InvalidateRow(int rowNumber)
+ {
+ Rectangle rowRect = GetRowRect(rowNumber);
+ if (!rowRect.IsEmpty)
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridPainting.TraceVerbose, "DataGridPainting: Invalidating row " + rowNumber.ToString(CultureInfo.InvariantCulture));
+ Invalidate(rowRect);
+ }
+ }
+
+ private void InvalidateRowHeader(int rowNumber)
+ {
+ if (rowNumber >= firstVisibleRow && rowNumber < firstVisibleRow + numVisibleRows)
+ {
+ if (!layout.RowHeadersVisible)
+ {
+ return;
+ }
+
+ Rectangle invalid = new Rectangle
+ {
+ Y = GetRowTop(rowNumber),
+ X = layout.RowHeaders.X,
+ Width = layout.RowHeaders.Width,
+ Height = DataGridRows[rowNumber].Height
+ };
+ Invalidate(invalid);
+ }
+ }
+
+ // NOTE:
+ // because of Rtl, we assume that the only place that calls InvalidateRowRect is
+ // the DataGridRelationshipRow
+ internal void InvalidateRowRect(int rowNumber, Rectangle r)
+ {
+ Rectangle rowRect = GetRowRect(rowNumber);
+ if (!rowRect.IsEmpty)
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridPainting.TraceVerbose, "DataGridPainting: Invalidating a rect in row " + rowNumber.ToString(CultureInfo.InvariantCulture));
+ Rectangle inner = new Rectangle(rowRect.X + r.X, rowRect.Y + r.Y, r.Width, r.Height);
+ if (vertScrollBar.Visible && isRightToLeft())
+ {
+ inner.X -= vertScrollBar.Width;
+ }
+
+ Invalidate(inner);
+ }
+ }
+
+ ///
+ /// Gets a value that indicates whether a specified row's node is expanded or collapsed.
+ ///
+ public bool IsExpanded(int rowNumber)
+ {
+ if (rowNumber < 0 || rowNumber > DataGridRowsLength)
+ {
+ throw new ArgumentOutOfRangeException(nameof(rowNumber));
+ }
+
+ DataGridRow[] localGridRows = DataGridRows;
+
+ //
+
+ DataGridRow row = localGridRows[rowNumber];
+ if (row is DataGridRelationshipRow relRow)
+ {
+ return relRow.Expanded;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether a
+ /// specified row is selected.
+ ///
+ public bool IsSelected(int row)
+ {
+ //
+ DataGridRow[] localGridRows = DataGridRows;
+ return localGridRows[row].Selected;
+ }
+
+ internal static bool IsTransparentColor(Color color)
+ {
+ return color.A < 255;
+ }
+
+ ///
+ /// Determines if Scrollbars should be visible,
+ /// updates their bounds and the bounds of all
+ /// other regions in the DataGrid's Layout.
+ ///
+ private void LayoutScrollBars()
+ {
+ // if we set the dataSource to null, then take away the scrollbars.
+ if (listManager is null || myGridTable is null)
+ {
+ horizScrollBar.Visible = false;
+ vertScrollBar.Visible = false;
+ return;
+ }
+
+ // Scrollbars are a tricky issue.
+ // We need to see if we can cram our columns and rows
+ // in without scrollbars and if they don't fit, we make
+ // scrollbars visible and then fixup our regions for the
+ // data and headers.
+ bool needHorizScrollbar = false;
+ bool needVertScrollbar = false;
+ bool recountRows = false;
+ bool alignToRight = isRightToLeft();
+
+ int nGridCols = myGridTable.GridColumnStyles.Count;
+
+ // if we call LayoutScrollBars before CreateDataGridRows
+ // then the columns will have their default width ( 100 )
+ // CreateDataGridRows will possibly change the columns' width
+ //
+ // and anyway, ComputeVisibleRows will call the DataGridRows accessor
+ //
+ DataGridRow[] gridRows = DataGridRows;
+
+ // at this stage, the data grid columns may have their width set to -1 ( ie, their width is uninitialized )
+ // make sure that the totalWidth is at least 0
+ int totalWidth = Math.Max(0, GetColumnWidthSum());
+
+ if (totalWidth > layout.Data.Width && !needHorizScrollbar)
+ {
+ int horizHeight = horizScrollBar.Height;
+ layout.Data.Height -= horizHeight;
+ if (layout.RowHeadersVisible)
+ {
+ layout.RowHeaders.Height -= horizHeight;
+ }
+
+ needHorizScrollbar = true;
+ }
+
+ int oldFirstVisibleRow = firstVisibleRow;
+
+ ComputeVisibleRows();
+ if (numTotallyVisibleRows != DataGridRowsLength && !needVertScrollbar)
+ {
+ int vertWidth = vertScrollBar.Width;
+ layout.Data.Width -= vertWidth;
+ if (layout.ColumnHeadersVisible)
+ {
+ if (alignToRight)
+ {
+ layout.ColumnHeaders.X += vertWidth;
+ }
+
+ layout.ColumnHeaders.Width -= vertWidth;
+ }
+
+ needVertScrollbar = true;
+ }
+
+ firstVisibleCol = ComputeFirstVisibleColumn();
+ // we compute the number of visible columns only after we set up the vertical scroll bar.
+ ComputeVisibleColumns();
+
+ if (needVertScrollbar && totalWidth > layout.Data.Width && !needHorizScrollbar)
+ {
+ firstVisibleRow = oldFirstVisibleRow;
+ int horizHeight = horizScrollBar.Height;
+ layout.Data.Height -= horizHeight;
+ if (layout.RowHeadersVisible)
+ {
+ layout.RowHeaders.Height -= horizHeight;
+ }
+
+ needHorizScrollbar = true;
+ recountRows = true;
+ }
+
+ if (recountRows)
+ {
+ ComputeVisibleRows();
+ if (numTotallyVisibleRows != DataGridRowsLength && !needVertScrollbar)
+ {
+ int vertWidth = vertScrollBar.Width;
+ layout.Data.Width -= vertWidth;
+ if (layout.ColumnHeadersVisible)
+ {
+ if (alignToRight)
+ {
+ layout.ColumnHeaders.X += vertWidth;
+ }
+
+ layout.ColumnHeaders.Width -= vertWidth;
+ }
+
+ needVertScrollbar = true;
+ }
+ }
+
+ layout.ResizeBoxRect = new Rectangle();
+ if (needVertScrollbar && needHorizScrollbar)
+ {
+ Rectangle data = layout.Data;
+ layout.ResizeBoxRect = new Rectangle(alignToRight ? data.X : data.Right,
+ data.Bottom,
+ vertScrollBar.Width,
+ horizScrollBar.Height);
+ }
+
+ if (needHorizScrollbar && nGridCols > 0)
+ {
+ //Debug.WriteLineIf(CompModSwitches.DataGridScrolling.TraceVerbose, "DataGridScrolling: foo");
+
+ int widthNotVisible = totalWidth - layout.Data.Width;
+
+ horizScrollBar.Minimum = 0;
+ horizScrollBar.Maximum = totalWidth;
+ horizScrollBar.SmallChange = 1;
+ horizScrollBar.LargeChange = Math.Max(totalWidth - widthNotVisible, 0);
+ horizScrollBar.Enabled = Enabled;
+ horizScrollBar.RightToLeft = RightToLeft;
+ horizScrollBar.Bounds = new Rectangle(alignToRight ? layout.Inside.X + layout.ResizeBoxRect.Width : layout.Inside.X,
+ layout.Data.Bottom,
+ layout.Inside.Width - layout.ResizeBoxRect.Width,
+ horizScrollBar.Height);
+ horizScrollBar.Visible = true;
+ }
+ else
+ {
+ HorizontalOffset = 0;
+ horizScrollBar.Visible = false;
+ }
+
+ if (needVertScrollbar)
+ {
+ int vertScrollBarTop = layout.Data.Y;
+ if (layout.ColumnHeadersVisible)
+ {
+ vertScrollBarTop = layout.ColumnHeaders.Y;
+ }
+
+ // if numTotallyVisibleRows == 0 ( the height of the row is bigger than the height of
+ // the grid ) then scroll in increments of 1.
+ vertScrollBar.LargeChange = numTotallyVisibleRows != 0 ? numTotallyVisibleRows : 1;
+ vertScrollBar.Bounds = new Rectangle(alignToRight ? layout.Data.X : layout.Data.Right,
+ vertScrollBarTop,
+ vertScrollBar.Width,
+ layout.Data.Height + layout.ColumnHeaders.Height);
+ vertScrollBar.Enabled = Enabled;
+ vertScrollBar.Visible = true;
+ if (alignToRight)
+ {
+ layout.Data.X += vertScrollBar.Width;
+ }
+ }
+ else
+ {
+ vertScrollBar.Visible = false;
+ }
+ }
+
+ ///
+ /// Navigates back to the table previously displayed in the grid.
+ ///
+ public void NavigateBack()
+ {
+ if (!CommitEdit() || parentRows.IsEmpty())
+ {
+ return;
+ }
+
+ // when navigating back, if the grid is inAddNewRow state, cancel the currentEdit.
+ // we do not need to recreate the rows cause we are navigating back.
+ // the grid will catch any exception that happens.
+ if (gridState[GRIDSTATE_inAddNewRow])
+ {
+ gridState[GRIDSTATE_inAddNewRow] = false;
+ try
+ {
+ listManager.CancelCurrentEdit();
+ }
+ catch
+ {
+ }
+ }
+ else
+ {
+ UpdateListManager();
+ }
+
+ DataGridState newState = parentRows.PopTop();
+
+ ResetMouseState();
+
+ newState.PullState(this, false); // we do not want to create columns when navigating back
+
+ // we need to have originalState is not null when we process
+ // Set_ListManager in the NavigateBack/NavigateTo methods.
+ // otherwise the DataSource_MetaDataChanged event will not get registered
+ // properly
+ if (parentRows.GetTopParent() is null)
+ {
+ originalState = null;
+ }
+
+ DataGridRow[] localGridRows = DataGridRows;
+ // what if the user changed the ReadOnly property
+ // on the grid while the user was navigating to the child rows?
+ //
+ // what if the policy does not allow for allowAdd?
+ //
+ if ((ReadOnly || !policy.AllowAdd) == (localGridRows[DataGridRowsLength - 1] is DataGridAddNewRow))
+ {
+ int newDataGridRowsLength = (ReadOnly || !policy.AllowAdd) ? DataGridRowsLength - 1 : DataGridRowsLength + 1;
+ DataGridRow[] newDataGridRows = new DataGridRow[newDataGridRowsLength];
+ for (int i = 0; i < Math.Min(newDataGridRowsLength, DataGridRowsLength); i++)
+ {
+ newDataGridRows[i] = DataGridRows[i];
+ }
+
+ if (!ReadOnly && policy.AllowAdd)
+ {
+ newDataGridRows[newDataGridRowsLength - 1] = new DataGridAddNewRow(this, myGridTable, newDataGridRowsLength - 1);
+ }
+
+ SetDataGridRows(newDataGridRows, newDataGridRowsLength);
+ }
+
+ // when we navigate back from a child table,
+ // it may be the case that in between the user added a tableStyle that is different
+ // from the one that is currently in the grid
+ // in that case, we need to reset the dataGridTableStyle in the rows
+ localGridRows = DataGridRows;
+ if (localGridRows is not null && localGridRows.Length != 0)
+ {
+ DataGridTableStyle dgTable = localGridRows[0].DataGridTableStyle;
+ if (dgTable != myGridTable)
+ {
+ for (int i = 0; i < localGridRows.Length; i++)
+ {
+ localGridRows[i].DataGridTableStyle = myGridTable;
+ }
+ }
+ }
+
+ // if we have the default table, when we navigate back
+ // we also have the default gridColumns, w/ width = -1
+ // we need to set the width on the new gridColumns
+ //
+ if (myGridTable.GridColumnStyles.Count > 0 && myGridTable.GridColumnStyles[0].Width == -1)
+ {
+#if DEBUG
+ GridColumnStylesCollection cols = myGridTable.GridColumnStyles;
+ for (int i = 0; i < cols.Count; i++)
+ {
+ Debug.Assert(cols[i].Width == -1, "Sanity check");
+ }
+
+ Debug.Assert(myGridTable.IsDefault, "when we navigate to the parent rows and the columns have widths -1 we are using the default table");
+#endif // DEBUG
+ InitializeColumnWidths();
+ }
+
+ // reset the currentRow to the old position in the listmanager:
+ currentRow = ListManager.Position == -1 ? 0 : ListManager.Position;
+
+ // if the AllowNavigation changed while the user was navigating the
+ // child tables, so that the new navigation mode does not allow childNavigation anymore
+ // then reset the rows
+ if (!AllowNavigation)
+ {
+ RecreateDataGridRows();
+ }
+
+ caption.BackButtonActive = (parentRows.GetTopParent() is not null) && AllowNavigation;
+ caption.BackButtonVisible = caption.BackButtonActive;
+ caption.DownButtonActive = (parentRows.GetTopParent() is not null);
+
+ PerformLayout();
+ Invalidate();
+ // reposition the scroll bar
+ if (vertScrollBar.Visible)
+ {
+ vertScrollBar.Value = firstVisibleRow;
+ }
+
+ if (horizScrollBar.Visible)
+ {
+ horizScrollBar.Value = HorizontalOffset + negOffset;
+ }
+
+ Edit();
+ OnNavigate(new NavigateEventArgs(false));
+ }
+
+ ///
+ /// Navigates to the table specified by row and relation
+ /// name.
+ ///
+ public void NavigateTo(int rowNumber, string relationName)
+ {
+ // do not navigate if AllowNavigation is set to false
+ if (!AllowNavigation)
+ {
+ return;
+ }
+
+ DataGridRow[] localGridRows = DataGridRows;
+ if (rowNumber < 0 || rowNumber > DataGridRowsLength - (policy.AllowAdd ? 2 : 1))
+ {
+ throw new ArgumentOutOfRangeException(nameof(rowNumber));
+ }
+
+ EnsureBound();
+
+ DataGridRow source = localGridRows[rowNumber];
+
+ NavigateTo(relationName, source, false);
+ }
+
+ internal void NavigateTo(string relationName, DataGridRow source, bool fromRow)
+ {
+ // do not navigate if AllowNavigation is set to false
+ if (!AllowNavigation)
+ {
+ return;
+ }
+
+ // Commit the edit if possible
+ if (!CommitEdit())
+ {
+ return;
+ }
+
+ DataGridState childState;
+ try
+ {
+ childState = CreateChildState(relationName, source);
+ }
+ catch
+ {
+ // if we get an error when creating the RelatedCurrencyManager
+ // then navigateBack and ignore the exception.
+ //
+ NavigateBack();
+ return;
+ }
+
+ // call EndCurrentEdit before navigating.
+ // if we get an exception, we do not navigate.
+ //
+ try
+ {
+ listManager.EndCurrentEdit();
+ }
+ catch
+ {
+ return;
+ }
+
+ // Preserve our current state
+ // we need to do this after the EndCurrentEdit, otherwise the
+ // DataGridState will get the listChanged event from the EndCurrentEdit
+ DataGridState dgs = new DataGridState(this)
+ {
+ LinkingRow = source
+ };
+
+ // we need to update the Position in the ListManager
+ // ( the RelatedListManager uses only the position in the parentManager
+ // to create the childRows
+ //
+ // before the code was calling CurrentCell = this and such
+ // we should only call EndCurrentEdit ( which the code was doing anyway )
+ // and then set the position in the listManager to the new row.
+ //
+ if (source.RowNumber != CurrentRow)
+ {
+ listManager.Position = source.RowNumber;
+ }
+
+ // We save our state if the parent rows stack is empty.
+ if (parentRows.GetTopParent() is null)
+ {
+ originalState = dgs;
+ }
+
+ parentRows.AddParent(dgs);
+
+ NavigateTo(childState);
+
+ OnNavigate(new NavigateEventArgs(true));
+ if (fromRow)
+ {
+ // OnLinkClick(EventArgs.Empty);
+ }
+ }
+
+ private void NavigateTo(DataGridState childState)
+ {
+ // we are navigating... better stop editing.
+ EndEdit();
+
+ // also, we are no longer in editOrNavigate mode either
+ gridState[GRIDSTATE_isNavigating] = false;
+
+ // reset hot tracking
+ ResetMouseState();
+
+ // Retrieve the child state
+ childState.PullState(this, true); // true for creating columns when we navigate to child rows
+
+ if (listManager.Position != currentRow)
+ {
+ currentRow = listManager.Position == -1 ? 0 : listManager.Position;
+ }
+
+ if (parentRows.GetTopParent() is not null)
+ {
+ caption.BackButtonActive = AllowNavigation;
+ caption.BackButtonVisible = caption.BackButtonActive;
+ caption.DownButtonActive = true;
+ }
+
+ HorizontalOffset = 0;
+ PerformLayout();
+ Invalidate();
+ }
+
+ ///
+ /// Given a coordinate in the control this method returns
+ /// the equivalent point for a row.
+ ///
+ private Point NormalizeToRow(int x, int y, int row)
+ {
+ Debug.Assert(row >= firstVisibleRow && row < firstVisibleRow + numVisibleRows,
+ "Row " + row.ToString(CultureInfo.InvariantCulture) + "is not visible! firstVisibleRow = " +
+ firstVisibleRow.ToString(CultureInfo.InvariantCulture) + ", numVisibleRows = " +
+ numVisibleRows.ToString(CultureInfo.InvariantCulture));
+ Point origin = new Point(0, layout.Data.Y);
+
+ DataGridRow[] localGridRows = DataGridRows;
+ for (int r = firstVisibleRow; r < row; ++r)
+ {
+ origin.Y += localGridRows[r].Height;
+ }
+
+ // when hittesting for the PlusMinus, the code in the DataGridRelationshipRow
+ // will use real X coordinate ( the one from layout.RowHeaders ) to paint the glyph
+ //
+ return new Point(x, y - origin.Y);
+ }
+
+ internal void OnColumnCollectionChanged(object sender, CollectionChangeEventArgs e)
+ {
+ DataGridTableStyle table = (DataGridTableStyle)sender;
+ if (table.Equals(myGridTable))
+ {
+ // if we changed the column collection, then we need to set the property
+ // descriptors in the column collection.
+ // unless the user set the propertyDescriptor in the columnCollection
+ //
+ if (!myGridTable.IsDefault)
+ {
+ // if the element in the collectionChangeEventArgs is not null
+ // and the action is refresh, then it means that the user
+ // set the propDesc. we do not want to override this.
+ if (e.Action != CollectionChangeAction.Refresh || e.Element is null)
+ {
+ PairTableStylesAndGridColumns(listManager, myGridTable, false);
+ }
+ }
+
+ Invalidate();
+ PerformLayout();
+ }
+ }
+
+ ///
+ /// Paints column headers.
+ ///
+ private void PaintColumnHeaders(Graphics g)
+ {
+ bool alignToLeft = isRightToLeft();
+ Rectangle boundingRect = layout.ColumnHeaders;
+ if (!alignToLeft)
+ {
+ boundingRect.X -= negOffset;
+ }
+
+ boundingRect.Width += negOffset;
+
+ int columnHeaderWidth = PaintColumnHeaderText(g, boundingRect);
+
+ if (alignToLeft)
+ {
+ boundingRect.X = boundingRect.Right - columnHeaderWidth;
+ }
+
+ boundingRect.Width = columnHeaderWidth;
+ if (!FlatMode)
+ {
+ ControlPaint.DrawBorder3D(g, boundingRect, Border3DStyle.RaisedInner);
+ boundingRect.Inflate(-1, -1);
+ // g.SetPen(OldSystemPens.Control);
+ // g.OldBrush = (OldSystemBrushes.Hollow);
+ boundingRect.Width--;
+ boundingRect.Height--;
+ g.DrawRectangle(SystemPens.Control, boundingRect);
+ }
+ }
+
+ private int PaintColumnHeaderText(Graphics g, Rectangle boundingRect)
+ {
+ int cx = 0;
+ Rectangle textBounds = boundingRect;
+ GridColumnStylesCollection gridColumns = myGridTable.GridColumnStyles;
+ bool alignRight = isRightToLeft();
+
+ int nGridCols = gridColumns.Count;
+ // for sorting
+ PropertyDescriptor sortProperty = null;
+ sortProperty = ListManager.GetSortProperty();
+
+ // Now paint the column header text!
+ for (int col = firstVisibleCol; col < nGridCols; ++col)
+ {
+ if (gridColumns[col].PropertyDescriptor is null)
+ {
+ continue;
+ }
+
+ if (cx > boundingRect.Width)
+ {
+ break;
+ }
+
+ bool columnSorted = sortProperty is not null && sortProperty.Equals(gridColumns[col].PropertyDescriptor);
+ TriangleDirection whichWay = TriangleDirection.Up;
+ if (columnSorted)
+ {
+ ListSortDirection direction = ListManager.GetSortDirection();
+ if (direction == ListSortDirection.Descending)
+ {
+ whichWay = TriangleDirection.Down;
+ }
+ }
+
+ if (alignRight)
+ {
+ textBounds.Width = gridColumns[col].Width -
+ (columnSorted ? textBounds.Height : 0);
+ textBounds.X = boundingRect.Right - cx - textBounds.Width;
+ }
+ else
+ {
+ textBounds.X = boundingRect.X + cx;
+ textBounds.Width = gridColumns[col].Width -
+ (columnSorted ? textBounds.Height : 0);
+ }
+
+ // at the moment we paint some pixels twice.
+ // we should not call FilLRectangle, once the real GDI+ is there, we will have no need to do that
+
+ // if the user set the HeaderBackBrush property on the
+ // dataGrid, then use that property
+ Brush headerBrush;
+ if (myGridTable.IsDefault)
+ {
+ headerBrush = HeaderBackBrush;
+ }
+ else
+ {
+ headerBrush = myGridTable.HeaderBackBrush;
+ }
+
+ g.FillRectangle(headerBrush, textBounds);
+ // granted, the code would be a lot cleaner if we were using a "new Rectangle"
+ // but like this will be faster
+ if (alignRight)
+ {
+ textBounds.X -= 2;
+ textBounds.Y += 2;
+ }
+ else
+ {
+ textBounds.X += 2;
+ textBounds.Y += 2;
+ }
+
+ StringFormat format = new StringFormat();
+
+ // the columnHeaderText alignment should be the same as
+ // the alignment in the column
+ //
+ HorizontalAlignment colAlignment = gridColumns[col].Alignment;
+ format.Alignment = colAlignment == HorizontalAlignment.Right ? StringAlignment.Far :
+ colAlignment == HorizontalAlignment.Center ? StringAlignment.Center :
+ StringAlignment.Near;
+
+ // part 1, section 1: the column headers should not wrap
+ format.FormatFlags |= StringFormatFlags.NoWrap;
+
+ if (alignRight)
+ {
+ format.FormatFlags |= StringFormatFlags.DirectionRightToLeft;
+ format.Alignment = StringAlignment.Near;
+ }
+
+ g.DrawString(gridColumns[col].HeaderText,
+ myGridTable.IsDefault ? HeaderFont : myGridTable.HeaderFont,
+ myGridTable.IsDefault ? HeaderForeBrush : myGridTable.HeaderForeBrush,
+ textBounds,
+ format);
+ format.Dispose();
+
+ if (alignRight)
+ {
+ textBounds.X += 2;
+ textBounds.Y -= 2;
+ }
+ else
+ {
+ textBounds.X -= 2;
+ textBounds.Y -= 2;
+ }
+
+ if (columnSorted)
+ {
+ //
+
+ Rectangle triBounds = new Rectangle(alignRight ? textBounds.X - textBounds.Height : textBounds.Right,
+ textBounds.Y,
+ textBounds.Height,
+ textBounds.Height);
+
+ g.FillRectangle(headerBrush, triBounds);
+ int deflateValue = Math.Max(0, (textBounds.Height - 5) / 2);
+ triBounds.Inflate(-deflateValue, -deflateValue);
+
+ Pen pen1 = new Pen(BackgroundBrush);
+ Pen pen2 = new Pen(myGridTable.BackBrush);
+ Triangle.Paint(g, triBounds, whichWay, headerBrush, pen1, pen2, pen1, true);
+ pen1.Dispose();
+ pen2.Dispose();
+ }
+
+ int paintedWidth = textBounds.Width + (columnSorted ? textBounds.Height : 0);
+
+ if (!FlatMode)
+ {
+ if (alignRight && columnSorted)
+ {
+ textBounds.X -= textBounds.Height;
+ }
+
+ textBounds.Width = paintedWidth;
+
+ ControlPaint.DrawBorder3D(g, textBounds, Border3DStyle.RaisedInner);
+ }
+
+ cx += paintedWidth;
+ }
+
+ // paint the possible exposed portion to the right ( or left, as the case may be)
+ if (cx < boundingRect.Width)
+ {
+ textBounds = boundingRect;
+
+ if (!alignRight)
+ {
+ textBounds.X += cx;
+ }
+
+ textBounds.Width -= cx;
+ g.FillRectangle(backgroundBrush, textBounds);
+ }
+
+ return cx;
+ }
+
+ ///
+ /// Paints a border around the bouding rectangle given
+ ///
+ private void PaintBorder(Graphics g, Rectangle bounds)
+ {
+ if (BorderStyle == BorderStyle.None)
+ {
+ return;
+ }
+
+ if (BorderStyle == BorderStyle.Fixed3D)
+ {
+ Border3DStyle style = Border3DStyle.Sunken;
+ ControlPaint.DrawBorder3D(g, bounds, style);
+ }
+ else if (BorderStyle == BorderStyle.FixedSingle)
+ {
+ Brush br;
+
+ if (myGridTable.IsDefault)
+ {
+ br = HeaderForeBrush;
+ }
+ else
+ {
+ br = myGridTable.HeaderForeBrush;
+ }
+
+ g.FillRectangle(br, bounds.X, bounds.Y, bounds.Width + 2, 2);
+ g.FillRectangle(br, bounds.Right - 2, bounds.Y, 2, bounds.Height + 2);
+ g.FillRectangle(br, bounds.X, bounds.Bottom - 2, bounds.Width + 2, 2);
+ g.FillRectangle(br, bounds.X, bounds.Y, 2, bounds.Height + 2);
+ }
+ else
+ {
+ Pen pen = SystemPens.WindowFrame;
+ bounds.Width--;
+ bounds.Height--;
+ g.DrawRectangle(pen, bounds);
+ }
+ }
+
+ ///
+ /// Paints the grid in the bounding rectangle given.
+ /// This includes the column headers and each visible row.
+ ///
+ private void PaintGrid(Graphics g, Rectangle gridBounds)
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridPainting.TraceVerbose, "DataGridPainting: PaintGrid on " + gridBounds.ToString());
+
+ Rectangle rc = gridBounds;
+
+ if (listManager is not null)
+ {
+ if (layout.ColumnHeadersVisible)
+ {
+ Region r = g.Clip;
+ g.SetClip(layout.ColumnHeaders);
+ PaintColumnHeaders(g);
+ g.Clip = r;
+ r.Dispose();
+ int columnHeaderHeight = layout.ColumnHeaders.Height;
+ rc.Y += columnHeaderHeight;
+ rc.Height -= columnHeaderHeight;
+ }
+
+ if (layout.TopLeftHeader.Width > 0)
+ {
+ if (myGridTable.IsDefault)
+ {
+ g.FillRectangle(HeaderBackBrush, layout.TopLeftHeader);
+ }
+ else
+ {
+ g.FillRectangle(myGridTable.HeaderBackBrush, layout.TopLeftHeader);
+ }
+
+ if (!FlatMode)
+ {
+ ControlPaint.DrawBorder3D(g, layout.TopLeftHeader, Border3DStyle.RaisedInner);
+ }
+ }
+
+ PaintRows(g, ref rc);
+ }
+
+ // paint the possible exposed portion below
+ if (rc.Height > 0)
+ {
+ g.FillRectangle(backgroundBrush, rc);
+ }
+ }
+
+ private void DeleteDataGridRows(int deletedRows)
+ {
+ if (deletedRows == 0)
+ {
+ return;
+ }
+
+ int currentRowCount = DataGridRowsLength;
+ int newDataGridRowsLength = currentRowCount - deletedRows + (gridState[GRIDSTATE_inAddNewRow] ? 1 : 0);
+ DataGridRow[] newDataGridRows = new DataGridRow[newDataGridRowsLength];
+ DataGridRow[] gridRows = DataGridRows;
+
+ // the number of selected entries so far in the array
+ int selectedEntries = 0;
+
+ for (int i = 0; i < currentRowCount; i++)
+ {
+ if (gridRows[i].Selected)
+ {
+ selectedEntries++;
+ }
+ else
+ {
+ newDataGridRows[i - selectedEntries] = gridRows[i];
+ newDataGridRows[i - selectedEntries].number = i - selectedEntries;
+ }
+ }
+
+ if (gridState[GRIDSTATE_inAddNewRow])
+ {
+ newDataGridRows[currentRowCount - selectedEntries] = new DataGridAddNewRow(this, myGridTable, currentRowCount - selectedEntries);
+ gridState[GRIDSTATE_inAddNewRow] = false;
+ }
+
+ Debug.Assert(selectedEntries == deletedRows, "all the rows that would have been deleted should have been selected: selectedGridEntries " + selectedEntries.ToString(CultureInfo.InvariantCulture) + " deletedRows " + deletedRows.ToString(CultureInfo.InvariantCulture));
+
+ SetDataGridRows(newDataGridRows, newDataGridRowsLength);
+ }
+
+ ///
+ /// Paints the visible rows on the grid.
+ ///
+ private void PaintRows(Graphics g, ref Rectangle boundingRect)
+ {
+ int cy = 0;
+ bool alignRight = isRightToLeft();
+ Rectangle rowBounds = boundingRect;
+ Rectangle dataBounds = Rectangle.Empty;
+ bool paintRowHeaders = layout.RowHeadersVisible;
+ Rectangle headerBounds = Rectangle.Empty;
+
+ int numRows = DataGridRowsLength;
+ DataGridRow[] localGridRows = DataGridRows;
+ int numCols = myGridTable.GridColumnStyles.Count - firstVisibleCol;
+
+ for (int row = firstVisibleRow; row < numRows; row++)
+ {
+ if (cy > boundingRect.Height)
+ {
+ break;
+ }
+
+ rowBounds = boundingRect;
+ rowBounds.Height = localGridRows[row].Height;
+ rowBounds.Y = boundingRect.Y + cy;
+
+ // will add some errors
+#if false
+ if (forDebug == 0 || forDebug == 1)
+ {
+ object dRowView = listManager[row];
+ DataRow dRow= ((DataRowView) dRowView).Row;
+ // dRow.RowError = "Error " + forDebug.ToString();
+ dRow.SetColumnError(forDebug, "another error " + forDebug.ToString());
+
+ /*
+ if (localGridRows[row].DataRow is not null)
+ {
+ localGridRows[row].DataRow.RowError = "error " + forDebug.ToString();
+ localGridRows[row].DataRow.SetColumnError(forDebug, "another error " + forDebug.ToString());
+ }
+ */
+ forDebug ++;
+ }
+#endif // false
+ if (paintRowHeaders)
+ {
+ headerBounds = rowBounds;
+ headerBounds.Width = layout.RowHeaders.Width;
+
+ if (alignRight)
+ {
+ headerBounds.X = rowBounds.Right - headerBounds.Width;
+ }
+
+ if (g.IsVisible(headerBounds))
+ {
+ localGridRows[row].PaintHeader(g, headerBounds, alignRight, gridState[GRIDSTATE_isEditing]);
+ g.ExcludeClip(headerBounds);
+ }
+
+ if (!alignRight)
+ {
+ rowBounds.X += headerBounds.Width;
+ }
+
+ rowBounds.Width -= headerBounds.Width;
+ }
+
+ if (g.IsVisible(rowBounds))
+ {
+ dataBounds = rowBounds;
+ if (!alignRight)
+ {
+ dataBounds.X -= negOffset;
+ }
+
+ dataBounds.Width += negOffset;
+
+ localGridRows[row].Paint(g, dataBounds, rowBounds, firstVisibleCol, numCols, alignRight);
+ }
+
+ cy += rowBounds.Height;
+ }
+
+ boundingRect.Y += cy;
+ boundingRect.Height -= cy;
+ }
+
+ ///
+ /// Gets or sets a value that indicates whether a key should be processed
+ /// further.
+ ///
+ protected override bool ProcessDialogKey(Keys keyData)
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridKeys.TraceVerbose, "DataGridKeys: ProcessDialogKey " + TypeDescriptor.GetConverter(typeof(Keys)).ConvertToString(keyData));
+ DataGridRow[] localGridRows = DataGridRows;
+ if (listManager is not null && DataGridRowsLength > 0 && localGridRows[currentRow].OnKeyPress(keyData))
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridKeys.TraceVerbose, "DataGridKeys: Current Row ate the keystroke");
+ return true;
+ }
+
+ switch (keyData & Keys.KeyCode)
+ {
+ case Keys.Tab:
+ case Keys.Up:
+ case Keys.Down:
+ case Keys.Left:
+ case Keys.Right:
+ case Keys.Next:
+ case Keys.Prior:
+ case Keys.Enter:
+ case Keys.Escape:
+ case Keys.Oemplus:
+ case Keys.Add:
+ case Keys.OemMinus:
+ case Keys.Subtract:
+ case Keys.Space:
+ case Keys.Delete:
+ case Keys.A:
+ KeyEventArgs ke = new KeyEventArgs(keyData);
+ if (ProcessGridKey(ke))
+ {
+ return true;
+ }
+
+ break;
+
+ case Keys.C:
+ if ((keyData & Keys.Control) != 0 && (keyData & Keys.Alt) == 0)
+ {
+ // the user pressed Ctrl-C
+ if (!Bound)
+ {
+ break;
+ }
+
+ // need to distinguish between selecting a set of rows, and
+ // selecting just one column.
+ if (numSelectedRows == 0)
+ {
+ // copy the data from one column only
+ if (currentRow < ListManager.Count)
+ {
+ GridColumnStylesCollection columns = myGridTable.GridColumnStyles;
+ if (currentCol >= 0 && currentCol < columns.Count)
+ {
+ DataGridColumnStyle column = columns[currentCol];
+ string text = column.GetDisplayText(column.GetColumnValueAtRow(ListManager, currentRow));
+
+ // copy the data to the clipboard
+ Clipboard.SetDataObject(text);
+ return true;
+ }
+ }
+ }
+ else
+ {
+ // the user selected a set of rows to copy the data from
+
+ int numRowsOutputted = 0; // the number of rows written to "text"
+ string text = string.Empty;
+
+ for (int i = 0; i < DataGridRowsLength; ++i)
+ {
+ if (localGridRows[i].Selected)
+ {
+ GridColumnStylesCollection columns = myGridTable.GridColumnStyles;
+ int numCols = columns.Count;
+ for (int j = 0; j < numCols; j++)
+ {
+ DataGridColumnStyle column = columns[j];
+ text += column.GetDisplayText(column.GetColumnValueAtRow(ListManager, i));
+
+ // do not put the delimiter at the end of the last column
+ if (j < numCols - 1)
+ {
+ text += GetOutputTextDelimiter();
+ }
+ }
+
+ // put the hard enter "\r\n" only if this is not the last selected row
+ if (numRowsOutputted < numSelectedRows - 1)
+ {
+ text += "\r\n";
+ }
+
+ numRowsOutputted++;
+ }
+ }
+
+ // copy the data to the clipboard
+ Clipboard.SetDataObject(text);
+ return true;
+ }
+ }
+
+ break;
+ }
+
+ return base.ProcessDialogKey(keyData);
+ }
+
+ private void DeleteRows(DataGridRow[] localGridRows)
+ {
+ int rowsDeleted = 0;
+
+ int currentRowsCount = listManager is null ? 0 : listManager.Count;
+
+ if (Visible)
+ {
+ BeginUpdateInternal();
+ }
+
+ try
+ {
+ if (ListManager is not null)
+ {
+ for (int i = 0; i < DataGridRowsLength; i++)
+ {
+ if (localGridRows[i].Selected)
+ {
+ if (localGridRows[i] is DataGridAddNewRow)
+ {
+ Debug.Assert(i == DataGridRowsLength - 1, "the location of addNewRow is " + i.ToString(CultureInfo.InvariantCulture) + " and there are " + DataGridRowsLength.ToString(CultureInfo.InvariantCulture) + " rows ");
+ localGridRows[i].Selected = false;
+ }
+ else
+ {
+ ListManager.RemoveAt(i - rowsDeleted);
+ rowsDeleted++;
+ }
+ }
+ }
+ }
+ }
+ catch
+ {
+ // if we got an exception from the back end
+ // when deleting the rows then we should reset
+ // our rows and re-throw the exception
+ //
+ RecreateDataGridRows();
+ gridState[GRIDSTATE_inDeleteRow] = false;
+ if (Visible)
+ {
+ EndUpdateInternal();
+ }
+
+ throw;
+ }
+
+ // keep the copy of the old rows in place
+ // it may be the case that deleting one row could cause multiple rows to be deleted in the same list
+ //
+ if (listManager is not null && currentRowsCount == listManager.Count + rowsDeleted)
+ {
+ DeleteDataGridRows(rowsDeleted);
+ }
+ else
+ {
+ RecreateDataGridRows();
+ }
+
+ gridState[GRIDSTATE_inDeleteRow] = false;
+ if (Visible)
+ {
+ EndUpdateInternal();
+ }
+
+ if (listManager is not null && currentRowsCount != listManager.Count + rowsDeleted)
+ {
+ Invalidate();
+ }
+ }
+
+ // convention:
+ // if we return -1 it means that the user was going left and there were no visible columns to the left of the current one
+ // if we return cols.Count + 1 it means that the user was going right and there were no visible columns to the right of the currrent
+ private int MoveLeftRight(GridColumnStylesCollection cols, int startCol, bool goRight)
+ {
+ int i;
+ if (goRight)
+ {
+ for (i = startCol + 1; i < cols.Count; i++)
+ {
+ // if (cols[i].Visible && cols[i].PropertyDescriptor is not null)
+ if (cols[i].PropertyDescriptor is not null)
+ {
+ return i;
+ }
+ }
+
+ return i;
+ }
+ else
+ {
+ for (i = startCol - 1; i >= 0; i--)
+ {
+ // if (cols[i].Visible && cols[i].PropertyDescriptor is not null)
+ if (cols[i].PropertyDescriptor is not null)
+ {
+ return i;
+ }
+ }
+
+ return i;
+ }
+ }
+
+ ///
+ /// Processes keys for grid navigation.
+ ///
+ protected bool ProcessGridKey(KeyEventArgs ke)
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridKeys.TraceVerbose, "DataGridKeys: ProcessGridKey " + TypeDescriptor.GetConverter(typeof(Keys)).ConvertToString(ke.KeyCode));
+ if (listManager is null || myGridTable is null)
+ {
+ return false;
+ }
+
+ DataGridRow[] localGridRows = DataGridRows;
+ KeyEventArgs biDiKe = ke;
+ // check for Bi-Di
+ //
+ if (isRightToLeft())
+ {
+ switch (ke.KeyCode)
+ {
+ case Keys.Left:
+ biDiKe = new KeyEventArgs((Keys.Right | ke.Modifiers));
+ break;
+ case Keys.Right:
+ biDiKe = new KeyEventArgs((Keys.Left | ke.Modifiers));
+ break;
+ default:
+ break;
+ }
+ }
+
+ GridColumnStylesCollection cols = myGridTable.GridColumnStyles;
+ int firstColumnMarkedVisible = 0;
+ int lastColumnMarkedVisible = cols.Count;
+ for (int i = 0; i < cols.Count; i++)
+ {
+ if (cols[i].PropertyDescriptor is not null)
+ {
+ firstColumnMarkedVisible = i;
+ break;
+ }
+ }
+
+ for (int i = cols.Count - 1; i >= 0; i--)
+ {
+ if (cols[i].PropertyDescriptor is not null)
+ {
+ lastColumnMarkedVisible = i;
+ break;
+ }
+ }
+
+ switch (biDiKe.KeyCode)
+ {
+ case Keys.Tab:
+ return ProcessTabKey(biDiKe.KeyData);
+ case Keys.Up:
+ gridState[GRIDSTATE_childLinkFocused] = false;
+ if (dataGridRowsLength == 0)
+ {
+ return true;
+ }
+
+ if (biDiKe.Control && !biDiKe.Alt)
+ {
+ if (biDiKe.Shift)
+ {
+ DataGridRow[] gridRows = DataGridRows;
+
+ int savedCurrentRow = currentRow;
+ CurrentRow = 0;
+
+ ResetSelection();
+
+ for (int i = 0; i <= savedCurrentRow; i++)
+ {
+ gridRows[i].Selected = true;
+ }
+
+ numSelectedRows = savedCurrentRow + 1;
+ // hide the edit box
+ //
+ EndEdit();
+ return true;
+ }
+
+ // do not make the parentRowsVisible = false;
+ // ParentRowsVisible = false;
+ ResetSelection();
+ CurrentRow = 0;
+ Debug.Assert(ListManager.Position == CurrentCell.RowNumber || listManager.Count == 0, "current row out of ssync with DataSource");
+ return true;
+ }
+ else if (biDiKe.Shift)
+ {
+ DataGridRow[] gridRows = DataGridRows;
+ // keep a continous selected region
+ if (gridRows[currentRow].Selected)
+ {
+ if (currentRow >= 1)
+ {
+ if (gridRows[currentRow - 1].Selected)
+ {
+ if (currentRow >= DataGridRowsLength - 1 || !gridRows[currentRow + 1].Selected)
+ {
+ numSelectedRows--;
+ gridRows[currentRow].Selected = false;
+ }
+ }
+ else
+ {
+ numSelectedRows += gridRows[currentRow - 1].Selected ? 0 : 1;
+ gridRows[currentRow - 1].Selected = true;
+ }
+
+ CurrentRow--;
+ }
+ }
+ else
+ {
+ numSelectedRows++;
+ gridRows[currentRow].Selected = true;
+ if (currentRow >= 1)
+ {
+ numSelectedRows += gridRows[currentRow - 1].Selected ? 0 : 1;
+ gridRows[currentRow - 1].Selected = true;
+ CurrentRow--;
+ }
+ }
+
+ // hide the edit box:
+ //
+ EndEdit();
+ Debug.Assert(ListManager.Position == CurrentCell.RowNumber || listManager.Count == 0, "current row out of ssync with DataSource");
+ return true;
+ }
+ else if (biDiKe.Alt)
+ {
+ // will need to collapse all child table links
+ // -1 is for all rows, and false is for collapsing the rows
+ SetRowExpansionState(-1, false);
+ Debug.Assert(ListManager.Position == CurrentCell.RowNumber || listManager.Count == 0, "current row out of ssync with DataSource");
+ return true;
+ }
+
+ ResetSelection();
+ CurrentRow -= 1;
+ Edit();
+ Debug.Assert(ListManager.Position == CurrentCell.RowNumber || listManager.Count == 0, "current row out of ssync with DataSource");
+ break;
+ case Keys.Down:
+ gridState[GRIDSTATE_childLinkFocused] = false;
+ if (dataGridRowsLength == 0)
+ {
+ return true;
+ }
+
+ if (biDiKe.Control && !biDiKe.Alt)
+ {
+ if (biDiKe.Shift)
+ {
+ int savedCurrentRow = currentRow;
+ CurrentRow = Math.Max(0, DataGridRowsLength - (policy.AllowAdd ? 2 : 1));
+ DataGridRow[] gridRows = DataGridRows;
+
+ ResetSelection();
+
+ for (int i = savedCurrentRow; i <= currentRow; i++)
+ {
+ gridRows[i].Selected = true;
+ }
+
+ numSelectedRows = currentRow - savedCurrentRow + 1;
+ // hide the edit box
+ //
+ EndEdit();
+ return true;
+ }
+
+ // do not make the parentRowsVisible = true;
+ // ParentRowsVisible = true;
+ ResetSelection();
+ CurrentRow = Math.Max(0, DataGridRowsLength - (policy.AllowAdd ? 2 : 1));
+ Debug.Assert(ListManager.Position == CurrentCell.RowNumber || listManager.Count == 0, "current row out of ssync with DataSource");
+ return true;
+ }
+ else if (biDiKe.Shift)
+ {
+ DataGridRow[] gridRows = DataGridRows;
+
+ // keep a continous selected region
+ if (gridRows[currentRow].Selected)
+ {
+ // -1 because we index from 0
+ if (currentRow < DataGridRowsLength - (policy.AllowAdd ? 1 : 0) - 1)
+ {
+ if (gridRows[currentRow + 1].Selected)
+ {
+ if (currentRow == 0 || !gridRows[currentRow - 1].Selected)
+ {
+ numSelectedRows--;
+ gridRows[currentRow].Selected = false;
+ }
+ }
+ else
+ {
+ numSelectedRows += gridRows[currentRow + 1].Selected ? 0 : 1;
+ gridRows[currentRow + 1].Selected = true;
+ }
+
+ CurrentRow++;
+ }
+ }
+ else
+ {
+ numSelectedRows++;
+ gridRows[currentRow].Selected = true;
+ // -1 because we index from 0, and -1 so this is not the last row
+ // so it adds to -2
+ if (currentRow < DataGridRowsLength - (policy.AllowAdd ? 1 : 0) - 1)
+ {
+ CurrentRow++;
+ numSelectedRows += gridRows[currentRow].Selected ? 0 : 1;
+ gridRows[currentRow].Selected = true;
+ }
+ }
+
+ // hide the edit box:
+ //
+ EndEdit();
+ Debug.Assert(ListManager.Position == CurrentCell.RowNumber || listManager.Count == 0, "current row out of ssync with DataSource");
+ return true;
+ }
+ else if (biDiKe.Alt)
+ {
+ // will need to expande all child table links
+ // -1 is for all rows, and true is for expanding the rows
+ SetRowExpansionState(-1, true);
+ return true;
+ }
+
+ ResetSelection();
+ Edit();
+ CurrentRow += 1;
+ Debug.Assert(ListManager.Position == CurrentCell.RowNumber || listManager.Count == 0, "current row out of ssync with DataSource");
+ break;
+ case Keys.OemMinus:
+ case Keys.Subtract:
+ gridState[GRIDSTATE_childLinkFocused] = false;
+ if (biDiKe.Control && !biDiKe.Alt)
+ {
+ SetRowExpansionState(-1, false);
+ return true;
+ }
+
+ return false;
+ case Keys.Oemplus:
+ case Keys.Add:
+ gridState[GRIDSTATE_childLinkFocused] = false;
+ if (biDiKe.Control)
+ {
+ SetRowExpansionState(-1, true);
+ // hide the edit box
+ //
+ EndEdit();
+ return true;
+ }
+
+ return false;
+ case Keys.Space:
+ gridState[GRIDSTATE_childLinkFocused] = false;
+ if (dataGridRowsLength == 0)
+ {
+ return true;
+ }
+
+ if (biDiKe.Shift)
+ {
+ ResetSelection();
+ EndEdit();
+ DataGridRow[] gridRows = DataGridRows;
+ gridRows[currentRow].Selected = true;
+ numSelectedRows = 1;
+
+ return true;
+ }
+
+ return false;
+ case Keys.Next:
+ gridState[GRIDSTATE_childLinkFocused] = false;
+ if (dataGridRowsLength == 0)
+ {
+ return true;
+ }
+
+ if (biDiKe.Shift)
+ {
+ int savedCurrentRow = currentRow;
+ CurrentRow = Math.Min(DataGridRowsLength - (policy.AllowAdd ? 2 : 1), currentRow + numTotallyVisibleRows);
+
+ DataGridRow[] gridRows = DataGridRows;
+ for (int i = savedCurrentRow; i <= currentRow; i++)
+ {
+ if (!gridRows[i].Selected)
+ {
+ gridRows[i].Selected = true;
+ numSelectedRows++;
+ }
+ }
+
+ // hide edit box
+ //
+ EndEdit();
+ }
+ else if (biDiKe.Control && !biDiKe.Alt)
+ {
+ // map ctrl-pageDown to show the parentRows
+ ParentRowsVisible = true;
+ }
+ else
+ {
+ ResetSelection();
+ CurrentRow = Math.Min(DataGridRowsLength - (policy.AllowAdd ? 2 : 1),
+ CurrentRow + numTotallyVisibleRows);
+ }
+
+ break;
+ case Keys.Prior:
+ if (dataGridRowsLength == 0)
+ {
+ return true;
+ }
+
+ gridState[GRIDSTATE_childLinkFocused] = false;
+ if (biDiKe.Shift)
+ {
+ int savedCurrentRow = currentRow;
+ CurrentRow = Math.Max(0, CurrentRow - numTotallyVisibleRows);
+
+ DataGridRow[] gridRows = DataGridRows;
+ for (int i = savedCurrentRow; i >= currentRow; i--)
+ {
+ if (!gridRows[i].Selected)
+ {
+ gridRows[i].Selected = true;
+ numSelectedRows++;
+ }
+ }
+
+ // hide the edit box
+ //
+ EndEdit();
+ }
+ else if (biDiKe.Control && !biDiKe.Alt)
+ {
+ // map ctrl-pageUp to hide the parentRows
+ ParentRowsVisible = false;
+ }
+ else
+ {
+ ResetSelection();
+ CurrentRow = Math.Max(0,
+ CurrentRow - numTotallyVisibleRows);
+ }
+
+ break;
+ case Keys.Left:
+ gridState[GRIDSTATE_childLinkFocused] = false;
+ ResetSelection();
+ if ((biDiKe.Modifiers & Keys.Modifiers) == Keys.Alt)
+ {
+ if (Caption.BackButtonVisible)
+ {
+ NavigateBack();
+ }
+
+ return true;
+ }
+
+ if ((biDiKe.Modifiers & Keys.Control) == Keys.Control)
+ {
+ // we should navigate to the first visible column
+ CurrentColumn = firstColumnMarkedVisible;
+ break;
+ }
+
+ if (currentCol == firstColumnMarkedVisible && currentRow != 0)
+ {
+ CurrentRow -= 1;
+ int newCol = MoveLeftRight(myGridTable.GridColumnStyles, myGridTable.GridColumnStyles.Count, false);
+ Debug.Assert(newCol != -1, "there should be at least a visible column, right?");
+ CurrentColumn = newCol;
+ }
+ else
+ {
+ int newCol = MoveLeftRight(myGridTable.GridColumnStyles, currentCol, false);
+ if (newCol == -1)
+ {
+ if (currentRow == 0)
+ {
+ return true;
+ }
+ else
+ {
+ // go to the previous row:
+ CurrentRow -= 1;
+ CurrentColumn = lastColumnMarkedVisible;
+ }
+ }
+ else
+ {
+ CurrentColumn = newCol;
+ }
+ }
+
+ break;
+ case Keys.Right:
+ gridState[GRIDSTATE_childLinkFocused] = false;
+ ResetSelection();
+ if ((biDiKe.Modifiers & Keys.Control) == Keys.Control && !biDiKe.Alt)
+ {
+ // we should navigate to the last column that is marked as Visible
+ CurrentColumn = lastColumnMarkedVisible;
+ break;
+ }
+
+ if (currentCol == lastColumnMarkedVisible && currentRow != DataGridRowsLength - 1)
+ {
+ CurrentRow += 1;
+ // navigate to the first visible column
+ CurrentColumn = firstColumnMarkedVisible;
+ }
+ else
+ {
+ int newCol = MoveLeftRight(myGridTable.GridColumnStyles, currentCol, true);
+ if (newCol == cols.Count + 1)
+ {
+ // navigate to the first visible column
+ // and the next row
+ //
+ CurrentColumn = firstColumnMarkedVisible;
+ CurrentRow++;
+ }
+ else
+ {
+ CurrentColumn = newCol;
+ }
+ }
+
+ break;
+ case Keys.F2:
+ gridState[GRIDSTATE_childLinkFocused] = false;
+ ResetSelection();
+ Edit();
+ break;
+#if DEBUG
+ case Keys.F12:
+ gridState[GRIDSTATE_childLinkFocused] = false;
+ AddNewRow();
+ break;
+#endif
+ case Keys.Home:
+ gridState[GRIDSTATE_childLinkFocused] = false;
+ if (dataGridRowsLength == 0)
+ {
+ return true;
+ }
+
+ ResetSelection();
+ CurrentColumn = 0;
+ if (biDiKe.Control && !biDiKe.Alt)
+ {
+ int currentRowSaved = currentRow;
+ CurrentRow = 0;
+
+ if (biDiKe.Shift)
+ {
+ // Ctrl-Shift-Home will select all the rows up to the first one
+ DataGridRow[] gridRows = DataGridRows;
+ for (int i = 0; i <= currentRowSaved; i++)
+ {
+ gridRows[i].Selected = true;
+ numSelectedRows++;
+ }
+
+ // hide the edit box:
+ EndEdit();
+ }
+
+ Debug.Assert(ListManager.Position == CurrentCell.RowNumber || listManager.Count == 0, "current row out of ssync with DataSource");
+ return true;
+ }
+
+ Debug.Assert(ListManager.Position == CurrentCell.RowNumber || listManager.Count == 0, "current row out of ssync with DataSource");
+ break;
+ case Keys.Delete:
+ gridState[GRIDSTATE_childLinkFocused] = false;
+ if (policy.AllowRemove && numSelectedRows > 0)
+ {
+#if DEBUG
+ // when the list is empty, then the position
+ // in the listManager is -1, and the currentPosition in the grid is 0
+ if (ListManager is not null && ListManager.Count > 0)
+ {
+ Debug.Assert(ListManager.Position == currentRow,
+ "Current row out of sync with DataSource",
+ "The DataSource's Position property should be mirrored by the CurrentCell.RowNumber of the DataGrid.");
+ }
+#endif // DEBUG
+
+ gridState[GRIDSTATE_inDeleteRow] = true;
+ DeleteRows(localGridRows);
+ // set the currentRow to the position in the list
+ currentRow = listManager.Count == 0 ? 0 : listManager.Position;
+ numSelectedRows = 0;
+ }
+ else
+ {
+ // if we did not use the the Delete key, let the dataGridTextBox use it
+ return false;
+ }
+
+ break;
+ case Keys.End:
+ gridState[GRIDSTATE_childLinkFocused] = false;
+ if (dataGridRowsLength == 0)
+ {
+ return true;
+ }
+
+ ResetSelection();
+ // go the the last visible column
+ CurrentColumn = lastColumnMarkedVisible;
+
+ if (biDiKe.Control && !biDiKe.Alt)
+ {
+ int savedCurrentRow = currentRow;
+ CurrentRow = Math.Max(0, DataGridRowsLength - (policy.AllowAdd ? 2 : 1));
+
+ if (biDiKe.Shift)
+ {
+ // Ctrl-Shift-Home will select all the rows up to the first one
+ DataGridRow[] gridRows = DataGridRows;
+ for (int i = savedCurrentRow; i <= currentRow; i++)
+ {
+ gridRows[i].Selected = true;
+ }
+
+ numSelectedRows = currentRow - savedCurrentRow + 1;
+ // hide the edit box
+ //
+ EndEdit();
+ }
+
+ Debug.Assert(ListManager.Position == CurrentCell.RowNumber || listManager.Count == 0, "current row out of ssync with DataSource");
+ return true;
+ }
+
+ Debug.Assert(ListManager.Position == CurrentCell.RowNumber || listManager.Count == 0, "current row out of ssync with DataSource");
+ break;
+ case Keys.Enter:
+ gridState[GRIDSTATE_childLinkFocused] = false;
+ ResetSelection();
+
+ // yield the return key if there is no editing
+ if (!gridState[GRIDSTATE_isEditing])
+ {
+ return false;
+ }
+
+ // Ctrl-Enter will call EndCurrentEdit
+ if ((biDiKe.Modifiers & Keys.Control) != 0 && !biDiKe.Alt)
+ {
+ EndEdit();
+ HandleEndCurrentEdit();
+ Edit(); // put the edit box on the screen
+ }
+ else
+ {
+ // Do not commit the edit, cause reseting the
+ // current cell will do that
+ //
+ // CommitEdit();
+
+ CurrentRow = currentRow + 1;
+ }
+
+ break;
+ case Keys.A:
+ gridState[GRIDSTATE_childLinkFocused] = false;
+ if (biDiKe.Control && !biDiKe.Alt)
+ {
+ DataGridRow[] gridRows = DataGridRows;
+ for (int i = 0; i < DataGridRowsLength; i++)
+ {
+ if (gridRows[i] is DataGridRelationshipRow)
+ {
+ gridRows[i].Selected = true;
+ }
+ }
+
+ numSelectedRows = DataGridRowsLength - (policy.AllowAdd ? 1 : 0);
+ // hide the edit box
+ //
+ EndEdit();
+ return true;
+ }
+
+ return false;
+ case Keys.Escape:
+ gridState[GRIDSTATE_childLinkFocused] = false;
+ ResetSelection();
+ if (gridState[GRIDSTATE_isEditing])
+ {
+ // rollback
+ AbortEdit();
+
+ // we have to invalidate the row header ( make it display the row selector instead of the pencil )
+ if (layout.RowHeadersVisible && currentRow > -1)
+ {
+ Rectangle rowHdrRect = GetRowRect(currentRow);
+ rowHdrRect.Width = layout.RowHeaders.Width;
+ Invalidate(rowHdrRect);
+ }
+
+ // now put the edit column back on the screen
+ Edit();
+ }
+ else
+ {
+ // add this protected virtual method for the XML designer team
+ CancelEditing();
+ Edit();
+ return false;
+ }
+
+ break;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Previews a keyboard message and returns a value indicating if the key was
+ /// consumed.
+ ///
+ protected override bool ProcessKeyPreview(ref Message m)
+ {
+ if (m.MsgInternal == PInvokeCore.WM_KEYDOWN)
+ {
+ KeyEventArgs ke = new KeyEventArgs((Keys)(int)m.WParamInternal | ModifierKeys);
+ switch (ke.KeyCode)
+ {
+ case Keys.Up:
+ case Keys.Down:
+ case Keys.Prior:
+ case Keys.Next:
+ case Keys.Right:
+ case Keys.Left:
+ case Keys.Tab:
+ case Keys.Escape:
+ case Keys.Enter:
+ case Keys.OemMinus:
+ case Keys.Subtract:
+ case Keys.Oemplus:
+ case Keys.Add:
+ case Keys.Space:
+ case Keys.Home:
+ case Keys.End:
+ case Keys.F2:
+ case Keys.Delete:
+ case Keys.A:
+ return ProcessGridKey(ke);
+ }
+
+ // Ctrl-Tab will be sent as a tab paired w/ a control on the KeyUp message
+ //
+ }
+ else if (m.MsgInternal == PInvokeCore.WM_KEYUP)
+ {
+ KeyEventArgs ke = new KeyEventArgs((Keys)(int)m.WParamInternal | ModifierKeys);
+ if (ke.KeyCode == Keys.Tab)
+ {
+ return ProcessGridKey(ke);
+ }
+ }
+
+ return base.ProcessKeyPreview(ref m);
+ }
+
+ ///
+ /// Gets a value indicating whether the Tab key should be processed.
+ ///
+ protected bool ProcessTabKey(Keys keyData)
+ {
+ if (listManager is null || myGridTable is null)
+ {
+ return false;
+ }
+
+ bool wasEditing = false;
+ int columnCount = myGridTable.GridColumnStyles.Count;
+ bool biDi = isRightToLeft();
+ ResetSelection();
+
+ // Try to commit changes to cell if we were editing
+ if (gridState[GRIDSTATE_isEditing])
+ {
+ wasEditing = true;
+ if (!CommitEdit())
+ {
+ //MessageBox.Show("Could not commit changes! Press Escape to abort edit");
+ Edit(); // if we can't commit the value put the edit box so that the user sees where the focus is
+ return true;
+ }
+ }
+
+ if ((keyData & Keys.Control) == Keys.Control)
+ {
+ // when the user hits ctrl-alt-tab just ignore it.
+ if ((keyData & Keys.Alt) == Keys.Alt)
+ {
+ return true;
+ }
+
+ // navigate to the next control in the form
+ Keys ke = keyData & ~(Keys.Control);
+ EndEdit();
+
+ gridState[GRIDSTATE_editControlChanging] = true;
+ try
+ {
+ Focus();
+ }
+ finally
+ {
+ gridState[GRIDSTATE_editControlChanging] = false;
+ }
+
+ return base.ProcessDialogKey(ke);
+ }
+
+ // see if the child relationships can use this TAB key
+ DataGridRow[] localRows = DataGridRows;
+ GridColumnStylesCollection cols = myGridTable.GridColumnStyles;
+
+ int lastColumnMarkedVisible = 0;
+ int firstColumnMarkedVisible = cols.Count - 1;
+ //
+
+ if (localRows.Length == 0)
+ {
+ EndEdit();
+
+ return base.ProcessDialogKey(keyData);
+ }
+
+ for (int i = 0; i < cols.Count; i++)
+ {
+ // if (cols[i].Visible && cols[i].PropertyDescriptor is not null) {
+ if (cols[i].PropertyDescriptor is not null)
+ {
+ firstColumnMarkedVisible = i;
+ break;
+ }
+ }
+
+ for (int i = cols.Count - 1; i >= 0; i--)
+ {
+ // if (cols[i].Visible && cols[i].PropertyDescriptor is not null) {
+ if (cols[i].PropertyDescriptor is not null)
+ {
+ lastColumnMarkedVisible = i;
+ break;
+ }
+ }
+
+ if (CurrentColumn == lastColumnMarkedVisible)
+ {
+ if (gridState[GRIDSTATE_childLinkFocused] || (!gridState[GRIDSTATE_childLinkFocused] && (keyData & Keys.Shift) != Keys.Shift))
+ {
+ if (localRows[CurrentRow].ProcessTabKey(keyData, layout.RowHeaders, isRightToLeft()))
+ {
+ if (cols.Count > 0)
+ {
+ cols[CurrentColumn].ConcedeFocus();
+ }
+
+ gridState[GRIDSTATE_childLinkFocused] = true;
+ // let the grid regain focus
+ // introduced because of that BeginInvoke thing in the OnLeave method....
+ if (gridState[GRIDSTATE_canFocus] && CanFocus && !Focused)
+ {
+ Focus();
+ }
+
+ return true;
+ }
+ }
+
+ // actually, it turns out that we should leave the
+ // control if we are in the last row
+ if ((currentRow == DataGridRowsLength - 1) && ((keyData & Keys.Shift) == 0))
+ {
+ EndEdit();
+ return base.ProcessDialogKey(keyData);
+ }
+ }
+
+ if (CurrentColumn == firstColumnMarkedVisible)
+ {
+ // if the childLink is focused, then navigate within the relations
+ // in the row, otherwise expand the relations list for the row above
+ if (!gridState[GRIDSTATE_childLinkFocused])
+ {
+ if (CurrentRow != 0 && (keyData & Keys.Shift) == Keys.Shift)
+ {
+ if (localRows[CurrentRow - 1].ProcessTabKey(keyData, layout.RowHeaders, isRightToLeft()))
+ {
+ CurrentRow--;
+ if (cols.Count > 0)
+ {
+ cols[CurrentColumn].ConcedeFocus();
+ }
+
+ gridState[GRIDSTATE_childLinkFocused] = true;
+ // let the grid regain focus
+ // introduced because of that BeginInvoke thing in the OnLeave method....
+ if (gridState[GRIDSTATE_canFocus] && CanFocus && !Focused)
+ {
+ Focus();
+ }
+
+ return true;
+ }
+ }
+ }
+ else
+ {
+ if (localRows[CurrentRow].ProcessTabKey(keyData, layout.RowHeaders, isRightToLeft()))
+ {
+ return true;
+ }
+ else
+ {
+ // we were on the firstColumn, previously the link was focused
+ // we have to navigate to the last column
+ gridState[GRIDSTATE_childLinkFocused] = false;
+ CurrentColumn = lastColumnMarkedVisible;
+ return true;
+ }
+ }
+
+ // if we are on the first cell ( not on the addNewRow )
+ // then shift - tab should move to the next control on the form
+ if (currentRow == 0 && ((keyData & Keys.Shift) == Keys.Shift))
+ {
+ EndEdit();
+ return base.ProcessDialogKey(keyData);
+ }
+ }
+
+ // move
+ if ((keyData & Keys.Shift) != Keys.Shift)
+ {
+ // forward
+ if (CurrentColumn == lastColumnMarkedVisible)
+ {
+ if (CurrentRow != DataGridRowsLength - 1)
+ {
+ CurrentColumn = firstColumnMarkedVisible;
+ }
+
+ CurrentRow += 1;
+ }
+ else
+ {
+ int nextCol = MoveLeftRight(cols, currentCol, true); // true for going right;
+ Debug.Assert(nextCol < cols.Count, "we already checked that we are not at the lastColumnMarkedVisible");
+ CurrentColumn = nextCol;
+ }
+ }
+ else
+ {
+ // backward
+ if (CurrentColumn == firstColumnMarkedVisible)
+ {
+ if (CurrentRow != 0)
+ {
+ CurrentColumn = lastColumnMarkedVisible;
+ }
+
+ if (!gridState[GRIDSTATE_childLinkFocused]) //
+ {
+ CurrentRow--;
+ }
+ }
+ else if (gridState[GRIDSTATE_childLinkFocused] && CurrentColumn == lastColumnMarkedVisible)
+ {
+ // part deux: when we hilite the childLink and then press shift-tab, we
+ // don't want to navigate at the second to last column
+ InvalidateRow(currentRow);
+ Edit();
+ }
+ else
+ {
+ int prevCol = MoveLeftRight(cols, currentCol, false); // false for going left
+ Debug.Assert(prevCol != -1, "we already checked that we are not at the first columnMarked visible");
+ CurrentColumn = prevCol;
+ }
+ }
+
+ // if we got here, then invalidate childLinkFocused
+ //
+ gridState[GRIDSTATE_childLinkFocused] = false;
+
+ // Begin another edit if we were editing before
+ if (wasEditing)
+ {
+ ResetSelection();
+ Edit();
+ }
+
+ return true;
+ }
+
+ virtual protected void CancelEditing()
+ {
+ CancelCursorUpdate();
+ // yield the escape key if there is no editing
+ // make the last row a DataGridAddNewRow
+ if (gridState[GRIDSTATE_inAddNewRow])
+ {
+ gridState[GRIDSTATE_inAddNewRow] = false;
+ DataGridRow[] localGridRows = DataGridRows;
+
+ localGridRows[DataGridRowsLength - 1] = new DataGridAddNewRow(this, myGridTable, DataGridRowsLength - 1);
+ SetDataGridRows(localGridRows, DataGridRowsLength);
+ }
+ }
+
+ internal void RecalculateFonts()
+ {
+ try
+ {
+ linkFont = new Font(Font, FontStyle.Underline);
+ }
+ catch
+ {
+ }
+
+ fontHeight = Font.Height;
+ linkFontHeight = LinkFont.Height;
+ captionFontHeight = CaptionFont.Height;
+
+ if (myGridTable is null || myGridTable.IsDefault)
+ {
+ headerFontHeight = HeaderFont.Height;
+ }
+ else
+ {
+ headerFontHeight = myGridTable.HeaderFont.Height;
+ }
+ }
+
+ // the BackButtonClicked event:
+ //
+ ///
+ /// Occurs when the BackButton is clicked.
+ ///
+ [
+ SRCategory(nameof(SR.CatAction)),
+ ]
+ public event EventHandler BackButtonClick
+ {
+ add => Events.AddHandler(EVENT_BACKBUTTONCLICK, value);
+ remove => Events.RemoveHandler(EVENT_BACKBUTTONCLICK, value);
+ }
+
+ // the DownButtonClick event
+ //
+ ///
+ /// Occurs when the Down button is clicked.
+ ///
+ [
+ SRCategory(nameof(SR.CatAction)),
+ ]
+ public event EventHandler ShowParentDetailsButtonClick
+ {
+ add => Events.AddHandler(EVENT_DOWNBUTTONCLICK, value);
+ remove => Events.RemoveHandler(EVENT_DOWNBUTTONCLICK, value);
+ }
+
+ private void ResetMouseState()
+ {
+ oldRow = -1;
+ gridState[GRIDSTATE_overCaption] = true;
+ }
+
+ ///
+ /// Turns off selection for all rows that are selected.
+ ///
+ protected void ResetSelection()
+ {
+ if (numSelectedRows > 0)
+ {
+ DataGridRow[] localGridRows = DataGridRows;
+ for (int i = 0; i < DataGridRowsLength; ++i)
+ {
+ if (localGridRows[i].Selected)
+ {
+ localGridRows[i].Selected = false;
+ }
+ }
+ }
+
+ numSelectedRows = 0;
+ lastRowSelected = -1;
+ }
+
+ private void ResetParentRows()
+ {
+ parentRows.Clear();
+ originalState = null;
+ caption.BackButtonActive = caption.DownButtonActive = caption.BackButtonVisible = false;
+ caption.SetDownButtonDirection(!layout.ParentRowsVisible);
+ }
+
+ ///
+ /// Re-initializes all UI related state.
+ ///
+ private void ResetUIState()
+ {
+ gridState[GRIDSTATE_childLinkFocused] = false;
+ ResetSelection();
+ ResetMouseState();
+ PerformLayout();
+ Invalidate(); // we want to invalidate after we set up the scrollbars
+
+ // invalidate the horizontalscrollbar and the vertical scrollbar
+ //
+ if (horizScrollBar.Visible)
+ {
+ horizScrollBar.Invalidate();
+ }
+
+ if (vertScrollBar.Visible)
+ {
+ vertScrollBar.Invalidate();
+ }
+ }
+
+ ///
+ /// Scrolls the datagrid down an arbritrary number of rows.
+ ///
+ private void ScrollDown(int rows)
+ {
+ //Debug.WriteLineIf(CompModSwitches.DataGridScrolling.TraceVerbose, "DataGridScrolling: ScrollDown, rows = " + rows.ToString());
+ if (rows != 0)
+ {
+ ClearRegionCache();
+
+ // we should put "dataGridRowsLength -1"
+ int newFirstRow = Math.Max(0, Math.Min(firstVisibleRow + rows, DataGridRowsLength - 1));
+ int oldFirstRow = firstVisibleRow;
+ firstVisibleRow = newFirstRow;
+ vertScrollBar.Value = newFirstRow;
+ bool wasEditing = gridState[GRIDSTATE_isEditing];
+ ComputeVisibleRows();
+
+ if (gridState[GRIDSTATE_isScrolling])
+ {
+ Edit();
+ // isScrolling is set to TRUE when the user scrolls.
+ // once we move the edit box, we finished processing the scroll event, so set isScrolling to FALSE
+ // to set isScrolling to TRUE, we need another scroll event.
+ gridState[GRIDSTATE_isScrolling] = false;
+ }
+ else
+ {
+ EndEdit();
+ }
+
+ int deltaY = ComputeRowDelta(oldFirstRow, newFirstRow);
+ Rectangle rowsRect = layout.Data;
+ if (layout.RowHeadersVisible)
+ {
+ rowsRect = Rectangle.Union(rowsRect, layout.RowHeaders);
+ }
+
+ RECT scrollArea = rowsRect;
+ User32.ScrollWindow(this, 0, deltaY, ref scrollArea, ref scrollArea);
+ OnScroll(EventArgs.Empty);
+
+ if (wasEditing)
+ {
+ // invalidate the rowHeader for the
+ InvalidateRowHeader(currentRow);
+ }
+ }
+ }
+
+
+ ///
+ /// Scrolls the datagrid right an arbritrary number of columns.
+ ///
+ private void ScrollRight(int columns)
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridScrolling.TraceVerbose, "DataGridScrolling: ScrollRight, columns = " + columns.ToString(CultureInfo.InvariantCulture));
+ int newCol = firstVisibleCol + columns;
+
+ GridColumnStylesCollection gridColumns = myGridTable.GridColumnStyles;
+ int newColOffset = 0;
+ int nGridCols = gridColumns.Count;
+ int nVisibleCols = 0;
+
+ // if we try to scroll past the last totally visible column,
+ // then the toolTips will dissapear
+ if (myGridTable.IsDefault)
+ {
+ nVisibleCols = nGridCols;
+ }
+ else
+ {
+ for (int i = 0; i < nGridCols; i++)
+ {
+ if (gridColumns[i].PropertyDescriptor is not null)
+ {
+ nVisibleCols++;
+ }
+ }
+ }
+
+#pragma warning disable SA1408 // Conditional expressions should declare precedence
+ if (lastTotallyVisibleCol == nVisibleCols - 1 && columns > 0 ||
+ firstVisibleCol == 0 && columns < 0 && negOffset == 0)
+#pragma warning restore SA1408 // Conditional expressions should declare precedence
+ {
+ return;
+ }
+
+ newCol = Math.Min(newCol, nGridCols - 1);
+
+ for (int i = 0; i < newCol; i++)
+ {
+ // if (gridColumns[i].Visible && gridColumns[i].PropertyDescriptor is not null)
+ if (gridColumns[i].PropertyDescriptor is not null)
+ {
+ newColOffset += gridColumns[i].Width;
+ }
+ }
+
+ HorizontalOffset = newColOffset;
+ }
+
+ ///
+ /// Scrolls a given column into visibility.
+ ///
+ private void ScrollToColumn(int targetCol)
+ {
+ // do not flush the columns to the left
+ // so, scroll only as many columns as is necessary.
+ //
+
+ int dCols = targetCol - firstVisibleCol;
+
+ if (targetCol > lastTotallyVisibleCol && lastTotallyVisibleCol != -1)
+ {
+ dCols = targetCol - lastTotallyVisibleCol;
+ }
+
+ // if only part of the currentCol is visible
+ // then we should still scroll
+ if (dCols != 0 || negOffset != 0)
+ {
+ ScrollRight(dCols);
+ }
+ }
+
+ ///
+ /// Selects a given row
+ ///
+ public void Select(int row)
+ {
+ //
+ Debug.WriteLineIf(CompModSwitches.DataGridSelection.TraceVerbose, "Selecting row " + row.ToString(CultureInfo.InvariantCulture));
+ DataGridRow[] localGridRows = DataGridRows;
+ if (!localGridRows[row].Selected)
+ {
+ localGridRows[row].Selected = true;
+ numSelectedRows++;
+ }
+
+ // when selecting a row, hide the edit box
+ //
+ EndEdit();
+ }
+
+ // this function will pair the listManager w/ a table from the TableStylesCollection.
+ // and for each column in the TableStylesCollection will pair them w/ a propertyDescriptor
+ // from the listManager
+ //
+ // prerequisite: the current table is either the default table, or has the same name as the
+ // list in the listManager.
+ //
+ private void PairTableStylesAndGridColumns(CurrencyManager lm, DataGridTableStyle gridTable, bool forceColumnCreation)
+ {
+ PropertyDescriptorCollection props = lm.GetItemProperties();
+ GridColumnStylesCollection gridCols = gridTable.GridColumnStyles;
+
+ // ]it is possible to have a dataTable w/ an empty string for a name.
+ if (!gridTable.IsDefault && string.Compare(lm.GetListName(), gridTable.MappingName, true, CultureInfo.InvariantCulture) == 0)
+ {
+ // we will force column creation only at runtime
+ if (gridTable.GridColumnStyles.Count == 0 && !DesignMode)
+ {
+ // we have to create some default columns for each of the propertyDescriptors
+ //
+ if (forceColumnCreation)
+ {
+ gridTable.SetGridColumnStylesCollection(lm);
+ }
+ else
+ {
+ gridTable.SetRelationsList(lm);
+ }
+ }
+ else
+ {
+ // it may the case that the user will have two lists w/ the same name.
+ // When switching binding between those different lists, we need to invalidate
+ // the propertyDescriptors from the current gridColumns
+ //
+ for (int i = 0; i < gridCols.Count; i++)
+ {
+ gridCols[i].PropertyDescriptor = null;
+ }
+
+ // pair the propertyDescriptor from each column to the actual property descriptor
+ // from the listManager
+ //
+ for (int i = 0; i < props.Count; i++)
+ {
+ DataGridColumnStyle col = gridCols.MapColumnStyleToPropertyName(props[i].Name);
+ if (col is not null)
+ {
+ col.PropertyDescriptor = props[i];
+ }
+ }
+
+ // TableStyle::SetGridColumnStylesCollection will also set the
+ // relations list in the tableStyle.
+ gridTable.SetRelationsList(lm);
+ }
+ }
+ else
+ {
+ // we should put an assert, that this is the default Table Style
+#if DEBUG
+ Debug.Assert(gridTable.IsDefault, "if we don't have a match, then the dataGRid should have the default table");
+#endif // DEBUG
+ gridTable.SetGridColumnStylesCollection(lm);
+ if (gridTable.GridColumnStyles.Count > 0 && gridTable.GridColumnStyles[0].Width == -1)
+ {
+#if DEBUG
+ GridColumnStylesCollection cols = gridTable.GridColumnStyles;
+ for (int i = 0; i < cols.Count; i++)
+ {
+ Debug.Assert(cols[i].Width == -1, "if one column's width is not initialized, the same should be happening for the rest of the columns");
+ }
+#endif // DEBUG
+ InitializeColumnWidths();
+ }
+ }
+ }
+
+ ///
+ /// Sets the current GridTable for the DataGrid.
+ /// This GridTable is the table which is currently
+ /// being displayed on the grid.
+ ///
+ internal void SetDataGridTable(DataGridTableStyle newTable, bool forceColumnCreation)
+ {
+ // we have to listen to the dataGridTable for the propertyChangedEvent
+ if (myGridTable is not null)
+ {
+ // unwire the propertyChanged event
+ UnWireTableStylePropChanged(myGridTable);
+
+ if (myGridTable.IsDefault)
+ {
+ // reset the propertyDescriptors on the default table.
+ myGridTable.GridColumnStyles.ResetPropertyDescriptors();
+
+ // reset the relationship list from the default table
+ myGridTable.ResetRelationsList();
+ }
+ }
+
+ myGridTable = newTable;
+
+ WireTableStylePropChanged(myGridTable);
+
+ layout.RowHeadersVisible = newTable.IsDefault ? RowHeadersVisible : newTable.RowHeadersVisible;
+
+ // we need to force the grid into the dataGridTableStyle
+ // this way the controls in the columns will be parented
+ // consider this scenario: when the user finished InitializeComponent, it added
+ // a bunch of tables. all of those tables will have the DataGrid property set to this
+ // grid. however, in InitializeComponent the tables will not have parented the
+ // edit controls w/ the grid.
+ //
+ // the code in DataGridTextBoxColumn already checks to see if the edits are parented
+ // before parenting them.
+ //
+ if (newTable is not null)
+ {
+ newTable.DataGrid = this;
+ }
+
+ // pair the tableStyles and GridColumns
+ //
+ if (listManager is not null)
+ {
+ PairTableStylesAndGridColumns(listManager, myGridTable, forceColumnCreation);
+ }
+
+ // reset the relations UI on the newTable
+ if (newTable is not null)
+ {
+ newTable.ResetRelationsUI();
+ }
+
+ // set the isNavigating to false
+ gridState[GRIDSTATE_isNavigating] = false;
+
+ horizScrollBar.Value = 0;
+ firstVisibleRow = 0;
+ currentCol = 0;
+ // if we add a tableStyle that mapps to the
+ // current listName, then we should set the currentRow to the
+ // position in the listManager
+ if (listManager is null)
+ {
+ currentRow = 0;
+ }
+ else
+ {
+ currentRow = listManager.Position == -1 ? 0 : listManager.Position;
+ }
+
+ ResetHorizontalOffset();
+ negOffset = 0;
+ ResetUIState();
+
+ // check the hierarchy
+ checkHierarchy = true;
+ }
+
+ ///
+ /// Scrolls the data area down to make room for the parent rows
+ /// and lays out the different regions of the DataGrid.
+ ///
+ internal void SetParentRowsVisibility(bool visible)
+ {
+ Rectangle parentRowsRect = layout.ParentRows;
+ Rectangle underParentRows = layout.Data;
+
+ if (layout.RowHeadersVisible)
+ {
+ underParentRows.X -= isRightToLeft() ? 0 : layout.RowHeaders.Width;
+ underParentRows.Width += layout.RowHeaders.Width;
+ }
+
+ if (layout.ColumnHeadersVisible)
+ {
+ underParentRows.Y -= layout.ColumnHeaders.Height;
+ underParentRows.Height += layout.ColumnHeaders.Height;
+ }
+
+ // hide the Edit Box
+ EndEdit();
+
+ if (visible)
+ {
+ layout.ParentRowsVisible = true;
+ PerformLayout();
+ Invalidate();
+ }
+ else
+ {
+ RECT scrollRECT = new Rectangle(underParentRows.X, underParentRows.Y - layout.ParentRows.Height, underParentRows.Width, underParentRows.Height + layout.ParentRows.Height);
+
+ User32.ScrollWindow(this, 0, -parentRowsRect.Height, ref scrollRECT, ref scrollRECT);
+
+ // If the vertical scrollbar was visible before and not after
+ // the ScrollWindow call, then we will not get invalidated
+ // completely. We need to translate the visual bounds of
+ // the scrollbar's old location up and invalidate.
+ //
+ if (vertScrollBar.Visible)
+ {
+ Rectangle fixupRect = vertScrollBar.Bounds;
+ fixupRect.Y -= parentRowsRect.Height;
+ fixupRect.Height += parentRowsRect.Height;
+ Invalidate(fixupRect);
+ }
+
+ Debug.WriteLineIf(CompModSwitches.DataGridParents.TraceVerbose, "DataGridParents: Making parent rows invisible.");
+ layout.ParentRowsVisible = false;
+ PerformLayout();
+ }
+ }
+
+ ///
+ /// Sets whether a row is expanded or not.
+ ///
+ private void SetRowExpansionState(int row, bool expanded)
+ {
+ if (row < -1 || row > DataGridRowsLength - (policy.AllowAdd ? 2 : 1))
+ {
+ throw new ArgumentOutOfRangeException(nameof(row));
+ }
+
+ DataGridRow[] localGridRows = DataGridRows;
+ if (row == -1)
+ {
+ DataGridRelationshipRow[] expandableRows = GetExpandableRows();
+ bool repositionEditControl = false;
+
+ for (int r = 0; r < expandableRows.Length; ++r)
+ {
+ if (expandableRows[r].Expanded != expanded)
+ {
+ expandableRows[r].Expanded = expanded;
+ repositionEditControl = true;
+ }
+ }
+
+ if (repositionEditControl)
+ {
+ // we need to reposition the edit control
+ if (gridState[GRIDSTATE_isNavigating] || gridState[GRIDSTATE_isEditing])
+ {
+ ResetSelection();
+ Edit();
+ }
+ }
+ }
+ else if (localGridRows[row] is DataGridRelationshipRow expandableRow)
+ {
+ if (expandableRow.Expanded != expanded)
+ {
+ // we need to reposition the edit control
+ if (gridState[GRIDSTATE_isNavigating] || gridState[GRIDSTATE_isEditing])
+ {
+ ResetSelection();
+ Edit();
+ }
+
+ expandableRow.Expanded = expanded;
+ }
+ }
+ }
+
+ private void ObjectSiteChange(IContainer container, IComponent component, bool site)
+ {
+ if (site)
+ {
+ if (component.Site is null)
+ {
+ container.Add(component);
+ }
+ }
+ else
+ {
+ if (component.Site is not null && component.Site.Container == container)
+ {
+ container.Remove(component);
+ }
+ }
+ }
+
+ public void SubObjectsSiteChange(bool site)
+ {
+ DataGrid dgrid = this;
+ if (dgrid.DesignMode && dgrid.Site is not null)
+ {
+ IDesignerHost host = (IDesignerHost)dgrid.Site.GetService(typeof(IDesignerHost));
+ if (host is not null)
+ {
+ DesignerTransaction trans = host.CreateTransaction();
+ try
+ {
+ IContainer container = dgrid.Site.Container;
+
+ DataGridTableStyle[] tables = new DataGridTableStyle[dgrid.TableStyles.Count];
+ dgrid.TableStyles.CopyTo(tables, 0);
+
+ for (int i = 0; i < tables.Length; i++)
+ {
+ DataGridTableStyle table = tables[i];
+ ObjectSiteChange(container, table, site);
+
+ DataGridColumnStyle[] columns = new DataGridColumnStyle[table.GridColumnStyles.Count];
+ table.GridColumnStyles.CopyTo(columns, 0);
+
+ for (int j = 0; j < columns.Length; j++)
+ {
+ DataGridColumnStyle column = columns[j];
+ ObjectSiteChange(container, column, site);
+ }
+ }
+ }
+ finally
+ {
+ trans.Commit();
+ }
+ }
+ }
+ }
+
+ ///
+ /// Unselects a given row
+ ///
+ public void UnSelect(int row)
+ {
+ //
+ Debug.WriteLineIf(CompModSwitches.DataGridSelection.TraceVerbose, "DataGridSelection: Unselecting row " + row.ToString(CultureInfo.InvariantCulture));
+ DataGridRow[] localGridRows = DataGridRows;
+ if (localGridRows[row].Selected)
+ {
+ localGridRows[row].Selected = false;
+ numSelectedRows--;
+ }
+ }
+
+ ///
+ /// Asks the cursor to update.
+ ///
+ private void UpdateListManager()
+ {
+ Debug.WriteLineIf(CompModSwitches.DataGridCursor.TraceVerbose, "DataGridCursor: Requesting EndEdit()");
+ try
+ {
+ if (listManager is not null)
+ {
+ EndEdit();
+ listManager.EndCurrentEdit();
+ }
+ }
+ catch
+ {
+ }
+ }
+
+ ///
+ /// Will return the string that will be used as a delimiter between columns
+ /// when copying rows contents to the Clipboard.
+ /// At the moment, return "\t"
+ ///
+ protected virtual string GetOutputTextDelimiter()
+ {
+ return "\t";
+ }
+
+ ///
+ /// The accessible object class for a DataGrid. The child accessible objects
+ /// are accessible objects corresponding to the propertygrid entries.
+ ///
+ [ComVisible(true)]
+ internal class DataGridAccessibleObject : ControlAccessibleObject
+ {
+ ///
+ /// Construct a PropertyGridViewAccessibleObject
+ ///
+ public DataGridAccessibleObject(DataGrid owner) : base(owner)
+ {
+ }
+
+ internal DataGrid DataGrid
+ {
+ get
+ {
+ return (DataGrid)Owner;
+ }
+ }
+
+ private int ColumnCountPrivate
+ {
+ get
+ {
+ return ((DataGrid)Owner).myGridTable.GridColumnStyles.Count;
+ }
+ }
+
+ private int RowCountPrivate
+ {
+ get
+ {
+ return ((DataGrid)Owner).dataGridRows.Length;
+ }
+ }
+
+ public override string Name
+ {
+ get
+ {
+ // Special case: If an explicit name has been set in the AccessibleName property, use that.
+ // Note: Any non-null value in AccessibleName overrides the default accessible name logic,
+ // even an empty string (this is the only way to *force* the accessible name to be blank).
+ string name = Owner.AccessibleName;
+ if (name is not null)
+ {
+ return name;
+ }
+
+ // Otherwise just return the default label string, minus any mnemonics
+ return "DataGrid";
+ }
+
+ set
+ {
+ // If anyone tries to set the accessible name, just cache the value in the control's
+ // AccessibleName property. This value will then end up overriding the normal accessible
+ // name logic, until such time as AccessibleName is set back to null.
+ Owner.AccessibleName = value;
+ }
+ }
+
+ public override AccessibleRole Role
+ {
+ get
+ {
+ AccessibleRole role = Owner.AccessibleRole;
+ if (role != AccessibleRole.Default)
+ {
+ return role;
+ }
+
+ return AccessibleRole.Table;
+ }
+ }
+
+ public override AccessibleObject GetChild(int index)
+ {
+ DataGrid dataGrid = (DataGrid)Owner;
+
+ int cols = ColumnCountPrivate;
+ int rows = RowCountPrivate;
+
+ if (dataGrid.dataGridRows is null)
+ {
+ dataGrid.CreateDataGridRows();
+ }
+
+ if (index < 1)
+ {
+ return dataGrid.ParentRowsAccessibleObject;
+ }
+ else
+ {
+ index -= 1;
+ if (index < cols)
+ {
+ return dataGrid.myGridTable.GridColumnStyles[index].HeaderAccessibleObject;
+ }
+ else
+ {
+ index -= cols;
+
+ if (index < rows)
+ {
+ Debug.Assert(dataGrid.dataGridRows[index].RowNumber == index, "Row number is wrong!");
+ return dataGrid.dataGridRows[index].AccessibleObject;
+ }
+ else
+ {
+ index -= rows;
+
+ if (dataGrid.horizScrollBar.Visible)
+ {
+ if (index == 0)
+ {
+ return dataGrid.horizScrollBar.AccessibilityObject;
+ }
+
+ index--;
+ }
+
+ if (dataGrid.vertScrollBar.Visible)
+ {
+ if (index == 0)
+ {
+ return dataGrid.vertScrollBar.AccessibilityObject;
+ }
+
+ index--;
+ }
+
+ int colCount = dataGrid.myGridTable.GridColumnStyles.Count;
+ int rowCount = dataGrid.dataGridRows.Length;
+ int currentRow = index / colCount;
+ int currentCol = index % colCount;
+
+ if (currentRow < dataGrid.dataGridRows.Length && currentCol < dataGrid.myGridTable.GridColumnStyles.Count)
+ {
+ return dataGrid.dataGridRows[currentRow].AccessibleObject.GetChild(currentCol);
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public override int GetChildCount()
+ {
+ int n = 1 + ColumnCountPrivate + ((DataGrid)Owner).DataGridRowsLength;
+ if (DataGrid.horizScrollBar.Visible)
+ {
+ n++;
+ }
+
+ if (DataGrid.vertScrollBar.Visible)
+ {
+ n++;
+ }
+
+ n += DataGrid.DataGridRows.Length * DataGrid.myGridTable.GridColumnStyles.Count;
+ return n;
+ }
+
+ public override AccessibleObject GetFocused()
+ {
+ if (DataGrid.Focused)
+ {
+ return GetSelected();
+ }
+
+ return null;
+ }
+
+ public override AccessibleObject GetSelected()
+ {
+ if (DataGrid.DataGridRows.Length == 0 || DataGrid.myGridTable.GridColumnStyles.Count == 0)
+ {
+ return null;
+ }
+
+ DataGridCell cell = DataGrid.CurrentCell;
+ return GetChild(1 + ColumnCountPrivate + cell.RowNumber).GetChild(cell.ColumnNumber);
+ }
+
+ public override AccessibleObject HitTest(int x, int y)
+ {
+ Point client = DataGrid.PointToClient(new Point(x, y));
+ HitTestInfo hti = DataGrid.HitTest(client.X, client.Y);
+
+ switch (hti.Type)
+ {
+ case HitTestType.RowHeader:
+ return GetChild(1 + ColumnCountPrivate + hti.Row);
+ case HitTestType.Cell:
+ return GetChild(1 + ColumnCountPrivate + hti.Row).GetChild(hti.Column);
+ case HitTestType.ColumnHeader:
+ return GetChild(1 + hti.Column);
+ case HitTestType.ParentRows:
+ return DataGrid.ParentRowsAccessibleObject;
+ case HitTestType.None:
+ case HitTestType.ColumnResize:
+ case HitTestType.RowResize:
+ case HitTestType.Caption:
+ break;
+ }
+
+ return null;
+ }
+
+ public override AccessibleObject Navigate(AccessibleNavigation navdir)
+ {
+ // We're only handling FirstChild and LastChild here
+ if (GetChildCount() > 0)
+ {
+ switch (navdir)
+ {
+ case AccessibleNavigation.FirstChild:
+ return GetChild(0);
+ case AccessibleNavigation.LastChild:
+ return GetChild(GetChildCount() - 1);
+ }
+ }
+
+ return null; // Perform default behavior
+ }
+ }
+
+ //
+ // This simple data structure holds all of the layout information
+ // for the DataGrid.
+ //
+ internal class LayoutData
+ {
+ internal bool dirty = true;
+ // region inside the Control's borders.
+ public Rectangle Inside = Rectangle.Empty;
+
+ public Rectangle RowHeaders = Rectangle.Empty;
+
+ public Rectangle TopLeftHeader = Rectangle.Empty;
+ public Rectangle ColumnHeaders = Rectangle.Empty;
+ public Rectangle Data = Rectangle.Empty;
+
+ public Rectangle Caption = Rectangle.Empty;
+ public Rectangle ParentRows = Rectangle.Empty;
+
+ public Rectangle ResizeBoxRect = Rectangle.Empty;
+
+ public bool ColumnHeadersVisible;
+ public bool RowHeadersVisible;
+ public bool CaptionVisible;
+ public bool ParentRowsVisible;
+
+ // used for resizing.
+ public Rectangle ClientRectangle = Rectangle.Empty;
+
+ public LayoutData()
+ {
+ }
+
+ public LayoutData(LayoutData src)
+ {
+ GrabLayout(src);
+ }
+
+ private void GrabLayout(LayoutData src)
+ {
+ Inside = src.Inside;
+ TopLeftHeader = src.TopLeftHeader;
+ ColumnHeaders = src.ColumnHeaders;
+ RowHeaders = src.RowHeaders;
+ Data = src.Data;
+ Caption = src.Caption;
+ ParentRows = src.ParentRows;
+ ResizeBoxRect = src.ResizeBoxRect;
+ ColumnHeadersVisible = src.ColumnHeadersVisible;
+ RowHeadersVisible = src.RowHeadersVisible;
+ CaptionVisible = src.CaptionVisible;
+ ParentRowsVisible = src.ParentRowsVisible;
+ ClientRectangle = src.ClientRectangle;
+ }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder(200);
+ sb.Append(base.ToString());
+ sb.Append(" { \n");
+ sb.Append("Inside = ");
+ sb.Append(Inside.ToString());
+ sb.Append('\n');
+ sb.Append("TopLeftHeader = ");
+ sb.Append(TopLeftHeader.ToString());
+ sb.Append('\n');
+ sb.Append("ColumnHeaders = ");
+ sb.Append(ColumnHeaders.ToString());
+ sb.Append('\n');
+ sb.Append("RowHeaders = ");
+ sb.Append(RowHeaders.ToString());
+ sb.Append('\n');
+ sb.Append("Data = ");
+ sb.Append(Data.ToString());
+ sb.Append('\n');
+ sb.Append("Caption = ");
+ sb.Append(Caption.ToString());
+ sb.Append('\n');
+ sb.Append("ParentRows = ");
+ sb.Append(ParentRows.ToString());
+ sb.Append('\n');
+ sb.Append("ResizeBoxRect = ");
+ sb.Append(ResizeBoxRect.ToString());
+ sb.Append('\n');
+ sb.Append("ColumnHeadersVisible = ");
+ sb.Append(ColumnHeadersVisible);
+ sb.Append('\n');
+ sb.Append("RowHeadersVisible = ");
+ sb.Append(RowHeadersVisible);
+ sb.Append('\n');
+ sb.Append("CaptionVisible = ");
+ sb.Append(CaptionVisible);
+ sb.Append('\n');
+ sb.Append("ParentRowsVisible = ");
+ sb.Append(ParentRowsVisible);
+ sb.Append('\n');
+ sb.Append("ClientRectangle = ");
+ sb.Append(ClientRectangle.ToString());
+ sb.Append(" } ");
+ return sb.ToString();
+ }
+ }
+
+ ///
+ /// Contains information
+ /// about the part of the control the user
+ /// has clicked. This class cannot be inherited.
+ ///
+ public sealed class HitTestInfo
+ {
+ internal HitTestType type = HitTestType.None;
+
+ internal int row;
+ internal int col;
+
+ ///
+ /// Allows the object to inform you the
+ /// extent of the grid.
+ ///
+ public static readonly HitTestInfo Nowhere = new HitTestInfo();
+
+ internal HitTestInfo()
+ {
+ type = (HitTestType)0;
+ row = col = -1;
+ }
+
+ internal HitTestInfo(HitTestType type)
+ {
+ this.type = type;
+ row = col = -1;
+ }
+
+ ///
+ /// Gets the number of the clicked column.
+ ///
+ public int Column
+ {
+ get
+ {
+ return col;
+ }
+ }
+
+ ///
+ /// Gets the
+ /// number of the clicked row.
+ ///
+ public int Row
+ {
+ get
+ {
+ return row;
+ }
+ }
+
+ ///
+ /// Gets the part of the control, other than the row or column, that was
+ /// clicked.
+ ///
+ public HitTestType Type
+ {
+ get
+ {
+ return type;
+ }
+ }
+
+ ///
+ /// Indicates whether two objects are identical.
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj is HitTestInfo ci)
+ {
+ return (type == ci.type &&
+ row == ci.row &&
+ col == ci.col);
+ }
+
+ return false;
+ }
+
+ ///
+ /// Gets the hash code for the instance.
+ ///
+ public override int GetHashCode() => HashCode.Combine(type, row, col);
+
+ ///
+ /// Gets the type, row number, and column number.
+ ///
+ public override string ToString()
+ {
+ return "{ " + ((type).ToString()) + "," + row.ToString(CultureInfo.InvariantCulture) + "," + col.ToString(CultureInfo.InvariantCulture) + "}";
+ }
+ }
+
+ ///
+ /// Specifies the part of the
+ /// control the user has clicked.
+ ///
+ [Flags]
+ public enum HitTestType
+ {
+ None = 0x00000000,
+ Cell = 0x00000001,
+ ColumnHeader = 0x00000002,
+ RowHeader = 0x00000004,
+ ColumnResize = 0x00000008,
+ RowResize = 0x00000010,
+ Caption = 0x00000020,
+ ParentRows = 0x00000040
+ }
+
+ ///
+ /// Holds policy information for what the grid can and cannot do.
+ ///
+ private class Policy
+ {
+ private bool allowAdd = true;
+ private bool allowEdit = true;
+ private bool allowRemove = true;
+
+ public Policy()
+ {
+ }
+
+ public bool AllowAdd
+ {
+ get
+ {
+ return allowAdd;
+ }
+ set
+ {
+ if (allowAdd != value)
+ {
+ allowAdd = value;
+ }
+ }
+ }
+
+ public bool AllowEdit
+ {
+ get
+ {
+ return allowEdit;
+ }
+ set
+ {
+ if (allowEdit != value)
+ {
+ allowEdit = value;
+ }
+ }
+ }
+
+ public bool AllowRemove
+ {
+ get
+ {
+ return allowRemove;
+ }
+ set
+ {
+ if (allowRemove != value)
+ {
+ allowRemove = value;
+ }
+ }
+ }
+
+ // returns true if the UI needs to be updated (here because addnew has changed)
+ public bool UpdatePolicy(CurrencyManager listManager, bool gridReadOnly)
+ {
+ bool change = false;
+ // only IBindingList can have an AddNewRow
+ IBindingList bl = listManager is null ? null : listManager.List as IBindingList;
+ if (listManager is null)
+ {
+ if (!allowAdd)
+ {
+ change = true;
+ }
+
+ allowAdd = allowEdit = allowRemove = true;
+ }
+ else
+ {
+ if (AllowAdd != listManager.AllowAdd && !gridReadOnly)
+ {
+ change = true;
+ }
+
+ AllowAdd = listManager.AllowAdd && !gridReadOnly && bl is not null && bl.SupportsChangeNotification;
+ AllowEdit = listManager.AllowEdit && !gridReadOnly;
+ AllowRemove = listManager.AllowRemove && !gridReadOnly && bl is not null && bl.SupportsChangeNotification; //
+ }
+
+ return change;
+ }
+ }
+
+ //
+ // Given the x coordinate and the Width of rectangle R1 inside rectangle rect,
+ // this function returns the x coordinate of the rectangle that
+ // corresponds to the Bi-Di transformation
+ //
+ private int MirrorRectangle(Rectangle R1, Rectangle rect, bool rightToLeft)
+ {
+ if (rightToLeft)
+ {
+ return rect.Right + rect.X - R1.Right;
+ }
+ else
+ {
+ return R1.X;
+ }
+ }
+
+ //
+ // Given the x coordinate of a point inside rectangle rect,
+ // this function returns the x coordinate of the point that
+ // corresponds to the Bi-Di transformation
+ //
+ private int MirrorPoint(int x, Rectangle rect, bool rightToLeft)
+ {
+ if (rightToLeft)
+ {
+ return rect.Right + rect.X - x;
+ }
+ else
+ {
+ return x;
+ }
+ }
+
+ // This function will return true if the RightToLeft property of the dataGrid is
+ // set to YES
+ private bool isRightToLeft()
+ {
+ return (RightToLeft == RightToLeft.Yes);
+ }
}
-
- protected virtual void OnBorderStyleChanged(EventArgs e) { }
-
- protected virtual void OnCaptionVisibleChanged(EventArgs e) { }
-
- protected virtual void OnCurrentCellChanged(EventArgs e) { }
-
- protected virtual void OnFlatModeChanged(EventArgs e) { }
-
- protected virtual void OnBackgroundColorChanged(EventArgs e) { }
-
- protected virtual void OnAllowNavigationChanged(EventArgs e) { }
-
- protected virtual void OnParentRowsVisibleChanged(EventArgs e) { }
-
- protected virtual void OnParentRowsLabelStyleChanged(EventArgs e) { }
-
- protected virtual void OnReadOnlyChanged(EventArgs e) { }
-
- protected void OnNavigate(NavigateEventArgs e) { }
-
- protected void OnRowHeaderClick(EventArgs e) { }
-
- protected void OnScroll(EventArgs e) { }
-
- protected virtual void GridHScrolled(object sender, ScrollEventArgs se) { }
-
- protected virtual void GridVScrolled(object sender, ScrollEventArgs se) { }
-
- protected void OnBackButtonClicked(object sender, EventArgs e) { }
-
- protected virtual void OnDataSourceChanged(EventArgs e) { }
-
- protected void OnShowParentDetailsButtonClicked(object sender, EventArgs e) { }
-
- public event NavigateEventHandler Navigate
- {
- add { }
- remove { }
- }
-
- protected event EventHandler RowHeaderClick
- {
- add { }
- remove { }
- }
-
- public event EventHandler Scroll
- {
- add { }
- remove { }
- }
-
- public bool BeginEdit(DataGridColumnStyle gridColumn, int rowNumber) => throw null;
-
- public void BeginInit() { }
-
- public void Collapse(int row) { }
-
- protected internal virtual void ColumnStartedEditing(Rectangle bounds) { }
-
- protected internal virtual void ColumnStartedEditing(Control editingControl) { }
-
- public bool EndEdit(DataGridColumnStyle gridColumn, int rowNumber, bool shouldAbort) => throw null;
-
- public void Expand(int row) { }
-
- protected virtual DataGridColumnStyle CreateGridColumn(PropertyDescriptor prop, bool isDefault) => throw null;
-
- protected virtual DataGridColumnStyle CreateGridColumn(PropertyDescriptor prop) => throw null;
-
- public void EndInit() { }
-
- public Rectangle GetCurrentCellBounds() => throw null;
-
- public Rectangle GetCellBounds(int row, int col) => throw null;
-
- public Rectangle GetCellBounds(DataGridCell dgc) => throw null;
-
- public HitTestInfo HitTest(int x, int y) => throw null;
-
- public HitTestInfo HitTest(Point position) => throw null;
-
- public bool IsExpanded(int rowNumber) => throw null;
-
- public bool IsSelected(int row) => throw null;
-
- public void NavigateBack() { }
-
- public void NavigateTo(int rowNumber, string relationName) { }
-
- protected bool ProcessGridKey(KeyEventArgs ke) => throw null;
-
- protected bool ProcessTabKey(Keys keyData) => throw null;
-
- protected virtual void CancelEditing() { }
-
- public event EventHandler BackButtonClick
- {
- add { }
- remove { }
- }
-
- public event EventHandler ShowParentDetailsButtonClick
- {
- add { }
- remove { }
- }
-
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public new event EventHandler TextChanged
- {
- add { }
- remove { }
- }
-
- protected void ResetSelection() { }
-
- public void Select(int row) { }
-
- public void SubObjectsSiteChange(bool site) { }
-
- public void UnSelect(int row) { }
-
- protected virtual string GetOutputTextDelimiter() => throw null;
-}
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridAddNewRow.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridAddNewRow.cs
new file mode 100644
index 00000000000..48af3cd7715
--- /dev/null
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridAddNewRow.cs
@@ -0,0 +1,127 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma warning disable IDE0040, IDE1006, SA1005, SA1206, SA1400, CA1510, CSIsNull001, CSIsNull002, WFDEV006
+
+#nullable disable
+
+using System.Drawing;
+
+namespace System.Windows.Forms;
+ ///
+ /// This class fully encapsulates the painting logic for an addnew row
+ /// appearing in a DataGrid.
+ ///
+ internal class DataGridAddNewRow : DataGridRow
+ {
+ private bool dataBound;
+
+ public DataGridAddNewRow(DataGrid dGrid, DataGridTableStyle gridTable, int rowNum)
+ : base(dGrid, gridTable, rowNum)
+ {
+ }
+
+ // =------------------------------------------------------------------
+ // = Methods
+ // =------------------------------------------------------------------
+
+ ///
+ /// Since the DataView does not return a valid DataRow for
+ /// a newly added row, the DataGrid sets this property to
+ /// true to signal that the AddNewRow can safely render
+ /// row contents and permit editing, etc because a DataRecord
+ /// exists in the cursor that created this row.
+ ///
+ public bool DataBound
+ {
+ get
+ {
+ return dataBound;
+ }
+ set
+ {
+ dataBound = value;
+ }
+ }
+
+ public override void OnEdit()
+ {
+ if (!DataBound)
+ {
+ DataGrid.AddNewRow();
+ }
+ }
+
+ public override void OnRowLeave()
+ {
+ if (DataBound)
+ {
+ DataBound = false;
+ }
+ }
+
+ // the addNewRow has nothing to do with losing focus
+ //
+ internal override void LoseChildFocus(Rectangle rowHeader, bool alignToRight)
+ {
+ }
+
+ // the newDataRow has nothing to do with TAB keys
+ //
+ internal override bool ProcessTabKey(Keys keyData, Rectangle rowHeaders, bool alignToRight)
+ {
+ return false;
+ }
+
+ ///
+ /// Paints the row.
+ ///
+ public override int Paint(Graphics g, Rectangle bounds, Rectangle trueRowBounds, int firstVisibleColumn, int columnCount)
+ {
+ return Paint(g, bounds, trueRowBounds, firstVisibleColumn, columnCount, false);
+ }
+
+ public override int Paint(Graphics g,
+ Rectangle bounds,
+ Rectangle trueRowBounds,
+ int firstVisibleColumn,
+ int columnCount,
+ bool alignToRight)
+ {
+ Rectangle dataBounds = bounds;
+ DataGridLineStyle gridStyle;
+ if (dgTable.IsDefault)
+ {
+ gridStyle = DataGrid.GridLineStyle;
+ }
+ else
+ {
+ gridStyle = dgTable.GridLineStyle;
+ }
+
+ int bWidth = DataGrid == null ? 0 : gridStyle == DataGridLineStyle.Solid ? 1 : 0;
+ dataBounds.Height -= bWidth;
+ int cx = base.PaintData(g, dataBounds, firstVisibleColumn, columnCount, alignToRight);
+
+ if (bWidth > 0)
+ {
+ PaintBottomBorder(g, bounds, cx, bWidth, alignToRight);
+ }
+
+ return cx;
+ }
+
+ protected override void PaintCellContents(Graphics g, Rectangle cellBounds, DataGridColumnStyle column,
+ Brush backBr, Brush foreBrush, bool alignToRight)
+ {
+ if (DataBound)
+ {
+ CurrencyManager listManager = DataGrid.ListManager;
+ column.Paint(g, cellBounds, listManager, RowNumber, alignToRight);
+ }
+ else
+ {
+ base.PaintCellContents(g, cellBounds, column, backBr, foreBrush, alignToRight);
+ }
+ }
+ }
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridBoolColumn.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridBoolColumn.cs
index 92c4b866ea2..8b269899f94 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridBoolColumn.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridBoolColumn.cs
@@ -1,105 +1,536 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#pragma warning disable IDE0040, IDE1006, SA1005, SA1206, SA1400, CA1510, CSIsNull001, CSIsNull002, RS0016, IDE0100, CA1822
+
+#nullable disable
+
using System.ComponentModel;
using System.Drawing;
namespace System.Windows.Forms;
+ ///
+ /// Specifies a column in
+ /// which each cell contains a check box for representing
+ /// a boolean value.
+ ///
+ public class DataGridBoolColumn : DataGridColumnStyle
+ {
+ private const int idealCheckSize = 14;
-#nullable disable
+ private bool isEditing;
+ private bool isSelected;
+ private bool allowNull = true;
+ private int editingRow = -1;
+ private object currentValue = Convert.DBNull;
-///
-/// This type is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code.
-///
-[Obsolete(
- Obsoletions.DataGridMessage,
- error: false,
- DiagnosticId = Obsoletions.UnsupportedControlsDiagnosticId,
- UrlFormat = Obsoletions.SharedUrlFormat)]
-[EditorBrowsable(EditorBrowsableState.Never)]
-[Browsable(false)]
-public class DataGridBoolColumn : DataGridColumnStyle
-{
- public DataGridBoolColumn() => throw new PlatformNotSupportedException();
-
- public DataGridBoolColumn(PropertyDescriptor prop) => throw new PlatformNotSupportedException();
-
- public DataGridBoolColumn(PropertyDescriptor prop, bool isDefault) => throw new PlatformNotSupportedException();
-
- [DefaultValue(true)]
- public bool AllowNull
- {
- get => throw null;
- set { }
- }
+ private object trueValue = true;
+ private object falseValue = false;
+ private object nullValue = Convert.DBNull;
- public event EventHandler AllowNullChanged
- {
- add { }
- remove { }
- }
+ private static readonly object EventTrueValue = new object();
+ private static readonly object EventFalseValue = new object();
+ private static readonly object EventAllowNull = new object();
- [TypeConverter(typeof(StringConverter))]
- [DefaultValue(true)]
- public object TrueValue
- {
- get => throw null;
- set { }
- }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public DataGridBoolColumn() : base() { }
- public event EventHandler TrueValueChanged
- {
- add { }
- remove { }
- }
+ ///
+ /// Initializes a new instance of a with the specified .
+ ///
+ public DataGridBoolColumn(PropertyDescriptor prop)
+ : base(prop) { }
- [TypeConverter(typeof(StringConverter))]
- [DefaultValue(false)]
- public object FalseValue
- {
- get => throw null;
- set { }
- }
+ public DataGridBoolColumn(PropertyDescriptor prop, bool isDefault)
+ : base(prop, isDefault) { }
- public event EventHandler FalseValueChanged
- {
- add { }
- remove { }
- }
+ ///
+ /// Gets or sets the actual value used when setting the
+ /// value of the column to .
+ ///
+ [TypeConverter(typeof(StringConverter)),
+ DefaultValue(true)]
+ public object TrueValue
+ {
+ get
+ {
+ return trueValue;
+ }
+ set
+ {
+ if (!trueValue.Equals(value))
+ {
+ trueValue = value;
+ OnTrueValueChanged(EventArgs.Empty);
+ Invalidate();
+ }
+ }
+ }
- [TypeConverter(typeof(StringConverter))]
- public object NullValue
- {
- get => throw null;
- set { }
- }
+ public event EventHandler TrueValueChanged
+ {
+ add => Events.AddHandler(EventTrueValue, value);
+ remove => Events.RemoveHandler(EventTrueValue, value);
+ }
+
+ ///
+ /// Gets or sets the actual value used when setting the value of the column to
+ /// .
+ ///
+ [TypeConverter(typeof(StringConverter)), DefaultValue(false)]
+ public object FalseValue
+ {
+ get
+ {
+ return falseValue;
+ }
+ set
+ {
+ if (!falseValue.Equals(value))
+ {
+ falseValue = value;
+ OnFalseValueChanged(EventArgs.Empty);
+ Invalidate();
+ }
+ }
+ }
+
+ public event EventHandler FalseValueChanged
+ {
+ add => Events.AddHandler(EventFalseValue, value);
+ remove => Events.RemoveHandler(EventFalseValue, value);
+ }
+
+ ///
+ /// Gets or sets the actual value used when setting the value of the column to
+ /// .
+ ///
+ [TypeConverter(typeof(StringConverter))]
+ public object NullValue
+ {
+ get
+ {
+ return nullValue;
+ }
+ set
+ {
+ if (!nullValue.Equals(value))
+ {
+ nullValue = value;
+ OnFalseValueChanged(EventArgs.Empty);
+ Invalidate();
+ }
+ }
+ }
+
+ // =------------------------------------------------------------------
+ // = Methods
+ // =------------------------------------------------------------------
+
+ // when the grid is in addNewRow it means that the user did not start typing
+ // so there is no data to be pushed back into the backEnd.
+ // make isEditing false so that in the Commit call we do not do any work.
+ //
+ protected internal override void ConcedeFocus()
+ {
+ base.ConcedeFocus();
+ isSelected = false;
+ isEditing = false;
+ }
+
+ private Rectangle GetCheckBoxBounds(Rectangle bounds, bool alignToRight)
+ {
+ if (alignToRight)
+ {
+ return new Rectangle(bounds.X + ((bounds.Width - idealCheckSize) / 2),
+ bounds.Y + ((bounds.Height - idealCheckSize) / 2),
+ bounds.Width < idealCheckSize ? bounds.Width : idealCheckSize,
+ idealCheckSize);
+ }
+ else
+ {
+ return new Rectangle(Math.Max(0, bounds.X + ((bounds.Width - idealCheckSize) / 2)),
+ Math.Max(0, bounds.Y + ((bounds.Height - idealCheckSize) / 2)),
+ bounds.Width < idealCheckSize ? bounds.Width : idealCheckSize,
+ idealCheckSize);
+ }
+ }
+
+ ///
+ /// Gets the value at the specified row.
+ ///
+ protected internal override object GetColumnValueAtRow(CurrencyManager source, int rowNum)
+ {
+ object baseValue = base.GetColumnValueAtRow(source, rowNum);
+ object value = Convert.DBNull;
+ if (baseValue.Equals(trueValue))
+ {
+ value = true;
+ }
+ else if (baseValue.Equals(falseValue))
+ {
+ value = false;
+ }
+
+ return value;
+ }
+
+ private bool IsReadOnly()
+ {
+ bool ret = ReadOnly;
+ if (DataGridTableStyle != null)
+ {
+ ret = ret || DataGridTableStyle.ReadOnly;
+ if (DataGridTableStyle.DataGrid != null)
+ {
+ ret = ret || DataGridTableStyle.DataGrid.ReadOnly;
+ }
+ }
+
+ return ret;
+ }
+
+ ///
+ /// Sets the value a a specified row.
+ ///
+ protected internal override void SetColumnValueAtRow(CurrencyManager source, int rowNum, object value)
+ {
+ object baseValue = null;
+ if (true.Equals(value))
+ {
+ baseValue = TrueValue;
+ }
+ else if (false.Equals(value))
+ {
+ baseValue = FalseValue;
+ }
+ else if (Convert.IsDBNull(value))
+ {
+ baseValue = NullValue;
+ }
- // We need a concrete implementation for an abstract method.
- protected internal override Size GetPreferredSize(Graphics g, object value) => throw null;
+ currentValue = baseValue;
+ base.SetColumnValueAtRow(source, rowNum, baseValue);
+ }
- protected internal override int GetMinimumHeight() => throw null;
+ ///
+ /// Gets the optimum width and height of a cell given
+ /// a specific value to contain.
+ ///
+ protected internal override Size GetPreferredSize(Graphics g, object value)
+ {
+ return new Size(idealCheckSize + 2, idealCheckSize + 2);
+ }
- protected internal override int GetPreferredHeight(Graphics g, object value) => throw null;
+ ///
+ /// Gets
+ /// the height of a cell in a column.
+ ///
+ protected internal override int GetMinimumHeight()
+ {
+ return idealCheckSize + 2;
+ }
- protected internal override void Abort(int rowNum) { }
+ ///
+ /// Gets the height used when resizing columns.
+ ///
+ protected internal override int GetPreferredHeight(Graphics g, object value)
+ {
+ return idealCheckSize + 2;
+ }
- protected internal override bool Commit(CurrencyManager dataSource, int rowNum) => throw null;
+ ///
+ /// Initiates a request to interrupt an edit procedure.
+ ///
+ protected internal override void Abort(int rowNum)
+ {
+ isSelected = false;
+ isEditing = false;
+ Invalidate();
+ return;
+ }
- protected internal override void Edit(CurrencyManager source,
- int rowNum,
- Rectangle bounds,
- bool readOnly,
- string displayText,
- bool cellIsVisible)
- { }
+ ///
+ /// Initiates a request to complete an editing procedure.
+ ///
+ protected internal override bool Commit(CurrencyManager dataSource, int rowNum)
+ {
+ isSelected = false;
+ // always invalidate
+ Invalidate();
+ if (!isEditing)
+ {
+ return true;
+ }
- protected internal override void Paint(Graphics g,
- Rectangle bounds,
- CurrencyManager source,
- int rowNum,
- bool alignToRight)
- { }
+ SetColumnValueAtRow(dataSource, rowNum, currentValue);
+ isEditing = false;
+ return true;
+ }
- protected internal override void Paint(Graphics g, Rectangle bounds, CurrencyManager source, int rowNum)
- { }
-}
+ ///
+ /// Prepares the cell for editing a value.
+ ///
+ protected internal override void Edit(CurrencyManager source,
+ int rowNum,
+ Rectangle bounds,
+ bool readOnly,
+ string displayText,
+ bool cellIsVisible)
+ {
+ // toggle state right now...
+ isSelected = true;
+
+ // move the focus away from the previous column and give it to the grid
+ //
+ DataGrid grid = DataGridTableStyle.DataGrid;
+ if (!grid.Focused)
+ {
+ grid.Focus();
+ }
+
+ if (!readOnly && !IsReadOnly())
+ {
+ editingRow = rowNum;
+ currentValue = GetColumnValueAtRow(source, rowNum);
+ }
+
+ base.Invalidate();
+ }
+
+ ///
+ /// Provides a handler for determining which key was pressed, and whether to
+ /// process it.
+ ///
+ internal override bool KeyPress(int rowNum, Keys keyData)
+ {
+ if (isSelected && editingRow == rowNum && !IsReadOnly())
+ {
+ if ((keyData & Keys.KeyCode) == Keys.Space)
+ {
+ ToggleValue();
+ Invalidate();
+ return true;
+ }
+ }
+
+ return base.KeyPress(rowNum, keyData);
+ }
+
+ ///
+ /// Indicates whether the a mouse down event occurred at the specified row, at
+ /// the specified x and y coordinates.
+ ///
+ internal override bool MouseDown(int rowNum, int x, int y)
+ {
+ base.MouseDown(rowNum, x, y);
+ if (isSelected && editingRow == rowNum && !IsReadOnly())
+ {
+ ToggleValue();
+ Invalidate();
+ return true;
+ }
+
+ return false;
+ }
+
+ private void OnTrueValueChanged(EventArgs e)
+ {
+ if (Events[EventTrueValue] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ private void OnFalseValueChanged(EventArgs e)
+ {
+ if (Events[EventFalseValue] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ private void OnAllowNullChanged(EventArgs e)
+ {
+ if (Events[EventAllowNull] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ ///
+ /// Draws the
+ /// with the given ,
+ /// and row number.
+ ///
+ protected internal override void Paint(Graphics g, Rectangle bounds, CurrencyManager source, int rowNum)
+ {
+ Paint(g, bounds, source, rowNum, false);
+ }
+
+ ///
+ /// Draws the
+ /// with the given , ,
+ /// row number, and alignment settings.
+ ///
+ protected internal override void Paint(Graphics g, Rectangle bounds, CurrencyManager source, int rowNum, bool alignToRight)
+ {
+ Paint(g, bounds, source, rowNum, DataGridTableStyle.BackBrush, DataGridTableStyle.ForeBrush, alignToRight);
+ }
+
+ ///
+ /// Draws the with the given , ,
+ /// row number, , and .
+ ///
+ protected internal override void Paint(Graphics g, Rectangle bounds, CurrencyManager source, int rowNum,
+ Brush backBrush, Brush foreBrush,
+ bool alignToRight)
+ {
+ object value = (isEditing && editingRow == rowNum) ? currentValue : GetColumnValueAtRow(source, rowNum);
+ ButtonState checkedState = ButtonState.Inactive;
+ if (!Convert.IsDBNull(value))
+ {
+ checkedState = ((bool)value ? ButtonState.Checked : ButtonState.Normal);
+ }
+
+ Rectangle box = GetCheckBoxBounds(bounds, alignToRight);
+
+ Region r = g.Clip;
+ g.ExcludeClip(box);
+
+ Brush selectionBrush = DataGridTableStyle.IsDefault ? DataGridTableStyle.DataGrid.SelectionBackBrush : DataGridTableStyle.SelectionBackBrush;
+ if (isSelected && editingRow == rowNum && !IsReadOnly())
+ {
+ g.FillRectangle(selectionBrush, bounds);
+ }
+ else
+ {
+ g.FillRectangle(backBrush, bounds);
+ }
+
+ g.Clip = r;
+
+ if (checkedState == ButtonState.Inactive)
+ {
+ ControlPaint.DrawMixedCheckBox(g, box, ButtonState.Checked);
+ }
+ else
+ {
+ ControlPaint.DrawCheckBox(g, box, checkedState);
+ }
+
+ // if the column is read only we should still show selection
+ if (IsReadOnly() && isSelected && source.Position == rowNum)
+ {
+ bounds.Inflate(-1, -1);
+ Pen pen = new Pen(selectionBrush)
+ {
+ DashStyle = System.Drawing.Drawing2D.DashStyle.Dash
+ };
+ g.DrawRectangle(pen, bounds);
+ pen.Dispose();
+ // restore the bounds rectangle
+ bounds.Inflate(1, 1);
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether null values are allowed.
+ ///
+ [
+ SRCategory(nameof(SR.CatBehavior)),
+ DefaultValue(true),
+ ]
+ public bool AllowNull
+ {
+ get
+ {
+ return allowNull;
+ }
+ set
+ {
+ if (allowNull != value)
+ {
+ allowNull = value;
+ // if we do not allow null, and the gridColumn had
+ // a null in it, discard it
+ if (!value && Convert.IsDBNull(currentValue))
+ {
+ currentValue = false;
+ Invalidate();
+ }
+
+ OnAllowNullChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ public event EventHandler AllowNullChanged
+ {
+ add => Events.AddHandler(EventAllowNull, value);
+ remove => Events.RemoveHandler(EventAllowNull, value);
+ }
+
+ ///
+ /// Enters a into the column.
+ ///
+ protected internal override void EnterNullValue()
+ {
+ // do not throw an exception when the column is marked as readOnly or
+ // does not allowNull
+ if (!AllowNull || IsReadOnly())
+ {
+ return;
+ }
+
+ if (currentValue != Convert.DBNull)
+ {
+ currentValue = Convert.DBNull;
+ Invalidate();
+ }
+ }
+
+ private void ResetNullValue()
+ {
+ NullValue = Convert.DBNull;
+ }
+
+ private bool ShouldSerializeNullValue()
+ {
+ return nullValue != Convert.DBNull;
+ }
+
+ private void ToggleValue()
+ {
+ if (currentValue is bool && ((bool)currentValue) == false)
+ {
+ currentValue = true;
+ }
+ else
+ {
+ if (AllowNull)
+ {
+ if (Convert.IsDBNull(currentValue))
+ {
+ currentValue = false;
+ }
+ else
+ {
+ currentValue = Convert.DBNull;
+ }
+ }
+ else
+ {
+ currentValue = false;
+ }
+ }
+
+ // we started editing
+ isEditing = true;
+ // tell the dataGrid that things are changing
+ // we put Rectangle.Empty cause toggle will invalidate the row anyhow
+ DataGridTableStyle.DataGrid.ColumnStartedEditing(Rectangle.Empty);
+ }
+ }
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridCaption.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridCaption.cs
new file mode 100644
index 00000000000..aa6b8257be2
--- /dev/null
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridCaption.cs
@@ -0,0 +1,923 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma warning disable IDE0040, IDE1006, SA1005, SA1206, SA1400, CA1510, CSIsNull001, CSIsNull002, IDE0300, IDE0031, IDE0100, IDE0060, CA1822
+
+#nullable disable
+
+using System.ComponentModel;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Globalization;
+using DSR = System.Windows.Forms.DataGridStrings;
+
+namespace System.Windows.Forms;
+ ///
+ /// Represents a caption in the DataGrid control.
+ ///
+ internal class DataGridCaption
+ {
+ internal EventHandlerList events;
+
+ private const int xOffset = 3;
+ private const int yOffset = 1;
+ private const int textPadding = 2;
+ private const int buttonToText = 4;
+ private static readonly ColorMap[] colorMap = new ColorMap[] { new ColorMap() };
+
+ private static readonly Point minimumBounds = new Point(50, 30);
+
+ private readonly DataGrid dataGrid;
+ private bool backButtonVisible;
+ private bool downButtonVisible;
+
+ private SolidBrush backBrush = DefaultBackBrush;
+ private SolidBrush foreBrush = DefaultForeBrush;
+ private readonly Pen textBorderPen = DefaultTextBorderPen;
+
+ private string text = string.Empty;
+ private bool textBorderVisible;
+ private Font textFont;
+
+ // use the datagridFont when the textFont is not set
+ // we cache this font ( cause we have to make it bold every time we paint the caption )
+ //
+ private Font dataGridFont;
+
+ private bool backActive;
+ private bool downActive;
+ private bool backPressed;
+ private bool downPressed;
+
+ // if the downButton should point down or not
+ private bool downButtonDown;
+
+ private static Bitmap leftButtonBitmap;
+ private static Bitmap leftButtonBitmap_bidi;
+ private static Bitmap magnifyingGlassBitmap;
+
+ private Rectangle backButtonRect;
+ private Rectangle downButtonRect;
+ private Rectangle textRect;
+
+ private CaptionLocation lastMouseLocation = CaptionLocation.Nowhere;
+
+ private EventEntry eventList;
+ private static readonly object EVENT_BACKWARDCLICKED = new object();
+ private static readonly object EVENT_DOWNCLICKED = new object();
+ private static readonly object EVENT_CAPTIONCLICKED = new object();
+
+ internal DataGridCaption(DataGrid dataGrid)
+ {
+ this.dataGrid = dataGrid;
+ downButtonVisible = dataGrid.ParentRowsVisible;
+ colorMap[0].OldColor = Color.White;
+ colorMap[0].NewColor = ForeColor;
+ OnGridFontChanged();
+ }
+
+ internal void OnGridFontChanged()
+ {
+ if (dataGridFont == null || !dataGridFont.Equals(dataGrid.Font))
+ {
+ try
+ {
+ dataGridFont = new Font(dataGrid.Font, FontStyle.Bold);
+ }
+ catch
+ {
+ }
+ }
+ }
+
+ // =------------------------------------------------------------------
+ // = Properties
+ // =------------------------------------------------------------------
+
+ internal bool BackButtonActive
+ {
+ get
+ {
+ return backActive;
+ }
+ set
+ {
+ if (backActive != value)
+ {
+ backActive = value;
+ InvalidateCaptionRect(backButtonRect);
+ }
+ }
+ }
+
+ internal bool DownButtonActive
+ {
+ get
+ {
+ return downActive;
+ }
+ set
+ {
+ if (downActive != value)
+ {
+ downActive = value;
+ InvalidateCaptionRect(downButtonRect);
+ }
+ }
+ }
+
+ internal static SolidBrush DefaultBackBrush
+ {
+ get
+ {
+ return (SolidBrush)SystemBrushes.ActiveCaption;
+ }
+ }
+
+ internal static Pen DefaultTextBorderPen
+ {
+ get
+ {
+ return new Pen(SystemColors.ActiveCaptionText);
+ }
+ }
+
+ internal static SolidBrush DefaultForeBrush
+ {
+ get
+ {
+ return (SolidBrush)SystemBrushes.ActiveCaptionText;
+ }
+ }
+
+ internal Color BackColor
+ {
+ get
+ {
+ return backBrush.Color;
+ }
+ set
+ {
+ if (!backBrush.Color.Equals(value))
+ {
+ if (value.IsEmpty)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridEmptyColor, "Caption BackColor"));
+ }
+
+ backBrush = new SolidBrush(value);
+ Invalidate();
+ }
+ }
+ }
+
+ internal EventHandlerList Events
+ {
+ get
+ {
+ if (events == null)
+ {
+ events = new EventHandlerList();
+ }
+
+ return events;
+ }
+ }
+
+ internal Font Font
+ {
+ get
+ {
+ // use the dataGridFont only if the user
+ // did not set the CaptionFont
+ //
+ if (textFont == null)
+ {
+ return dataGridFont;
+ }
+ else
+ {
+ return textFont;
+ }
+ }
+ set
+ {
+ if (textFont == null || !textFont.Equals(value))
+ {
+ textFont = value;
+ // this property gets called in the constructor before dataGrid has a caption
+ // and we don't need this special-handling then...
+ if (dataGrid.Caption != null)
+ {
+ dataGrid.RecalculateFonts();
+ dataGrid.PerformLayout();
+ dataGrid.Invalidate(); // smaller invalidate rect?
+ }
+ }
+ }
+ }
+
+ internal bool ShouldSerializeFont()
+ {
+ return textFont != null && !textFont.Equals(dataGridFont);
+ }
+
+ internal bool ShouldSerializeBackColor()
+ {
+ return !backBrush.Equals(DefaultBackBrush);
+ }
+
+ internal void ResetBackColor()
+ {
+ if (ShouldSerializeBackColor())
+ {
+ backBrush = DefaultBackBrush;
+ Invalidate();
+ }
+ }
+
+ internal void ResetForeColor()
+ {
+ if (ShouldSerializeForeColor())
+ {
+ foreBrush = DefaultForeBrush;
+ Invalidate();
+ }
+ }
+
+ internal bool ShouldSerializeForeColor()
+ {
+ return !foreBrush.Equals(DefaultForeBrush);
+ }
+
+ internal void ResetFont()
+ {
+ textFont = null;
+ Invalidate();
+ }
+
+ internal string Text
+ {
+ get
+ {
+ return text;
+ }
+ set
+ {
+ if (value == null)
+ {
+ text = string.Empty;
+ }
+ else
+ {
+ text = value;
+ }
+
+ Invalidate();
+ }
+ }
+
+ internal bool TextBorderVisible
+ {
+ get
+ {
+ return textBorderVisible;
+ }
+ set
+ {
+ textBorderVisible = value;
+ Invalidate();
+ }
+ }
+
+ internal Color ForeColor
+ {
+ get
+ {
+ return foreBrush.Color;
+ }
+ set
+ {
+ if (value.IsEmpty)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridEmptyColor, "Caption ForeColor"));
+ }
+
+ foreBrush = new SolidBrush(value);
+ colorMap[0].NewColor = ForeColor;
+ Invalidate();
+ }
+ }
+
+ internal Point MinimumBounds
+ {
+ get
+ {
+ return minimumBounds;
+ }
+ }
+
+ internal bool BackButtonVisible
+ {
+ get
+ {
+ return backButtonVisible;
+ }
+ set
+ {
+ if (backButtonVisible != value)
+ {
+ backButtonVisible = value;
+ Invalidate();
+ }
+ }
+ }
+
+ internal bool DownButtonVisible
+ {
+ get
+ {
+ return downButtonVisible;
+ }
+ set
+ {
+ if (downButtonVisible != value)
+ {
+ downButtonVisible = value;
+ Invalidate();
+ }
+ }
+ }
+
+ // =------------------------------------------------------------------
+ // = Methods
+ // =------------------------------------------------------------------
+
+ protected virtual void AddEventHandler(object key, Delegate handler)
+ {
+ // Locking 'this' here is ok since this is an internal class.
+ lock (this)
+ {
+ if (handler == null)
+ {
+ return;
+ }
+
+ for (EventEntry e = eventList; e != null; e = e.next)
+ {
+ if (e.key == key)
+ {
+ e.handler = Delegate.Combine(e.handler, handler);
+ return;
+ }
+ }
+
+ eventList = new EventEntry(eventList, key, handler);
+ }
+ }
+
+ ///
+ /// Adds a listener for the BackwardClicked event.
+ ///
+ internal event EventHandler BackwardClicked
+ {
+ add => Events.AddHandler(EVENT_BACKWARDCLICKED, value);
+ remove => Events.RemoveHandler(EVENT_BACKWARDCLICKED, value);
+ }
+
+ ///
+ /// Adds a listener for the CaptionClicked event.
+ ///
+ internal event EventHandler CaptionClicked
+ {
+ add => Events.AddHandler(EVENT_CAPTIONCLICKED, value);
+ remove => Events.RemoveHandler(EVENT_CAPTIONCLICKED, value);
+ }
+
+ internal event EventHandler DownClicked
+ {
+ add => Events.AddHandler(EVENT_DOWNCLICKED, value);
+ remove => Events.RemoveHandler(EVENT_DOWNCLICKED, value);
+ }
+
+ private void Invalidate()
+ {
+ if (dataGrid != null)
+ {
+ dataGrid.InvalidateCaption();
+ }
+ }
+
+ private void InvalidateCaptionRect(Rectangle r)
+ {
+ if (dataGrid != null)
+ {
+ dataGrid.InvalidateCaptionRect(r);
+ }
+ }
+
+ private void InvalidateLocation(CaptionLocation loc)
+ {
+ Rectangle r;
+ switch (loc)
+ {
+ case CaptionLocation.BackButton:
+ r = backButtonRect;
+ r.Inflate(1, 1);
+ InvalidateCaptionRect(r);
+ break;
+ case CaptionLocation.DownButton:
+ r = downButtonRect;
+ r.Inflate(1, 1);
+ InvalidateCaptionRect(r);
+ break;
+ }
+ }
+
+ protected void OnBackwardClicked(EventArgs e)
+ {
+ if (backActive)
+ {
+ ((EventHandler)Events[EVENT_BACKWARDCLICKED])?.Invoke(this, e);
+ }
+ }
+
+ protected void OnCaptionClicked(EventArgs e)
+ {
+ ((EventHandler)Events[EVENT_CAPTIONCLICKED])?.Invoke(this, e);
+ }
+
+ protected void OnDownClicked(EventArgs e)
+ {
+ if (downActive && downButtonVisible)
+ {
+ ((EventHandler)Events[EVENT_DOWNCLICKED])?.Invoke(this, e);
+ }
+ }
+
+ private Bitmap GetBitmap(string bitmapName)
+ {
+ try
+ {
+ return ScaleHelper.GetIconResourceAsDefaultSizeBitmap(typeof(DataGridCaption), bitmapName);
+ }
+ catch (Exception e)
+ {
+ Debug.Fail("Failed to load bitmap: " + bitmapName, e.ToString());
+ return null;
+ }
+ }
+
+ private Bitmap GetBackButtonBmp(bool alignRight)
+ {
+ if (alignRight)
+ {
+ if (leftButtonBitmap_bidi == null)
+ {
+ leftButtonBitmap_bidi = GetBitmap("DataGridCaption.backarrow_bidi");
+ }
+
+ return leftButtonBitmap_bidi;
+ }
+ else
+ {
+ if (leftButtonBitmap == null)
+ {
+ leftButtonBitmap = GetBitmap("DataGridCaption.backarrow");
+ }
+
+ return leftButtonBitmap;
+ }
+ }
+
+ private Bitmap GetDetailsBmp()
+ {
+ if (magnifyingGlassBitmap == null)
+ {
+ magnifyingGlassBitmap = GetBitmap("DataGridCaption.Details");
+ }
+
+ return magnifyingGlassBitmap;
+ }
+
+ protected virtual Delegate GetEventHandler(object key)
+ {
+ // Locking 'this' here is ok since this is an internal class.
+ lock (this)
+ {
+ for (EventEntry e = eventList; e != null; e = e.next)
+ {
+ if (e.key == key)
+ {
+ return e.handler;
+ }
+ }
+
+ return null;
+ }
+ }
+
+ internal Rectangle GetBackButtonRect(Rectangle bounds, bool alignRight, int downButtonWidth)
+ {
+ Bitmap backButtonBmp = GetBackButtonBmp(false);
+ Size backButtonSize;
+ lock (backButtonBmp)
+ {
+ backButtonSize = backButtonBmp.Size;
+ }
+
+ return new Rectangle(bounds.Right - xOffset * 4 - downButtonWidth - backButtonSize.Width,
+ bounds.Y + yOffset + textPadding,
+ backButtonSize.Width,
+ backButtonSize.Height);
+ }
+
+ internal int GetDetailsButtonWidth()
+ {
+ int width = 0;
+ Bitmap detailsBmp = GetDetailsBmp();
+ lock (detailsBmp)
+ {
+ width = detailsBmp.Size.Width;
+ }
+
+ return width;
+ }
+
+ internal Rectangle GetDetailsButtonRect(Rectangle bounds, bool alignRight)
+ {
+ Size downButtonSize;
+ Bitmap detailsBmp = GetDetailsBmp();
+ lock (detailsBmp)
+ {
+ downButtonSize = detailsBmp.Size;
+ }
+
+ int downButtonWidth = downButtonSize.Width;
+ return new Rectangle(bounds.Right - xOffset * 2 - downButtonWidth,
+ bounds.Y + yOffset + textPadding,
+ downButtonWidth,
+ downButtonSize.Height);
+ }
+
+ ///
+ /// Called by the dataGrid when it needs the caption
+ /// to repaint.
+ ///
+ internal void Paint(Graphics g, Rectangle bounds, bool alignRight)
+ {
+ Size textSize = new Size((int)g.MeasureString(text, Font).Width + 2, Font.Height + 2);
+
+ downButtonRect = GetDetailsButtonRect(bounds, alignRight);
+ int downButtonWidth = GetDetailsButtonWidth();
+ backButtonRect = GetBackButtonRect(bounds, alignRight, downButtonWidth);
+
+ int backButtonArea = backButtonVisible ? backButtonRect.Width + xOffset + buttonToText : 0;
+ int downButtonArea = downButtonVisible && !dataGrid.ParentRowsIsEmpty() ? downButtonWidth + xOffset + buttonToText : 0;
+
+ int textWidthLeft = bounds.Width - xOffset - backButtonArea - downButtonArea;
+
+ textRect = new Rectangle(
+ bounds.X,
+ bounds.Y + yOffset,
+ Math.Min(textWidthLeft, 2 * textPadding + textSize.Width),
+ 2 * textPadding + textSize.Height);
+
+ // align the caption text box, downButton, and backButton
+ // if the RigthToLeft property is set to true
+ if (alignRight)
+ {
+ textRect.X = bounds.Right - textRect.Width;
+ backButtonRect.X = bounds.X + xOffset * 4 + downButtonWidth;
+ downButtonRect.X = bounds.X + xOffset * 2;
+ }
+
+ Debug.WriteLineIf(CompModSwitches.DGCaptionPaint.TraceVerbose, "text size = " + textSize.ToString());
+ Debug.WriteLineIf(CompModSwitches.DGCaptionPaint.TraceVerbose, "downButtonWidth = " + downButtonWidth.ToString(CultureInfo.InvariantCulture));
+ Debug.WriteLineIf(CompModSwitches.DGCaptionPaint.TraceVerbose, "textWidthLeft = " + textWidthLeft.ToString(CultureInfo.InvariantCulture));
+ Debug.WriteLineIf(CompModSwitches.DGCaptionPaint.TraceVerbose, "backButtonRect " + backButtonRect.ToString());
+ Debug.WriteLineIf(CompModSwitches.DGCaptionPaint.TraceVerbose, "textRect " + textRect.ToString());
+ Debug.WriteLineIf(CompModSwitches.DGCaptionPaint.TraceVerbose, "downButtonRect " + downButtonRect.ToString());
+
+ // we should use the code that is commented out
+ // with today's code, there are pixels on the backButtonRect and the downButtonRect
+ // that are getting painted twice
+ //
+ g.FillRectangle(backBrush, bounds);
+
+ if (backButtonVisible)
+ {
+ PaintBackButton(g, backButtonRect, alignRight);
+ if (backActive)
+ {
+ if (lastMouseLocation == CaptionLocation.BackButton)
+ {
+ backButtonRect.Inflate(1, 1);
+ ControlPaint.DrawBorder3D(g, backButtonRect,
+ backPressed ? Border3DStyle.SunkenInner : Border3DStyle.RaisedInner);
+ }
+ }
+ }
+
+ PaintText(g, textRect, alignRight);
+
+ if (downButtonVisible && !dataGrid.ParentRowsIsEmpty())
+ {
+ PaintDownButton(g, downButtonRect);
+ // the rules have changed, yet again.
+ // now: if we show the parent rows and the mouse is
+ // not on top of this icon, then let the icon be depressed.
+ // if the mouse is pressed over the icon, then show the icon pressed
+ // if the mouse is over the icon and not pressed, then show the icon SunkenInner;
+ //
+ if (lastMouseLocation == CaptionLocation.DownButton)
+ {
+ downButtonRect.Inflate(1, 1);
+ ControlPaint.DrawBorder3D(g, downButtonRect,
+ downPressed ? Border3DStyle.SunkenInner : Border3DStyle.RaisedInner);
+ }
+ }
+ }
+
+ private void PaintIcon(Graphics g, Rectangle bounds, Bitmap b)
+ {
+ ImageAttributes attr = new ImageAttributes();
+ attr.SetRemapTable(colorMap, ColorAdjustType.Bitmap);
+ g.DrawImage(b, bounds, 0, 0, bounds.Width, bounds.Height, GraphicsUnit.Pixel, attr);
+ attr.Dispose();
+ }
+
+ private void PaintBackButton(Graphics g, Rectangle bounds, bool alignRight)
+ {
+ Bitmap backButtonBmp = GetBackButtonBmp(alignRight);
+ lock (backButtonBmp)
+ {
+ PaintIcon(g, bounds, backButtonBmp);
+ }
+ }
+
+ private void PaintDownButton(Graphics g, Rectangle bounds)
+ {
+ Bitmap detailsBmp = GetDetailsBmp();
+ lock (detailsBmp)
+ {
+ PaintIcon(g, bounds, detailsBmp);
+ }
+ }
+
+ private void PaintText(Graphics g, Rectangle bounds, bool alignToRight)
+ {
+ Rectangle textBounds = bounds;
+
+ if (textBounds.Width <= 0 || textBounds.Height <= 0)
+ {
+ return;
+ }
+
+ if (textBorderVisible)
+ {
+ g.DrawRectangle(textBorderPen, textBounds.X, textBounds.Y, textBounds.Width - 1, textBounds.Height - 1);
+ textBounds.Inflate(-1, -1);
+ }
+
+ if (textPadding > 0)
+ {
+ Rectangle border = textBounds;
+ border.Height = textPadding;
+ g.FillRectangle(backBrush, border);
+
+ border.Y = textBounds.Bottom - textPadding;
+ g.FillRectangle(backBrush, border);
+
+ border = new Rectangle(textBounds.X, textBounds.Y + textPadding,
+ textPadding, textBounds.Height - 2 * textPadding);
+ g.FillRectangle(backBrush, border);
+
+ border.X = textBounds.Right - textPadding;
+ g.FillRectangle(backBrush, border);
+ textBounds.Inflate(-textPadding, -textPadding);
+ }
+
+ g.FillRectangle(backBrush, textBounds);
+
+ // Brush foreBrush = new SolidBrush(dataGrid.CaptionForeColor);
+ StringFormat format = new StringFormat();
+ if (alignToRight)
+ {
+ format.FormatFlags |= StringFormatFlags.DirectionRightToLeft;
+ format.Alignment = StringAlignment.Far;
+ }
+
+ g.DrawString(text, Font, foreBrush, textBounds, format);
+ format.Dispose();
+ // foreBrush.Dispose();
+ }
+
+ private CaptionLocation FindLocation(int x, int y)
+ {
+ if (!backButtonRect.IsEmpty)
+ {
+ if (backButtonRect.Contains(x, y))
+ {
+ return CaptionLocation.BackButton;
+ }
+ }
+
+ if (!downButtonRect.IsEmpty)
+ {
+ if (downButtonRect.Contains(x, y))
+ {
+ return CaptionLocation.DownButton;
+ }
+ }
+
+ if (!textRect.IsEmpty)
+ {
+ if (textRect.Contains(x, y))
+ {
+ return CaptionLocation.Text;
+ }
+ }
+
+ return CaptionLocation.Nowhere;
+ }
+
+ private bool DownButtonDown
+ {
+ get
+ {
+ return downButtonDown;
+ }
+ set
+ {
+ if (downButtonDown != value)
+ {
+ downButtonDown = value;
+ InvalidateLocation(CaptionLocation.DownButton);
+ }
+ }
+ }
+
+ internal bool GetDownButtonDirection()
+ {
+ return DownButtonDown;
+ }
+
+ ///
+ /// Called by the dataGrid when the mouse is pressed
+ /// inside the caption.
+ ///
+ internal void MouseDown(int x, int y)
+ {
+ CaptionLocation loc = FindLocation(x, y);
+ switch (loc)
+ {
+ case CaptionLocation.BackButton:
+ backPressed = true;
+ InvalidateLocation(loc);
+ break;
+ case CaptionLocation.DownButton:
+ downPressed = true;
+ InvalidateLocation(loc);
+ break;
+ case CaptionLocation.Text:
+ OnCaptionClicked(EventArgs.Empty);
+ break;
+ }
+ }
+
+ ///
+ /// Called by the dataGrid when the mouse is released
+ /// inside the caption.
+ ///
+ internal void MouseUp(int x, int y)
+ {
+ CaptionLocation loc = FindLocation(x, y);
+ switch (loc)
+ {
+ case CaptionLocation.DownButton:
+ if (downPressed == true)
+ {
+ downPressed = false;
+ OnDownClicked(EventArgs.Empty);
+ }
+
+ break;
+ case CaptionLocation.BackButton:
+ if (backPressed == true)
+ {
+ backPressed = false;
+ OnBackwardClicked(EventArgs.Empty);
+ }
+
+ break;
+ }
+ }
+
+ ///
+ /// Called by the dataGrid when the mouse leaves
+ /// the caption area.
+ ///
+ internal void MouseLeft()
+ {
+ CaptionLocation oldLoc = lastMouseLocation;
+ lastMouseLocation = CaptionLocation.Nowhere;
+ InvalidateLocation(oldLoc);
+ }
+
+ ///
+ /// Called by the dataGrid when the mouse is
+ /// inside the caption.
+ ///
+ internal void MouseOver(int x, int y)
+ {
+ CaptionLocation newLoc = FindLocation(x, y);
+
+ InvalidateLocation(lastMouseLocation);
+ InvalidateLocation(newLoc);
+ lastMouseLocation = newLoc;
+ }
+
+ protected virtual void RaiseEvent(object key, EventArgs e)
+ {
+ Delegate handler = GetEventHandler(key);
+ if (handler != null)
+ {
+ ((EventHandler)handler)(this, e);
+ }
+ }
+
+ protected virtual void RemoveEventHandler(object key, Delegate handler)
+ {
+ // Locking 'this' here is ok since this is an internal class.
+ lock (this)
+ {
+ if (handler == null)
+ {
+ return;
+ }
+
+ for (EventEntry e = eventList, prev = null; e != null; prev = e, e = e.next)
+ {
+ if (e.key == key)
+ {
+ e.handler = Delegate.Remove(e.handler, handler);
+ if (e.handler == null)
+ {
+ if (prev == null)
+ {
+ eventList = e.next;
+ }
+ else
+ {
+ prev.next = e.next;
+ }
+ }
+
+ return;
+ }
+ }
+ }
+ }
+
+ protected virtual void RemoveEventHandlers()
+ {
+ eventList = null;
+ }
+
+ internal void SetDownButtonDirection(bool pointDown)
+ {
+ DownButtonDown = pointDown;
+ }
+
+ ///
+ /// Toggles the direction the "Down Button" is pointing.
+ ///
+ internal bool ToggleDownButtonDirection()
+ {
+ DownButtonDown = !DownButtonDown;
+ return DownButtonDown;
+ }
+
+ internal enum CaptionLocation
+ {
+ Nowhere,
+ BackButton,
+ DownButton,
+ Text
+ }
+
+ private sealed class EventEntry
+ {
+ internal EventEntry next;
+ internal object key;
+ internal Delegate handler;
+
+ internal EventEntry(EventEntry next, object key, Delegate handler)
+ {
+ this.next = next;
+ this.key = key;
+ this.handler = handler;
+ }
+ }
+ }
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridCell.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridCell.cs
index cdf148139bb..bb0b052da3c 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridCell.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridCell.cs
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#pragma warning disable CA1066, CA1815, RS0016, IDE0251
+
using System.ComponentModel;
namespace System.Windows.Forms;
@@ -17,21 +19,40 @@ namespace System.Windows.Forms;
UrlFormat = Obsoletions.SharedUrlFormat)]
[EditorBrowsable(EditorBrowsableState.Never)]
[Browsable(false)]
-[SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "This type is provided for public surface compatibility with .NET Framework only. It can't run.")]
public struct DataGridCell
{
-#pragma warning disable IDE0251 // Make member 'readonly' - applies to `set` methods.
- public DataGridCell(int r, int c) => throw new PlatformNotSupportedException();
+ public DataGridCell(int r, int c)
+ {
+ RowNumber = r;
+ ColumnNumber = c;
+ }
public int ColumnNumber
{
- readonly get => throw null;
- set { }
+ readonly get;
+ set;
}
public int RowNumber
{
- readonly get => throw null;
- set { }
+ readonly get;
+ set;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is not DataGridCell rhs)
+ {
+ return false;
+ }
+
+ return rhs.RowNumber == RowNumber && rhs.ColumnNumber == ColumnNumber;
+ }
+
+ public override int GetHashCode() => HashCode.Combine(RowNumber, ColumnNumber);
+
+ public override string ToString()
+ {
+ return $"DataGridCell {{RowNumber = {RowNumber}, ColumnNumber = {ColumnNumber}}}";
}
}
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridColumnStyle.CompModSwitches.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridColumnStyle.CompModSwitches.cs
deleted file mode 100644
index 5c87a8e3e1c..00000000000
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridColumnStyle.CompModSwitches.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.ComponentModel;
-
-namespace System.Windows.Forms;
-
-#nullable disable
-
-public partial class DataGridColumnStyle
-{
- ///
- /// This type is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code.
- ///
- [Obsolete(
- Obsoletions.DataGridMessage,
- error: false,
- DiagnosticId = Obsoletions.UnsupportedControlsDiagnosticId,
- UrlFormat = Obsoletions.SharedUrlFormat)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- [Browsable(false)]
- protected class CompModSwitches
- {
- public CompModSwitches() => throw new PlatformNotSupportedException();
-
- public static TraceSwitch DGEditColumnEditing => throw new PlatformNotSupportedException();
- }
-}
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridColumnStyle.DataGridColumnHeaderAccessibleObject.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridColumnStyle.DataGridColumnHeaderAccessibleObject.cs
deleted file mode 100644
index 16c7313ab19..00000000000
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridColumnStyle.DataGridColumnHeaderAccessibleObject.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.ComponentModel;
-using System.Runtime.InteropServices;
-
-namespace System.Windows.Forms;
-
-#nullable disable
-
-public partial class DataGridColumnStyle
-{
- ///
- /// This type is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code.
- ///
- [ComVisible(true)]
- [Obsolete(
- Obsoletions.DataGridMessage,
- error: false,
- DiagnosticId = Obsoletions.UnsupportedControlsDiagnosticId,
- UrlFormat = Obsoletions.SharedUrlFormat)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- [Browsable(false)]
- protected class DataGridColumnHeaderAccessibleObject : AccessibleObject
- {
- public DataGridColumnHeaderAccessibleObject(DataGridColumnStyle owner) => throw new PlatformNotSupportedException();
-
- public DataGridColumnHeaderAccessibleObject() => throw new PlatformNotSupportedException();
-
- protected DataGridColumnStyle Owner => throw null;
- }
-}
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridColumnStyle.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridColumnStyle.cs
index 4fdfb9d1b48..408edd07ee6 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridColumnStyle.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridColumnStyle.cs
@@ -1,227 +1,784 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.ComponentModel;
-using System.Drawing;
-using System.Drawing.Design;
-
-namespace System.Windows.Forms;
+#pragma warning disable IDE0040, IDE1006, SA1005, SA1206, SA1400, CA1510, CSIsNull001, CSIsNull002, IDE0059, IDE0270, IDE0078, RS0016, IDE0066
#nullable disable
-///
-/// This type is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code.
-///
-[Obsolete(
- Obsoletions.DataGridMessage,
- error: false,
- DiagnosticId = Obsoletions.UnsupportedControlsDiagnosticId,
- UrlFormat = Obsoletions.SharedUrlFormat)]
-[EditorBrowsable(EditorBrowsableState.Never)]
-[Browsable(false)]
-[ToolboxItem(false)]
-[DesignTimeVisible(false)]
-[DefaultProperty("Header")]
-public abstract partial class DataGridColumnStyle : Component, IDataGridColumnStyleEditingNotificationService
-{
- public DataGridColumnStyle() => throw new PlatformNotSupportedException();
-
- public DataGridColumnStyle(PropertyDescriptor prop) => throw new PlatformNotSupportedException();
-
- [Localizable(true)]
- [DefaultValue(HorizontalAlignment.Left)]
- public virtual HorizontalAlignment Alignment
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler AlignmentChanged
- {
- add { }
- remove { }
- }
-
- [Browsable(false)]
- public AccessibleObject HeaderAccessibleObject => throw null;
-
- [DefaultValue(null)]
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Advanced)]
- public virtual PropertyDescriptor PropertyDescriptor
- {
- get => throw null;
- set { }
- }
-
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Advanced)]
- public event EventHandler PropertyDescriptorChanged
- {
- add { }
- remove { }
- }
-
- [Browsable(false)]
- public virtual DataGridTableStyle DataGridTableStyle => throw null;
-
- protected int FontHeight => throw null;
-
- public event EventHandler FontChanged
- {
- add { }
- remove { }
- }
-
- [Localizable(true)]
- public virtual string HeaderText
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler HeaderTextChanged
- {
- add { }
- remove { }
- }
-
- [Editor($"System.Windows.Forms.Design.DataGridColumnStyleMappingNameEditor, {Assemblies.SystemDesign}", typeof(UITypeEditor))]
- [Localizable(true)]
- [DefaultValue("")]
- public string MappingName
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler MappingNameChanged
- {
- add { }
- remove { }
- }
-
- [Localizable(true)]
- public virtual string NullText
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler NullTextChanged
- {
- add { }
- remove { }
- }
-
- [DefaultValue(false)]
- public virtual bool ReadOnly
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler ReadOnlyChanged
- {
- add { }
- remove { }
- }
-
- [Localizable(true)]
- [DefaultValue(100)]
- public virtual int Width
- {
- get => throw null;
- set { }
- }
+using System.Collections;
+using System.ComponentModel;
+using System.Drawing;
+using System.Runtime.InteropServices;
+using DSR = System.Windows.Forms.DataGridStrings;
- public event EventHandler WidthChanged
+namespace System.Windows.Forms;
+ ///
+ /// Specifies the appearance and text formatting and behavior of a control column.
+ ///
+ [ToolboxItem(false)]
+ [DesignTimeVisible(false)]
+ [DefaultProperty(nameof(HeaderText))]
+ public abstract class DataGridColumnStyle : Component, IDataGridColumnStyleEditingNotificationService
{
- add { }
- remove { }
+ private HorizontalAlignment _alignment = HorizontalAlignment.Left;
+ private PropertyDescriptor _propertyDescriptor;
+ private DataGridTableStyle _dataGridTableStyle;
+ private string _mappingName = string.Empty;
+ private string _headerName = string.Empty;
+ private bool _invalid;
+ private string _nullText = DSR.DataGridNullText;
+ private bool _readOnly;
+ private bool _updating;
+ internal int _width = -1;
+ private AccessibleObject _headerAccessibleObject;
+
+ private static readonly object s_alignmentEvent = new object();
+ private static readonly object s_propertyDescriptorEvent = new object();
+ private static readonly object s_headerTextEvent = new object();
+ private static readonly object s_mappingNameEvent = new object();
+ private static readonly object s_nullTextEvent = new object();
+ private static readonly object s_readOnlyEvent = new object();
+ private static readonly object s_widthEvent = new object();
+
+ ///
+ /// In a derived class, initializes a new instance of the
+ /// class.
+ ///
+ public DataGridColumnStyle()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the
+ /// class with the specified .
+ ///
+ public DataGridColumnStyle(PropertyDescriptor prop) : this()
+ {
+ PropertyDescriptor = prop;
+ if (prop != null)
+ {
+ _readOnly = prop.IsReadOnly;
+ }
+ }
+
+ private protected DataGridColumnStyle(PropertyDescriptor prop, bool isDefault) : this(prop)
+ {
+#if DEBUG
+ IsDefault = isDefault;
+#endif
+ if (isDefault && prop != null)
+ {
+ // take the header name from the property name
+ _headerName = prop.Name;
+ _mappingName = prop.Name;
+ }
+ }
+
+#if DEBUG
+ internal bool IsDefault { get; }
+#endif
+
+ ///
+ /// Gets or sets the alignment of text in a column.
+ ///
+ [SRCategory(nameof(SR.CatDisplay))]
+ [Localizable(true)]
+ [DefaultValue(HorizontalAlignment.Left)]
+ public virtual HorizontalAlignment Alignment
+ {
+ get => _alignment;
+ set
+ {
+ SourceGenerated.EnumValidator.Validate(value);
+
+ if (_alignment != value)
+ {
+ _alignment = value;
+ OnAlignmentChanged(EventArgs.Empty);
+ Invalidate();
+ }
+ }
+ }
+
+ public event EventHandler AlignmentChanged
+ {
+ add => Events.AddHandler(s_alignmentEvent, value);
+ remove => Events.RemoveHandler(s_alignmentEvent, value);
+ }
+
+ ///
+ /// When overridden in a derived class, updates the value of a specified row with
+ /// the given text.
+ ///
+ protected internal virtual void UpdateUI(CurrencyManager source, int rowNum, string displayText)
+ {
+ }
+
+ ///
+ /// Gets or sets the background color of the column.
+ ///
+ [Browsable(false)]
+ public AccessibleObject HeaderAccessibleObject
+ {
+ get => _headerAccessibleObject ?? (_headerAccessibleObject = CreateHeaderAccessibleObject());
+ }
+
+ ///
+ /// Gets or sets the that determines the
+ /// attributes of data displayed by the .
+ ///
+ [DefaultValue(null)]
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ public virtual PropertyDescriptor PropertyDescriptor
+ {
+ get => _propertyDescriptor;
+ set
+ {
+ if (_propertyDescriptor != value)
+ {
+ _propertyDescriptor = value;
+ OnPropertyDescriptorChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Advanced)]
+ public event EventHandler PropertyDescriptorChanged
+ {
+ add => Events.AddHandler(s_propertyDescriptorEvent, value);
+ remove => Events.RemoveHandler(s_propertyDescriptorEvent, value);
+ }
+
+ protected virtual AccessibleObject CreateHeaderAccessibleObject()
+ {
+ return new DataGridColumnHeaderAccessibleObject(this);
+ }
+
+ ///
+ /// When overridden in a derived class, sets the
+ /// control that this column belongs to.
+ ///
+ protected virtual void SetDataGrid(DataGrid value)
+ {
+ SetDataGridInColumn(value);
+ }
+
+ ///
+ /// When overridden in a derived class, sets the
+ /// for the column.
+ ///
+ protected virtual void SetDataGridInColumn(DataGrid value)
+ {
+ // we need to set up the PropertyDescriptor
+ if (PropertyDescriptor == null && value != null)
+ {
+ CurrencyManager lm = value.ListManager;
+ if (lm == null)
+ {
+ return;
+ }
+
+ PropertyDescriptorCollection propCollection = lm.GetItemProperties();
+ int propCount = propCollection.Count;
+ for (int i = 0; i < propCollection.Count; i++)
+ {
+ PropertyDescriptor prop = propCollection[i];
+ if (!typeof(IList).IsAssignableFrom(prop.PropertyType) && prop.Name.Equals(HeaderText))
+ {
+ PropertyDescriptor = prop;
+ return;
+ }
+ }
+ }
+ }
+
+ internal void SetDataGridInternalInColumn(DataGrid value)
+ {
+ if (value == null || value.Initializing)
+ {
+ return;
+ }
+
+ SetDataGridInColumn(value);
+ }
+
+ ///
+ /// Gets the System.Windows.Forms.DataGridTableStyle for the column.
+ ///
+ [Browsable(false)]
+ public virtual DataGridTableStyle DataGridTableStyle => _dataGridTableStyle;
+
+ internal void SetDataGridTableInColumn(DataGridTableStyle value, bool force)
+ {
+ if (_dataGridTableStyle != null && _dataGridTableStyle.Equals(value) && !force)
+ {
+ return;
+ }
+
+ if (value != null && value.DataGrid != null && !value.DataGrid.Initializing)
+ {
+ SetDataGridInColumn(value.DataGrid);
+ }
+
+ _dataGridTableStyle = value;
+ }
+
+ ///
+ /// Gets the height of the column's font.
+ ///
+ protected int FontHeight
+ {
+ get => DataGridTableStyle?.DataGrid?.FontHeight ?? DataGridTableStyle.defaultFontHeight;
+ }
+
+ public event EventHandler FontChanged
+ {
+ add { }
+ remove { }
+ }
+
+ ///
+ /// Gets or sets the text of the column header.
+ ///
+ [Localizable(true)]
+ [SRCategory(nameof(SR.CatDisplay))]
+ public virtual string HeaderText
+ {
+ get => _headerName;
+ set
+ {
+ if (value == null)
+ {
+ value = string.Empty;
+ }
+
+ if (!_headerName.Equals(value))
+ {
+ _headerName = value;
+ OnHeaderTextChanged(EventArgs.Empty);
+ // we only invalidate columns that are visible ( ie, their propertyDescriptor is not null)
+ if (PropertyDescriptor != null)
+ {
+ Invalidate();
+ }
+ }
+ }
+ }
+
+ public event EventHandler HeaderTextChanged
+ {
+ add => Events.AddHandler(s_headerTextEvent, value);
+ remove => Events.RemoveHandler(s_headerTextEvent, value);
+ }
+
+ [Editor("System.Windows.Forms.Design.DataGridColumnStyleMappingNameEditor, " + Assemblies.SystemDesign, typeof(Drawing.Design.UITypeEditor))]
+ [Localizable(true)]
+ [DefaultValue("")]
+ public string MappingName
+ {
+ get => _mappingName;
+ set
+ {
+ if (value == null)
+ {
+ value = string.Empty;
+ }
+
+ if (!_mappingName.Equals(value))
+ {
+ string originalMappingName = _mappingName;
+ _mappingName = value;
+ try
+ {
+ _dataGridTableStyle?.GridColumnStyles.CheckForMappingNameDuplicates(this);
+ }
+ catch
+ {
+ _mappingName = originalMappingName;
+ throw;
+ }
+
+ OnMappingNameChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ public event EventHandler MappingNameChanged
+ {
+ add => Events.AddHandler(s_mappingNameEvent, value);
+ remove => Events.RemoveHandler(s_mappingNameEvent, value);
+ }
+
+ ///
+ /// Indicates whether the System.Windows.Forms.DataGridColumnStyle.HeaderText property
+ /// should be persisted.
+ ///
+ private bool ShouldSerializeHeaderText()
+ {
+ return (_headerName.Length != 0);
+ }
+
+ ///
+ /// Resets the System.Windows.Forms.DataGridColumnStyle.HeaderText to its default value.
+ ///
+ public void ResetHeaderText() => HeaderText = string.Empty;
+
+ ///
+ /// Gets or sets the text that is displayed when the column contains a null
+ /// value.
+ ///
+ [Localizable(true)]
+ [SRCategory(nameof(SR.CatDisplay))]
+ public virtual string NullText
+ {
+ get => _nullText;
+ set
+ {
+ if (_nullText != value)
+ {
+ _nullText = value;
+ OnNullTextChanged(EventArgs.Empty);
+ Invalidate();
+ }
+ }
+ }
+
+ public event EventHandler NullTextChanged
+ {
+ add => Events.AddHandler(s_nullTextEvent, value);
+ remove => Events.RemoveHandler(s_nullTextEvent, value);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the data in the column cannot be edited.
+ ///
+ [DefaultValue(false)]
+ public virtual bool ReadOnly
+ {
+ get => _readOnly;
+ set
+ {
+ if (_readOnly != value)
+ {
+ _readOnly = value;
+ OnReadOnlyChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ public event EventHandler ReadOnlyChanged
+ {
+ add => Events.AddHandler(s_readOnlyEvent, value);
+ remove => Events.RemoveHandler(s_readOnlyEvent, value);
+ }
+
+ ///
+ /// Gets or sets the width of the column.
+ ///
+ [SRCategory(nameof(SR.CatLayout))]
+ [Localizable(true)]
+ [DefaultValue(100)]
+ public virtual int Width
+ {
+ get => _width;
+ set
+ {
+ if (_width != value)
+ {
+ _width = value;
+ DataGrid grid = DataGridTableStyle?.DataGrid;
+ if (grid != null)
+ {
+ // rearrange the scroll bars
+ grid.PerformLayout();
+
+ // force the grid to repaint
+ grid.InvalidateInside();
+ }
+
+ OnWidthChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ public event EventHandler WidthChanged
+ {
+ add => Events.AddHandler(s_widthEvent, value);
+ remove => Events.RemoveHandler(s_widthEvent, value);
+ }
+
+ ///
+ /// Suspends the painting of the column until the
+ /// method is called.
+ ///
+ protected void BeginUpdate() => _updating = true;
+
+ ///
+ /// Resumes the painting of columns suspended by calling the
+ /// method.
+ ///
+ protected void EndUpdate()
+ {
+ _updating = false;
+ if (_invalid)
+ {
+ _invalid = false;
+ Invalidate();
+ }
+ }
+
+ internal virtual string GetDisplayText(object value) => value.ToString();
+
+ private void ResetNullText() => NullText = DSR.DataGridNullText;
+
+ private bool ShouldSerializeNullText() => !DSR.DataGridNullText.Equals(_nullText);
+
+ ///
+ /// When overridden in a derived class, gets the optimum width and height of the
+ /// specified value.
+ ///
+ protected internal abstract Size GetPreferredSize(Graphics g, object value);
+
+ ///
+ /// Gets the minimum height of a row.
+ ///
+ protected internal abstract int GetMinimumHeight();
+
+ ///
+ /// When overridden in a derived class, gets the height to be used in for
+ /// automatically resizing columns.
+ ///
+ protected internal abstract int GetPreferredHeight(Graphics g, object value);
+
+ ///
+ /// Gets the value in the specified row from the specified System.Windows.Forms.ListManager.
+ ///
+ protected internal virtual object GetColumnValueAtRow(CurrencyManager source, int rowNum)
+ {
+ CheckValidDataSource(source);
+ PropertyDescriptor descriptor = PropertyDescriptor;
+ if (descriptor == null)
+ {
+ throw new InvalidOperationException(DSR.DataGridColumnNoPropertyDescriptor);
+ }
+
+ return descriptor.GetValue(source[rowNum]);
+ }
+
+ ///
+ /// Redraws the column and causes a paint message to be sent to the control.
+ ///
+ protected virtual void Invalidate()
+ {
+ if (_updating)
+ {
+ _invalid = true;
+ return;
+ }
+
+ DataGridTableStyle?.InvalidateColumn(this);
+ }
+
+ ///
+ /// Checks if the specified DataView is valid.
+ ///
+ protected void CheckValidDataSource(CurrencyManager value)
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ // The code may delete a gridColumn that was editing.
+ // In that case, we still have to push the value into the backend
+ // and we only need the propertyDescriptor to push the value.
+ // (take a look at gridEditAndDeleteEditColumn)
+ if (PropertyDescriptor == null)
+ {
+ throw new InvalidOperationException(string.Format(DSR.DataGridColumnUnbound, HeaderText));
+ }
+ }
+
+ ///
+ /// When overridden in a derived class, initiates a request to interrrupt an edit
+ /// procedure.
+ ///
+ protected internal abstract void Abort(int rowNum);
+
+ ///
+ /// When overridden in a derived class, inititates a request to complete an
+ /// editing procedure.
+ ///
+ protected internal abstract bool Commit(CurrencyManager dataSource, int rowNum);
+
+ ///
+ /// When overridden in a deriving class, prepares a cell for editing.
+ ///
+ protected internal virtual void Edit(CurrencyManager source, int rowNum, Rectangle bounds, bool readOnly)
+ {
+ Edit(source, rowNum, bounds, readOnly, null, true);
+ }
+
+ ///
+ /// Prepares the cell for editing, passing the specified ,
+ /// row number, , argument indicating whether
+ /// the column is read-only, and the text to display in the new control.
+ ///
+ protected internal virtual void Edit(CurrencyManager source, int rowNum, Rectangle bounds, bool readOnly, string displayText)
+ {
+ Edit(source, rowNum, bounds, readOnly, displayText, true);
+ }
+
+ ///
+ /// When overridden in a deriving class, prepares a cell for editing.
+ ///
+ protected internal abstract void Edit(CurrencyManager source, int rowNum, Rectangle bounds, bool readOnly, string displayText, bool cellIsVisible);
+
+ ///
+ /// Indicates whether the a mouse down event occurred at the specified row, at
+ /// the specified x and y coordinates.
+ ///
+ internal virtual bool MouseDown(int rowNum, int x, int y)
+ {
+ return false;
+ }
+
+ ///
+ /// When overriden in a derived class, enters a
+ /// into the column.
+ ///
+ protected internal virtual void EnterNullValue()
+ {
+ }
+
+ ///
+ /// Provides a handler for determining which key was pressed, and whether to
+ /// process it.
+ ///
+ internal virtual bool KeyPress(int rowNum, Keys keyData)
+ {
+ // if this is read only then do not do anything
+ if (ReadOnly || (DataGridTableStyle != null && DataGridTableStyle.DataGrid != null && DataGridTableStyle.DataGrid.ReadOnly))
+ {
+ return false;
+ }
+
+ if (keyData == (Keys.Control | Keys.NumPad0) || keyData == (Keys.Control | Keys.D0))
+ {
+ EnterNullValue();
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// When overridden in a derived class, directs the column to concede focus with
+ /// an appropriate action.
+ ///
+ protected internal virtual void ConcedeFocus()
+ {
+ }
+
+ ///
+ /// Paints the a with the specified
+ /// , ,
+ /// System.Windows.Forms.CurrencyManager, and row number.
+ ///
+ protected internal abstract void Paint(Graphics g, Rectangle bounds, CurrencyManager source, int rowNum);
+
+ ///
+ /// When overridden in a derived class, paints a
+ /// with the specified , ,
+ /// see Rectangle, row number, and alignment.
+ ///
+ protected internal abstract void Paint(Graphics g, Rectangle bounds, CurrencyManager source, int rowNum, bool alignToRight);
+
+ ///
+ /// Paints a with the specified , , see System.Data.DataView, row number, background color, foreground color, and alignment.
+ ///
+ protected internal virtual void Paint(Graphics g, Rectangle bounds, CurrencyManager source, int rowNum,
+ Brush backBrush, Brush foreBrush, bool alignToRight)
+ {
+ Paint(g, bounds, source, rowNum, alignToRight);
+ }
+
+ private void OnPropertyDescriptorChanged(EventArgs e)
+ {
+ EventHandler eh = Events[s_propertyDescriptorEvent] as EventHandler;
+ eh?.Invoke(this, e);
+ }
+
+ private void OnAlignmentChanged(EventArgs e)
+ {
+ EventHandler eh = Events[s_alignmentEvent] as EventHandler;
+ eh?.Invoke(this, e);
+ }
+
+ private void OnHeaderTextChanged(EventArgs e)
+ {
+ EventHandler eh = Events[s_headerTextEvent] as EventHandler;
+ eh?.Invoke(this, e);
+ }
+
+ private void OnMappingNameChanged(EventArgs e)
+ {
+ EventHandler eh = Events[s_mappingNameEvent] as EventHandler;
+ eh?.Invoke(this, e);
+ }
+
+ private void OnReadOnlyChanged(EventArgs e)
+ {
+ EventHandler eh = Events[s_readOnlyEvent] as EventHandler;
+ eh?.Invoke(this, e);
+ }
+
+ private void OnNullTextChanged(EventArgs e)
+ {
+ EventHandler eh = Events[s_nullTextEvent] as EventHandler;
+ eh?.Invoke(this, e);
+ }
+
+ private void OnWidthChanged(EventArgs e)
+ {
+ EventHandler eh = Events[s_widthEvent] as EventHandler;
+ eh?.Invoke(this, e);
+ }
+
+ ///
+ /// Sets the value in a specified row with the value from a specified see DataView.
+ ///
+ protected internal virtual void SetColumnValueAtRow(CurrencyManager source, int rowNum, object value)
+ {
+ CheckValidDataSource(source);
+ PropertyDescriptor descriptor = PropertyDescriptor;
+ if (descriptor == null)
+ {
+ throw new InvalidOperationException(DSR.DataGridColumnNoPropertyDescriptor);
+ }
+
+ if (source.Position != rowNum)
+ {
+ throw new ArgumentException(DSR.DataGridColumnListManagerPosition, nameof(rowNum));
+ }
+
+ if (source[rowNum] is IEditableObject editableObject)
+ {
+ editableObject.BeginEdit();
+ }
+
+ descriptor.SetValue(source[rowNum], value);
+ }
+
+ protected internal virtual void ColumnStartedEditing(Control editingControl)
+ {
+ DataGridTableStyle?.DataGrid?.ColumnStartedEditing(editingControl);
+ }
+
+ void IDataGridColumnStyleEditingNotificationService.ColumnStartedEditing(Control editingControl)
+ {
+ ColumnStartedEditing(editingControl);
+ }
+
+ protected internal virtual void ReleaseHostedControl()
+ {
+ }
+
+ protected class CompModSwitches
+ {
+ private static TraceSwitch dgEditColumnEditing;
+
+ public static TraceSwitch DGEditColumnEditing
+ {
+ get
+ {
+ if (dgEditColumnEditing == null)
+ {
+ dgEditColumnEditing = new TraceSwitch("DGEditColumnEditing", "Editing related tracing");
+ }
+
+ return dgEditColumnEditing;
+ }
+ }
+ }
+
+ [ComVisible(true)]
+ protected class DataGridColumnHeaderAccessibleObject : AccessibleObject
+ {
+ public DataGridColumnHeaderAccessibleObject(DataGridColumnStyle owner) : this()
+ {
+ Debug.Assert(owner != null, "DataGridColumnHeaderAccessibleObject must have a valid owner DataGridColumn");
+ Owner = owner;
+ }
+
+ public DataGridColumnHeaderAccessibleObject() : base()
+ {
+ }
+
+ public override Rectangle Bounds
+ {
+ get
+ {
+ // we need to get the width and the X coordinate of this column on the screen
+ // we can't just cache this, cause the column may be moved in the collection
+ if (Owner.PropertyDescriptor == null)
+ {
+ return Rectangle.Empty;
+ }
+
+ DataGrid dg = DataGrid;
+ if (dg.DataGridRowsLength == 0)
+ {
+ return Rectangle.Empty;
+ }
+
+ // We need to find this column's offset in the gridColumnCollection.
+ GridColumnStylesCollection cols = Owner._dataGridTableStyle.GridColumnStyles;
+ int offset = -1;
+ for (int i = 0; i < cols.Count; i++)
+ {
+ if (cols[i] == Owner)
+ {
+ offset = i;
+ break;
+ }
+ }
+
+ Debug.Assert(offset >= 0, "this column must be in a collection, otherwise its bounds are useless");
+ Rectangle rect = dg.GetCellBounds(0, offset);
+ // Now add the Y coordinate of the datagrid.Layout.Data.
+ // It should be the same as dataGrid.Layout.ColumnHeaders
+ rect.Y = dg.GetColumnHeadersRect().Y;
+ return dg.RectangleToScreen(rect);
+ }
+ }
+
+ public override string Name => Owner._headerName;
+
+ protected DataGridColumnStyle Owner { get; }
+
+ public override AccessibleObject Parent => DataGrid.AccessibilityObject;
+
+ private DataGrid DataGrid => Owner._dataGridTableStyle.dataGrid;
+
+ public override AccessibleRole Role => AccessibleRole.ColumnHeader;
+
+ public override AccessibleObject Navigate(AccessibleNavigation navdir)
+ {
+ switch (navdir)
+ {
+ case AccessibleNavigation.Right:
+ case AccessibleNavigation.Next:
+ case AccessibleNavigation.Down:
+ return Parent.GetChild(1 + Owner._dataGridTableStyle.GridColumnStyles.IndexOf(Owner) + 1);
+ case AccessibleNavigation.Up:
+ case AccessibleNavigation.Left:
+ case AccessibleNavigation.Previous:
+ return Parent.GetChild(1 + Owner._dataGridTableStyle.GridColumnStyles.IndexOf(Owner) - 1);
+ }
+
+ return null;
+ }
+ }
}
-
- protected virtual AccessibleObject CreateHeaderAccessibleObject() => throw null;
-
- protected virtual void SetDataGrid(DataGrid value) { }
-
- protected virtual void SetDataGridInColumn(DataGrid value) { }
-
- protected void BeginUpdate() { }
-
- protected void EndUpdate() { }
-
- protected internal abstract Size GetPreferredSize(Graphics g, object value);
-
- protected internal abstract int GetMinimumHeight();
-
- protected internal abstract int GetPreferredHeight(Graphics g, object value);
-
- protected internal virtual object GetColumnValueAtRow(CurrencyManager source, int rowNum) => throw null;
-
- protected virtual void Invalidate() { }
-
- protected void CheckValidDataSource(CurrencyManager value) { }
-
- protected internal abstract void Abort(int rowNum);
-
- protected internal abstract bool Commit(CurrencyManager dataSource, int rowNum);
-
- protected internal virtual void Edit(
- CurrencyManager source,
- int rowNum,
- Rectangle bounds,
- bool readOnly)
- { }
-
- protected internal virtual void Edit(
- CurrencyManager source,
- int rowNum,
- Rectangle bounds,
- bool readOnly,
- string displayText)
- { }
-
- protected internal abstract void Edit(
- CurrencyManager source,
- int rowNum,
- Rectangle bounds,
- bool readOnly,
- string displayText,
- bool cellIsVisible);
-
- protected internal virtual void EnterNullValue() { }
-
- protected internal virtual void ConcedeFocus() { }
-
- protected internal abstract void Paint(
- Graphics g,
- Rectangle bounds,
- CurrencyManager source,
- int rowNum,
- bool alignToRight);
-
- protected internal virtual void Paint(
- Graphics g,
- Rectangle bounds,
- CurrencyManager source,
- int rowNum,
- Brush backBrush,
- Brush foreBrush,
- bool alignToRight)
- { }
-
- protected internal abstract void Paint(Graphics g, Rectangle bounds, CurrencyManager source, int rowNum);
-
- protected internal virtual void ReleaseHostedControl() { }
-
- public void ResetHeaderText() { }
-
- protected internal virtual void SetColumnValueAtRow(CurrencyManager source, int rowNum, object value) { }
-
- void IDataGridColumnStyleEditingNotificationService.ColumnStartedEditing(Control editingControl) { }
-
- protected internal virtual void ColumnStartedEditing(Control editingControl) { }
-
- protected internal virtual void UpdateUI(CurrencyManager source, int rowNum, string displayText) { }
-}
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridLineStyle.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridLineStyle.cs
index 97fc0aa249f..2e97ef3e786 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridLineStyle.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridLineStyle.cs
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#pragma warning disable WFDEV006
+
using System.ComponentModel;
namespace System.Windows.Forms;
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridParentRows.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridParentRows.cs
new file mode 100644
index 00000000000..b8b6ea9f68f
--- /dev/null
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridParentRows.cs
@@ -0,0 +1,1409 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma warning disable IDE0040, IDE1006, SA1005, SA1206, SA1400, CA1510, CSIsNull001, CSIsNull002, WFDEV006, IDE0300, IDE0031, IDE0059, IDE0078, IDE0060, CA1822
+
+#nullable disable
+
+using System.Collections;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Runtime.InteropServices;
+using System.Text;
+using DSR = System.Windows.Forms.DataGridStrings;
+
+namespace System.Windows.Forms;
+ internal class DataGridParentRows
+ {
+ // siting
+ //
+ private readonly DataGrid dataGrid;
+
+ // ui
+ //
+ // private Color backColor = DataGrid.defaultParentRowsBackColor;
+ // private Color foreColor = DataGrid.defaultParentRowsForeColor;
+
+ private SolidBrush backBrush = DataGrid.DefaultParentRowsBackBrush;
+ private SolidBrush foreBrush = DataGrid.DefaultParentRowsForeBrush;
+
+ private readonly int borderWidth = 1;
+ // private Color borderColor = SystemColors.WindowFrame;
+ private Brush borderBrush = new SolidBrush(SystemColors.WindowFrame);
+
+ private static Bitmap rightArrow;
+ private static Bitmap leftArrow;
+
+ private readonly ColorMap[] colorMap = new ColorMap[] { new ColorMap() };
+
+ // private bool gridLineDots = false;
+ // private Color gridLineColor = SystemColors.Control;
+ // private Brush gridLineBrush = SystemBrushes.Control;
+ private readonly Pen gridLinePen = SystemPens.Control;
+
+ private int totalHeight;
+ private int textRegionHeight;
+
+ // now that we have left and right arrows, we also have layout
+ private readonly Layout layout = new Layout();
+
+ // mouse info
+ //
+ // private bool overLeftArrow = false;
+ // private bool overRightArrow = false;
+ private bool downLeftArrow;
+ private bool downRightArrow;
+
+ // a horizOffset of 0 means that the layout for the parent
+ // rows is left aligned.
+ // a horizOffset of 1 means that the leftmost unit of information ( let it be a
+ // table name, a column name or a column value ) is not visible.
+ // a horizOffset of 2 means that the leftmost 2 units of information are not visible, and so on
+ //
+ private int horizOffset;
+
+ // storage for parent row states
+ //
+ private readonly ArrayList parents = new ArrayList();
+ private int parentsCount;
+ private readonly ArrayList rowHeights = new ArrayList();
+ AccessibleObject accessibleObject;
+
+ internal DataGridParentRows(DataGrid dataGrid)
+ {
+ colorMap[0].OldColor = Color.Black;
+ this.dataGrid = dataGrid;
+ // UpdateGridLinePen();
+ }
+
+ // =------------------------------------------------------------------
+ // = Properties
+ // =------------------------------------------------------------------
+
+ public AccessibleObject AccessibleObject
+ {
+ get
+ {
+ if (accessibleObject is null)
+ {
+ accessibleObject = new DataGridParentRowsAccessibleObject(this);
+ }
+
+ return accessibleObject;
+ }
+ }
+
+ internal Color BackColor
+ {
+ get
+ {
+ return backBrush.Color;
+ }
+ set
+ {
+ if (value.IsEmpty)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridEmptyColor, "Parent Rows BackColor"));
+ }
+
+ if (value != backBrush.Color)
+ {
+ backBrush = new SolidBrush(value);
+ Invalidate();
+ }
+ }
+ }
+
+ internal SolidBrush BackBrush
+ {
+ get
+ {
+ return backBrush;
+ }
+ set
+ {
+ if (value != backBrush)
+ {
+ CheckNull(value, "BackBrush");
+ backBrush = value;
+ Invalidate();
+ }
+ }
+ }
+
+ internal SolidBrush ForeBrush
+ {
+ get
+ {
+ return foreBrush;
+ }
+ set
+ {
+ if (value != foreBrush)
+ {
+ CheckNull(value, "BackBrush");
+ foreBrush = value;
+ Invalidate();
+ }
+ }
+ }
+
+ // since the layout of the parentRows is computed on every paint message,
+ // we can actually return true ClientRectangle coordinates
+ internal Rectangle GetBoundsForDataGridStateAccesibility(DataGridState dgs)
+ {
+ Rectangle ret = Rectangle.Empty;
+ int rectY = 0;
+ for (int i = 0; i < parentsCount; i++)
+ {
+ int height = (int)rowHeights[i];
+ if (parents[i] == dgs)
+ {
+ ret.X = layout.leftArrow.IsEmpty ? layout.data.X : layout.leftArrow.Right;
+ ret.Height = height;
+ ret.Y = rectY;
+ ret.Width = layout.data.Width;
+ return ret;
+ }
+
+ rectY += height;
+ }
+
+ return ret;
+ }
+
+ /*
+ internal Color BorderColor {
+ get {
+ return borderColor;
+ }
+ set {
+ if (value != borderColor) {
+ borderColor = value;
+ Invalidate();
+ }
+ }
+ }
+ */
+
+ internal Brush BorderBrush
+ {
+ get
+ {
+ return borderBrush;
+ }
+ set
+ {
+ if (value != borderBrush)
+ {
+ borderBrush = value;
+ Invalidate();
+ }
+ }
+ }
+
+ /*
+ internal Brush GridLineBrush {
+ get {
+ return gridLineBrush;
+ }
+ set {
+ if (value != gridLineBrush) {
+ gridLineBrush = value;
+ UpdateGridLinePen();
+ Invalidate();
+ }
+ }
+ }
+
+ internal bool GridLineDots {
+ get {
+ return gridLineDots;
+ }
+ set {
+ if (gridLineDots != value) {
+ gridLineDots = value;
+ UpdateGridLinePen();
+ Invalidate();
+ }
+ }
+ }
+ */
+
+ internal int Height
+ {
+ get
+ {
+ return totalHeight;
+ }
+ }
+
+ internal Color ForeColor
+ {
+ get
+ {
+ return foreBrush.Color;
+ }
+ set
+ {
+ if (value.IsEmpty)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridEmptyColor, "Parent Rows ForeColor"));
+ }
+
+ if (value != foreBrush.Color)
+ {
+ foreBrush = new SolidBrush(value);
+ Invalidate();
+ }
+ }
+ }
+
+ internal bool Visible
+ {
+ get
+ {
+ return dataGrid.ParentRowsVisible;
+ }
+ set
+ {
+ dataGrid.ParentRowsVisible = value;
+ }
+ }
+
+ // =------------------------------------------------------------------
+ // = Methods
+ // =------------------------------------------------------------------
+
+ ///
+ /// Adds a DataGridState object to the top of the list of parents.
+ ///
+ internal void AddParent(DataGridState dgs)
+ {
+ CurrencyManager childDataSource = (CurrencyManager)dataGrid.BindingContext[dgs.DataSource, dgs.DataMember];
+ parents.Add(dgs);
+ SetParentCount(parentsCount + 1);
+ Debug.Assert(GetTopParent() is not null, "we should have a parent at least");
+ }
+
+ internal void Clear()
+ {
+ for (int i = 0; i < parents.Count; i++)
+ {
+ DataGridState dgs = parents[i] as DataGridState;
+ dgs.RemoveChangeNotification();
+ }
+
+ parents.Clear();
+ rowHeights.Clear();
+ totalHeight = 0;
+ SetParentCount(0);
+ }
+
+ internal void SetParentCount(int count)
+ {
+ parentsCount = count;
+ dataGrid.Caption.BackButtonVisible = (parentsCount > 0) && (dataGrid.AllowNavigation);
+ }
+
+ internal void CheckNull(object value, string propName)
+ {
+ if (value is null)
+ {
+ throw new ArgumentNullException(nameof(propName));
+ }
+ }
+
+ internal void Dispose()
+ {
+ gridLinePen.Dispose();
+ }
+
+ ///
+ /// Retrieves the top most parent in the list of parents.
+ ///
+ internal DataGridState GetTopParent()
+ {
+ if (parentsCount < 1)
+ {
+ return null;
+ }
+
+ return (DataGridState)(((ICloneable)(parents[parentsCount - 1])).Clone());
+ }
+
+ ///
+ /// Determines if there are any parent rows contained in this object.
+ ///
+ internal bool IsEmpty()
+ {
+ return parentsCount == 0;
+ }
+
+ ///
+ /// Similar to GetTopParent() but also removes it.
+ ///
+ internal DataGridState PopTop()
+ {
+ if (parentsCount < 1)
+ {
+ return null;
+ }
+
+ SetParentCount(parentsCount - 1);
+ DataGridState ret = (DataGridState)parents[parentsCount];
+ ret.RemoveChangeNotification();
+ parents.RemoveAt(parentsCount);
+ return ret;
+ }
+
+ internal void Invalidate()
+ {
+ if (dataGrid is not null)
+ {
+ dataGrid.InvalidateParentRows();
+ }
+ }
+
+ internal void InvalidateRect(Rectangle rect)
+ {
+ if (dataGrid is not null)
+ {
+ Rectangle r = new Rectangle(rect.X, rect.Y, rect.Width + borderWidth, rect.Height + borderWidth);
+ dataGrid.InvalidateParentRowsRect(r);
+ }
+ }
+
+ // called from DataGrid::OnLayout
+ internal void OnLayout()
+ {
+ if (parentsCount == rowHeights.Count)
+ {
+ return;
+ }
+
+ int height = 0;
+ if (totalHeight == 0)
+ {
+ totalHeight += 2 * borderWidth;
+ }
+
+ // figure out how tall each row's text will be
+ //
+ textRegionHeight = (int)dataGrid.Font.Height + 2;
+
+ // make the height of the Column.Font count for the height
+ // of the parentRows;
+ //
+ // if the user wants to programatically
+ // navigate to a relation in the constructor of the form
+ // ( ie, when the form does not process PerformLayout )
+ // the grid will receive an OnLayout message when there is more
+ // than one parent in the grid
+ if (parentsCount > rowHeights.Count)
+ {
+ Debug.Assert(parentsCount == rowHeights.Count + 1 || rowHeights.Count == 0, "see comment above for more info");
+ int rowHeightsCount = rowHeights.Count;
+ for (int i = rowHeightsCount; i < parentsCount; i++)
+ {
+ DataGridState dgs = (DataGridState)parents[i];
+ GridColumnStylesCollection cols = dgs.GridColumnStyles;
+
+ int colsHeight = 0;
+
+ for (int j = 0; j < cols.Count; j++)
+ {
+ colsHeight = Math.Max(colsHeight, cols[j].GetMinimumHeight());
+ }
+
+ height = Math.Max(colsHeight, textRegionHeight);
+
+ // the height of the bottom border
+ height++;
+ rowHeights.Add(height);
+
+ totalHeight += height;
+ }
+ }
+ else
+ {
+ Debug.Assert(parentsCount == rowHeights.Count - 1, "we do layout only for push/popTop");
+ if (parentsCount == 0)
+ {
+ totalHeight = 0;
+ }
+ else
+ {
+ totalHeight -= (int)rowHeights[rowHeights.Count - 1];
+ }
+
+ rowHeights.RemoveAt(rowHeights.Count - 1);
+ }
+ }
+
+ private int CellCount()
+ {
+ int cellCount = 0;
+ cellCount = ColsCount();
+
+ if (dataGrid.ParentRowsLabelStyle == DataGridParentRowsLabelStyle.TableName ||
+ dataGrid.ParentRowsLabelStyle == DataGridParentRowsLabelStyle.Both)
+ {
+ cellCount++;
+ }
+
+ return cellCount;
+ }
+
+ private void ResetMouseInfo()
+ {
+ // overLeftArrow = false;
+ // overRightArrow = false;
+ downLeftArrow = false;
+ downRightArrow = false;
+ }
+
+ private void LeftArrowClick(int cellCount)
+ {
+ if (horizOffset > 0)
+ {
+ ResetMouseInfo();
+ horizOffset -= 1;
+ Invalidate();
+ }
+ else
+ {
+ ResetMouseInfo();
+ InvalidateRect(layout.leftArrow);
+ }
+ }
+
+ private void RightArrowClick(int cellCount)
+ {
+ if (horizOffset < cellCount - 1)
+ {
+ ResetMouseInfo();
+ horizOffset += 1;
+ Invalidate();
+ }
+ else
+ {
+ ResetMouseInfo();
+ InvalidateRect(layout.rightArrow);
+ }
+ }
+
+ // the only mouse clicks that are handled are
+ // the mouse clicks on the LeftArrow and RightArrow
+ //
+ internal void OnMouseDown(int x, int y, bool alignToRight)
+ {
+ if (layout.rightArrow.IsEmpty)
+ {
+ Debug.Assert(layout.leftArrow.IsEmpty, "we can't have the leftArrow w/o the rightArrow");
+ return;
+ }
+
+ int cellCount = CellCount();
+
+ if (layout.rightArrow.Contains(x, y))
+ {
+ // draw a nice sunken border around the right arrow area
+ // we want to keep a cell on the screen
+
+ downRightArrow = true;
+
+ if (alignToRight)
+ {
+ LeftArrowClick(cellCount);
+ }
+ else
+ {
+ RightArrowClick(cellCount);
+ }
+ }
+ else if (layout.leftArrow.Contains(x, y))
+ {
+ downLeftArrow = true;
+
+ if (alignToRight)
+ {
+ RightArrowClick(cellCount);
+ }
+ else
+ {
+ LeftArrowClick(cellCount);
+ }
+ }
+ else
+ {
+ if (downLeftArrow)
+ {
+ downLeftArrow = false;
+ InvalidateRect(layout.leftArrow);
+ }
+
+ if (downRightArrow)
+ {
+ downRightArrow = false;
+ InvalidateRect(layout.rightArrow);
+ }
+ }
+ }
+
+ internal void OnMouseLeave()
+ {
+ if (downLeftArrow)
+ {
+ downLeftArrow = false;
+ InvalidateRect(layout.leftArrow);
+ }
+
+ if (downRightArrow)
+ {
+ downRightArrow = false;
+ InvalidateRect(layout.rightArrow);
+ }
+ }
+
+ internal void OnMouseMove(int x, int y)
+ {
+ /*
+ if (!layout.leftArrow.IsEmpty && layout.leftArrow.Contains(x,y))
+ {
+ ResetMouseInfo();
+ overLeftArrow = true;
+ InvalidateRect(layout.leftArrow);
+ return;
+ }
+ if (!layout.rightArrow.IsEmpty && layout.rightArrow.Contains(x,y))
+ {
+ ResetMouseInfo();
+ overRightArrow = true;
+ InvalidateRect(layout.rightArrow);
+ return;
+ }
+ */
+
+ if (downLeftArrow)
+ {
+ downLeftArrow = false;
+ InvalidateRect(layout.leftArrow);
+ }
+
+ if (downRightArrow)
+ {
+ downRightArrow = false;
+ InvalidateRect(layout.rightArrow);
+ }
+ }
+
+ internal void OnMouseUp(int x, int y)
+ {
+ ResetMouseInfo();
+ if (!layout.rightArrow.IsEmpty && layout.rightArrow.Contains(x, y))
+ {
+ InvalidateRect(layout.rightArrow);
+ return;
+ }
+
+ if (!layout.leftArrow.IsEmpty && layout.leftArrow.Contains(x, y))
+ {
+ InvalidateRect(layout.leftArrow);
+ return;
+ }
+ }
+
+ internal void OnResize(Rectangle oldBounds)
+ {
+ Invalidate();
+ }
+
+ ///
+ /// Paints the parent rows
+ ///
+ internal void Paint(Graphics g, Rectangle visualbounds, bool alignRight)
+ {
+ Rectangle bounds = visualbounds;
+ // Paint the border around our bounds
+ if (borderWidth > 0)
+ {
+ PaintBorder(g, bounds);
+ bounds.Inflate(-borderWidth, -borderWidth);
+ }
+
+ PaintParentRows(g, bounds, alignRight);
+ }
+
+ private void PaintBorder(Graphics g, Rectangle bounds)
+ {
+ Rectangle border = bounds;
+
+ // top
+ border.Height = borderWidth;
+ g.FillRectangle(borderBrush, border);
+
+ // bottom
+ border.Y = bounds.Bottom - borderWidth;
+ g.FillRectangle(borderBrush, border);
+
+ // left
+ border = new Rectangle(bounds.X, bounds.Y + borderWidth,
+ borderWidth, bounds.Height - 2 * borderWidth);
+ g.FillRectangle(borderBrush, border);
+
+ // right
+ border.X = bounds.Right - borderWidth;
+ g.FillRectangle(borderBrush, border);
+ }
+
+ // will return the width of the text box that will fit all the
+ // tables names
+ private int GetTableBoxWidth(Graphics g, Font font)
+ {
+ // try to make the font BOLD
+ Font textFont = font;
+ try
+ {
+ textFont = new Font(font, FontStyle.Bold);
+ }
+ catch
+ {
+ }
+
+ int width = 0;
+ for (int row = 0; row < parentsCount; row++)
+ {
+ DataGridState dgs = (DataGridState)parents[row];
+ // Graphics.MeasureString(...) returns different results for ": " than for " :"
+ //
+ string displayTableName = dgs.ListManager.GetListName() + " :";
+ int size = (int)g.MeasureString(displayTableName, textFont).Width;
+ width = Math.Max(size, width);
+ }
+
+ return width;
+ }
+
+ // will return the width of the text box that will
+ // fit all the column names
+ private int GetColBoxWidth(Graphics g, Font font, int colNum)
+ {
+ int width = 0;
+
+ for (int row = 0; row < parentsCount; row++)
+ {
+ DataGridState dgs = (DataGridState)parents[row];
+ GridColumnStylesCollection columns = dgs.GridColumnStyles;
+ if (colNum < columns.Count)
+ {
+ // Graphics.MeasureString(...) returns different results for ": " than for " :"
+ //
+ string colName = columns[colNum].HeaderText + " :";
+ int size = (int)g.MeasureString(colName, font).Width;
+ width = Math.Max(size, width);
+ }
+ }
+
+ return width;
+ }
+
+ // will return the width of the best fit for the column
+ //
+ private int GetColDataBoxWidth(Graphics g, int colNum)
+ {
+ int width = 0;
+ for (int row = 0; row < parentsCount; row++)
+ {
+ DataGridState dgs = (DataGridState)parents[row];
+ GridColumnStylesCollection columns = dgs.GridColumnStyles;
+ if (colNum < columns.Count)
+ {
+ object value = columns[colNum].GetColumnValueAtRow((CurrencyManager)dataGrid.BindingContext[dgs.DataSource, dgs.DataMember],
+ dgs.LinkingRow.RowNumber);
+ int size = columns[colNum].GetPreferredSize(g, value).Width;
+ width = Math.Max(size, width);
+ }
+ }
+
+ return width;
+ }
+
+ // will return the count of the table with the largest number of columns
+ private int ColsCount()
+ {
+ int colNum = 0;
+ for (int row = 0; row < parentsCount; row++)
+ {
+ DataGridState dgs = (DataGridState)parents[row];
+ colNum = Math.Max(colNum, dgs.GridColumnStyles.Count);
+ }
+
+ return colNum;
+ }
+
+ // will return the total width required to paint the parentRows
+ private int TotalWidth(int tableNameBoxWidth, int[] colsNameWidths, int[] colsDataWidths)
+ {
+ int totalWidth = 0;
+ totalWidth += tableNameBoxWidth;
+ Debug.Assert(colsNameWidths.Length == colsDataWidths.Length, "both arrays are as long as the largest column count in dgs");
+ for (int i = 0; i < colsNameWidths.Length; i++)
+ {
+ totalWidth += colsNameWidths[i];
+ totalWidth += colsDataWidths[i];
+ }
+
+ // let 3 pixels in between datacolumns
+ // see DonnaWa
+ totalWidth += 3 * (colsNameWidths.Length - 1);
+ return totalWidth;
+ }
+
+ // computes the layout for the parent rows
+ //
+ private void ComputeLayout(Rectangle bounds, int tableNameBoxWidth, int[] colsNameWidths, int[] colsDataWidths)
+ {
+ int totalWidth = TotalWidth(tableNameBoxWidth, colsNameWidths, colsDataWidths);
+ if (totalWidth > bounds.Width)
+ {
+ layout.leftArrow = new Rectangle(bounds.X, bounds.Y, 15, bounds.Height);
+ layout.data = new Rectangle(layout.leftArrow.Right, bounds.Y, bounds.Width - 30, bounds.Height);
+ layout.rightArrow = new Rectangle(layout.data.Right, bounds.Y, 15, bounds.Height);
+ }
+ else
+ {
+ layout.data = bounds;
+ layout.leftArrow = Rectangle.Empty;
+ layout.rightArrow = Rectangle.Empty;
+ }
+ }
+
+ private void PaintParentRows(Graphics g, Rectangle bounds, bool alignToRight)
+ {
+ // variables needed for aligning the table and column names
+ int tableNameBoxWidth = 0;
+ int numCols = ColsCount();
+ int[] colsNameWidths = new int[numCols];
+ int[] colsDataWidths = new int[numCols];
+
+ // compute the size of the box that will contain the tableName
+ //
+ if (dataGrid.ParentRowsLabelStyle == DataGridParentRowsLabelStyle.TableName ||
+ dataGrid.ParentRowsLabelStyle == DataGridParentRowsLabelStyle.Both)
+ {
+ tableNameBoxWidth = GetTableBoxWidth(g, dataGrid.Font);
+ }
+
+ // initialiaze the arrays that contain the column names and the column size
+ //
+ for (int i = 0; i < numCols; i++)
+ {
+ if (dataGrid.ParentRowsLabelStyle == DataGridParentRowsLabelStyle.ColumnName ||
+ dataGrid.ParentRowsLabelStyle == DataGridParentRowsLabelStyle.Both)
+ {
+ colsNameWidths[i] = GetColBoxWidth(g, dataGrid.Font, i);
+ }
+ else
+ {
+ colsNameWidths[i] = 0;
+ }
+
+ colsDataWidths[i] = GetColDataBoxWidth(g, i);
+ }
+
+ // compute the layout
+ //
+ ComputeLayout(bounds, tableNameBoxWidth, colsNameWidths, colsDataWidths);
+
+ // paint the navigation arrows, if necessary
+ //
+ if (!layout.leftArrow.IsEmpty)
+ {
+ g.FillRectangle(BackBrush, layout.leftArrow);
+ PaintLeftArrow(g, layout.leftArrow, alignToRight);
+ }
+
+ // paint the parent rows:
+ //
+ Rectangle rowBounds = layout.data;
+ for (int row = 0; row < parentsCount; ++row)
+ {
+ rowBounds.Height = (int)rowHeights[row];
+ if (rowBounds.Y > bounds.Bottom)
+ {
+ break;
+ }
+
+ int paintedWidth = PaintRow(g, rowBounds, row, dataGrid.Font, alignToRight, tableNameBoxWidth, colsNameWidths, colsDataWidths);
+ if (row == parentsCount - 1)
+ {
+ break;
+ }
+
+ // draw the grid line below
+ g.DrawLine(gridLinePen, rowBounds.X, rowBounds.Bottom,
+ rowBounds.X + paintedWidth,
+ rowBounds.Bottom);
+ rowBounds.Y += rowBounds.Height;
+ }
+
+ if (!layout.rightArrow.IsEmpty)
+ {
+ g.FillRectangle(BackBrush, layout.rightArrow);
+ PaintRightArrow(g, layout.rightArrow, alignToRight);
+ }
+ }
+
+ private Bitmap GetBitmap(string bitmapName)
+ {
+ try
+ {
+ return ScaleHelper.GetIconResourceAsDefaultSizeBitmap(typeof(DataGridParentRows), bitmapName);
+ }
+ catch (Exception e)
+ {
+ Debug.Fail("Failed to load bitmap: " + bitmapName, e.ToString());
+ return null;
+ }
+ }
+
+ private Bitmap GetRightArrowBitmap()
+ {
+ if (rightArrow is null)
+ {
+ rightArrow = GetBitmap("DataGridParentRows.RightArrow");
+ }
+
+ return rightArrow;
+ }
+
+ private Bitmap GetLeftArrowBitmap()
+ {
+ if (leftArrow is null)
+ {
+ leftArrow = GetBitmap("DataGridParentRows.LeftArrow");
+ }
+
+ return leftArrow;
+ }
+
+ private void PaintBitmap(Graphics g, Bitmap b, Rectangle bounds)
+ {
+ // center the bitmap in the bounds:
+ int bmpX = bounds.X + (bounds.Width - b.Width) / 2;
+ int bmpY = bounds.Y + (bounds.Height - b.Height) / 2;
+ Rectangle bmpRect = new Rectangle(bmpX, bmpY, b.Width, b.Height);
+
+ g.FillRectangle(BackBrush, bmpRect);
+
+ // now draw the bitmap
+ ImageAttributes attr = new ImageAttributes();
+ colorMap[0].NewColor = ForeColor;
+ attr.SetRemapTable(colorMap, ColorAdjustType.Bitmap);
+ g.DrawImage(b, bmpRect, 0, 0, bmpRect.Width, bmpRect.Height, GraphicsUnit.Pixel, attr);
+ attr.Dispose();
+ }
+
+ /*
+ private void PaintOverButton(Graphics g, Rectangle bounds)
+ {
+ }
+ */
+
+ private void PaintDownButton(Graphics g, Rectangle bounds)
+ {
+ g.DrawLine(Pens.Black, bounds.X, bounds.Y, bounds.X + bounds.Width, bounds.Y); // the top
+ g.DrawLine(Pens.White, bounds.X + bounds.Width, bounds.Y, bounds.X + bounds.Width, bounds.Y + bounds.Height); // the right side
+ g.DrawLine(Pens.White, bounds.X + bounds.Width, bounds.Y + bounds.Height, bounds.X, bounds.Y + bounds.Height); // the right side
+ g.DrawLine(Pens.Black, bounds.X, bounds.Y + bounds.Height, bounds.X, bounds.Y); // the left side
+ }
+
+ private void PaintLeftArrow(Graphics g, Rectangle bounds, bool alignToRight)
+ {
+ Bitmap bmp = GetLeftArrowBitmap();
+ // paint the border around this bitmap if this is the case
+ //
+ /*
+ if (overLeftArrow)
+ {
+ Debug.Assert(!downLeftArrow, "can both of those happen?");
+ PaintOverButton(g, bounds);
+ layout.leftArrow.Inflate(-1,-1);
+ }
+ */
+ if (downLeftArrow)
+ {
+ PaintDownButton(g, bounds);
+ layout.leftArrow.Inflate(-1, -1);
+ lock (bmp)
+ {
+ PaintBitmap(g, bmp, bounds);
+ }
+
+ layout.leftArrow.Inflate(1, 1);
+ }
+ else
+ {
+ lock (bmp)
+ {
+ PaintBitmap(g, bmp, bounds);
+ }
+ }
+ }
+
+ private void PaintRightArrow(Graphics g, Rectangle bounds, bool alignToRight)
+ {
+ Bitmap bmp = GetRightArrowBitmap();
+ // paint the border around this bitmap if this is the case
+ //
+ /*
+ if (overRightArrow)
+ {
+ Debug.Assert(!downRightArrow, "can both of those happen?");
+ PaintOverButton(g, bounds);
+ layout.rightArrow.Inflate(-1,-1);
+ }
+ */
+ if (downRightArrow)
+ {
+ PaintDownButton(g, bounds);
+ layout.rightArrow.Inflate(-1, -1);
+ lock (bmp)
+ {
+ PaintBitmap(g, bmp, bounds);
+ }
+
+ layout.rightArrow.Inflate(1, 1);
+ }
+ else
+ {
+ lock (bmp)
+ {
+ PaintBitmap(g, bmp, bounds);
+ }
+ }
+ }
+
+ private int PaintRow(Graphics g, Rectangle bounds, int row, Font font, bool alignToRight,
+ int tableNameBoxWidth, int[] colsNameWidths, int[] colsDataWidths)
+ {
+ DataGridState dgs = (DataGridState)parents[row];
+ Rectangle paintBounds = bounds;
+ Rectangle rowBounds = bounds;
+ paintBounds.Height = (int)rowHeights[row];
+ rowBounds.Height = (int)rowHeights[row];
+
+ int paintedWidth = 0;
+ // used for scrolling: when paiting, we will skip horizOffset cells in the dataGrid ParentRows
+ int skippedCells = 0;
+
+ // paint the table name
+ if (dataGrid.ParentRowsLabelStyle == DataGridParentRowsLabelStyle.TableName ||
+ dataGrid.ParentRowsLabelStyle == DataGridParentRowsLabelStyle.Both)
+ {
+ if (skippedCells < horizOffset)
+ {
+ // skip this
+ skippedCells++;
+ }
+ else
+ {
+ paintBounds.Width = Math.Min(paintBounds.Width, tableNameBoxWidth);
+ paintBounds.X = MirrorRect(bounds, paintBounds, alignToRight);
+ string displayTableName = dgs.ListManager.GetListName() + ": ";
+ PaintText(g, paintBounds, displayTableName, font, true, alignToRight); // true is for painting bold
+ paintedWidth += paintBounds.Width;
+ }
+ }
+
+ if (paintedWidth >= bounds.Width)
+ {
+ return bounds.Width; // we painted everything
+ }
+
+ rowBounds.Width -= paintedWidth;
+ rowBounds.X += alignToRight ? 0 : paintedWidth;
+ paintedWidth += PaintColumns(g, rowBounds, dgs, font, alignToRight, colsNameWidths, colsDataWidths, skippedCells);
+
+ // paint the possible space left after columns
+ if (paintedWidth < bounds.Width)
+ {
+ paintBounds.X = bounds.X + paintedWidth;
+ paintBounds.Width = bounds.Width - paintedWidth;
+ paintBounds.X = MirrorRect(bounds, paintBounds, alignToRight);
+ g.FillRectangle(BackBrush, paintBounds);
+ }
+
+ return paintedWidth;
+ }
+
+ private int PaintColumns(Graphics g, Rectangle bounds, DataGridState dgs, Font font, bool alignToRight,
+ int[] colsNameWidths, int[] colsDataWidths, int skippedCells)
+ {
+ Rectangle paintBounds = bounds;
+ Rectangle rowBounds = bounds;
+ GridColumnStylesCollection cols = dgs.GridColumnStyles;
+ int cx = 0;
+
+ for (int i = 0; i < cols.Count; i++)
+ {
+ if (cx >= bounds.Width)
+ {
+ break;
+ }
+
+ // paint the column name, if we have to
+ if (dataGrid.ParentRowsLabelStyle == DataGridParentRowsLabelStyle.ColumnName ||
+ dataGrid.ParentRowsLabelStyle == DataGridParentRowsLabelStyle.Both)
+ {
+ if (skippedCells < horizOffset)
+ {
+ // skip this column
+ }
+ else
+ {
+ paintBounds.X = bounds.X + cx;
+ paintBounds.Width = Math.Min(bounds.Width - cx, colsNameWidths[i]);
+ paintBounds.X = MirrorRect(bounds, paintBounds, alignToRight);
+
+ string colName = cols[i].HeaderText + ": ";
+ PaintText(g, paintBounds, colName, font, false, alignToRight); // false is for not painting bold
+
+ cx += paintBounds.Width;
+ }
+ }
+
+ if (cx >= bounds.Width)
+ {
+ break;
+ }
+
+ if (skippedCells < horizOffset)
+ {
+ // skip this cell
+ skippedCells++;
+ }
+ else
+ {
+ // paint the cell contents
+ paintBounds.X = bounds.X + cx;
+ paintBounds.Width = Math.Min(bounds.Width - cx, colsDataWidths[i]);
+ paintBounds.X = MirrorRect(bounds, paintBounds, alignToRight);
+
+ // when we paint the data grid parent rows, we want to paint the data at the position
+ // stored in the currency manager.
+ cols[i].Paint(g, paintBounds, (CurrencyManager)dataGrid.BindingContext[dgs.DataSource, dgs.DataMember],
+ dataGrid.BindingContext[dgs.DataSource, dgs.DataMember].Position, BackBrush, ForeBrush, alignToRight);
+
+ cx += paintBounds.Width;
+
+ // draw the line to the right (or left, according to alignRight)
+ //
+ g.DrawLine(new Pen(SystemColors.ControlDark),
+ alignToRight ? paintBounds.X : paintBounds.Right,
+ paintBounds.Y,
+ alignToRight ? paintBounds.X : paintBounds.Right,
+ paintBounds.Bottom);
+
+ // this is how wide the line is....
+ cx++;
+
+ // put 3 pixels in between columns
+ // see DonnaWa
+ //
+ if (i < cols.Count - 1)
+ {
+ paintBounds.X = bounds.X + cx;
+ paintBounds.Width = Math.Min(bounds.Width - cx, 3);
+ paintBounds.X = MirrorRect(bounds, paintBounds, alignToRight);
+
+ g.FillRectangle(BackBrush, paintBounds);
+ cx += 3;
+ }
+ }
+ }
+
+ return cx;
+ }
+
+ ///
+ /// Draws on the screen the text. It is used only to paint the Table Name and the column Names
+ /// Returns the width of bounding rectangle that was passed in
+ ///
+ private int PaintText(Graphics g, Rectangle textBounds, string text, Font font, bool bold, bool alignToRight)
+ {
+ Font textFont = font;
+ if (bold)
+ {
+ try
+ {
+ textFont = new Font(font, FontStyle.Bold);
+ }
+ catch { }
+ }
+ else
+ {
+ textFont = font;
+ }
+
+ // right now, we paint the entire box, cause it will be used anyway
+ g.FillRectangle(BackBrush, textBounds);
+ StringFormat format = new StringFormat();
+ if (alignToRight)
+ {
+ format.FormatFlags |= StringFormatFlags.DirectionRightToLeft;
+ format.Alignment = StringAlignment.Far;
+ }
+
+ format.FormatFlags |= StringFormatFlags.NoWrap;
+ // part 1, section 3: put the table and the column name in the
+ // parent rows at the same height as the dataGridTextBoxColumn draws the string
+ //
+ textBounds.Offset(0, 2);
+ textBounds.Height -= 2;
+ g.DrawString(text, textFont, ForeBrush, textBounds, format);
+ format.Dispose();
+ return textBounds.Width;
+ }
+
+ // will return the X coordinate of the containedRect mirrored within the surroundingRect
+ // according to the value of alignToRight
+ private int MirrorRect(Rectangle surroundingRect, Rectangle containedRect, bool alignToRight)
+ {
+ Debug.Assert(containedRect.X >= surroundingRect.X && containedRect.Right <= surroundingRect.Right, "containedRect is not contained in surroundingRect");
+ if (alignToRight)
+ {
+ return surroundingRect.Right - containedRect.Right + surroundingRect.X;
+ }
+ else
+ {
+ return containedRect.X;
+ }
+ }
+
+ private class Layout
+ {
+ public Rectangle data;
+ public Rectangle leftArrow;
+ public Rectangle rightArrow;
+
+ public Layout()
+ {
+ data = Rectangle.Empty;
+ leftArrow = Rectangle.Empty;
+ rightArrow = Rectangle.Empty;
+ }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder(200);
+ sb.Append("ParentRows Layout: \n");
+ sb.Append("data = ");
+ sb.Append(data.ToString());
+ sb.Append("\n leftArrow = ");
+ sb.Append(leftArrow.ToString());
+ sb.Append("\n rightArrow = ");
+ sb.Append(rightArrow.ToString());
+ sb.Append('\n');
+
+ return sb.ToString();
+ }
+ }
+
+ [ComVisible(true)]
+ protected internal class DataGridParentRowsAccessibleObject : AccessibleObject
+ {
+ readonly DataGridParentRows owner;
+
+ public DataGridParentRowsAccessibleObject(DataGridParentRows owner) : base()
+ {
+ Debug.Assert(owner is not null, "DataGridParentRowsAccessibleObject must have a valid owner");
+ this.owner = owner;
+ }
+
+ internal DataGridParentRows Owner
+ {
+ get
+ {
+ return owner;
+ }
+ }
+
+ public override Rectangle Bounds
+ {
+ get
+ {
+ return owner.dataGrid.RectangleToScreen(owner.dataGrid.ParentRowsBounds);
+ }
+ }
+
+ public override string DefaultAction
+ {
+ get
+ {
+ return SR.AccDGNavigateBack;
+ }
+ }
+
+ public override string Name
+ {
+ get
+ {
+ return SR.AccDGParentRows;
+ }
+ }
+
+ public override AccessibleObject Parent
+ {
+ get
+ {
+ return owner.dataGrid.AccessibilityObject;
+ }
+ }
+
+ public override AccessibleRole Role
+ {
+ get
+ {
+ return AccessibleRole.List;
+ }
+ }
+
+ public override AccessibleStates State
+ {
+ get
+ {
+ AccessibleStates state = AccessibleStates.ReadOnly;
+
+ if (owner.parentsCount == 0)
+ {
+ state |= AccessibleStates.Invisible;
+ }
+
+ if (owner.dataGrid.ParentRowsVisible)
+ {
+ state |= AccessibleStates.Expanded;
+ }
+ else
+ {
+ state |= AccessibleStates.Collapsed;
+ }
+
+ return state;
+ }
+ }
+
+ public override string Value
+ {
+ get
+ {
+ return null;
+ }
+ }
+
+ public override void DoDefaultAction()
+ {
+ owner.dataGrid.NavigateBack();
+ }
+
+ public override AccessibleObject GetChild(int index)
+ {
+ return ((DataGridState)owner.parents[index]).ParentRowAccessibleObject;
+ }
+
+ public override int GetChildCount()
+ {
+ return owner.parentsCount;
+ }
+
+ ///
+ /// Returns the currently focused child, if any.
+ /// Returns this if the object itself is focused.
+ ///
+ public override AccessibleObject GetFocused()
+ {
+ return null;
+ }
+
+ internal AccessibleObject GetNext(AccessibleObject child)
+ {
+ int children = GetChildCount();
+ bool hit = false;
+
+ for (int i = 0; i < children; i++)
+ {
+ if (hit)
+ {
+ return GetChild(i);
+ }
+
+ if (GetChild(i) == child)
+ {
+ hit = true;
+ }
+ }
+
+ return null;
+ }
+
+ internal AccessibleObject GetPrev(AccessibleObject child)
+ {
+ int children = GetChildCount();
+ bool hit = false;
+
+ for (int i = children - 1; i >= 0; i--)
+ {
+ if (hit)
+ {
+ return GetChild(i);
+ }
+
+ if (GetChild(i) == child)
+ {
+ hit = true;
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Navigate to the next or previous grid entry.
+ ///
+ public override AccessibleObject Navigate(AccessibleNavigation navdir)
+ {
+ switch (navdir)
+ {
+ case AccessibleNavigation.Right:
+ case AccessibleNavigation.Next:
+ case AccessibleNavigation.Down:
+ return Parent.GetChild(1);
+ case AccessibleNavigation.Up:
+ case AccessibleNavigation.Left:
+ case AccessibleNavigation.Previous:
+ return Parent.GetChild(GetChildCount() - 1);
+ case AccessibleNavigation.FirstChild:
+ if (GetChildCount() > 0)
+ {
+ return GetChild(0);
+ }
+
+ break;
+ case AccessibleNavigation.LastChild:
+ if (GetChildCount() > 0)
+ {
+ return GetChild(GetChildCount() - 1);
+ }
+
+ break;
+ }
+
+ return null;
+ }
+
+ public override void Select(AccessibleSelection flags)
+ {
+ }
+ }
+ }
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridParentRowsLabel.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridParentRowsLabel.cs
index b8ecd8d2075..80145504be7 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridParentRowsLabel.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridParentRowsLabel.cs
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#pragma warning disable WFDEV006
+
using System.ComponentModel;
namespace System.Windows.Forms;
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridPreferredColumnWidthTypeConverter.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridPreferredColumnWidthTypeConverter.cs
index b1af37e754e..81ffb0f587a 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridPreferredColumnWidthTypeConverter.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridPreferredColumnWidthTypeConverter.cs
@@ -1,24 +1,76 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#pragma warning disable RS0016
+
+#nullable disable
+
using System.ComponentModel;
+using System.Globalization;
namespace System.Windows.Forms;
+ public class DataGridPreferredColumnWidthTypeConverter : TypeConverter
+ {
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ if (sourceType == typeof(string) || sourceType == typeof(int))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
-#nullable disable
+ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
+ {
+ if (destinationType == typeof(string))
+ {
+ if (value.GetType() == typeof(int))
+ {
+ int pulica = (int)value;
+ if (pulica == -1)
+ {
+ return "AutoColumnResize (-1)";
+ }
+ else
+ {
+ return pulica.ToString(CultureInfo.CurrentCulture);
+ }
+ }
+ else
+ {
+ return base.ConvertTo(context, culture, value, destinationType);
+ }
+ }
+ else
+ {
+ return base.ConvertTo(context, culture, value, destinationType);
+ }
+ }
-///
-/// This type is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code.
-///
-[Obsolete(
- Obsoletions.DataGridMessage,
- error: false,
- DiagnosticId = Obsoletions.UnsupportedControlsDiagnosticId,
- UrlFormat = Obsoletions.SharedUrlFormat)]
-[EditorBrowsable(EditorBrowsableState.Never)]
-[Browsable(false)]
-public class DataGridPreferredColumnWidthTypeConverter : TypeConverter
-{
- // Added explicit constructor to prevent the creation of a default constructor by the compiler.
- public DataGridPreferredColumnWidthTypeConverter() => throw new PlatformNotSupportedException();
-}
+ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+ {
+ if (value.GetType() == typeof(string))
+ {
+ string text = value.ToString();
+ if (text.Equals("AutoColumnResize (-1)"))
+ {
+ return -1;
+ }
+ else
+ {
+ return int.Parse(text, CultureInfo.CurrentCulture);
+ }
+ }
+ else if (value.GetType() == typeof(int))
+ {
+ return (int)value;
+ }
+ else
+ {
+ throw GetConvertFromException(value);
+ }
+ }
+ }
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridRelationshipRow.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridRelationshipRow.cs
new file mode 100644
index 00000000000..3304b2273d1
--- /dev/null
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridRelationshipRow.cs
@@ -0,0 +1,1281 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma warning disable IDE0040, IDE1006, SA1005, SA1206, SA1400, CA1510, CSIsNull001, CSIsNull002, IDE0100, IDE0059, IDE0051, IDE0060, IDE0036, CA1822, CA1823
+
+#nullable disable
+
+using System.Collections;
+using System.ComponentModel;
+using System.Drawing;
+using System.Globalization;
+using System.Runtime.InteropServices;
+
+namespace System.Windows.Forms;
+ ///
+ /// This class fully encapsulates the painting logic for a row
+ /// appearing in a DataGrid.
+ ///
+ internal class DataGridRelationshipRow : DataGridRow
+ {
+ private const bool defaultOpen = false;
+ private const int expandoBoxWidth = 14;
+ private const int indentWidth = 20;
+ // private const int relationshipSpacing = 1;
+ private const int triangleSize = 5;
+
+ private bool expanded = defaultOpen;
+ // private bool hasRelationships = false;
+ // private Font linkFont = null;
+ // private new DataGrid dataGrid; // Currently used only to obtain a Graphics object for measuring text
+
+ // private Rectangle relationshipRect = Rectangle.Empty;
+ // private int relationshipHeight = 0;
+
+ // relationships
+ // we should get this directly from the dgTable.
+ // private ArrayList relationships;
+ // private int focusedRelation = -1;
+ // private int focusedTextWidth;
+
+ public DataGridRelationshipRow(DataGrid dataGrid, DataGridTableStyle dgTable, int rowNumber)
+ : base(dataGrid, dgTable, rowNumber)
+ {
+ // this.dataGrid = dataGrid;
+ // linkFont = dataGrid.LinkFont;
+ // relationshipHeight = dataGrid.LinkFontHeight + this.dgTable.relationshipSpacing;
+
+ // if (DataGrid.AllowNavigation) {
+ // hasRelationships = dgTable.RelationsList.Count > 0;
+ // }
+ }
+
+ internal protected override int MinimumRowHeight(GridColumnStylesCollection cols)
+ {
+ /*
+ if (DataGrid != null && DataGrid.LinkFontHeight + this.dgTable.relationshipSpacing != relationshipHeight) {
+ relationshipRect = Rectangle.Empty;
+ relationshipHeight = DataGrid.LinkFontHeight + this.dgTable.relationshipSpacing;
+ }
+ */
+
+ return base.MinimumRowHeight(cols) + (expanded ? GetRelationshipRect().Height : 0);
+ }
+
+ internal protected override int MinimumRowHeight(DataGridTableStyle dgTable)
+ {
+ /*
+ if (DataGrid != null && DataGrid.LinkFontHeight + this.dgTable.relationshipSpacing != relationshipHeight) {
+ relationshipRect = Rectangle.Empty;
+ relationshipHeight = DataGrid.LinkFontHeight + this.dgTable.relationshipSpacing;
+ }
+ */
+
+ return base.MinimumRowHeight(dgTable) + (expanded ? GetRelationshipRect().Height : 0);
+ }
+
+ // =------------------------------------------------------------------
+ // = Properties
+ // =------------------------------------------------------------------
+
+ public virtual bool Expanded
+ {
+ get
+ {
+ return expanded;
+ }
+ set
+ {
+ if (expanded == value)
+ {
+ return;
+ }
+
+ if (expanded)
+ {
+ Collapse();
+ }
+ else
+ {
+ Expand();
+ }
+ }
+ }
+
+ /*
+ private Color BorderColor {
+ get {
+ if (DataGrid == null)
+ return Color.Empty;
+ return DataGrid.GridLineColor;
+ }
+ }
+ */
+
+#if FALSE
+ private int BorderWidth {
+ get {
+ DataGrid dataGrid = this.DataGrid;
+ if (dataGrid == null)
+ return 0;
+ // if the user set the GridLineStyle property on the dataGrid.
+ // then use the value of that property
+ DataGridLineStyle gridStyle;
+ int gridLineWidth;
+ if (this.dgTable.IsDefault) {
+ gridStyle = this.DataGrid.GridLineStyle;
+ gridLineWidth = this.DataGrid.GridLineWidth;
+ } else {
+ gridStyle = this.dgTable.GridLineStyle;
+ gridLineWidth = this.dgTable.GridLineWidth;
+ }
+
+ if (gridStyle == DataGridLineStyle.None)
+ return 0;
+
+ return gridLineWidth;
+ }
+ }
+#endif //FALSE
+
+ private int FocusedRelation
+ {
+ get
+ {
+ return dgTable.FocusedRelation;
+ }
+ set
+ {
+ dgTable.FocusedRelation = value;
+ }
+ }
+
+ // =------------------------------------------------------------------
+ // = Methods
+ // =------------------------------------------------------------------
+
+ private void Collapse()
+ {
+ Debug.Assert(dgTable.DataGrid.AllowNavigation, "how can the user collapse the relations if the grid does not allow navigation?");
+ if (expanded)
+ {
+ expanded = false;
+ // relationshipRect = Rectangle.Empty;
+ FocusedRelation = -1;
+ DataGrid.OnRowHeightChanged(this);
+ }
+ }
+
+ protected override AccessibleObject CreateAccessibleObject()
+ {
+ return new DataGridRelationshipRowAccessibleObject(this);
+ }
+
+ private void Expand()
+ {
+ Debug.Assert(dgTable.DataGrid.AllowNavigation, "how can the user expand the relations if the grid does not allow navigation?");
+ if (expanded == false
+ && DataGrid != null
+ && dgTable != null
+ && dgTable.RelationsList.Count > 0)
+ {
+ expanded = true;
+ FocusedRelation = -1;
+
+ // relationshipRect = Rectangle.Empty;
+ DataGrid.OnRowHeightChanged(this);
+ }
+ }
+
+ public override int Height
+ {
+ get
+ {
+ int height = base.Height;
+ if (expanded)
+ {
+ return height + GetRelationshipRect().Height;
+ }
+ else
+ {
+ return height;
+ }
+ }
+ set
+ {
+ // we should use the RelationshipRect only when the row is expanded
+ if (expanded)
+ {
+ base.Height = value - GetRelationshipRect().Height;
+ }
+ else
+ {
+ base.Height = value;
+ }
+ }
+ }
+
+ // so the edit box will not paint under the
+ // grid line of the row
+ public override Rectangle GetCellBounds(int col)
+ {
+ Rectangle cellBounds = base.GetCellBounds(col);
+ // decrement base.Height by 1, so the edit box will not
+ // paint over the bottom line.
+ cellBounds.Height = base.Height - 1;
+ return cellBounds;
+ }
+
+ ///
+ /// Given an origin, this procedure returns
+ /// a rectangle that describes the location of an outline box.
+ ///
+ private Rectangle GetOutlineRect(int xOrigin, int yOrigin)
+ {
+ Rectangle outline = new Rectangle(xOrigin + 2,
+ yOrigin + 2,
+ 9,
+ 9);
+ return outline;
+ }
+
+ public override Rectangle GetNonScrollableArea()
+ {
+ if (expanded)
+ {
+ return GetRelationshipRect();
+ }
+ else
+ {
+ return Rectangle.Empty;
+ }
+ }
+
+ private Rectangle GetRelationshipRect()
+ {
+ Debug.Assert(expanded, "we should need this rectangle only when the row is expanded");
+ Rectangle ret = dgTable.RelationshipRect;
+ ret.Y = base.Height - dgTable.BorderWidth;
+ return ret;
+ }
+
+#if FALSE
+ private Rectangle GetRelationshipRect() {
+ if (relationshipRect.IsEmpty) {
+ Debug.WriteLineIf(CompModSwitches.DGRelationShpRowLayout.TraceVerbose, "GetRelationshipRect grinding away");
+ if (!expanded) {
+ return(relationshipRect = new Rectangle(0,0,0,0));
+ }
+ Graphics g = DataGrid.CreateGraphicsInternal();
+ relationshipRect = new Rectangle();
+ relationshipRect.X = 0; //indentWidth;
+ relationshipRect.Y = base.Height - this.dgTable.BorderWidth;
+
+ // Determine the width of the widest relationship name
+ int longestRelationship = 0;
+ for (int r = 0; r < this.dgTable.RelationsList.Count; ++r) {
+ int rwidth = (int) Math.Ceiling(g.MeasureString(((string) this.dgTable.RelationsList[r]), this.DataGrid.LinkFont).Width);
+ if (rwidth > longestRelationship)
+ longestRelationship = rwidth;
+ }
+
+ g.Dispose();
+
+ relationshipRect.Width = longestRelationship + 5;
+ relationshipRect.Width += 2; // relationshipRect border;
+ relationshipRect.Height = this.dgTable.BorderWidth + relationshipHeight * this.dgTable.RelationsList.Count;
+ relationshipRect.Height += 2; // relationship border
+ if (this.dgTable.RelationsList.Count > 0)
+ relationshipRect.Height += 2 * System.Windows.Forms.DataGridTableStyle.relationshipSpacing;
+ }
+ return relationshipRect;
+ }
+
+#endif// FALSE
+
+ private Rectangle GetRelationshipRectWithMirroring()
+ {
+ Rectangle relRect = GetRelationshipRect();
+ bool rowHeadersVisible = dgTable.IsDefault ? DataGrid.RowHeadersVisible : dgTable.RowHeadersVisible;
+ if (rowHeadersVisible)
+ {
+ int rowHeaderWidth = dgTable.IsDefault ? DataGrid.RowHeaderWidth : dgTable.RowHeaderWidth;
+ relRect.X += DataGrid.GetRowHeaderRect().X + rowHeaderWidth;
+ }
+
+ relRect.X = MirrorRelationshipRectangle(relRect, DataGrid.GetRowHeaderRect(), DataGrid.RightToLeft == RightToLeft.Yes);
+ return relRect;
+ }
+
+ ///
+ /// Called by the DataGrid when a click occurs in the row's client
+ /// area. The coordinates are normalized to the rectangle's top
+ /// left point.
+ ///
+ private bool PointOverPlusMinusGlyph(int x, int y, Rectangle rowHeaders, bool alignToRight)
+ {
+ if (dgTable == null || dgTable.DataGrid == null || !dgTable.DataGrid.AllowNavigation)
+ {
+ return false;
+ }
+
+ Rectangle insideRowHeaders = rowHeaders;
+ if (!DataGrid.FlatMode)
+ {
+ insideRowHeaders.Inflate(-1, -1);
+ }
+
+ Rectangle outline = GetOutlineRect(insideRowHeaders.Right - expandoBoxWidth, 0);
+
+ outline.X = MirrorRectangle(outline.X, outline.Width, insideRowHeaders, alignToRight);
+
+ return outline.Contains(x, y);
+ }
+
+ public override bool OnMouseDown(int x, int y, Rectangle rowHeaders, bool alignToRight)
+ {
+ bool rowHeadersVisible = dgTable.IsDefault ? DataGrid.RowHeadersVisible : dgTable.RowHeadersVisible;
+ if (rowHeadersVisible)
+ {
+ if (PointOverPlusMinusGlyph(x, y, rowHeaders, alignToRight))
+ {
+ if (dgTable.RelationsList.Count == 0)
+ {
+ return false;
+ }
+ else if (expanded)
+ {
+ Collapse();
+ }
+ else
+ {
+ Expand();
+ }
+
+ DataGrid.OnNodeClick(EventArgs.Empty);
+ return true;
+ }
+ }
+
+ if (!expanded)
+ {
+ return base.OnMouseDown(x, y, rowHeaders, alignToRight);
+ }
+
+ // hit test for relationships
+ Rectangle relRect = GetRelationshipRectWithMirroring();
+
+ if (relRect.Contains(x, y))
+ {
+ int r = RelationFromY(y);
+ if (r != -1)
+ {
+ // first, reset the FocusedRelation
+ FocusedRelation = -1;
+ DataGrid.NavigateTo(((string)dgTable.RelationsList[r]), this, true);
+ }
+
+ // DataGrid.OnLinkClick(EventArgs.Empty);
+ return true;
+ }
+
+ return base.OnMouseDown(x, y, rowHeaders, alignToRight);
+ }
+
+ public override bool OnMouseMove(int x, int y, Rectangle rowHeaders, bool alignToRight)
+ {
+ if (!expanded)
+ {
+ return false;
+ }
+
+ Rectangle relRect = GetRelationshipRectWithMirroring();
+
+ if (relRect.Contains(x, y))
+ {
+ DataGrid.Cursor = Cursors.Hand;
+ return true;
+ }
+
+ DataGrid.Cursor = Cursors.Default;
+ return base.OnMouseMove(x, y, rowHeaders, alignToRight);
+ }
+
+ // this function will not invalidate all of the
+ // row
+ public override void OnMouseLeft(Rectangle rowHeaders, bool alignToRight)
+ {
+ if (!expanded)
+ {
+ return;
+ }
+
+ Rectangle relRect = GetRelationshipRect();
+ relRect.X += rowHeaders.X + dgTable.RowHeaderWidth;
+ relRect.X = MirrorRelationshipRectangle(relRect, rowHeaders, alignToRight);
+
+ if (FocusedRelation != -1)
+ {
+ InvalidateRowRect(relRect);
+ FocusedRelation = -1;
+ }
+ }
+
+ public override void OnMouseLeft()
+ {
+ if (!expanded)
+ {
+ return;
+ }
+
+ if (FocusedRelation != -1)
+ {
+ InvalidateRow();
+ FocusedRelation = -1;
+ }
+
+ base.OnMouseLeft();
+ }
+
+ ///
+ /// Called by the DataGrid when a keypress occurs on a row with "focus."
+ ///
+ public override bool OnKeyPress(Keys keyData)
+ {
+ // ignore the shift key if it is not paired w/ the TAB key
+ if ((keyData & Keys.Modifiers) == Keys.Shift && (keyData & Keys.KeyCode) != Keys.Tab)
+ {
+ return false;
+ }
+
+ switch (keyData & Keys.KeyCode)
+ {
+ case Keys.F5:
+ if (dgTable == null || dgTable.DataGrid == null || !dgTable.DataGrid.AllowNavigation)
+ {
+ return false;
+ }
+
+ if (expanded)
+ {
+ Collapse();
+ }
+ else
+ {
+ Expand();
+ }
+
+ FocusedRelation = -1;
+ return true;
+
+ // to make the gridTest run w/ the numLock key on
+ //
+ case Keys.NumLock:
+ if (FocusedRelation != -1)
+ {
+ return false;
+ }
+ else
+ {
+ return base.OnKeyPress(keyData);
+ }
+
+ case Keys.Enter:
+ if (FocusedRelation != -1)
+ {
+ // somebody set the relation number up already
+ // navigate to the relation
+ DataGrid.NavigateTo(((string)dgTable.RelationsList[FocusedRelation]), this, true);
+
+ // now reset the FocusedRelation
+ FocusedRelation = -1;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+
+ case Keys.Tab:
+ return false;
+
+ default:
+ FocusedRelation = -1;
+ return base.OnKeyPress(keyData);
+ }
+ }
+
+ // will reset the FocusedRelation and will invalidate the
+ // rectangle so that the linkFont is no longer shown
+ internal override void LoseChildFocus(Rectangle rowHeaders, bool alignToRight)
+ {
+ // we only invalidate stuff if the row is expanded.
+ if (FocusedRelation == -1 || !expanded)
+ {
+ return;
+ }
+
+ FocusedRelation = -1;
+ Rectangle relRect = GetRelationshipRect();
+ relRect.X += rowHeaders.X + dgTable.RowHeaderWidth;
+ relRect.X = MirrorRelationshipRectangle(relRect, rowHeaders, alignToRight);
+ InvalidateRowRect(relRect);
+ }
+
+ // here is the logic for FOCUSED:
+ //
+ // first the dataGrid gets the KeyPress.
+ // the dataGrid passes it to the currentRow. if it is anything other
+ // than Enter or TAB, the currentRow resets the FocusedRelation variable.
+ //
+ // Then the dataGrid takes another look at the TAB key and if it is the case
+ // it passes it to the row. If the dataRelationshipRow can become focused,
+ // then it eats the TAB key, otherwise it will give it back to the dataGrid.
+ //
+ internal override bool ProcessTabKey(Keys keyData, Rectangle rowHeaders, bool alignToRight)
+ {
+ Debug.Assert((keyData & Keys.Control) != Keys.Control, "the DataGridRelationshipRow only handles TAB and TAB-SHIFT");
+ Debug.Assert((keyData & Keys.Alt) != Keys.Alt, "the DataGridRelationshipRow only handles TAB and TAB-SHIFT");
+
+ // if there are no relationships, this row can't do anything with the
+ // key
+ if (dgTable.RelationsList.Count == 0 || dgTable.DataGrid == null || !dgTable.DataGrid.AllowNavigation)
+ {
+ return false;
+ }
+
+ // expand the relationship box
+ if (!expanded)
+ {
+ Expand();
+ }
+
+ if ((keyData & Keys.Shift) == Keys.Shift)
+ {
+ if (FocusedRelation == 0)
+ {
+ // if user hits TAB-SHIFT and the focus was on the first relationship then
+ // reset FocusedRelation and let the dataGrid use the key
+ //
+ // consider: Microsoft: if the relationships box is expanded, should we collapse it on leave?
+ FocusedRelation = -1;
+ return false;
+ }
+
+ // we need to invalidate the relationshipRectangle, and cause the linkFont to move
+ // to the next relation
+ Rectangle relRect = GetRelationshipRect();
+ relRect.X += rowHeaders.X + dgTable.RowHeaderWidth;
+ relRect.X = MirrorRelationshipRectangle(relRect, rowHeaders, alignToRight);
+ InvalidateRowRect(relRect);
+
+ if (FocusedRelation == -1)
+ {
+ // is the first time that the user focuses on this
+ // set of relationships
+ FocusedRelation = dgTable.RelationsList.Count - 1;
+ }
+ else
+ {
+ FocusedRelation--;
+ }
+
+ return true;
+ }
+ else
+ {
+ if (FocusedRelation == dgTable.RelationsList.Count - 1)
+ {
+ // if the user hits TAB and the focus was on the last relationship then
+ // reset FocusedRelation and let the dataGrid use the key
+ //
+ // consider: Microsoft: if the relationships box is expanded, should we collapse it on leave?
+ FocusedRelation = -1;
+ return false;
+ }
+
+ // we need to invalidate the relationshipRectangle, and cause the linkFont to move
+ // to the next relation
+ Rectangle relRect = GetRelationshipRect();
+ relRect.X += rowHeaders.X + dgTable.RowHeaderWidth;
+ relRect.X = MirrorRelationshipRectangle(relRect, rowHeaders, alignToRight);
+ InvalidateRowRect(relRect);
+
+ FocusedRelation++;
+ return true;
+ }
+ }
+
+ ///
+ /// Paints the row.
+ ///
+ public override int Paint(Graphics g, Rectangle bounds, Rectangle trueRowBounds, int firstVisibleColumn, int numVisibleColumns)
+ {
+ return Paint(g, bounds, trueRowBounds, firstVisibleColumn, numVisibleColumns, false);
+ }
+
+ public override int Paint(Graphics g,
+ Rectangle bounds, // negative offsetted row bounds
+ Rectangle trueRowBounds, // real row bounds.
+ int firstVisibleColumn,
+ int numVisibleColumns,
+ bool alignToRight)
+ {
+ if (CompModSwitches.DGRelationShpRowPaint.TraceVerbose)
+ {
+ Debug.WriteLine("Painting row " + RowNumber.ToString(CultureInfo.InvariantCulture) + " with bounds " + bounds.ToString());
+ }
+
+ int bWidth = dgTable.BorderWidth;
+
+ // paint the data cells
+ Rectangle dataBounds = bounds;
+ dataBounds.Height = base.Height - bWidth;
+ int dataWidth = PaintData(g, dataBounds, firstVisibleColumn, numVisibleColumns, alignToRight);
+ int dataWidthOffsetted = dataWidth + bounds.X - trueRowBounds.X;
+
+ dataBounds.Offset(0, bWidth); // use bWidth, not 1
+ if (bWidth > 0)
+ {
+ PaintBottomBorder(g, dataBounds, dataWidth, bWidth, alignToRight);
+ }
+
+ if (expanded && dgTable.RelationsList.Count > 0)
+ {
+ // paint the relationships
+ Rectangle relationBounds = new Rectangle(trueRowBounds.X,
+ dataBounds.Bottom,
+ trueRowBounds.Width,
+ trueRowBounds.Height - dataBounds.Height - 2 * bWidth);
+ PaintRelations(g, relationBounds, trueRowBounds, dataWidthOffsetted,
+ firstVisibleColumn, numVisibleColumns, alignToRight);
+ relationBounds.Height += 1;
+ if (bWidth > 0)
+ {
+ PaintBottomBorder(g, relationBounds, dataWidthOffsetted, bWidth, alignToRight);
+ }
+ }
+
+ return dataWidth;
+ }
+
+ protected override void PaintCellContents(Graphics g, Rectangle cellBounds, DataGridColumnStyle column,
+ Brush backBr, Brush foreBrush, bool alignToRight)
+ {
+ CurrencyManager listManager = DataGrid.ListManager;
+
+ // painting the error..
+ //
+ string errString = string.Empty;
+ Rectangle bounds = cellBounds;
+ object errInfo = DataGrid.ListManager[number];
+ if (errInfo is IDataErrorInfo)
+ {
+ errString = ((IDataErrorInfo)errInfo)[column.PropertyDescriptor.Name];
+ }
+
+ if (!string.IsNullOrEmpty(errString))
+ {
+ Bitmap bmp = GetErrorBitmap();
+ Rectangle errRect;
+ lock (bmp)
+ {
+ errRect = PaintIcon(g, bounds, true, alignToRight, bmp, backBr);
+ }
+
+ // paint the errors correctly when RTL = true
+ if (alignToRight)
+ {
+ bounds.Width -= errRect.Width + xOffset;
+ }
+ else
+ {
+ bounds.X += errRect.Width + xOffset;
+ }
+
+ DataGrid.ToolTipProvider.AddToolTip(errString, (IntPtr)(DataGrid.ToolTipId++), errRect);
+ }
+
+ column.Paint(g, bounds, listManager, RowNumber, backBr, foreBrush, alignToRight);
+ }
+
+ public override void PaintHeader(Graphics g, Rectangle bounds, bool alignToRight, bool isDirty)
+ {
+ DataGrid grid = DataGrid;
+
+ Rectangle insideBounds = bounds;
+
+ if (!grid.FlatMode)
+ {
+ ControlPaint.DrawBorder3D(g, insideBounds, Border3DStyle.RaisedInner);
+ insideBounds.Inflate(-1, -1);
+ }
+
+ if (dgTable.IsDefault)
+ {
+ PaintHeaderInside(g, insideBounds, DataGrid.HeaderBackBrush, alignToRight, isDirty);
+ }
+ else
+ {
+ PaintHeaderInside(g, insideBounds, dgTable.HeaderBackBrush, alignToRight, isDirty);
+ }
+ }
+
+ public void PaintHeaderInside(Graphics g, Rectangle bounds, Brush backBr, bool alignToRight, bool isDirty)
+ {
+ // paint the row header
+ bool paintPlusMinus = dgTable.RelationsList.Count > 0 && dgTable.DataGrid.AllowNavigation;
+ int rowHeaderBoundsX = MirrorRectangle(bounds.X,
+ bounds.Width - (paintPlusMinus ? expandoBoxWidth : 0),
+ bounds, alignToRight);
+
+ if (!alignToRight)
+ {
+ Debug.Assert(bounds.X == rowHeaderBoundsX, "what's up doc?");
+ }
+
+ Rectangle rowHeaderBounds = new Rectangle(rowHeaderBoundsX,
+ bounds.Y,
+ bounds.Width - (paintPlusMinus ? expandoBoxWidth : 0),
+ bounds.Height);
+
+ base.PaintHeader(g, rowHeaderBounds, alignToRight, isDirty);
+
+ // Paint the expando on the right
+ int expandoBoxX = MirrorRectangle(bounds.X + rowHeaderBounds.Width, expandoBoxWidth, bounds, alignToRight);
+
+ if (!alignToRight)
+ {
+ Debug.Assert(rowHeaderBounds.Right == expandoBoxX, "what's up doc?");
+ }
+
+ Rectangle expandoBox = new Rectangle(expandoBoxX,
+ bounds.Y,
+ expandoBoxWidth,
+ bounds.Height);
+ if (paintPlusMinus)
+ {
+ PaintPlusMinusGlyph(g, expandoBox, backBr, alignToRight);
+ }
+ }
+
+ ///
+ /// Paints the relationships below the data area.
+ ///
+ private void PaintRelations(Graphics g, Rectangle bounds, Rectangle trueRowBounds,
+ int dataWidth, int firstCol, int nCols, bool alignToRight)
+ {
+ // Calculate the relationship rect.
+ // relationshipRect = Rectangle.Empty;
+ Rectangle relRect = GetRelationshipRect();
+ //relRect.Offset(trueRowBounds.X, trueRowBounds.Y);
+ relRect.X = alignToRight ? bounds.Right - relRect.Width : bounds.X;
+ relRect.Y = bounds.Y;
+ int paintedWidth = Math.Max(dataWidth, relRect.Width);
+
+ // Paint the stuff to the right , or left (Bi-Di) of the relationship rect.
+ Region r = g.Clip;
+ g.ExcludeClip(relRect);
+
+ g.FillRectangle(GetBackBrush(),
+ alignToRight ? bounds.Right - dataWidth : bounds.X,
+ bounds.Y,
+ dataWidth,
+ bounds.Height);
+
+ // Paint the relations' text
+ g.SetClip(bounds);
+
+ relRect.Height -= dgTable.BorderWidth; // use bWidth not 1
+ g.DrawRectangle(SystemPens.ControlText, relRect.X, relRect.Y, relRect.Width - 1, relRect.Height - 1);
+ relRect.Inflate(-1, -1);
+
+ int cy = PaintRelationText(g, relRect, alignToRight);
+
+ if (cy < relRect.Height)
+ {
+ g.FillRectangle(GetBackBrush(), relRect.X, relRect.Y + cy, relRect.Width, relRect.Height - cy);
+ }
+
+ g.Clip = r;
+
+ // paint any exposed area to the right or to the left (BI-DI)
+ if (paintedWidth < bounds.Width)
+ {
+ int bWidth;
+ if (dgTable.IsDefault)
+ {
+ bWidth = DataGrid.GridLineWidth;
+ }
+ else
+ {
+ bWidth = dgTable.GridLineWidth;
+ }
+
+ g.FillRectangle(DataGrid.BackgroundBrush,
+ alignToRight ? bounds.X : bounds.X + paintedWidth,
+ bounds.Y,
+ bounds.Width - paintedWidth - bWidth + 1, // + 1 cause the relationship rectangle was deflated
+ bounds.Height);
+
+ // Paint the border to the right of each cell
+ if (bWidth > 0)
+ {
+ Brush br;
+ // if the user changed the gridLineColor on the dataGrid
+ // from the defaultValue, then use that value;
+ if (dgTable.IsDefault)
+ {
+ br = DataGrid.GridLineBrush;
+ }
+ else
+ {
+ br = dgTable.GridLineBrush;
+ }
+
+ g.FillRectangle(br,
+ alignToRight ? bounds.Right - bWidth - paintedWidth : bounds.X + paintedWidth - bWidth,
+ bounds.Y,
+ bWidth,
+ bounds.Height);
+ }
+ }
+ }
+
+ private int PaintRelationText(Graphics g, Rectangle bounds, bool alignToRight)
+ {
+ g.FillRectangle(GetBackBrush(), bounds.X, bounds.Y, bounds.Width, System.Windows.Forms.DataGridTableStyle.relationshipSpacing);
+
+ int relationshipHeight = dgTable.RelationshipHeight;
+ Rectangle textBounds = new Rectangle(bounds.X, bounds.Y + System.Windows.Forms.DataGridTableStyle.relationshipSpacing,
+ bounds.Width,
+ relationshipHeight);
+ int cy = System.Windows.Forms.DataGridTableStyle.relationshipSpacing;
+ for (int r = 0; r < dgTable.RelationsList.Count; ++r)
+ {
+ if (cy > bounds.Height)
+ {
+ break;
+ }
+
+ Brush textBrush = dgTable.IsDefault ? DataGrid.LinkBrush : dgTable.LinkBrush;
+
+ Font textFont = DataGrid.Font;
+ textBrush = dgTable.IsDefault ? DataGrid.LinkBrush : dgTable.LinkBrush;
+ textFont = DataGrid.LinkFont;
+
+ g.FillRectangle(GetBackBrush(), textBounds);
+
+ StringFormat format = new StringFormat();
+ if (alignToRight)
+ {
+ format.FormatFlags |= StringFormatFlags.DirectionRightToLeft;
+ format.Alignment = StringAlignment.Far;
+ }
+
+ g.DrawString(((string)dgTable.RelationsList[r]), textFont, textBrush, textBounds,
+ format);
+ if (r == FocusedRelation && number == DataGrid.CurrentCell.RowNumber)
+ {
+ textBounds.Width = dgTable.FocusedTextWidth;
+ ControlPaint.DrawFocusRectangle(g, textBounds, ((SolidBrush)textBrush).Color, ((SolidBrush)GetBackBrush()).Color);
+ textBounds.Width = bounds.Width;
+ }
+
+ format.Dispose();
+
+ textBounds.Y += relationshipHeight;
+ cy += textBounds.Height;
+ }
+
+ return cy;
+ }
+
+ private void PaintPlusMinusGlyph(Graphics g, Rectangle bounds, Brush backBr, bool alignToRight)
+ {
+ if (CompModSwitches.DGRelationShpRowPaint.TraceVerbose)
+ {
+ Debug.WriteLine("PlusMinusGlyph painting in bounds -> " + bounds.ToString());
+ }
+
+ Rectangle outline = GetOutlineRect(bounds.X, bounds.Y);
+
+ outline = Rectangle.Intersect(bounds, outline);
+ if (outline.IsEmpty)
+ {
+ return;
+ }
+
+ g.FillRectangle(backBr, bounds);
+
+ if (CompModSwitches.DGRelationShpRowPaint.TraceVerbose)
+ {
+ Debug.WriteLine("Painting PlusMinusGlyph with outline -> " + outline.ToString());
+ }
+
+ // draw the +/- box
+ Pen drawPen = dgTable.IsDefault ? DataGrid.HeaderForePen : dgTable.HeaderForePen;
+ g.DrawRectangle(drawPen, outline.X, outline.Y, outline.Width - 1, outline.Height - 1);
+
+ int indent = 2;
+ // draw the -
+ g.DrawLine(drawPen,
+ outline.X + indent, outline.Y + outline.Width / 2,
+ outline.Right - indent - 1, outline.Y + outline.Width / 2); // -1 on the y coordinate
+
+ if (!expanded)
+ {
+ // draw the vertical line to make +
+ g.DrawLine(drawPen,
+ outline.X + outline.Height / 2, outline.Y + indent,
+ outline.X + outline.Height / 2, outline.Bottom - indent - 1); // -1... hinting
+ }
+ else
+ {
+ Point[] points = new Point[3];
+ points[0] = new Point(outline.X + outline.Height / 2, outline.Bottom);
+
+ points[1] = new Point(points[0].X, bounds.Y + 2 * indent + base.Height);
+
+ points[2] = new Point(alignToRight ? bounds.X : bounds.Right,
+ points[1].Y);
+ g.DrawLines(drawPen, points);
+ }
+ }
+
+ private int RelationFromY(int y)
+ {
+ int relation = -1;
+ int relationshipHeight = dgTable.RelationshipHeight;
+ Rectangle relRect = GetRelationshipRect();
+ int cy = base.Height - dgTable.BorderWidth + System.Windows.Forms.DataGridTableStyle.relationshipSpacing;
+ while (cy < relRect.Bottom)
+ {
+ if (cy > y)
+ {
+ break;
+ }
+
+ cy += relationshipHeight;
+ relation++;
+ }
+
+ if (relation >= dgTable.RelationsList.Count)
+ {
+ return -1;
+ }
+
+ return relation;
+ }
+
+ // given the relRect and the rowHeader, this function will return the
+ // X coordinate of the relationship rectangle as it should appear on the screen
+ private int MirrorRelationshipRectangle(Rectangle relRect, Rectangle rowHeader, bool alignToRight)
+ {
+ if (alignToRight)
+ {
+ return rowHeader.X - relRect.Width;
+ }
+ else
+ {
+ return relRect.X;
+ }
+ }
+
+ // given the X and Width of a rectangle R1 contained in rect,
+ // this will return the X coordinate of the rectangle that corresponds to R1 in Bi-Di transformation
+ private int MirrorRectangle(int x, int width, Rectangle rect, bool alignToRight)
+ {
+ if (alignToRight)
+ {
+ return rect.Right + rect.X - width - x;
+ }
+ else
+ {
+ return x;
+ }
+ }
+
+ [ComVisible(true)]
+ protected class DataGridRelationshipRowAccessibleObject : DataGridRowAccessibleObject
+ {
+ public DataGridRelationshipRowAccessibleObject(DataGridRow owner) : base(owner)
+ {
+ }
+
+ protected override void AddChildAccessibleObjects(IList children)
+ {
+ base.AddChildAccessibleObjects(children);
+ DataGridRelationshipRow row = (DataGridRelationshipRow)Owner;
+ if (row.dgTable.RelationsList != null)
+ {
+ for (int i = 0; i < row.dgTable.RelationsList.Count; i++)
+ {
+ children.Add(new DataGridRelationshipAccessibleObject(row, i));
+ }
+ }
+ }
+
+ private DataGridRelationshipRow RelationshipRow
+ {
+ get
+ {
+ return (DataGridRelationshipRow)Owner;
+ }
+ }
+
+ public override string DefaultAction
+ {
+ get
+ {
+ if (RelationshipRow.dgTable.RelationsList.Count > 0)
+ {
+ if (RelationshipRow.Expanded)
+ {
+ return SR.AccDGCollapse;
+ }
+ else
+ {
+ return SR.AccDGExpand;
+ }
+ }
+
+ return null;
+ }
+ }
+
+ public override AccessibleStates State
+ {
+ get
+ {
+ AccessibleStates state = base.State;
+ if (RelationshipRow.dgTable.RelationsList.Count > 0)
+ {
+ if (((DataGridRelationshipRow)Owner).Expanded)
+ {
+ state |= AccessibleStates.Expanded;
+ }
+ else
+ {
+ state |= AccessibleStates.Collapsed;
+ }
+ }
+
+ return state;
+ }
+ }
+
+ public override void DoDefaultAction()
+ {
+ if (RelationshipRow.dgTable.RelationsList.Count > 0)
+ {
+ ((DataGridRelationshipRow)Owner).Expanded = !((DataGridRelationshipRow)Owner).Expanded;
+ }
+ }
+
+ public override AccessibleObject GetFocused()
+ {
+ DataGridRelationshipRow row = (DataGridRelationshipRow)Owner;
+ int focusRel = row.dgTable.FocusedRelation;
+ if (focusRel == -1)
+ {
+ return base.GetFocused();
+ }
+ else
+ {
+ return GetChild(GetChildCount() - row.dgTable.RelationsList.Count + focusRel);
+ }
+ }
+ }
+
+ [ComVisible(true)]
+ protected class DataGridRelationshipAccessibleObject : AccessibleObject
+ {
+ readonly DataGridRelationshipRow owner;
+ readonly int relationship;
+
+ public DataGridRelationshipAccessibleObject(DataGridRelationshipRow owner, int relationship) : base()
+ {
+ Debug.Assert(owner != null, "DataGridRelationshipAccessibleObject must have a valid owner DataGridRow");
+ this.owner = owner;
+ this.relationship = relationship;
+ }
+
+ public override Rectangle Bounds
+ {
+ get
+ {
+ Rectangle rowBounds = DataGrid.GetRowBounds(owner);
+
+ Rectangle bounds = owner.Expanded ? owner.GetRelationshipRectWithMirroring() : Rectangle.Empty;
+ bounds.Y += owner.dgTable.RelationshipHeight * relationship;
+ bounds.Height = owner.Expanded ? owner.dgTable.RelationshipHeight : 0; // when the row is collapsed the height of the relationship object should be 0
+ // GetRelationshipRectWithMirroring will use the row headers width
+ if (!owner.Expanded)
+ {
+ bounds.X += rowBounds.X;
+ }
+
+ bounds.Y += rowBounds.Y;
+
+ return owner.DataGrid.RectangleToScreen(bounds);
+ }
+ }
+
+ public override string Name
+ {
+ get
+ {
+ return (string)owner.dgTable.RelationsList[relationship];
+ }
+ }
+
+ protected DataGridRelationshipRow Owner
+ {
+ get
+ {
+ return owner;
+ }
+ }
+
+ public override AccessibleObject Parent
+ {
+ get
+ {
+ return owner.AccessibleObject;
+ }
+ }
+
+ protected DataGrid DataGrid
+ {
+ get
+ {
+ return owner.DataGrid;
+ }
+ }
+
+ public override AccessibleRole Role
+ {
+ get
+ {
+ return AccessibleRole.Link;
+ }
+ }
+
+ public override AccessibleStates State
+ {
+ get
+ {
+ DataGridRow[] dgRows = DataGrid.DataGridRows;
+ if (Array.IndexOf(dgRows, owner) == -1)
+ {
+ return AccessibleStates.Unavailable;
+ }
+
+ AccessibleStates state = AccessibleStates.Selectable
+ | AccessibleStates.Focusable
+ | AccessibleStates.Linked;
+
+ if (!owner.Expanded)
+ {
+ state |= AccessibleStates.Invisible;
+ }
+
+ if (DataGrid.Focused && Owner.dgTable.FocusedRelation == relationship)
+ {
+ state |= AccessibleStates.Focused;
+ }
+
+ return state;
+ }
+ }
+
+ public override string Value
+ {
+ get
+ {
+ DataGridRow[] dgRows = DataGrid.DataGridRows;
+ if (Array.IndexOf(dgRows, owner) == -1)
+ {
+ return null;
+ }
+ else
+ {
+ return (string)owner.dgTable.RelationsList[relationship];
+ }
+ }
+ set
+ {
+ // not supported
+ }
+ }
+
+ public override string DefaultAction
+ {
+ get
+ {
+ return SR.AccDGNavigate;
+ }
+ }
+
+ public override void DoDefaultAction()
+ {
+ ((DataGridRelationshipRow)Owner).Expanded = true;
+ owner.FocusedRelation = -1;
+ DataGrid.NavigateTo((string)owner.dgTable.RelationsList[relationship], owner, true);
+ DataGrid.BeginInvoke(new MethodInvoker(ResetAccessibilityLayer));
+ }
+
+ private void ResetAccessibilityLayer()
+ {
+ ((DataGrid.DataGridAccessibleObject)DataGrid.AccessibilityObject).NotifyClients(AccessibleEvents.Reorder, 0);
+ ((DataGrid.DataGridAccessibleObject)DataGrid.AccessibilityObject).NotifyClients(AccessibleEvents.Focus, DataGrid.CurrentCellAccIndex);
+ ((DataGrid.DataGridAccessibleObject)DataGrid.AccessibilityObject).NotifyClients(AccessibleEvents.Selection, DataGrid.CurrentCellAccIndex);
+ }
+
+ ///
+ /// Navigate to the next or previous grid entry.
+ ///
+ public override AccessibleObject Navigate(AccessibleNavigation navdir)
+ {
+ switch (navdir)
+ {
+ case AccessibleNavigation.Right:
+ case AccessibleNavigation.Next:
+ case AccessibleNavigation.Down:
+ if (relationship + 1 < owner.dgTable.RelationsList.Count)
+ {
+ return Parent.GetChild(Parent.GetChildCount() - owner.dgTable.RelationsList.Count + relationship + 1);
+ }
+
+ break;
+ case AccessibleNavigation.Up:
+ case AccessibleNavigation.Left:
+ case AccessibleNavigation.Previous:
+ if (relationship > 0)
+ {
+ return Parent.GetChild(Parent.GetChildCount() - owner.dgTable.RelationsList.Count + relationship - 1);
+ }
+
+ break;
+ }
+
+ return null;
+ }
+
+ public override void Select(AccessibleSelection flags)
+ {
+ // Focus the PropertyGridView window
+ //
+ if ((flags & AccessibleSelection.TakeFocus) == AccessibleSelection.TakeFocus)
+ {
+ DataGrid.Focus();
+ }
+
+ if ((flags & AccessibleSelection.TakeSelection) == AccessibleSelection.TakeSelection)
+ {
+ Owner.FocusedRelation = relationship;
+ }
+ }
+ }
+ }
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridRow.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridRow.cs
new file mode 100644
index 00000000000..70e20dbd4ff
--- /dev/null
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridRow.cs
@@ -0,0 +1,1199 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma warning disable IDE0040, IDE1006, SA1005, SA1206, SA1400, CA1510, CSIsNull001, CSIsNull002, WFDEV006, IDE0036, IDE0300, SA1129, IDE0270, IDE0078, IDE0059, IDE0060, CA1822
+
+#nullable disable
+
+using System.Collections;
+using System.ComponentModel;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Runtime.InteropServices;
+using DSR = System.Windows.Forms.DataGridStrings;
+
+namespace System.Windows.Forms;
+ ///
+ /// Encapsulates the painting logic for a new row added to a control.
+ ///
+ internal abstract class DataGridRow : MarshalByRefObject
+ {
+ internal protected int number; // row number
+ private bool selected;
+ private int height;
+ // protected DataRow dataRow;
+ private IntPtr tooltipID = new IntPtr(-1);
+ private string tooltip = string.Empty;
+ AccessibleObject accessibleObject;
+
+ // for accessibility...
+ //
+ // internal DataGrid dataGrid;
+
+ // will need this for the painting information ( row header color )
+ //
+ protected DataGridTableStyle dgTable;
+
+ // we will be mapping only the black color to
+ // the HeaderForeColor
+ //
+ private static readonly ColorMap[] colorMap = new ColorMap[] { new ColorMap() };
+
+ // bitmaps
+ //
+ private static Bitmap rightArrow;
+ private static Bitmap leftArrow;
+ private static Bitmap errorBmp;
+ private static Bitmap pencilBmp;
+ private static Bitmap starBmp;
+ protected const int xOffset = 3;
+ protected const int yOffset = 2;
+
+ ///
+ /// Initializes a new instance of a .
+ ///
+ public DataGridRow(DataGrid dataGrid, DataGridTableStyle dgTable, int rowNumber)
+ {
+ if (dataGrid == null || dgTable.DataGrid == null)
+ {
+ throw new ArgumentNullException(nameof(dataGrid));
+ }
+
+ if (rowNumber < 0)
+ {
+ throw new ArgumentException(DSR.DataGridRowRowNumber, nameof(rowNumber));
+ }
+
+ // this.dataGrid = dataGrid;
+ number = rowNumber;
+
+ // map the black color in the pictures to the DataGrid's HeaderForeColor
+ //
+ colorMap[0].OldColor = Color.Black;
+ colorMap[0].NewColor = dgTable.HeaderForeColor;
+
+ this.dgTable = dgTable;
+ height = MinimumRowHeight(dgTable);
+ }
+
+ public AccessibleObject AccessibleObject
+ {
+ get
+ {
+ if (accessibleObject == null)
+ {
+ accessibleObject = CreateAccessibleObject();
+ }
+
+ return accessibleObject;
+ }
+ }
+
+ protected virtual AccessibleObject CreateAccessibleObject()
+ {
+ return new DataGridRowAccessibleObject(this);
+ }
+
+ internal protected virtual int MinimumRowHeight(DataGridTableStyle dgTable)
+ {
+ return MinimumRowHeight(dgTable.GridColumnStyles);
+ }
+
+ internal protected virtual int MinimumRowHeight(GridColumnStylesCollection columns)
+ {
+ int h = dgTable.IsDefault ? DataGrid.PreferredRowHeight : dgTable.PreferredRowHeight;
+
+ try
+ {
+ if (dgTable.DataGrid.DataSource != null)
+ {
+ int nCols = columns.Count;
+ for (int i = 0; i < nCols; ++i)
+ {
+ // if (columns[i].Visible && columns[i].PropertyDescriptor != null)
+ if (columns[i].PropertyDescriptor != null)
+ {
+ h = Math.Max(h, columns[i].GetMinimumHeight());
+ }
+ }
+ }
+ }
+ catch
+ {
+ }
+
+ return h;
+ }
+
+ // =------------------------------------------------------------------
+ // = Properties
+ // =------------------------------------------------------------------
+
+ ///
+ /// Gets the control the row belongs to.
+ ///
+ public DataGrid DataGrid
+ {
+ get
+ {
+ return dgTable.DataGrid;
+ }
+ }
+
+ internal DataGridTableStyle DataGridTableStyle
+ {
+ get
+ {
+ return dgTable;
+ }
+ set
+ {
+ dgTable = value;
+ }
+ }
+
+ /*
+ public DataGridTable DataGridTable {
+ get {
+ return dgTable;
+ }
+ }
+ */
+
+ /*
+ public DataRow DataRow {
+ get {
+ return dataRow;
+ }
+ }
+ */
+
+ ///
+ /// Gets or sets the height of the row.
+ ///
+ public virtual int Height
+ {
+ get
+ {
+ return height;
+ }
+ set
+ {
+ // the height of the row should be at least 0.
+ // this way, if the row has a relationship list and the user resizes the row such that
+ // the new height does not accomodate the height of the relationship list
+ // the row will at least show the relationship list ( and not paint on the portion of the row above this one )
+ height = Math.Max(0, value);
+ // when we resize the row, or when we set the PreferredRowHeigth on the
+ // DataGridTableStyle, we change the height of the Row, which will cause to invalidate,
+ // then the grid itself will do another invalidate call.
+ dgTable.DataGrid.OnRowHeightChanged(this);
+ }
+ }
+
+ ///
+ /// Gets the row's number.
+ ///
+ public int RowNumber
+ {
+ get
+ {
+ return number;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the row is selected.
+ ///
+ public virtual bool Selected
+ {
+ get
+ {
+ return selected;
+ }
+ set
+ {
+ selected = value;
+ InvalidateRow();
+ }
+ }
+
+ // =------------------------------------------------------------------
+ // = Methods
+ // =------------------------------------------------------------------
+
+ ///
+ /// Gets the bitmap associated with the row.
+ ///
+ protected Bitmap GetBitmap(string bitmapName)
+ {
+ try
+ {
+ return ScaleHelper.GetIconResourceAsDefaultSizeBitmap(typeof(DataGridCaption), bitmapName);
+ }
+ catch (Exception e)
+ {
+ Debug.Fail("Failed to load bitmap: " + bitmapName, e.ToString());
+ throw;
+ }
+ }
+
+ ///
+ /// When overridden in a derived class, gets the
+ /// where a cell's contents gets painted.
+ ///
+ public virtual Rectangle GetCellBounds(int col)
+ {
+ int firstVisibleCol = dgTable.DataGrid.FirstVisibleColumn;
+ int cx = 0;
+ Rectangle cellBounds = new Rectangle();
+ GridColumnStylesCollection columns = dgTable.GridColumnStyles;
+ if (columns != null)
+ {
+ for (int i = firstVisibleCol; i < col; i++)
+ {
+ if (columns[i].PropertyDescriptor != null)
+ {
+ cx += columns[i].Width;
+ }
+ }
+
+ int borderWidth = dgTable.GridLineWidth;
+ cellBounds = new Rectangle(cx,
+ 0,
+ columns[col].Width - borderWidth,
+ Height - borderWidth);
+ }
+
+ return cellBounds;
+ }
+
+ ///
+ /// When overridden in a derived class, gets the of the non-scrollable area of
+ /// the row.
+ ///
+ public virtual Rectangle GetNonScrollableArea()
+ {
+ return Rectangle.Empty;
+ }
+
+ ///
+ /// Gets or sets the bitmap displayed in the row header of a new row.
+ ///
+ protected Bitmap GetStarBitmap()
+ {
+ if (starBmp == null)
+ {
+ starBmp = GetBitmap("DataGridRow.star");
+ }
+
+ return starBmp;
+ }
+
+ ///
+ /// Gets or sets the bitmap displayed in the row header that indicates a row can
+ /// be edited.
+ ///
+ protected Bitmap GetPencilBitmap()
+ {
+ if (pencilBmp == null)
+ {
+ pencilBmp = GetBitmap("DataGridRow.pencil");
+ }
+
+ return pencilBmp;
+ }
+
+ ///
+ /// Gets or sets the bitmap displayed on a row with an error.
+ ///
+ protected Bitmap GetErrorBitmap()
+ {
+ if (errorBmp == null)
+ {
+ errorBmp = GetBitmap("DataGridRow.error");
+ }
+
+ return errorBmp;
+ }
+
+ protected Bitmap GetLeftArrowBitmap()
+ {
+ if (leftArrow == null)
+ {
+ leftArrow = GetBitmap("DataGridRow.left");
+ }
+
+ return leftArrow;
+ }
+
+ protected Bitmap GetRightArrowBitmap()
+ {
+ if (rightArrow == null)
+ {
+ rightArrow = GetBitmap("DataGridRow.right");
+ }
+
+ return rightArrow;
+ }
+
+ public virtual void InvalidateRow()
+ {
+ dgTable.DataGrid.InvalidateRow(number);
+ }
+
+ public virtual void InvalidateRowRect(Rectangle r)
+ {
+ dgTable.DataGrid.InvalidateRowRect(number, r);
+ }
+
+ ///
+ /// When overridden in a derived class, notifies the grid that an edit will
+ /// occur.
+ ///
+ public virtual void OnEdit()
+ {
+ }
+
+ ///
+ /// When overridden in a derived class, called by the control when a key press occurs on a row with focus.
+ ///
+ public virtual bool OnKeyPress(Keys keyData)
+ {
+ int currentColIndex = dgTable.DataGrid.CurrentCell.ColumnNumber;
+ GridColumnStylesCollection columns = dgTable.GridColumnStyles;
+ if (columns != null && currentColIndex >= 0 && currentColIndex < columns.Count)
+ {
+ DataGridColumnStyle currentColumn = columns[currentColIndex];
+ if (currentColumn.KeyPress(RowNumber, keyData))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Called by the when a click occurs in the row's client area
+ /// specifed by the x and y coordinates and the specified
+ /// .
+ ///
+ public virtual bool OnMouseDown(int x, int y, Rectangle rowHeaders)
+ {
+ return OnMouseDown(x, y, rowHeaders, false);
+ }
+
+ ///
+ /// When overridden in a derived class, is called by the when a click occurs
+ /// in the row's
+ /// client area, specified by x and y coordinates.
+ ///
+ public virtual bool OnMouseDown(int x, int y, Rectangle rowHeaders, bool alignToRight)
+ {
+ // if we call base.OnMouseDown, then the row could not use this
+ // mouse click at all. in that case LoseChildFocus, so the edit control
+ // will become visible
+ LoseChildFocus(rowHeaders, alignToRight);
+
+ // we did not use this click at all.
+ return false;
+ }
+
+ public virtual bool OnMouseMove(int x, int y, Rectangle rowHeaders)
+ {
+ return false;
+ }
+
+ ///
+ /// When overridden in a derived class, is called by the when
+ /// the mouse moves within the row's client area.
+ ///
+ public virtual bool OnMouseMove(int x, int y, Rectangle rowHeaders, bool alignToRight)
+ {
+ return false;
+ }
+
+ public virtual void OnMouseLeft(Rectangle rowHeaders, bool alignToRight)
+ {
+ }
+
+ public virtual void OnMouseLeft()
+ {
+ }
+
+ ///
+ /// When overridden in a derived class, causes the RowEnter event to occur.
+ ///
+ public virtual void OnRowEnter() { }
+ public virtual void OnRowLeave() { }
+
+ // processes the Tab Key
+ // returns TRUE if the TAB key is processed
+ internal abstract bool ProcessTabKey(Keys keyData, Rectangle rowHeaders, bool alignToRight);
+
+ // tells the dataGridRow that it lost the focus
+ internal abstract void LoseChildFocus(Rectangle rowHeaders, bool alignToRight);
+
+ ///
+ /// Paints the row.
+ ///
+ public abstract int Paint(Graphics g,
+ Rectangle dataBounds,
+ Rectangle rowBounds,
+ int firstVisibleColumn,
+ int numVisibleColumns);
+
+ public abstract int Paint(Graphics g,
+ Rectangle dataBounds,
+ Rectangle rowBounds,
+ int firstVisibleColumn,
+ int numVisibleColumns,
+ bool alignToRight);
+
+ ///
+ /// Draws a border on the bottom DataGrid.GridLineWidth pixels
+ /// of the bounding rectangle passed in.
+ ///
+ protected virtual void PaintBottomBorder(Graphics g, Rectangle bounds, int dataWidth)
+ {
+ PaintBottomBorder(g, bounds, dataWidth, dgTable.GridLineWidth, false);
+ }
+
+ protected virtual void PaintBottomBorder(Graphics g, Rectangle bounds, int dataWidth, int borderWidth, bool alignToRight)
+ {
+ // paint bottom border
+ Rectangle bottomBorder = new Rectangle(alignToRight ? bounds.Right - dataWidth : bounds.X,
+ bounds.Bottom - borderWidth,
+ dataWidth,
+ borderWidth);
+
+ g.FillRectangle(dgTable.IsDefault ? DataGrid.GridLineBrush : dgTable.GridLineBrush, bottomBorder);
+
+ // paint any exposed region to the right
+ if (dataWidth < bounds.Width)
+ {
+ g.FillRectangle(dgTable.DataGrid.BackgroundBrush,
+ alignToRight ? bounds.X : bottomBorder.Right,
+ bottomBorder.Y,
+ bounds.Width - bottomBorder.Width,
+ borderWidth);
+ }
+ }
+
+ ///
+ /// Paints the row.
+ ///
+ public virtual int PaintData(Graphics g,
+ Rectangle bounds,
+ int firstVisibleColumn,
+ int columnCount)
+ {
+ return PaintData(g, bounds, firstVisibleColumn, columnCount, false);
+ }
+
+ public virtual int PaintData(Graphics g,
+ Rectangle bounds,
+ int firstVisibleColumn,
+ int columnCount,
+ bool alignToRight)
+ {
+ Debug.WriteLineIf(CompModSwitches.DGRowPaint.TraceVerbose, "Painting DataGridAddNewRow: bounds = " + bounds.ToString());
+
+ Rectangle cellBounds = bounds;
+ int bWidth = dgTable.IsDefault ? DataGrid.GridLineWidth : dgTable.GridLineWidth;
+ int cx = 0;
+
+ DataGridCell current = dgTable.DataGrid.CurrentCell;
+
+ GridColumnStylesCollection columns = dgTable.GridColumnStyles;
+ int nCols = columns.Count;
+ for (int col = firstVisibleColumn; col < nCols; ++col)
+ {
+ if (cx > bounds.Width)
+ {
+ break;
+ }
+
+ // if (!columns[col].Visible || columns[col].PropertyDescriptor == null)
+ if (columns[col].PropertyDescriptor == null || columns[col].Width <= 0)
+ {
+ continue;
+ }
+
+ cellBounds.Width = columns[col].Width - bWidth;
+
+ if (alignToRight)
+ {
+ cellBounds.X = bounds.Right - cx - cellBounds.Width;
+ }
+ else
+ {
+ cellBounds.X = bounds.X + cx;
+ }
+
+ // Paint the data with the the DataGridColumn
+ Brush backBr = BackBrushForDataPaint(ref current, columns[col], col);
+ Brush foreBrush = ForeBrushForDataPaint(ref current, columns[col], col);
+
+ PaintCellContents(g,
+ cellBounds,
+ columns[col],
+ backBr,
+ foreBrush,
+ alignToRight);
+
+ // Paint the border to the right of each cell
+ if (bWidth > 0)
+ {
+ g.FillRectangle(dgTable.IsDefault ? DataGrid.GridLineBrush : dgTable.GridLineBrush,
+ alignToRight ? cellBounds.X - bWidth : cellBounds.Right,
+ cellBounds.Y,
+ bWidth,
+ cellBounds.Height);
+ }
+
+ cx += cellBounds.Width + bWidth;
+ }
+
+ // Paint any exposed area to the right ( or left ) of the data cell area
+ if (cx < bounds.Width)
+ {
+ g.FillRectangle(dgTable.DataGrid.BackgroundBrush,
+ alignToRight ? bounds.X : bounds.X + cx,
+ bounds.Y,
+ bounds.Width - cx,
+ bounds.Height);
+ }
+
+ return cx;
+ }
+
+ protected virtual void PaintCellContents(Graphics g, Rectangle cellBounds, DataGridColumnStyle column,
+ Brush backBr, Brush foreBrush)
+ {
+ PaintCellContents(g, cellBounds, column, backBr, foreBrush, false);
+ }
+
+ protected virtual void PaintCellContents(Graphics g, Rectangle cellBounds, DataGridColumnStyle column,
+ Brush backBr, Brush foreBrush, bool alignToRight)
+ {
+ g.FillRectangle(backBr, cellBounds);
+ }
+
+ //
+ // This function will do the following: if paintIcon is set to true, then
+ // will draw the image on the RowHeader. if paintIcon is set to false,
+ // then this function will fill the rectangle on which otherwise will
+ // have been drawn the image
+ //
+ // will return the rectangle that includes the Icon
+ //
+ protected Rectangle PaintIcon(Graphics g, Rectangle visualBounds, bool paintIcon, bool alignToRight, Bitmap bmp)
+ {
+ return PaintIcon(g, visualBounds, paintIcon, alignToRight, bmp,
+ dgTable.IsDefault ? DataGrid.HeaderBackBrush : dgTable.HeaderBackBrush);
+ }
+
+ protected Rectangle PaintIcon(Graphics g, Rectangle visualBounds, bool paintIcon, bool alignToRight, Bitmap bmp, Brush backBrush)
+ {
+ Size bmpSize = bmp.Size;
+ Rectangle bmpRect = new Rectangle(alignToRight ? visualBounds.Right - xOffset - bmpSize.Width : visualBounds.X + xOffset,
+ visualBounds.Y + yOffset,
+ bmpSize.Width,
+ bmpSize.Height);
+ g.FillRectangle(backBrush, visualBounds);
+ if (paintIcon)
+ {
+ colorMap[0].NewColor = dgTable.IsDefault ? DataGrid.HeaderForeColor : dgTable.HeaderForeColor;
+ colorMap[0].OldColor = Color.Black;
+ ImageAttributes attr = new ImageAttributes();
+ attr.SetRemapTable(colorMap, ColorAdjustType.Bitmap);
+ g.DrawImage(bmp, bmpRect, 0, 0, bmpRect.Width, bmpRect.Height, GraphicsUnit.Pixel, attr);
+ // g.DrawImage(bmp, bmpRect);
+ attr.Dispose();
+ }
+
+ return bmpRect;
+ }
+
+ // assume that the row is not aligned to right, and that the row is not dirty
+ public virtual void PaintHeader(Graphics g, Rectangle visualBounds)
+ {
+ PaintHeader(g, visualBounds, false);
+ }
+
+ // assume that the row is not dirty
+ public virtual void PaintHeader(Graphics g, Rectangle visualBounds, bool alignToRight)
+ {
+ PaintHeader(g, visualBounds, alignToRight, false);
+ }
+
+ public virtual void PaintHeader(Graphics g, Rectangle visualBounds, bool alignToRight, bool rowIsDirty)
+ {
+ Rectangle bounds = visualBounds;
+
+ // paint the first part of the row header: the Arror or Pencil/Star
+ Bitmap bmp;
+ if (this is DataGridAddNewRow)
+ {
+ bmp = GetStarBitmap();
+ lock (bmp)
+ {
+ bounds.X += PaintIcon(g, bounds, true, alignToRight, bmp).Width + xOffset;
+ }
+
+ return;
+ }
+ else if (rowIsDirty)
+ {
+ bmp = GetPencilBitmap();
+ lock (bmp)
+ {
+ bounds.X += PaintIcon(g, bounds, RowNumber == DataGrid.CurrentCell.RowNumber, alignToRight, bmp).Width + xOffset;
+ }
+ }
+ else
+ {
+ bmp = alignToRight ? GetLeftArrowBitmap() : GetRightArrowBitmap();
+ lock (bmp)
+ {
+ bounds.X += PaintIcon(g, bounds, RowNumber == DataGrid.CurrentCell.RowNumber, alignToRight, bmp).Width + xOffset;
+ }
+ }
+
+ // Paint the error icon
+ //
+ object errorInfo = DataGrid.ListManager[number];
+ if (!(errorInfo is IDataErrorInfo))
+ {
+ return;
+ }
+
+ string errString = ((IDataErrorInfo)errorInfo).Error;
+ if (errString == null)
+ {
+ errString = string.Empty;
+ }
+
+ if (tooltip != errString)
+ {
+ if (!string.IsNullOrEmpty(tooltip))
+ {
+ DataGrid.ToolTipProvider.RemoveToolTip(tooltipID);
+ tooltip = string.Empty;
+ tooltipID = new IntPtr(-1);
+ }
+ }
+
+ if (string.IsNullOrEmpty(errString))
+ {
+ return;
+ }
+
+ // we now have an error string: paint the errorIcon and add the tooltip
+ Rectangle errRect;
+ bmp = GetErrorBitmap();
+ lock (bmp)
+ {
+ errRect = PaintIcon(g, bounds, true, alignToRight, bmp);
+ }
+
+ bounds.X += errRect.Width + xOffset;
+
+ tooltip = errString;
+ tooltipID = (IntPtr)((int)DataGrid.ToolTipId++);
+ DataGrid.ToolTipProvider.AddToolTip(tooltip, tooltipID, errRect);
+ }
+
+ protected Brush GetBackBrush()
+ {
+ Brush br = dgTable.IsDefault ? DataGrid.BackBrush : dgTable.BackBrush;
+ if (DataGrid.LedgerStyle && (RowNumber % 2 == 1))
+ {
+ br = dgTable.IsDefault ? DataGrid.AlternatingBackBrush : dgTable.AlternatingBackBrush;
+ }
+
+ return br;
+ }
+
+ ///
+ /// Returns the BackColor and TextColor that the Graphics object should use
+ /// for the appropriate values for a given row and column when painting the data.
+ ///
+ protected Brush BackBrushForDataPaint(ref DataGridCell current, DataGridColumnStyle gridColumn, int column)
+ {
+ Brush backBr = GetBackBrush();
+
+ if (Selected)
+ {
+ backBr = dgTable.IsDefault ? DataGrid.SelectionBackBrush : dgTable.SelectionBackBrush;
+ }
+
+ /*
+ if (RowNumber == current.RowNumber && column == current.ColumnNumber) {
+ backBr = grid.CurrentCellBackBrush;
+ }
+ */
+ return backBr;
+ }
+
+ protected Brush ForeBrushForDataPaint(ref DataGridCell current, DataGridColumnStyle gridColumn, int column)
+ {
+ // Brush foreBrush = gridColumn.ForeBrush;
+ Brush foreBrush = dgTable.IsDefault ? DataGrid.ForeBrush : dgTable.ForeBrush;
+
+ if (Selected)
+ {
+ foreBrush = dgTable.IsDefault ? DataGrid.SelectionForeBrush : dgTable.SelectionForeBrush;
+ }
+
+ /*
+ if (RowNumber == current.RowNumber && column == current.ColumnNumber) {
+ foreColor = grid.CurrentCellForeColor;
+ }
+ */
+ return foreBrush;
+ }
+
+ [ComVisible(true)]
+ protected class DataGridRowAccessibleObject : AccessibleObject
+ {
+ ArrayList cells;
+ readonly DataGridRow owner;
+
+ internal static string CellToDisplayString(DataGrid grid, int row, int column)
+ {
+ if (column < grid.myGridTable.GridColumnStyles.Count)
+ {
+ return grid.myGridTable.GridColumnStyles[column].PropertyDescriptor.Converter.ConvertToString(grid[row, column]);
+ }
+ else
+ {
+ return "";
+ }
+ }
+
+ internal static object DisplayStringToCell(DataGrid grid, int row, int column, string value)
+ {
+ if (column < grid.myGridTable.GridColumnStyles.Count)
+ {
+ return grid.myGridTable.GridColumnStyles[column].PropertyDescriptor.Converter.ConvertFromString(value);
+ }
+
+ // ignore...
+ //
+ return null;
+ }
+
+ public DataGridRowAccessibleObject(DataGridRow owner) : base()
+ {
+ Debug.Assert(owner != null, "DataGridRowAccessibleObject must have a valid owner DataGridRow");
+ this.owner = owner;
+ DataGrid grid = DataGrid;
+ Debug.WriteLineIf(DataGrid.DataGridAcc.TraceVerbose, "Create row accessible object");
+
+ EnsureChildren();
+ }
+
+ private void EnsureChildren()
+ {
+ if (cells == null)
+ {
+ // default size... little extra for relationships...
+ //
+ cells = new ArrayList(DataGrid.myGridTable.GridColumnStyles.Count + 2);
+ AddChildAccessibleObjects(cells);
+ }
+ }
+
+ protected virtual void AddChildAccessibleObjects(IList children)
+ {
+ Debug.WriteLineIf(DataGrid.DataGridAcc.TraceVerbose, "Create row's accessible children");
+ Debug.Indent();
+ GridColumnStylesCollection cols = DataGrid.myGridTable.GridColumnStyles;
+ int len = cols.Count;
+ Debug.WriteLineIf(DataGrid.DataGridAcc.TraceVerbose, len + " columns present");
+ for (int i = 0; i < len; i++)
+ {
+ children.Add(CreateCellAccessibleObject(i));
+ }
+
+ Debug.Unindent();
+ }
+
+ protected virtual AccessibleObject CreateCellAccessibleObject(int column)
+ {
+ return new DataGridCellAccessibleObject(owner, column);
+ }
+
+ public override Rectangle Bounds
+ {
+ get
+ {
+ return DataGrid.RectangleToScreen(DataGrid.GetRowBounds(owner));
+ }
+ }
+
+ public override string Name
+ {
+ get
+ {
+ if (owner is DataGridAddNewRow)
+ {
+ return SR.AccDGNewRow;
+ }
+ else
+ {
+ return DataGridRowAccessibleObject.CellToDisplayString(DataGrid, owner.RowNumber, 0);
+ }
+ }
+ }
+
+ protected DataGridRow Owner
+ {
+ get
+ {
+ return owner;
+ }
+ }
+
+ public override AccessibleObject Parent
+ {
+ get
+ {
+ return DataGrid.AccessibilityObject;
+ }
+ }
+
+ private DataGrid DataGrid
+ {
+ get
+ {
+ return owner.DataGrid;
+ }
+ }
+
+ public override AccessibleRole Role
+ {
+ get
+ {
+ return AccessibleRole.Row;
+ }
+ }
+
+ public override AccessibleStates State
+ {
+ get
+ {
+ AccessibleStates state = AccessibleStates.Selectable | AccessibleStates.Focusable;
+
+ // Determine focus
+ //
+ if (DataGrid.CurrentCell.RowNumber == owner.RowNumber)
+ {
+ state |= AccessibleStates.Focused;
+ }
+
+ // Determine selected
+ //
+ if (DataGrid.CurrentRowIndex == owner.RowNumber)
+ {
+ state |= AccessibleStates.Selected;
+ }
+
+ return state;
+ }
+ }
+
+ public override string Value
+ {
+ get
+ {
+ return Name;
+ }
+ }
+
+ public override AccessibleObject GetChild(int index)
+ {
+ if (index < cells.Count)
+ {
+ return (AccessibleObject)cells[index];
+ }
+
+ return null;
+ }
+
+ public override int GetChildCount()
+ {
+ return cells.Count;
+ }
+
+ ///
+ /// Returns the currently focused child, if any.
+ /// Returns this if the object itself is focused.
+ ///
+ public override AccessibleObject GetFocused()
+ {
+ if (DataGrid.Focused)
+ {
+ DataGridCell cell = DataGrid.CurrentCell;
+ if (cell.RowNumber == owner.RowNumber)
+ {
+ return (AccessibleObject)cells[cell.ColumnNumber];
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Navigate to the next or previous grid entry.entry.
+ ///
+ public override AccessibleObject Navigate(AccessibleNavigation navdir)
+ {
+ switch (navdir)
+ {
+ case AccessibleNavigation.Down:
+ case AccessibleNavigation.Right:
+ case AccessibleNavigation.Next:
+ return DataGrid.AccessibilityObject.GetChild(1 + owner.dgTable.GridColumnStyles.Count + owner.RowNumber + 1);
+
+ case AccessibleNavigation.Up:
+ case AccessibleNavigation.Left:
+ case AccessibleNavigation.Previous:
+ return DataGrid.AccessibilityObject.GetChild(1 + owner.dgTable.GridColumnStyles.Count + owner.RowNumber - 1);
+
+ case AccessibleNavigation.FirstChild:
+ if (GetChildCount() > 0)
+ {
+ return GetChild(0);
+ }
+
+ break;
+ case AccessibleNavigation.LastChild:
+ if (GetChildCount() > 0)
+ {
+ return GetChild(GetChildCount() - 1);
+ }
+
+ break;
+ }
+
+ return null;
+ }
+
+ public override void Select(AccessibleSelection flags)
+ {
+ // Focus the PropertyGridView window
+ //
+ if ((flags & AccessibleSelection.TakeFocus) == AccessibleSelection.TakeFocus)
+ {
+ DataGrid.Focus();
+ }
+
+ // Select the grid entry
+ //
+ if ((flags & AccessibleSelection.TakeSelection) == AccessibleSelection.TakeSelection)
+ {
+ DataGrid.CurrentRowIndex = owner.RowNumber;
+ }
+ }
+ }
+
+ [ComVisible(true)]
+ protected class DataGridCellAccessibleObject : AccessibleObject
+ {
+ readonly DataGridRow owner;
+ readonly int column;
+
+ public DataGridCellAccessibleObject(DataGridRow owner, int column) : base()
+ {
+ Debug.Assert(owner != null, "DataGridColumnAccessibleObject must have a valid owner DataGridRow");
+ this.owner = owner;
+ this.column = column;
+ Debug.WriteLineIf(DataGrid.DataGridAcc.TraceVerbose, "Create cell accessible object");
+ }
+
+ public override Rectangle Bounds
+ {
+ get
+ {
+ return DataGrid.RectangleToScreen(DataGrid.GetCellBounds(new DataGridCell(owner.RowNumber, column)));
+ }
+ }
+
+ public override string Name
+ {
+ get
+ {
+ return DataGrid.myGridTable.GridColumnStyles[column].HeaderText;
+ }
+ }
+
+ public override AccessibleObject Parent
+ {
+ get
+ {
+ return owner.AccessibleObject;
+ }
+ }
+
+ protected DataGrid DataGrid
+ {
+ get
+ {
+ return owner.DataGrid;
+ }
+ }
+
+ public override string DefaultAction
+ {
+ get
+ {
+ return SR.AccDGEdit;
+ }
+ }
+
+ public override AccessibleRole Role
+ {
+ get
+ {
+ return AccessibleRole.Cell;
+ }
+ }
+
+ public override AccessibleStates State
+ {
+ get
+ {
+ AccessibleStates state = AccessibleStates.Selectable | AccessibleStates.Focusable;
+
+ // Determine focus
+ //
+ if (DataGrid.CurrentCell.RowNumber == owner.RowNumber
+ && DataGrid.CurrentCell.ColumnNumber == column)
+ {
+ if (DataGrid.Focused)
+ {
+ state |= AccessibleStates.Focused;
+ }
+
+ state |= AccessibleStates.Selected;
+ }
+
+ return state;
+ }
+ }
+
+ public override string Value
+ {
+ get
+ {
+ if (owner is DataGridAddNewRow)
+ {
+ return null;
+ }
+ else
+ {
+ return DataGridRowAccessibleObject.CellToDisplayString(DataGrid, owner.RowNumber, column);
+ }
+ }
+
+ set
+ {
+ if (!(owner is DataGridAddNewRow))
+ {
+ object realValue = DataGridRowAccessibleObject.DisplayStringToCell(DataGrid, owner.RowNumber, column, value);
+ DataGrid[owner.RowNumber, column] = realValue;
+ }
+ }
+ }
+
+ public override void DoDefaultAction()
+ {
+ Select(AccessibleSelection.TakeFocus | AccessibleSelection.TakeSelection);
+ }
+
+ ///
+ /// Returns the currently focused child, if any.
+ /// Returns this if the object itself is focused.
+ ///
+ public override AccessibleObject GetFocused()
+ {
+ // Datagrid always returns the cell as the focused thing... so do we!
+ //
+ return DataGrid.AccessibilityObject.GetFocused();
+ }
+
+ ///
+ /// Navigate to the next or previous grid entry.
+ ///
+ public override AccessibleObject Navigate(AccessibleNavigation navdir)
+ {
+ switch (navdir)
+ {
+ case AccessibleNavigation.Right:
+ case AccessibleNavigation.Next:
+ if (column < owner.AccessibleObject.GetChildCount() - 1)
+ {
+ return owner.AccessibleObject.GetChild(column + 1);
+ }
+ else
+ {
+ AccessibleObject o = DataGrid.AccessibilityObject.GetChild(1 + owner.dgTable.GridColumnStyles.Count + owner.RowNumber + 1);
+ if (o != null)
+ {
+ return o.Navigate(AccessibleNavigation.FirstChild);
+ }
+ }
+
+ break;
+ case AccessibleNavigation.Down:
+ return DataGrid.AccessibilityObject.GetChild(1 + owner.dgTable.GridColumnStyles.Count + owner.RowNumber + 1).Navigate(AccessibleNavigation.FirstChild);
+ case AccessibleNavigation.Up:
+ return DataGrid.AccessibilityObject.GetChild(1 + owner.dgTable.GridColumnStyles.Count + owner.RowNumber - 1).Navigate(AccessibleNavigation.FirstChild);
+ case AccessibleNavigation.Left:
+ case AccessibleNavigation.Previous:
+ if (column > 0)
+ {
+ return owner.AccessibleObject.GetChild(column - 1);
+ }
+ else
+ {
+ AccessibleObject o = DataGrid.AccessibilityObject.GetChild(1 + owner.dgTable.GridColumnStyles.Count + owner.RowNumber - 1);
+ if (o != null)
+ {
+ return o.Navigate(AccessibleNavigation.LastChild);
+ }
+ }
+
+ break;
+
+ case AccessibleNavigation.FirstChild:
+ case AccessibleNavigation.LastChild:
+
+ break;
+ }
+
+ return null;
+ }
+
+ public override void Select(AccessibleSelection flags)
+ {
+ // Focus the PropertyGridView window
+ //
+ if ((flags & AccessibleSelection.TakeFocus) == AccessibleSelection.TakeFocus)
+ {
+ DataGrid.Focus();
+ }
+
+ // Select the grid entry
+ //
+ if ((flags & AccessibleSelection.TakeSelection) == AccessibleSelection.TakeSelection)
+ {
+ DataGrid.CurrentCell = new DataGridCell(owner.RowNumber, column);
+ }
+ }
+ }
+ }
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridState.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridState.cs
new file mode 100644
index 00000000000..ec1d23a50ca
--- /dev/null
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridState.cs
@@ -0,0 +1,261 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma warning disable IDE0040, IDE1006, SA1005, SA1206, SA1400, CA1510, CSIsNull001, CSIsNull002, IDE0301, IDE0031, IDE0066
+
+#nullable disable
+
+using System.Drawing;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace System.Windows.Forms;
+ ///
+ /// Encapsulates the state of a DataGrid that changes when the
+ /// user navigates back and forth through ADO.NET data relations.
+ ///
+ internal sealed class DataGridState : ICloneable
+ {
+ // fields
+ //
+ public object DataSource;
+ public string DataMember;
+ public CurrencyManager ListManager;
+ public DataGridRow[] DataGridRows = Array.Empty();
+ public DataGrid DataGrid;
+ public int DataGridRowsLength;
+ public GridColumnStylesCollection GridColumnStyles;
+
+ public int FirstVisibleRow;
+ public int FirstVisibleCol;
+
+ public int CurrentRow;
+ public int CurrentCol;
+
+ public DataGridRow LinkingRow;
+ AccessibleObject parentRowAccessibleObject;
+
+ public DataGridState()
+ {
+ }
+
+ public DataGridState(DataGrid dataGrid)
+ {
+ PushState(dataGrid);
+ }
+
+ internal AccessibleObject ParentRowAccessibleObject
+ {
+ get
+ {
+ if (parentRowAccessibleObject == null)
+ {
+ parentRowAccessibleObject = new DataGridStateParentRowAccessibleObject(this);
+ }
+
+ return parentRowAccessibleObject;
+ }
+ }
+
+ // methods
+ //
+
+ public object Clone()
+ {
+ DataGridState dgs = new DataGridState
+ {
+ DataGridRows = DataGridRows,
+ DataSource = DataSource,
+ DataMember = DataMember,
+ FirstVisibleRow = FirstVisibleRow,
+ FirstVisibleCol = FirstVisibleCol,
+ CurrentRow = CurrentRow,
+ CurrentCol = CurrentCol,
+ GridColumnStyles = GridColumnStyles,
+ ListManager = ListManager,
+ DataGrid = DataGrid
+ };
+ return dgs;
+ }
+
+ ///
+ /// Called by a DataGrid when it wishes to preserve its
+ /// transient state in the current DataGridState object.
+ ///
+ public void PushState(DataGrid dataGrid)
+ {
+ DataSource = dataGrid.DataSource;
+ DataMember = dataGrid.DataMember;
+ DataGrid = dataGrid;
+ DataGridRows = dataGrid.DataGridRows;
+ DataGridRowsLength = dataGrid.DataGridRowsLength;
+ FirstVisibleRow = dataGrid.firstVisibleRow;
+ FirstVisibleCol = dataGrid.firstVisibleCol;
+ CurrentRow = dataGrid.currentRow;
+ GridColumnStyles = new GridColumnStylesCollection(dataGrid.myGridTable);
+
+ GridColumnStyles.Clear();
+ foreach (DataGridColumnStyle style in dataGrid.myGridTable.GridColumnStyles)
+ {
+ GridColumnStyles.Add(style);
+ }
+
+ ListManager = dataGrid.ListManager;
+ ListManager.ItemChanged += new ItemChangedEventHandler(DataSource_Changed);
+ ListManager.MetaDataChanged += new EventHandler(DataSource_MetaDataChanged);
+ CurrentCol = dataGrid.currentCol;
+ }
+
+ // this is needed so that the parent rows will remove notification from the list
+ // when the datagridstate is no longer needed;
+ public void RemoveChangeNotification()
+ {
+ ListManager.ItemChanged -= new ItemChangedEventHandler(DataSource_Changed);
+ ListManager.MetaDataChanged -= new EventHandler(DataSource_MetaDataChanged);
+ }
+
+ ///
+ /// Called by a grid when it wishes to match its transient
+ /// state with the current DataGridState object.
+ ///
+ public void PullState(DataGrid dataGrid, bool createColumn)
+ {
+ // dataGrid.DataSource = DataSource;
+ // dataGrid.DataMember = DataMember;
+ dataGrid.Set_ListManager(DataSource, DataMember, true, createColumn); // true for forcing new listManager,
+
+ /*
+ if (DataSource.Table.ParentRelations.Count > 0)
+ dataGrid.PopulateColumns();
+ */
+
+ dataGrid.firstVisibleRow = FirstVisibleRow;
+ dataGrid.firstVisibleCol = FirstVisibleCol;
+ dataGrid.currentRow = CurrentRow;
+ dataGrid.currentCol = CurrentCol;
+ dataGrid.SetDataGridRows(DataGridRows, DataGridRowsLength);
+ }
+
+ private void DataSource_Changed(object sender, ItemChangedEventArgs e)
+ {
+ if (DataGrid != null && ListManager.Position == e.Index)
+ {
+ DataGrid.InvalidateParentRows();
+ return;
+ }
+
+ if (DataGrid != null)
+ {
+ DataGrid.ParentRowsDataChanged();
+ }
+ }
+
+ private void DataSource_MetaDataChanged(object sender, EventArgs e)
+ {
+ if (DataGrid != null)
+ {
+ DataGrid.ParentRowsDataChanged();
+ }
+ }
+
+ [ComVisible(true)]
+ internal class DataGridStateParentRowAccessibleObject : AccessibleObject
+ {
+ readonly DataGridState owner;
+
+ public DataGridStateParentRowAccessibleObject(DataGridState owner) : base()
+ {
+ Debug.Assert(owner != null, "DataGridRowAccessibleObject must have a valid owner DataGridRow");
+ this.owner = owner;
+ }
+
+ public override Rectangle Bounds
+ {
+ get
+ {
+ DataGridParentRows dataGridParentRows = ((DataGridParentRows.DataGridParentRowsAccessibleObject)Parent).Owner;
+ DataGrid g = owner.LinkingRow.DataGrid;
+ Rectangle r = dataGridParentRows.GetBoundsForDataGridStateAccesibility(owner);
+ r.Y += g.ParentRowsBounds.Y;
+ return g.RectangleToScreen(r);
+ }
+ }
+
+ public override string Name
+ {
+ get
+ {
+ return SR.AccDGParentRow;
+ }
+ }
+
+ public override AccessibleObject Parent
+ {
+ get
+ {
+ return owner.LinkingRow.DataGrid.ParentRowsAccessibleObject;
+ }
+ }
+
+ public override AccessibleRole Role
+ {
+ get
+ {
+ return AccessibleRole.ListItem;
+ }
+ }
+
+ public override string Value
+ {
+ get
+ {
+ StringBuilder sb = new StringBuilder();
+
+ CurrencyManager source = (CurrencyManager)owner.LinkingRow.DataGrid.BindingContext[owner.DataSource, owner.DataMember];
+
+ sb.Append(owner.ListManager.GetListName());
+ sb.Append(": ");
+
+ bool needComma = false;
+ foreach (DataGridColumnStyle col in owner.GridColumnStyles)
+ {
+ if (needComma)
+ {
+ sb.Append(", ");
+ }
+
+ string colName = col.HeaderText;
+ string cellValue = col.PropertyDescriptor.Converter.ConvertToString(col.PropertyDescriptor.GetValue(source.Current));
+ sb.Append(colName);
+ sb.Append(": ");
+ sb.Append(cellValue);
+ needComma = true;
+ }
+
+ return sb.ToString();
+ }
+ }
+
+ ///
+ /// Navigate to the next or previous grid entry.
+ ///
+ public override AccessibleObject Navigate(AccessibleNavigation navdir)
+ {
+ DataGridParentRows.DataGridParentRowsAccessibleObject parentAcc = (DataGridParentRows.DataGridParentRowsAccessibleObject)Parent;
+
+ switch (navdir)
+ {
+ case AccessibleNavigation.Down:
+ case AccessibleNavigation.Right:
+ case AccessibleNavigation.Next:
+ return parentAcc.GetNext(this);
+ case AccessibleNavigation.Up:
+ case AccessibleNavigation.Left:
+ case AccessibleNavigation.Previous:
+ return parentAcc.GetPrev(this);
+ }
+
+ return null;
+ }
+ }
+ }
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridStrings.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridStrings.cs
new file mode 100644
index 00000000000..85ccccf90ad
--- /dev/null
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridStrings.cs
@@ -0,0 +1,48 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma warning disable SA1518
+
+namespace System.Windows.Forms;
+
+internal static class DataGridStrings
+{
+ public static string DataGridCaptionBackButtonToolTip => "Back";
+ public static string DataGridCaptionDetailsButtonToolTip => "Details";
+ public static string DataGridColumnCollectionGetEnumerator => "Enumeration is not supported for this collection.";
+ public static string DataGridColumnCollectionMissing => "The specified column does not belong to this collection.";
+ public static string DataGridColumnListManagerPosition => "The specified row index is not valid for the current list manager position.";
+ public static string DataGridColumnNoPropertyDescriptor => "The column is not bound to a property descriptor.";
+ public static string DataGridColumnStyleDuplicateMappingName => "A column with the same mapping name already exists.";
+ public static string DataGridColumnUnbound => "The column '{0}' is not bound to a data source.";
+ public static string DataGridColumnWidth => "PreferredColumnWidth must be greater than or equal to zero.";
+ public static string DataGridDefaultColumnCollectionChanged => "The default column collection cannot be modified.";
+ public static string DataGridDefaultTableSet => "The property '{0}' cannot be set on the default table style.";
+ public static string DataGridEmptyColor => "The color specified for '{0}' must not be empty.";
+ public static string DataGridErrorMessageBoxCaption => "DataGrid";
+ public static string DataGridExceptionInPaint => "An exception occurred while painting the DataGrid.";
+ public static string DataGridBeginInit => "EndInit must be called before this operation can continue.";
+ public static string DataGridNullText => "(null)";
+ public static string DataGridPushedIncorrectValueIntoColumn => "The value pushed into the column is invalid: {0}";
+ public static string DataGridRowRowHeight => "PreferredRowHeight must be greater than zero.";
+ public static string DataGridRowRowNumber => "The row number must be greater than or equal to zero.";
+ public static string DataGridSetListManager => "The current data source cannot be assigned to this DataGrid.";
+ public static string DataGridSetSelectIndex => "The current row cannot be selected.";
+ public static string DataGridSettingCurrentCellNotGood => "The current cell could not be set to the requested location.";
+ public static string DataGridTableCollectionGetEnumerator => "Enumeration is not supported for this collection.";
+ public static string DataGridTableCollectionMissingTable => "The specified table style does not belong to this collection.";
+ public static string DataGridTableStyleCollectionAddedParentedTableStyle => "The table style already belongs to a DataGrid.";
+ public static string DataGridTableStyleDuplicateMappingName => "A table style with the same mapping name already exists.";
+ public static string DataGridTableStyleTransparentAlternatingBackColorNotAllowed => "AlternatingBackColor cannot be transparent.";
+ public static string DataGridTableStyleTransparentBackColorNotAllowed => "BackColor cannot be transparent.";
+ public static string DataGridTableStyleTransparentHeaderBackColorNotAllowed => "HeaderBackColor cannot be transparent.";
+ public static string DataGridTableStyleTransparentSelectionBackColorNotAllowed => "SelectionBackColor cannot be transparent.";
+ public static string DataGridToolTipEmptyIcon => "The tooltip icon bounds must not be empty.";
+ public static string DataGridTransparentAlternatingBackColorNotAllowed => "AlternatingBackColor cannot be transparent.";
+ public static string DataGridTransparentBackColorNotAllowed => "BackColor cannot be transparent.";
+ public static string DataGridTransparentCaptionBackColorNotAllowed => "CaptionBackColor cannot be transparent.";
+ public static string DataGridTransparentHeaderBackColorNotAllowed => "HeaderBackColor cannot be transparent.";
+ public static string DataGridTransparentParentRowsBackColorNotAllowed => "ParentRowsBackColor cannot be transparent.";
+ public static string DataGridTransparentSelectionBackColorNotAllowed => "SelectionBackColor cannot be transparent.";
+ public static string DataGridUnbound => "The DataGrid is not bound to a data source.";
+}
\ No newline at end of file
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridTableStyle.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridTableStyle.cs
index 30c1cbd0f1a..f3e3261be8e 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridTableStyle.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridTableStyle.cs
@@ -1,394 +1,1794 @@
-// 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.
-using System.ComponentModel;
-using System.Drawing;
-using System.Drawing.Design;
-
-namespace System.Windows.Forms;
+#pragma warning disable IDE0040, IDE1006, SA1005, SA1206, SA1400, WFDEV006, IDE0036, SA1507, IDE0031, IDE0078, IDE0059, RS0016
#nullable disable
-///
-/// This type is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code.
-///
-[Obsolete(
- Obsoletions.DataGridMessage,
- error: false,
- DiagnosticId = Obsoletions.UnsupportedControlsDiagnosticId,
- UrlFormat = Obsoletions.SharedUrlFormat)]
-[EditorBrowsable(EditorBrowsableState.Never)]
-[Browsable(false)]
-[ToolboxItem(false)]
-[DesignTimeVisible(false)]
-public class DataGridTableStyle : Component, IDataGridEditingService
-{
- public DataGridTableStyle() : this(isDefaultTableStyle: false) => throw new PlatformNotSupportedException();
-
- public DataGridTableStyle(bool isDefaultTableStyle) => throw new PlatformNotSupportedException();
-
- public DataGridTableStyle(CurrencyManager listManager) : this() => throw new PlatformNotSupportedException();
-
- [DefaultValue(true)]
- public bool AllowSorting
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler AllowSortingChanged
- {
- add { }
- remove { }
- }
-
- public Color AlternatingBackColor
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler AlternatingBackColorChanged
- {
- add { }
- remove { }
- }
-
- public void ResetAlternatingBackColor() { }
-
- protected virtual bool ShouldSerializeAlternatingBackColor() => throw null;
-
- protected bool ShouldSerializeBackColor() => throw null;
-
- protected bool ShouldSerializeForeColor() => throw null;
-
- public Color BackColor
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler BackColorChanged
- {
- add { }
- remove { }
- }
-
- public void ResetBackColor() { }
-
- public Color ForeColor
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler ForeColorChanged
- {
- add { }
- remove { }
- }
-
- public void ResetForeColor() { }
-
- public Color GridLineColor
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler GridLineColorChanged
- {
- add { }
- remove { }
- }
-
- protected virtual bool ShouldSerializeGridLineColor() => throw null;
-
- public void ResetGridLineColor() { }
-
- [DefaultValue(DataGridLineStyle.Solid)]
- public DataGridLineStyle GridLineStyle
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler GridLineStyleChanged
- {
- add { }
- remove { }
- }
-
- public Color HeaderBackColor
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler HeaderBackColorChanged
- {
- add { }
- remove { }
- }
-
- protected virtual bool ShouldSerializeHeaderBackColor() => throw null;
-
- public void ResetHeaderBackColor() { }
-
- [Localizable(true)]
- [AmbientValue(null)]
- public Font HeaderFont
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler HeaderFontChanged
- {
- add { }
- remove { }
- }
-
- public void ResetHeaderFont() { }
-
- public Color HeaderForeColor
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler HeaderForeColorChanged
- {
- add { }
- remove { }
- }
-
- protected virtual bool ShouldSerializeHeaderForeColor() => throw null;
-
- public void ResetHeaderForeColor() { }
-
- public Color LinkColor
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler LinkColorChanged
- {
- add { }
- remove { }
- }
-
- protected virtual bool ShouldSerializeLinkColor() => throw null;
-
- public void ResetLinkColor() { }
-
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public Color LinkHoverColor
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler LinkHoverColorChanged
- {
- add { }
- remove { }
- }
-
- protected virtual bool ShouldSerializeLinkHoverColor() => throw null;
-
- public void ResetLinkHoverColor() { }
+using DSR = System.Windows.Forms.DataGridStrings;
- [DefaultValue(75)]
- [Localizable(true)]
- [TypeConverter(typeof(DataGridPreferredColumnWidthTypeConverter))]
- public int PreferredColumnWidth
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler PreferredColumnWidthChanged
- {
- add { }
- remove { }
- }
-
- [Localizable(true)]
- public int PreferredRowHeight
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler PreferredRowHeightChanged
- {
- add { }
- remove { }
- }
-
- protected bool ShouldSerializePreferredRowHeight() => throw null;
-
- [DefaultValue(true)]
- public bool ColumnHeadersVisible
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler ColumnHeadersVisibleChanged
- {
- add { }
- remove { }
- }
-
- [DefaultValue(true)]
- public bool RowHeadersVisible
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler RowHeadersVisibleChanged
- {
- add { }
- remove { }
- }
-
- [DefaultValue(35)]
- [Localizable(true)]
- public int RowHeaderWidth
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler RowHeaderWidthChanged
- {
- add { }
- remove { }
- }
-
- public Color SelectionBackColor
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler SelectionBackColorChanged
- {
- add { }
- remove { }
- }
-
- protected bool ShouldSerializeSelectionBackColor() => throw null;
-
- public void ResetSelectionBackColor() { }
-
- [Description("The foreground color for the current data grid row")]
- public Color SelectionForeColor
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler SelectionForeColorChanged
- {
- add { }
- remove { }
- }
-
- protected virtual bool ShouldSerializeSelectionForeColor() => throw null;
-
- public void ResetSelectionForeColor() { }
-
- public static readonly DataGridTableStyle DefaultTableStyle;
-
- [Editor($"System.Windows.Forms.Design.DataGridTableStyleMappingNameEditor, {Assemblies.SystemDesign}", typeof(UITypeEditor))]
- [DefaultValue("")]
- public string MappingName
- {
- get => throw null;
- set { }
- }
-
- public event EventHandler MappingNameChanged
- {
- add { }
- remove { }
- }
-
- [Localizable(true)]
- [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
- public virtual GridColumnStylesCollection GridColumnStyles => throw null;
- [Browsable(false)]
- public virtual DataGrid DataGrid
- {
- get => throw null;
- set { }
- }
-
- [DefaultValue(false)]
- public virtual bool ReadOnly
- {
- get => throw null;
- set { }
- }
+using System.Collections;
+using System.ComponentModel;
+using System.Drawing;
- public event EventHandler ReadOnlyChanged
+namespace System.Windows.Forms;
+ ///
+ /// Represents the table drawn by the control at run time.
+ ///
+ [
+ ToolboxItem(false),
+ DesignTimeVisible(false),
+ //DefaultProperty("GridTableName")
+ ]
+ public class DataGridTableStyle : Component, IDataGridEditingService
{
- add { }
- remove { }
+ // internal for DataGridColumn accessibility...
+ //
+ internal DataGrid dataGrid;
+
+ // relationship UI
+ private int relationshipHeight;
+ internal const int relationshipSpacing = 1;
+ private Rectangle relationshipRect = Rectangle.Empty;
+ private int focusedRelation = -1;
+ private int focusedTextWidth;
+
+ // will contain a list of relationships that this table has
+ private readonly ArrayList relationsList = new ArrayList(2);
+
+ // the name of the table
+ private string mappingName = string.Empty;
+ private readonly GridColumnStylesCollection gridColumns;
+ private bool readOnly;
+ private readonly bool isDefaultTableStyle;
+
+ private static readonly object EventAllowSorting = new object();
+ private static readonly object EventGridLineColor = new object();
+ private static readonly object EventGridLineStyle = new object();
+ private static readonly object EventHeaderBackColor = new object();
+ private static readonly object EventHeaderForeColor = new object();
+ private static readonly object EventHeaderFont = new object();
+ private static readonly object EventLinkColor = new object();
+ private static readonly object EventLinkHoverColor = new object();
+ private static readonly object EventPreferredColumnWidth = new object();
+ private static readonly object EventPreferredRowHeight = new object();
+ private static readonly object EventColumnHeadersVisible = new object();
+ private static readonly object EventRowHeaderWidth = new object();
+ private static readonly object EventSelectionBackColor = new object();
+ private static readonly object EventSelectionForeColor = new object();
+ private static readonly object EventMappingName = new object();
+ private static readonly object EventAlternatingBackColor = new object();
+ private static readonly object EventBackColor = new object();
+ private static readonly object EventForeColor = new object();
+ private static readonly object EventReadOnly = new object();
+ private static readonly object EventRowHeadersVisible = new object();
+
+ // add a bunch of properties, taken from the dataGrid
+ //
+
+ // default values
+ //
+ private const bool defaultAllowSorting = true;
+ private const DataGridLineStyle defaultGridLineStyle = DataGridLineStyle.Solid;
+ private const int defaultPreferredColumnWidth = 75;
+ private const int defaultRowHeaderWidth = 35;
+ internal static readonly Font defaultFont = Control.DefaultFont;
+ internal static readonly int defaultFontHeight = defaultFont.Height;
+
+ // the actual place holders for properties
+ //
+ private bool allowSorting = defaultAllowSorting;
+ private SolidBrush alternatingBackBrush = DefaultAlternatingBackBrush;
+ private SolidBrush backBrush = DefaultBackBrush;
+ private SolidBrush foreBrush = DefaultForeBrush;
+ private SolidBrush gridLineBrush = DefaultGridLineBrush;
+ private DataGridLineStyle gridLineStyle = defaultGridLineStyle;
+ internal SolidBrush headerBackBrush = DefaultHeaderBackBrush;
+ internal Font headerFont; // this is ambient property to Font value.
+ internal SolidBrush headerForeBrush = DefaultHeaderForeBrush;
+ internal Pen headerForePen = DefaultHeaderForePen;
+ private SolidBrush linkBrush = DefaultLinkBrush;
+ internal int preferredColumnWidth = defaultPreferredColumnWidth;
+ private int preferredRowHeight = defaultFontHeight + 3;
+ private SolidBrush selectionBackBrush = DefaultSelectionBackBrush;
+ private SolidBrush selectionForeBrush = DefaultSelectionForeBrush;
+ private int rowHeaderWidth = defaultRowHeaderWidth;
+ private bool rowHeadersVisible = true;
+ private bool columnHeadersVisible = true;
+
+ // the dataGrid would need to know when the ColumnHeaderVisible, RowHeadersVisible, RowHeaderWidth
+ // and preferredColumnWidth, preferredRowHeight properties are changed in the current dataGridTableStyle
+ // also: for GridLineStyle, GridLineColor, ForeColor, BackColor, HeaderBackColor, HeaderFont, HeaderForeColor
+ // LinkColor, LinkHoverColor
+ //
+
+ [
+ SRCategory(nameof(SR.CatBehavior)),
+ DefaultValue(defaultAllowSorting),
+ ]
+ public bool AllowSorting
+ {
+ get
+ {
+ return allowSorting;
+ }
+ set
+ {
+ if (isDefaultTableStyle)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridDefaultTableSet, nameof(AllowSorting)));
+ }
+
+ if (allowSorting != value)
+ {
+ allowSorting = value;
+ OnAllowSortingChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ ///
+ /// [To be supplied]
+ ///
+ public event EventHandler AllowSortingChanged
+ {
+ add => Events.AddHandler(EventAllowSorting, value);
+ remove => Events.RemoveHandler(EventAllowSorting, value);
+ }
+
+ [
+ SRCategory(nameof(SR.CatColors)),
+ ]
+ public Color AlternatingBackColor
+ {
+ get
+ {
+ return alternatingBackBrush.Color;
+ }
+ set
+ {
+ if (isDefaultTableStyle)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridDefaultTableSet, nameof(AlternatingBackColor)));
+ }
+
+ if (System.Windows.Forms.DataGrid.IsTransparentColor(value))
+ {
+ throw new ArgumentException(DSR.DataGridTableStyleTransparentAlternatingBackColorNotAllowed, nameof(value));
+ }
+
+ if (value.IsEmpty)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridEmptyColor, nameof(AlternatingBackColor)), nameof(value));
+ }
+
+ if (!alternatingBackBrush.Color.Equals(value))
+ {
+ alternatingBackBrush = new SolidBrush(value);
+ InvalidateInside();
+ OnAlternatingBackColorChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ public event EventHandler AlternatingBackColorChanged
+ {
+ add => Events.AddHandler(EventAlternatingBackColor, value);
+ remove => Events.RemoveHandler(EventAlternatingBackColor, value);
+ }
+
+ public void ResetAlternatingBackColor()
+ {
+ if (ShouldSerializeAlternatingBackColor())
+ {
+ AlternatingBackColor = DefaultAlternatingBackBrush.Color;
+ InvalidateInside();
+ }
+ }
+
+ protected virtual bool ShouldSerializeAlternatingBackColor()
+ {
+ return !AlternatingBackBrush.Equals(DefaultAlternatingBackBrush);
+ }
+
+ internal SolidBrush AlternatingBackBrush
+ {
+ get
+ {
+ return alternatingBackBrush;
+ }
+ }
+
+ protected bool ShouldSerializeBackColor()
+ {
+ return !System.Windows.Forms.DataGridTableStyle.DefaultBackBrush.Equals(backBrush);
+ }
+
+ protected bool ShouldSerializeForeColor()
+ {
+ return !System.Windows.Forms.DataGridTableStyle.DefaultForeBrush.Equals(foreBrush);
+ }
+
+ internal SolidBrush BackBrush
+ {
+ get
+ {
+ return backBrush;
+ }
+ }
+
+ [
+ SRCategory(nameof(SR.CatColors)),
+ SRDescription(nameof(SR.ControlBackColorDescr))
+ ]
+ public Color BackColor
+ {
+ get
+ {
+ return backBrush.Color;
+ }
+ set
+ {
+ if (isDefaultTableStyle)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridDefaultTableSet, nameof(BackColor)));
+ }
+
+ if (System.Windows.Forms.DataGrid.IsTransparentColor(value))
+ {
+ throw new ArgumentException(DSR.DataGridTableStyleTransparentBackColorNotAllowed, nameof(value));
+ }
+
+ if (value.IsEmpty)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridEmptyColor, nameof(BackColor)), nameof(value));
+ }
+
+ if (!backBrush.Color.Equals(value))
+ {
+ backBrush = new SolidBrush(value);
+ InvalidateInside();
+ OnBackColorChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ public event EventHandler BackColorChanged
+ {
+ add => Events.AddHandler(EventBackColor, value);
+ remove => Events.RemoveHandler(EventBackColor, value);
+ }
+
+ public void ResetBackColor()
+ {
+ if (!backBrush.Equals(DefaultBackBrush))
+ {
+ BackColor = DefaultBackBrush.Color;
+ }
+ }
+
+ internal int BorderWidth
+ {
+ get
+ {
+ DataGrid dataGrid = DataGrid;
+ if (dataGrid is null)
+ {
+ return 0;
+ }
+
+ // if the user set the GridLineStyle property on the dataGrid.
+ // then use the value of that property
+ DataGridLineStyle gridStyle;
+ int gridLineWidth;
+ if (IsDefault)
+ {
+ gridStyle = DataGrid.GridLineStyle;
+ gridLineWidth = DataGrid.GridLineWidth;
+ }
+ else
+ {
+ gridStyle = GridLineStyle;
+ gridLineWidth = GridLineWidth;
+ }
+
+ if (gridStyle == DataGridLineStyle.None)
+ {
+ return 0;
+ }
+
+ return gridLineWidth;
+ }
+ }
+
+ internal static SolidBrush DefaultAlternatingBackBrush
+ {
+ get
+ {
+ return (SolidBrush)SystemBrushes.Window;
+ }
+ }
+
+ internal static SolidBrush DefaultBackBrush
+ {
+ get
+ {
+ return (SolidBrush)SystemBrushes.Window;
+ }
+ }
+
+ internal static SolidBrush DefaultForeBrush
+ {
+ get
+ {
+ return (SolidBrush)SystemBrushes.WindowText;
+ }
+ }
+
+ private static SolidBrush DefaultGridLineBrush
+ {
+ get
+ {
+ return (SolidBrush)SystemBrushes.Control;
+ }
+ }
+
+ private static SolidBrush DefaultHeaderBackBrush
+ {
+ get
+ {
+ return (SolidBrush)SystemBrushes.Control;
+ }
+ }
+
+ private static SolidBrush DefaultHeaderForeBrush
+ {
+ get
+ {
+ return (SolidBrush)SystemBrushes.ControlText;
+ }
+ }
+
+ private static Pen DefaultHeaderForePen
+ {
+ get
+ {
+ return new Pen(SystemColors.ControlText);
+ }
+ }
+
+ private static SolidBrush DefaultLinkBrush
+ {
+ get
+ {
+ return (SolidBrush)SystemBrushes.HotTrack;
+ }
+ }
+
+ private static SolidBrush DefaultSelectionBackBrush
+ {
+ get
+ {
+ return (SolidBrush)SystemBrushes.ActiveCaption;
+ }
+ }
+
+ private static SolidBrush DefaultSelectionForeBrush
+ {
+ get
+ {
+ return (SolidBrush)SystemBrushes.ActiveCaptionText;
+ }
+ }
+
+ internal int FocusedRelation
+ {
+ get
+ {
+ return focusedRelation;
+ }
+ set
+ {
+ if (focusedRelation != value)
+ {
+ focusedRelation = value;
+ if (focusedRelation == -1)
+ {
+ focusedTextWidth = 0;
+ }
+ else
+ {
+ Graphics g = DataGrid.CreateGraphicsInternal();
+ focusedTextWidth = (int)Math.Ceiling(g.MeasureString(((string)RelationsList[focusedRelation]), DataGrid.LinkFont).Width);
+ g.Dispose();
+ }
+ }
+ }
+ }
+
+ internal int FocusedTextWidth
+ {
+ get
+ {
+ return focusedTextWidth;
+ }
+ }
+
+ [
+ SRCategory(nameof(SR.CatColors)),
+ SRDescription(nameof(SR.ControlForeColorDescr))
+ ]
+ public Color ForeColor
+ {
+ get
+ {
+ return foreBrush.Color;
+ }
+ set
+ {
+ if (isDefaultTableStyle)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridDefaultTableSet, nameof(ForeColor)));
+ }
+
+ if (value.IsEmpty)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridEmptyColor, nameof(ForeColor)), nameof(value));
+ }
+
+ if (!foreBrush.Color.Equals(value))
+ {
+ foreBrush = new SolidBrush(value);
+ InvalidateInside();
+ OnForeColorChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ public event EventHandler ForeColorChanged
+ {
+ add => Events.AddHandler(EventForeColor, value);
+ remove => Events.RemoveHandler(EventForeColor, value);
+ }
+
+ internal SolidBrush ForeBrush
+ {
+ get
+ {
+ return foreBrush;
+ }
+ }
+
+ public void ResetForeColor()
+ {
+ if (!foreBrush.Equals(DefaultForeBrush))
+ {
+ ForeColor = DefaultForeBrush.Color;
+ }
+ }
+
+ [
+ SRCategory(nameof(SR.CatColors)),
+ ]
+ public Color GridLineColor
+ {
+ get
+ {
+ return gridLineBrush.Color;
+ }
+ set
+ {
+ if (isDefaultTableStyle)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridDefaultTableSet, nameof(GridLineColor)));
+ }
+
+ if (gridLineBrush.Color != value)
+ {
+ if (value.IsEmpty)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridEmptyColor, nameof(GridLineColor)), nameof(value));
+ }
+
+ gridLineBrush = new SolidBrush(value);
+ OnGridLineColorChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ public event EventHandler GridLineColorChanged
+ {
+ add => Events.AddHandler(EventGridLineColor, value);
+ remove => Events.RemoveHandler(EventGridLineColor, value);
+ }
+
+ protected virtual bool ShouldSerializeGridLineColor()
+ {
+ return !GridLineBrush.Equals(DefaultGridLineBrush);
+ }
+
+ public void ResetGridLineColor()
+ {
+ if (ShouldSerializeGridLineColor())
+ {
+ GridLineColor = DefaultGridLineBrush.Color;
+ }
+ }
+
+ internal SolidBrush GridLineBrush
+ {
+ get
+ {
+ return gridLineBrush;
+ }
+ }
+
+ internal int GridLineWidth
+ {
+ get
+ {
+ Debug.Assert(GridLineStyle == DataGridLineStyle.Solid || GridLineStyle == DataGridLineStyle.None, "are there any other styles?");
+ return GridLineStyle == DataGridLineStyle.Solid ? 1 : 0;
+ }
+ }
+
+ [
+ SRCategory(nameof(SR.CatAppearance)),
+ DefaultValue(defaultGridLineStyle),
+ ]
+ public DataGridLineStyle GridLineStyle
+ {
+ get
+ {
+ return gridLineStyle;
+ }
+ set
+ {
+ if (isDefaultTableStyle)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridDefaultTableSet, nameof(GridLineStyle)));
+ }
+
+ if (value is < DataGridLineStyle.None or > DataGridLineStyle.Solid)
+ {
+ throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(DataGridLineStyle));
+ }
+
+ if (gridLineStyle != value)
+ {
+ gridLineStyle = value;
+ OnGridLineStyleChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ public event EventHandler GridLineStyleChanged
+ {
+ add => Events.AddHandler(EventGridLineStyle, value);
+ remove => Events.RemoveHandler(EventGridLineStyle, value);
+ }
+
+ [
+ SRCategory(nameof(SR.CatColors)),
+ ]
+ public Color HeaderBackColor
+ {
+ get
+ {
+ return headerBackBrush.Color;
+ }
+ set
+ {
+ if (isDefaultTableStyle)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridDefaultTableSet, nameof(HeaderBackColor)));
+ }
+
+ if (System.Windows.Forms.DataGrid.IsTransparentColor(value))
+ {
+ throw new ArgumentException(DSR.DataGridTableStyleTransparentHeaderBackColorNotAllowed, nameof(value));
+ }
+
+ if (value.IsEmpty)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridEmptyColor, nameof(HeaderBackColor)), nameof(value));
+ }
+
+ if (!value.Equals(headerBackBrush.Color))
+ {
+ headerBackBrush = new SolidBrush(value);
+ OnHeaderBackColorChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ public event EventHandler HeaderBackColorChanged
+ {
+ add => Events.AddHandler(EventHeaderBackColor, value);
+ remove => Events.RemoveHandler(EventHeaderBackColor, value);
+ }
+
+ internal SolidBrush HeaderBackBrush
+ {
+ get
+ {
+ return headerBackBrush;
+ }
+ }
+
+ protected virtual bool ShouldSerializeHeaderBackColor()
+ {
+ return !HeaderBackBrush.Equals(DefaultHeaderBackBrush);
+ }
+
+ public void ResetHeaderBackColor()
+ {
+ if (ShouldSerializeHeaderBackColor())
+ {
+ HeaderBackColor = DefaultHeaderBackBrush.Color;
+ }
+ }
+
+ [
+ SRCategory(nameof(SR.CatAppearance)),
+ Localizable(true),
+ AmbientValue(null),
+ ]
+ public Font HeaderFont
+ {
+ get
+ {
+ return (headerFont ?? (DataGrid is null ? Control.DefaultFont : DataGrid.Font));
+ }
+ set
+ {
+ if (isDefaultTableStyle)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridDefaultTableSet, nameof(HeaderFont)));
+ }
+
+ if ((value is null && headerFont is not null) || (value is not null && !value.Equals(headerFont)))
+ {
+ headerFont = value;
+ OnHeaderFontChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ public event EventHandler HeaderFontChanged
+ {
+ add => Events.AddHandler(EventHeaderFont, value);
+ remove => Events.RemoveHandler(EventHeaderFont, value);
+ }
+
+ private bool ShouldSerializeHeaderFont()
+ {
+ return (headerFont is not null);
+ }
+
+ public void ResetHeaderFont()
+ {
+ if (headerFont is not null)
+ {
+ headerFont = null;
+ OnHeaderFontChanged(EventArgs.Empty);
+ }
+ }
+
+ [
+ SRCategory(nameof(SR.CatColors)),
+ ]
+ public Color HeaderForeColor
+ {
+ get
+ {
+ return headerForePen.Color;
+ }
+ set
+ {
+ if (isDefaultTableStyle)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridDefaultTableSet, nameof(HeaderForeColor)));
+ }
+
+ if (value.IsEmpty)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridEmptyColor, nameof(HeaderForeColor)), nameof(value));
+ }
+
+ if (!value.Equals(headerForePen.Color))
+ {
+ headerForePen = new Pen(value);
+ headerForeBrush = new SolidBrush(value);
+ OnHeaderForeColorChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ public event EventHandler HeaderForeColorChanged
+ {
+ add => Events.AddHandler(EventHeaderForeColor, value);
+ remove => Events.RemoveHandler(EventHeaderForeColor, value);
+ }
+
+ protected virtual bool ShouldSerializeHeaderForeColor()
+ {
+ return !HeaderForePen.Equals(DefaultHeaderForePen);
+ }
+
+ public void ResetHeaderForeColor()
+ {
+ if (ShouldSerializeHeaderForeColor())
+ {
+ HeaderForeColor = DefaultHeaderForeBrush.Color;
+ }
+ }
+
+ internal SolidBrush HeaderForeBrush
+ {
+ get
+ {
+ return headerForeBrush;
+ }
+ }
+
+ internal Pen HeaderForePen
+ {
+ get
+ {
+ return headerForePen;
+ }
+ }
+
+ [
+ SRCategory(nameof(SR.CatColors)),
+ ]
+ public Color LinkColor
+ {
+ get
+ {
+ return linkBrush.Color;
+ }
+ set
+ {
+ if (isDefaultTableStyle)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridDefaultTableSet, nameof(LinkColor)));
+ }
+
+ if (value.IsEmpty)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridEmptyColor, nameof(LinkColor)), nameof(value));
+ }
+
+ if (!linkBrush.Color.Equals(value))
+ {
+ linkBrush = new SolidBrush(value);
+ OnLinkColorChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ public event EventHandler LinkColorChanged
+ {
+ add => Events.AddHandler(EventLinkColor, value);
+ remove => Events.RemoveHandler(EventLinkColor, value);
+ }
+
+ protected virtual bool ShouldSerializeLinkColor()
+ {
+ return !LinkBrush.Equals(DefaultLinkBrush);
+ }
+
+ public void ResetLinkColor()
+ {
+ if (ShouldSerializeLinkColor())
+ {
+ LinkColor = DefaultLinkBrush.Color;
+ }
+ }
+
+ internal Brush LinkBrush
+ {
+ get
+ {
+ return linkBrush;
+ }
+ }
+
+ [
+ SRCategory(nameof(SR.CatColors)),
+ Browsable(false),
+ EditorBrowsable(EditorBrowsableState.Never)
+ ]
+ public Color LinkHoverColor
+ {
+ get
+ {
+ return LinkColor;
+ }
+ set
+ {
+ }
+ }
+
+ public event EventHandler LinkHoverColorChanged
+ {
+ add => Events.AddHandler(EventLinkHoverColor, value);
+ remove => Events.RemoveHandler(EventLinkHoverColor, value);
+ }
+
+ protected virtual bool ShouldSerializeLinkHoverColor()
+ {
+ return false;
+ }
+
+ internal Rectangle RelationshipRect
+ {
+ get
+ {
+ if (relationshipRect.IsEmpty)
+ {
+ ComputeRelationshipRect();
+ }
+
+ return relationshipRect;
+ }
+ }
+
+ private Rectangle ComputeRelationshipRect()
+ {
+ if (relationshipRect.IsEmpty && DataGrid.AllowNavigation)
+ {
+ Debug.WriteLineIf(CompModSwitches.DGRelationShpRowLayout.TraceVerbose, "GetRelationshipRect grinding away");
+ Graphics g = DataGrid.CreateGraphicsInternal();
+ relationshipRect = new Rectangle
+ {
+ X = 0 //indentWidth;
+ };
+ // relationshipRect.Y = base.Height - BorderWidth;
+
+ // Determine the width of the widest relationship name
+ int longestRelationship = 0;
+ for (int r = 0; r < RelationsList.Count; ++r)
+ {
+ int rwidth = (int)Math.Ceiling(g.MeasureString(((string)RelationsList[r]), DataGrid.LinkFont).Width)
+;
+ if (rwidth > longestRelationship)
+ {
+ longestRelationship = rwidth;
+ }
+ }
+
+ g.Dispose();
+
+ relationshipRect.Width = longestRelationship + 5;
+ relationshipRect.Width += 2; // relationshipRect border;
+ relationshipRect.Height = BorderWidth + relationshipHeight * RelationsList.Count;
+ relationshipRect.Height += 2; // relationship border
+ if (RelationsList.Count > 0)
+ {
+ relationshipRect.Height += 2 * relationshipSpacing;
+ }
+ }
+
+ return relationshipRect;
+ }
+
+ internal void ResetRelationsUI()
+ {
+ relationshipRect = Rectangle.Empty;
+ focusedRelation = -1;
+ relationshipHeight = dataGrid.LinkFontHeight + relationshipSpacing;
+ }
+
+ internal int RelationshipHeight
+ {
+ get
+ {
+ return relationshipHeight;
+ }
+ }
+
+ public void ResetLinkHoverColor()
+ {
+ }
+
+ [
+ DefaultValue(defaultPreferredColumnWidth),
+ SRCategory(nameof(SR.CatLayout)),
+ Localizable(true),
+ TypeConverter(typeof(DataGridPreferredColumnWidthTypeConverter))
+ ]
+ public int PreferredColumnWidth
+ {
+ get
+ {
+ return preferredColumnWidth;
+ }
+ set
+ {
+ if (isDefaultTableStyle)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridDefaultTableSet, nameof(PreferredColumnWidth)));
+ }
+
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), value, DSR.DataGridColumnWidth);
+ }
+
+ if (preferredColumnWidth != value)
+ {
+ preferredColumnWidth = value;
+ OnPreferredColumnWidthChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ public event EventHandler PreferredColumnWidthChanged
+ {
+ add => Events.AddHandler(EventPreferredColumnWidth, value);
+ remove => Events.RemoveHandler(EventPreferredColumnWidth, value);
+ }
+
+ [
+ SRCategory(nameof(SR.CatLayout)),
+ Localizable(true),
+ ]
+ public int PreferredRowHeight
+ {
+ get
+ {
+ return preferredRowHeight;
+ }
+ set
+ {
+ if (isDefaultTableStyle)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridDefaultTableSet, nameof(PreferredRowHeight)));
+ }
+
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), value, DSR.DataGridRowRowHeight);
+ }
+
+ if (preferredRowHeight != value)
+ {
+ preferredRowHeight = value;
+ OnPreferredRowHeightChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ public event EventHandler PreferredRowHeightChanged
+ {
+ add => Events.AddHandler(EventPreferredRowHeight, value);
+ remove => Events.RemoveHandler(EventPreferredRowHeight, value);
+ }
+
+ private void ResetPreferredRowHeight()
+ {
+ PreferredRowHeight = defaultFontHeight + 3;
+ }
+
+ protected bool ShouldSerializePreferredRowHeight()
+ {
+ return preferredRowHeight != defaultFontHeight + 3;
+ }
+
+ [
+ SRCategory(nameof(SR.CatDisplay)),
+ DefaultValue(true),
+ ]
+ public bool ColumnHeadersVisible
+ {
+ get
+ {
+ return columnHeadersVisible;
+ }
+ set
+ {
+ if (columnHeadersVisible != value)
+ {
+ columnHeadersVisible = value;
+ OnColumnHeadersVisibleChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ public event EventHandler ColumnHeadersVisibleChanged
+ {
+ add => Events.AddHandler(EventColumnHeadersVisible, value);
+ remove => Events.RemoveHandler(EventColumnHeadersVisible, value);
+ }
+
+ [
+ SRCategory(nameof(SR.CatDisplay)),
+ DefaultValue(true),
+ ]
+ public bool RowHeadersVisible
+ {
+ get
+ {
+ return rowHeadersVisible;
+ }
+ set
+ {
+ if (rowHeadersVisible != value)
+ {
+ rowHeadersVisible = value;
+ OnRowHeadersVisibleChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ public event EventHandler RowHeadersVisibleChanged
+ {
+ add => Events.AddHandler(EventRowHeadersVisible, value);
+ remove => Events.RemoveHandler(EventRowHeadersVisible, value);
+ }
+
+ [
+ SRCategory(nameof(SR.CatLayout)),
+ DefaultValue(defaultRowHeaderWidth),
+ Localizable(true),
+ ]
+ public int RowHeaderWidth
+ {
+ get
+ {
+ return rowHeaderWidth;
+ }
+ set
+ {
+ if (DataGrid is not null)
+ {
+ value = Math.Max(DataGrid.MinimumRowHeaderWidth(), value);
+ }
+
+ if (rowHeaderWidth != value)
+ {
+ rowHeaderWidth = value;
+ OnRowHeaderWidthChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ public event EventHandler RowHeaderWidthChanged
+ {
+ add => Events.AddHandler(EventRowHeaderWidth, value);
+ remove => Events.RemoveHandler(EventRowHeaderWidth, value);
+ }
+
+ [
+ SRCategory(nameof(SR.CatColors)),
+ ]
+ public Color SelectionBackColor
+ {
+ get
+ {
+ return selectionBackBrush.Color;
+ }
+ set
+ {
+ if (isDefaultTableStyle)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridDefaultTableSet, nameof(SelectionBackColor)));
+ }
+
+ if (System.Windows.Forms.DataGrid.IsTransparentColor(value))
+ {
+ throw new ArgumentException(DSR.DataGridTableStyleTransparentSelectionBackColorNotAllowed, nameof(value));
+ }
+
+ if (value.IsEmpty)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridEmptyColor, nameof(SelectionBackColor)), nameof(value));
+ }
+
+ if (!value.Equals(selectionBackBrush.Color))
+ {
+ selectionBackBrush = new SolidBrush(value);
+ InvalidateInside();
+ OnSelectionBackColorChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ public event EventHandler SelectionBackColorChanged
+ {
+ add => Events.AddHandler(EventSelectionBackColor, value);
+ remove => Events.RemoveHandler(EventSelectionBackColor, value);
+ }
+
+ internal SolidBrush SelectionBackBrush
+ {
+ get
+ {
+ return selectionBackBrush;
+ }
+ }
+
+ internal SolidBrush SelectionForeBrush
+ {
+ get
+ {
+ return selectionForeBrush;
+ }
+ }
+
+ protected bool ShouldSerializeSelectionBackColor()
+ {
+ return !DefaultSelectionBackBrush.Equals(selectionBackBrush);
+ }
+
+ public void ResetSelectionBackColor()
+ {
+ if (ShouldSerializeSelectionBackColor())
+ {
+ SelectionBackColor = DefaultSelectionBackBrush.Color;
+ }
+ }
+
+ [
+ Description("The foreground color for the current data grid row"),
+ SRCategory(nameof(SR.CatColors)),
+ ]
+ public Color SelectionForeColor
+ {
+ get
+ {
+ return selectionForeBrush.Color;
+ }
+ set
+ {
+ if (isDefaultTableStyle)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridDefaultTableSet, nameof(SelectionForeColor)));
+ }
+
+ if (value.IsEmpty)
+ {
+ throw new ArgumentException(string.Format(DSR.DataGridEmptyColor, nameof(SelectionForeColor)), nameof(value));
+ }
+
+ if (!value.Equals(selectionForeBrush.Color))
+ {
+ selectionForeBrush = new SolidBrush(value);
+ InvalidateInside();
+ OnSelectionForeColorChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ public event EventHandler SelectionForeColorChanged
+ {
+ add => Events.AddHandler(EventSelectionForeColor, value);
+ remove => Events.RemoveHandler(EventSelectionForeColor, value);
+ }
+
+ protected virtual bool ShouldSerializeSelectionForeColor()
+ {
+ return !SelectionForeBrush.Equals(DefaultSelectionForeBrush);
+ }
+
+ public void ResetSelectionForeColor()
+ {
+ if (ShouldSerializeSelectionForeColor())
+ {
+ SelectionForeColor = DefaultSelectionForeBrush.Color;
+ }
+ }
+
+ // will need this function from the dataGrid
+ //
+ private void InvalidateInside()
+ {
+ if (DataGrid is not null)
+ {
+ DataGrid.InvalidateInside();
+ }
+ }
+
+ public static readonly DataGridTableStyle DefaultTableStyle = new DataGridTableStyle(true);
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public DataGridTableStyle(bool isDefaultTableStyle)
+ {
+ gridColumns = new GridColumnStylesCollection(this, isDefaultTableStyle);
+ gridColumns.CollectionChanged += new CollectionChangeEventHandler(OnColumnCollectionChanged);
+ this.isDefaultTableStyle = isDefaultTableStyle;
+ }
+
+ public DataGridTableStyle() : this(false)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with the specified
+ /// .
+ ///
+ public DataGridTableStyle(CurrencyManager listManager) : this()
+ {
+ Debug.Assert(listManager is not null, "the DataGridTabel cannot use a null listManager");
+ mappingName = listManager.GetListName();
+ // set up the Relations and the columns
+ SetGridColumnStylesCollection(listManager);
+ }
+
+ internal void SetRelationsList(CurrencyManager listManager)
+ {
+ PropertyDescriptorCollection propCollection = listManager.GetItemProperties();
+ Debug.Assert(!IsDefault, "the grid can set the relations only on a table that was manually added by the user");
+ int propCount = propCollection.Count;
+ if (relationsList.Count > 0)
+ {
+ relationsList.Clear();
+ }
+
+ for (int i = 0; i < propCount; i++)
+ {
+ PropertyDescriptor prop = propCollection[i];
+ Debug.Assert(prop is not null, "prop is null: how that happened?");
+ if (PropertyDescriptorIsARelation(prop))
+ {
+ // relation
+ relationsList.Add(prop.Name);
+ }
+ }
+ }
+
+ internal void SetGridColumnStylesCollection(CurrencyManager listManager)
+ {
+ // when we are setting the gridColumnStyles, do not handle any gridColumnCollectionChanged events
+ gridColumns.CollectionChanged -= new CollectionChangeEventHandler(OnColumnCollectionChanged);
+
+ PropertyDescriptorCollection propCollection = listManager.GetItemProperties();
+
+ // we need to clear the relations list
+ if (relationsList.Count > 0)
+ {
+ relationsList.Clear();
+ }
+
+ Debug.Assert(propCollection is not null, "propCollection is null: how that happened?");
+ int propCount = propCollection.Count;
+ for (int i = 0; i < propCount; i++)
+ {
+ PropertyDescriptor prop = propCollection[i];
+ Debug.Assert(prop is not null, "prop is null: how that happened?");
+ // do not take into account the properties that are browsable.
+ if (!prop.IsBrowsable)
+ {
+ continue;
+ }
+
+ if (PropertyDescriptorIsARelation(prop))
+ {
+ // relation
+ relationsList.Add(prop.Name);
+ }
+ else
+ {
+ // column
+ DataGridColumnStyle col = CreateGridColumn(prop, isDefaultTableStyle);
+ if (isDefaultTableStyle)
+ {
+ gridColumns.AddDefaultColumn(col);
+ }
+ else
+ {
+ col.MappingName = prop.Name;
+ col.HeaderText = prop.Name;
+ gridColumns.Add(col);
+ }
+ }
+ }
+
+ // now we are able to handle the collectionChangeEvents
+ gridColumns.CollectionChanged += new CollectionChangeEventHandler(OnColumnCollectionChanged);
+ }
+
+ private static bool PropertyDescriptorIsARelation(PropertyDescriptor prop)
+ {
+ return typeof(IList).IsAssignableFrom(prop.PropertyType) && !typeof(Array).IsAssignableFrom(prop.PropertyType);
+ }
+
+ internal protected virtual DataGridColumnStyle CreateGridColumn(PropertyDescriptor prop)
+ {
+ return CreateGridColumn(prop, false);
+ }
+
+ internal protected virtual DataGridColumnStyle CreateGridColumn(PropertyDescriptor prop, bool isDefault)
+ {
+ ArgumentNullException.ThrowIfNull(prop);
+
+ DataGridColumnStyle ret = null;
+ Type dataType = prop.PropertyType;
+
+ if (dataType.Equals(typeof(bool)))
+ {
+ ret = new DataGridBoolColumn(prop, isDefault);
+ }
+ else if (dataType.Equals(typeof(string)))
+ {
+ ret = new DataGridTextBoxColumn(prop, isDefault);
+ }
+ else if (dataType.Equals(typeof(DateTime)))
+ {
+ ret = new DataGridTextBoxColumn(prop, "d", isDefault);
+ }
+ else if (dataType.Equals(typeof(short)) ||
+ dataType.Equals(typeof(int)) ||
+ dataType.Equals(typeof(long)) ||
+ dataType.Equals(typeof(ushort)) ||
+ dataType.Equals(typeof(uint)) ||
+ dataType.Equals(typeof(ulong)) ||
+ dataType.Equals(typeof(decimal)) ||
+ dataType.Equals(typeof(double)) ||
+ dataType.Equals(typeof(float)) ||
+ dataType.Equals(typeof(byte)) ||
+ dataType.Equals(typeof(sbyte)))
+ {
+ ret = new DataGridTextBoxColumn(prop, "G", isDefault);
+ }
+ else
+ {
+ ret = new DataGridTextBoxColumn(prop, isDefault);
+ }
+
+ return ret;
+ }
+
+ internal void ResetRelationsList()
+ {
+ if (isDefaultTableStyle)
+ {
+ relationsList.Clear();
+ }
+ }
+
+ // =------------------------------------------------------------------
+ // = Properties
+ // =------------------------------------------------------------------
+
+ ///
+ /// Gets the name of this grid table.
+ ///
+ [Editor("System.Windows.Forms.Design.DataGridTableStyleMappingNameEditor, " + Assemblies.SystemDesign, typeof(Drawing.Design.UITypeEditor)), DefaultValue("")]
+ public string MappingName
+ {
+ get
+ {
+ return mappingName;
+ }
+ set
+ {
+ if (value is null)
+ {
+ value = string.Empty;
+ }
+
+ if (value.Equals(mappingName))
+ {
+ return;
+ }
+
+ string originalMappingName = MappingName;
+ mappingName = value;
+
+ // this could throw
+ try
+ {
+ if (DataGrid is not null)
+ {
+ DataGrid.TableStyles.CheckForMappingNameDuplicates(this);
+ }
+ }
+ catch
+ {
+ mappingName = originalMappingName;
+ throw;
+ }
+
+ OnMappingNameChanged(EventArgs.Empty);
+ }
+ }
+
+ public event EventHandler MappingNameChanged
+ {
+ add => Events.AddHandler(EventMappingName, value);
+ remove => Events.RemoveHandler(EventMappingName, value);
+ }
+
+ ///
+ /// Gets the
+ /// list of relation objects for the grid table.
+ ///
+ internal ArrayList RelationsList
+ {
+ get
+ {
+ return relationsList;
+ }
+ }
+
+ ///
+ /// Gets the collection of columns drawn for this table.
+ ///
+ [
+ Localizable(true),
+ DesignerSerializationVisibility(DesignerSerializationVisibility.Content)
+ ]
+ public virtual GridColumnStylesCollection GridColumnStyles
+ {
+ get
+ {
+ return gridColumns;
+ }
+ }
+
+ ///
+ /// Gets or sets the
+ /// control displaying the table.
+ ///
+ internal void SetInternalDataGrid(DataGrid dG, bool force)
+ {
+ if (dataGrid is not null && dataGrid.Equals(dG) && !force)
+ {
+ return;
+ }
+ else
+ {
+ dataGrid = dG;
+ if (dG is not null && dG.Initializing)
+ {
+ return;
+ }
+
+ int nCols = gridColumns.Count;
+ for (int i = 0; i < nCols; i++)
+ {
+ gridColumns[i].SetDataGridInternalInColumn(dG);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the control for the drawn table.
+ ///
+ [Browsable(false)]
+ public virtual DataGrid DataGrid
+ {
+ get
+ {
+ return dataGrid;
+ }
+ set
+ {
+ SetInternalDataGrid(value, true);
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether columns can be
+ /// edited.
+ ///
+ [DefaultValue(false)]
+ public virtual bool ReadOnly
+ {
+ get
+ {
+ return readOnly;
+ }
+ set
+ {
+ if (readOnly != value)
+ {
+ readOnly = value;
+ OnReadOnlyChanged(EventArgs.Empty);
+ }
+ }
+ }
+
+ public event EventHandler ReadOnlyChanged
+ {
+ add => Events.AddHandler(EventReadOnly, value);
+ remove => Events.RemoveHandler(EventReadOnly, value);
+ }
+
+ // =------------------------------------------------------------------
+ // = Methods
+ // =------------------------------------------------------------------
+
+ ///
+ /// Requests an edit operation.
+ ///
+ public bool BeginEdit(DataGridColumnStyle gridColumn, int rowNumber)
+ {
+ DataGrid grid = DataGrid;
+ if (grid is null)
+ {
+ return false;
+ }
+ else
+ {
+ return grid.BeginEdit(gridColumn, rowNumber);
+ }
+ }
+
+ ///
+ /// Requests an end to an edit
+ /// operation.
+ ///
+ public bool EndEdit(DataGridColumnStyle gridColumn, int rowNumber, bool shouldAbort)
+ {
+ DataGrid grid = DataGrid;
+ if (grid is null)
+ {
+ return false;
+ }
+ else
+ {
+ return grid.EndEdit(gridColumn, rowNumber, shouldAbort);
+ }
+ }
+
+ internal void InvalidateColumn(DataGridColumnStyle column)
+ {
+ int index = GridColumnStyles.IndexOf(column);
+ if (index >= 0 && DataGrid is not null)
+ {
+ DataGrid.InvalidateColumn(index);
+ }
+ }
+
+ private void OnColumnCollectionChanged(object sender, CollectionChangeEventArgs e)
+ {
+ gridColumns.CollectionChanged -= new CollectionChangeEventHandler(OnColumnCollectionChanged);
+
+ try
+ {
+ DataGrid grid = DataGrid;
+ DataGridColumnStyle col = e.Element as DataGridColumnStyle;
+ if (e.Action == CollectionChangeAction.Add)
+ {
+ if (col is not null)
+ {
+ col.SetDataGridInternalInColumn(grid);
+ }
+ }
+ else if (e.Action == CollectionChangeAction.Remove)
+ {
+ if (col is not null)
+ {
+ col.SetDataGridInternalInColumn(null);
+ }
+ }
+ else
+ {
+ // refresh
+ Debug.Assert(e.Action == CollectionChangeAction.Refresh, "there are only Add, Remove and Refresh in the CollectionChangeAction");
+ // if we get a column in this collectionChangeEventArgs it means
+ // that the propertyDescriptor in that column changed.
+ if (e.Element is not null)
+ {
+ for (int i = 0; i < gridColumns.Count; i++)
+ {
+ gridColumns[i].SetDataGridInternalInColumn(null);
+ }
+ }
+ }
+
+ if (grid is not null)
+ {
+ grid.OnColumnCollectionChanged(this, e);
+ }
+ }
+ finally
+ {
+ gridColumns.CollectionChanged += new CollectionChangeEventHandler(OnColumnCollectionChanged);
+ }
+ }
+
+#if false
+ ///
+ /// The DataColumnCollection class actually wires up this
+ /// event handler to the PropertyChanged events of
+ /// a DataGridTable's columns.
+ ///
+ internal void OnColumnChanged(object sender, PropertyChangedEvent event) {
+ if (event.PropertyName.Equals("Visible"))
+ GenerateVisibleColumnsCache();
+ }
+#endif
+ protected virtual void OnReadOnlyChanged(EventArgs e)
+ {
+ if (Events[EventReadOnly] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnMappingNameChanged(EventArgs e)
+ {
+ if (Events[EventMappingName] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnAlternatingBackColorChanged(EventArgs e)
+ {
+ if (Events[EventAlternatingBackColor] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnForeColorChanged(EventArgs e)
+ {
+ if (Events[EventForeColor] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnBackColorChanged(EventArgs e)
+ {
+ if (Events[EventBackColor] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnAllowSortingChanged(EventArgs e)
+ {
+ if (Events[EventAllowSorting] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnGridLineColorChanged(EventArgs e)
+ {
+ if (Events[EventGridLineColor] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnGridLineStyleChanged(EventArgs e)
+ {
+ if (Events[EventGridLineStyle] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnHeaderBackColorChanged(EventArgs e)
+ {
+ if (Events[EventHeaderBackColor] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnHeaderFontChanged(EventArgs e)
+ {
+ if (Events[EventHeaderFont] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnHeaderForeColorChanged(EventArgs e)
+ {
+ if (Events[EventHeaderForeColor] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnLinkColorChanged(EventArgs e)
+ {
+ if (Events[EventLinkColor] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnLinkHoverColorChanged(EventArgs e)
+ {
+ if (Events[EventLinkHoverColor] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnPreferredRowHeightChanged(EventArgs e)
+ {
+ if (Events[EventPreferredRowHeight] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnPreferredColumnWidthChanged(EventArgs e)
+ {
+ if (Events[EventPreferredColumnWidth] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnColumnHeadersVisibleChanged(EventArgs e)
+ {
+ if (Events[EventColumnHeadersVisible] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnRowHeadersVisibleChanged(EventArgs e)
+ {
+ if (Events[EventRowHeadersVisible] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnRowHeaderWidthChanged(EventArgs e)
+ {
+ if (Events[EventRowHeaderWidth] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnSelectionForeColorChanged(EventArgs e)
+ {
+ if (Events[EventSelectionForeColor] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected virtual void OnSelectionBackColorChanged(EventArgs e)
+ {
+ if (Events[EventSelectionBackColor] is EventHandler eh)
+ {
+ eh(this, e);
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ GridColumnStylesCollection cols = GridColumnStyles;
+ if (cols is not null)
+ {
+ for (int i = 0; i < cols.Count; i++)
+ {
+ cols[i].Dispose();
+ }
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ internal bool IsDefault
+ {
+ get
+ {
+ return isDefaultTableStyle;
+ }
+ }
}
-
- public bool BeginEdit(DataGridColumnStyle gridColumn, int rowNumber) => throw null;
-
- public bool EndEdit(DataGridColumnStyle gridColumn, int rowNumber, bool shouldAbort) => throw null;
-
- protected internal virtual DataGridColumnStyle CreateGridColumn(PropertyDescriptor prop, bool isDefault) => throw null;
-
- protected internal virtual DataGridColumnStyle CreateGridColumn(PropertyDescriptor prop) => throw null;
-
- protected virtual void OnReadOnlyChanged(EventArgs e) { }
-
- protected virtual void OnMappingNameChanged(EventArgs e) { }
-
- protected virtual void OnAlternatingBackColorChanged(EventArgs e) { }
-
- protected virtual void OnForeColorChanged(EventArgs e) { }
-
- protected virtual void OnBackColorChanged(EventArgs e) { }
-
- protected virtual void OnAllowSortingChanged(EventArgs e) { }
-
- protected virtual void OnGridLineColorChanged(EventArgs e) { }
-
- protected virtual void OnGridLineStyleChanged(EventArgs e) { }
-
- protected virtual void OnHeaderBackColorChanged(EventArgs e) { }
-
- protected virtual void OnHeaderFontChanged(EventArgs e) { }
-
- protected virtual void OnHeaderForeColorChanged(EventArgs e) { }
-
- protected virtual void OnLinkColorChanged(EventArgs e) { }
-
- protected virtual void OnLinkHoverColorChanged(EventArgs e) { }
-
- protected virtual void OnPreferredRowHeightChanged(EventArgs e) { }
-
- protected virtual void OnPreferredColumnWidthChanged(EventArgs e) { }
-
- protected virtual void OnColumnHeadersVisibleChanged(EventArgs e) { }
-
- protected virtual void OnRowHeadersVisibleChanged(EventArgs e) { }
-
- protected virtual void OnRowHeaderWidthChanged(EventArgs e) { }
-
- protected virtual void OnSelectionForeColorChanged(EventArgs e) { }
-
- protected virtual void OnSelectionBackColorChanged(EventArgs e) { }
-}
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridTextBox.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridTextBox.cs
index a798de6ed36..c05cfbd2388 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridTextBox.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridTextBox.cs
@@ -1,37 +1,301 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#pragma warning disable IDE0040, IDE1006, SA1005, SA1206, SA1400, RS0016, IDE0078
+
+#nullable disable
+
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace System.Windows.Forms;
+ ///
+ /// Represents a control that is hosted in a
+ /// .
+ ///
+ [
+ ComVisible(true),
+ ClassInterface(ClassInterfaceType.AutoDispatch),
+ ToolboxItem(false),
+ DesignTimeVisible(false)
+ ]
+ public class DataGridTextBox : TextBox
+ {
+ private bool isInEditOrNavigateMode = true;
-#nullable disable
+ // only needed to signal the dataGrid that an edit
+ // takes place
+ private DataGrid dataGrid;
-///
-/// This type is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code.
-///
-[Obsolete(
- Obsoletions.DataGridMessage,
- error: false,
- DiagnosticId = Obsoletions.UnsupportedControlsDiagnosticId,
- UrlFormat = Obsoletions.SharedUrlFormat)]
-[EditorBrowsable(EditorBrowsableState.Never)]
-[Browsable(false)]
-[ComVisible(true)]
-[ClassInterface(ClassInterfaceType.AutoDispatch)]
-[ToolboxItem(false)]
-[DesignTimeVisible(false)]
-[DefaultProperty("GridEditName")]
-public class DataGridTextBox : TextBox
-{
- public DataGridTextBox() => throw new PlatformNotSupportedException();
-
- public void SetDataGrid(DataGrid parentGrid) { }
-
- public bool IsInEditOrNavigateMode
- {
- get => throw null;
- set { }
+ public DataGridTextBox() : base()
+ {
+ TabStop = false;
+ }
+
+ ///
+ /// Sets the to which this control belongs.
+ ///
+ public void SetDataGrid(DataGrid parentGrid)
+ {
+ dataGrid = parentGrid;
+ }
+
+ protected override void WndProc(ref Message m)
+ {
+ // but what if we get a CtrlV?
+ // what about deleting from the menu?
+ if (m.MsgInternal == PInvokeCore.WM_PASTE || m.MsgInternal == PInvokeCore.WM_CUT || m.MsgInternal == PInvokeCore.WM_CLEAR)
+ {
+ IsInEditOrNavigateMode = false;
+ dataGrid.ColumnStartedEditing(Bounds);
+ }
+
+ base.WndProc(ref m);
+ }
+
+ protected override void OnMouseWheel(MouseEventArgs e)
+ {
+ dataGrid.TextBoxOnMouseWheel(e);
+ }
+
+ protected override void OnKeyPress(KeyPressEventArgs e)
+ {
+ base.OnKeyPress(e);
+
+ // Shift-Space should not cause the grid to
+ // be put in edit mode
+ if (e.KeyChar == ' ' && (Control.ModifierKeys & Keys.Shift) == Keys.Shift)
+ {
+ return;
+ }
+
+ // if the edit box is in ReadOnly mode, then do not tell the DataGrid about the
+ // edit
+ if (ReadOnly)
+ {
+ return;
+ }
+
+ // Ctrl-* should not put the grid in edit mode
+ if ((Control.ModifierKeys & Keys.Control) == Keys.Control && ((Control.ModifierKeys & Keys.Alt) == 0))
+ {
+ return;
+ }
+
+ IsInEditOrNavigateMode = false;
+
+ // let the DataGrid know about the edit
+ dataGrid.ColumnStartedEditing(Bounds);
+ }
+
+ protected internal override bool ProcessKeyMessage(ref Message m)
+ {
+ Keys key = (Keys)(int)m.WParamInternal;
+ Keys modifierKeys = ModifierKeys;
+
+ if ((key | modifierKeys) == Keys.Enter || (key | modifierKeys) == Keys.Escape || ((key | modifierKeys) == (Keys.Enter | Keys.Control))
+ )
+ {
+ // enter and escape keys are sent directly to the DataGrid
+ // for those keys, eat the WM_CHAR part of the KeyMessage
+ //
+ if (m.MsgInternal == PInvokeCore.WM_CHAR)
+ {
+ return true;
+ }
+
+ return ProcessKeyPreview(ref m);
+ }
+
+ if (m.MsgInternal == PInvokeCore.WM_CHAR)
+ {
+ if (key == Keys.LineFeed) // eat the LineFeed we get when the user presses Ctrl-Enter in a gridTextBox
+ {
+ return true;
+ }
+
+ return ProcessKeyEventArgs(ref m);
+ }
+
+ // now the edit control will be always on top of the grid
+ // we only want to process the WM_KEYUP message ( the same way the grid was doing when the grid was getting all
+ // the keys )
+ if (m.MsgInternal == PInvokeCore.WM_KEYUP)
+ {
+ return true;
+ }
+
+ Keys keyData = key & Keys.KeyCode;
+
+ switch (keyData)
+ {
+ case Keys.Right:
+ // here is the deal with Keys.Right:
+ // if the end of the selection is at the end of the string
+ // send this character to the dataGrid
+ // else, process the KeyEvent
+ //
+ if (SelectionStart + SelectionLength == Text.Length)
+ {
+ return ProcessKeyPreview(ref m);
+ }
+
+ return ProcessKeyEventArgs(ref m);
+ case Keys.Left:
+ // if the end of the selection is at the begining of the string
+ // or if the entire text is selected and we did not start editing
+ // send this character to the dataGrid
+ // else, process the KeyEvent
+ //
+ if (SelectionStart + SelectionLength == 0 ||
+ (IsInEditOrNavigateMode && SelectionLength == Text.Length))
+ {
+ return ProcessKeyPreview(ref m);
+ }
+
+ return ProcessKeyEventArgs(ref m);
+ case Keys.Down:
+ // if the end of the selection is on the last line of the text then
+ // send this character to the dataGrid
+ // else, process the KeyEvent
+ //
+ int end = SelectionStart + SelectionLength;
+ if (Text.IndexOf("\r\n", end, StringComparison.Ordinal) == -1)
+ {
+ return ProcessKeyPreview(ref m);
+ }
+
+ return ProcessKeyEventArgs(ref m);
+ case Keys.Up:
+ // if the end of the selection is on the first line of the text then
+ // send this character to the dataGrid
+ // else, process the KeyEvent
+ //
+ if (Text.IndexOf("\r\n", StringComparison.Ordinal) < 0 || SelectionStart + SelectionLength < Text.IndexOf("\r\n", StringComparison.Ordinal))
+ {
+ return ProcessKeyPreview(ref m);
+ }
+
+ return ProcessKeyEventArgs(ref m);
+ case Keys.Home:
+ case Keys.End:
+ if (SelectionLength == Text.Length)
+ {
+ return ProcessKeyPreview(ref m);
+ }
+ else
+ {
+ return ProcessKeyEventArgs(ref m);
+ }
+
+ case Keys.Prior:
+ case Keys.Next:
+ case Keys.Oemplus:
+ case Keys.Add:
+ case Keys.OemMinus:
+ case Keys.Subtract:
+ if (IsInEditOrNavigateMode)
+ {
+ // this will ultimately call parent's ProcessKeyPreview
+ // in our case, DataGrid's ProcessKeyPreview
+ return ProcessKeyPreview(ref m);
+ }
+ else
+ {
+ return ProcessKeyEventArgs(ref m);
+ }
+
+ case Keys.Space:
+ if (IsInEditOrNavigateMode && (Control.ModifierKeys & Keys.Shift) == Keys.Shift)
+ {
+ // when we get a SHIFT-SPACEBAR message, disregard the WM_CHAR part of the message
+ if (m.MsgInternal == PInvokeCore.WM_CHAR)
+ {
+ return true;
+ }
+
+ // if the user pressed the SHIFT key at the same time with
+ // the space key, send the key message to the DataGrid
+ return ProcessKeyPreview(ref m);
+ }
+
+ return ProcessKeyEventArgs(ref m);
+ case Keys.A:
+ if (IsInEditOrNavigateMode && (Control.ModifierKeys & Keys.Control) == Keys.Control)
+ {
+ // when we get a Control-A message, disregard the WM_CHAR part of the message
+ if (m.MsgInternal == PInvokeCore.WM_CHAR)
+ {
+ return true;
+ }
+
+ // if the user pressed the Control key at the same time with
+ // the space key, send the key message to the DataGrid
+ return ProcessKeyPreview(ref m);
+ }
+
+ return ProcessKeyEventArgs(ref m);
+ case Keys.F2:
+ IsInEditOrNavigateMode = false;
+ // do not select all the text, but
+ // position the caret at the end of the text
+ SelectionStart = Text.Length;
+ return true;
+ case Keys.Delete:
+ if (IsInEditOrNavigateMode)
+ {
+ // pass the delete to the parent, in our case, the DataGrid
+ // if the dataGrid used the key, then we aren't gonne
+ // use it anymore, else we are
+ if (ProcessKeyPreview(ref m))
+ {
+ return true;
+ }
+ else
+ {
+ // the edit control will use the
+ // delete key: we are in Edit mode now:
+ IsInEditOrNavigateMode = false;
+ dataGrid.ColumnStartedEditing(Bounds);
+
+ return ProcessKeyEventArgs(ref m);
+ }
+ }
+ else
+ {
+ return ProcessKeyEventArgs(ref m);
+ }
+
+ case Keys.Tab:
+ // the TextBox gets the Control-Tab messages,
+ // not the parent
+ if ((ModifierKeys & Keys.Control) == Keys.Control)
+ {
+ return ProcessKeyPreview(ref m);
+ }
+ else
+ {
+ return ProcessKeyEventArgs(ref m);
+ }
+
+ default:
+ return ProcessKeyEventArgs(ref m);
+ }
+ }
+
+ public bool IsInEditOrNavigateMode
+ {
+ get
+ {
+ return isInEditOrNavigateMode;
+ }
+ set
+ {
+ isInEditOrNavigateMode = value;
+ if (value)
+ {
+ SelectAll();
+ }
+ }
+ }
}
-}
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridTextBoxColumn.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridTextBoxColumn.cs
index 6a2c81afd5d..1c597c174cc 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridTextBoxColumn.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridTextBoxColumn.cs
@@ -1,108 +1,644 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#pragma warning disable IDE0040, IDE1006, SA1005, SA1206, SA1400, CA1310, CA1510, CSIsNull001, CSIsNull002, RS0016, IDE0031, IDE0300, IDE0052, CA1822
+
+#nullable disable
+
using System.ComponentModel;
using System.Drawing;
-using System.Drawing.Design;
+using System.Globalization;
namespace System.Windows.Forms;
+ ///
+ /// Hosts a System.Windows.Forms.TextBox control in a cell of a System.Windows.Forms.DataGridColumnStyle for editing strings.
+ ///
+ public class DataGridTextBoxColumn : DataGridColumnStyle
+ {
+ // ui State
+ private readonly int xMargin = 2;
+ private readonly int yMargin = 1;
+ // private int fontHandle = 0;
+ private string format;
+ private TypeConverter typeConverter;
+ private IFormatProvider formatInfo;
+ private Reflection.MethodInfo parseMethod;
-#nullable disable
+ // hosted control
+ private readonly DataGridTextBox edit;
-///
-/// This type is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code.
-///
-[Obsolete(
- Obsoletions.DataGridMessage,
- error: false,
- DiagnosticId = Obsoletions.UnsupportedControlsDiagnosticId,
- UrlFormat = Obsoletions.SharedUrlFormat)]
-[EditorBrowsable(EditorBrowsableState.Never)]
-[Browsable(false)]
-public class DataGridTextBoxColumn : DataGridColumnStyle
-{
- public DataGridTextBoxColumn() => throw new PlatformNotSupportedException();
+ // editing state
+ private string oldValue;
+ private int editRow = -1;
- public DataGridTextBoxColumn(PropertyDescriptor prop) => throw new PlatformNotSupportedException();
+ ///
+ /// Initializes a new instance of the System.Windows.Forms.DataGridTextBoxColumn
+ /// class.
+ ///
+ public DataGridTextBoxColumn() : this(null, null)
+ {
+ }
- public DataGridTextBoxColumn(PropertyDescriptor prop, string format) => throw new PlatformNotSupportedException();
+ ///
+ /// Initializes a new instance of a System.Windows.Forms.DataGridTextBoxColumn with
+ /// a specified System.Data.DataColumn.
+ ///
+ public DataGridTextBoxColumn(PropertyDescriptor prop)
+ : this(prop, null, false)
+ {
+ }
- public DataGridTextBoxColumn(PropertyDescriptor prop, string format, bool isDefault) => throw new PlatformNotSupportedException();
+ ///
+ /// Initializes a new instance of a System.Windows.Forms.DataGridTextBoxColumn. with
+ /// the specified System.Data.DataColumn and System.Windows.Forms.ComponentModel.Format.
+ ///
+ public DataGridTextBoxColumn(PropertyDescriptor prop, string format) : this(prop, format, false) { }
- public DataGridTextBoxColumn(PropertyDescriptor prop, bool isDefault) => throw new PlatformNotSupportedException();
+ public DataGridTextBoxColumn(PropertyDescriptor prop, string format, bool isDefault) : base(prop, isDefault)
+ {
+ edit = new DataGridTextBox
+ {
+ BorderStyle = BorderStyle.None,
+ Multiline = true,
+ AcceptsReturn = true,
+ Visible = false
+ };
+ Format = format;
+ }
- [Browsable(false)]
- public virtual TextBox TextBox => throw null;
+ public DataGridTextBoxColumn(PropertyDescriptor prop, bool isDefault) : this(prop, null, isDefault) { }
- [DefaultValue(null)]
- [Editor($"System.Windows.Forms.Design.DataGridColumnStyleFormatEditor, {Assemblies.SystemDesign}", typeof(UITypeEditor))]
- public string Format
- {
- get => throw null;
- set { }
- }
+ // =------------------------------------------------------------------
+ // = Properties
+ // =------------------------------------------------------------------
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Advanced)]
- public IFormatProvider FormatInfo
- {
- get => throw null;
- set { }
- }
+ ///
+ /// Gets the hosted System.Windows.Forms.TextBox control.
+ ///
+ [Browsable(false)]
+ public virtual TextBox TextBox
+ {
+ get
+ {
+ return edit;
+ }
+ }
- [DefaultValue(null)]
- public override PropertyDescriptor PropertyDescriptor
- {
- set { }
- }
+ internal override bool KeyPress(int rowNum, Keys keyData)
+ {
+ if (edit.IsInEditOrNavigateMode)
+ {
+ return base.KeyPress(rowNum, keyData);
+ }
+
+ // if the edit box is editing, then
+ // pass this keystroke to the edit box
+ //
+ return false;
+ }
+
+ ///
+ /// Adds a System.Windows.Forms.TextBox control to the System.Windows.Forms.DataGrid control's System.Windows.Forms.Control.ControlCollection
+ /// .
+ ///
+ protected override void SetDataGridInColumn(DataGrid value)
+ {
+ base.SetDataGridInColumn(value);
+ if (edit.ParentInternal != null)
+ {
+ edit.ParentInternal.Controls.Remove(edit);
+ }
+
+ if (value != null)
+ {
+ value.Controls.Add(edit);
+ }
+
+ // we have to tell the edit control about its dataGrid
+ edit.SetDataGrid(value);
+ }
+
+ /* CUT as part of the new DataGridTableStyleSheet thing
+ public override Font Font {
+ set {
+ base.Font = value;
+ Font f = base.Font;
+ edit.Font = f;
+ // if (f != null) {
+ // fontHandle = f.Handle;
+ // }
+ }
+ }
+ */
+
+ ///
+ /// Gets or sets the System.Windows.Forms.ComponentModel.Format for the System.Windows.Forms.DataGridTextBoxColumn
+ /// .
+ ///
+ [
+ SRDescription(nameof(SR.FormatControlFormatDescr)),
+ DefaultValue(null)
+ ]
+ public override PropertyDescriptor PropertyDescriptor
+ {
+ set
+ {
+ base.PropertyDescriptor = value;
+ if (PropertyDescriptor != null)
+ {
+ if (PropertyDescriptor.PropertyType != typeof(object))
+ {
+ typeConverter = TypeDescriptor.GetConverter(PropertyDescriptor.PropertyType);
+ parseMethod = PropertyDescriptor.PropertyType.GetMethod("Parse", new Type[] { typeof(string), typeof(IFormatProvider) });
+ }
+ }
+ }
+ }
+
+ // add the corresponding value Editor: rip one from the valueEditor for the DisplayMember in the
+ // format object
+ [DefaultValue(null), Editor("System.Windows.Forms.Design.DataGridColumnStyleFormatEditor, " + Assemblies.SystemDesign, typeof(Drawing.Design.UITypeEditor))]
+ public string Format
+ {
+ get
+ {
+ return format;
+ }
+ set
+ {
+ if (value == null)
+ {
+ value = string.Empty;
+ }
+
+ if (format == null || !format.Equals(value))
+ {
+ format = value;
+
+ // if the associated typeConverter cannot convert from string,
+ // then we can't modify the column value. hence, make it readOnly
+ //
+ if (format.Length == 0)
+ {
+ if (typeConverter != null && !typeConverter.CanConvertFrom(typeof(string)))
+ {
+ ReadOnly = true;
+ }
+ }
+
+ Invalidate();
+ }
+ }
+ }
+
+ [Browsable(false), EditorBrowsable(EditorBrowsableState.Advanced)]
+ public IFormatProvider FormatInfo
+ {
+ get
+ {
+ return formatInfo;
+ }
+ set
+ {
+ if (formatInfo == null || !formatInfo.Equals(value))
+ {
+ formatInfo = value;
+ }
+ }
+ }
+
+ public override bool ReadOnly
+ {
+ get
+ {
+ return base.ReadOnly;
+ }
+ set
+ {
+ // if the gridColumn is can't convert the string to
+ // the backGround propertyDescriptor, then make the column ReadOnly
+ if (!value && (format == null || format.Length == 0))
+ {
+ if (typeConverter != null && !typeConverter.CanConvertFrom(typeof(string)))
+ {
+ return;
+ }
+ }
+
+ base.ReadOnly = value;
+ }
+ }
+
+ // =------------------------------------------------------------------
+ // = Methods
+ // =------------------------------------------------------------------
+
+ private void DebugOut(string s)
+ {
+ Debug.WriteLineIf(CompModSwitches.DGEditColumnEditing.TraceVerbose, "DGEditColumnEditing: " + s);
+ }
+
+ // will hide the edit control
+ ///
+ /// Informs the column the focus is being conceded.
+ ///
+ protected internal override void ConcedeFocus()
+ {
+ edit.Bounds = Rectangle.Empty;
+ // edit.Visible = false;
+ // HideEditBox();
+ }
+
+ ///
+ /// Hides the System.Windows.Forms.TextBox
+ /// control and moves the focus to the System.Windows.Forms.DataGrid
+ /// control.
+ ///
+ protected void HideEditBox()
+ {
+ bool wasFocused = edit.Focused;
+ edit.Visible = false;
+
+ // it seems that edit.Visible = false will take away the focus from
+ // the edit control. And this means that we will not give the focus to the grid
+ // If all the columns would have an edit control this would not be bad
+ // ( or if the grid is the only control on the form ),
+ // but when we have a DataGridBoolColumn then the focus will be taken away
+ // by the next control in the form.
+ //
+ // if (edit.Focused && this.DataGridTableStyle.DataGrid.CanFocus) {
+
+ // when the user deletes the current ( ie active ) column from the
+ // grid, the grid should still call EndEdit ( so that the changes that the user made
+ // before deleting the column will go to the backEnd)
+ // however, in that situation, we are left w/ the editColumn which is not parented.
+ // the grid will call Edit to reset the EditColumn
+ if (wasFocused && DataGridTableStyle != null && DataGridTableStyle.DataGrid != null && DataGridTableStyle.DataGrid.CanFocus)
+ {
+ DataGridTableStyle.DataGrid.Focus();
+ Debug.Assert(!edit.Focused, "the edit control just conceeded focus to the dataGrid");
+ }
+ }
+
+ protected internal override void UpdateUI(CurrencyManager source, int rowNum, string displayText)
+ {
+ edit.Text = GetText(GetColumnValueAtRow(source, rowNum));
+ if (!edit.ReadOnly && displayText != null)
+ {
+ edit.Text = displayText;
+ }
+ }
+
+ ///
+ /// Ends an edit operation on the System.Windows.Forms.DataGridColumnStyle
+ /// .
+ ///
+ protected void EndEdit()
+ {
+ edit.IsInEditOrNavigateMode = true;
+ DebugOut("Ending Edit");
+ Invalidate();
+ }
- protected void HideEditBox() { }
+ ///
+ /// Returns the optimum width and
+ /// height of the cell in a specified row relative
+ /// to the specified value.
+ ///
+ protected internal override Size GetPreferredSize(Graphics g, object value)
+ {
+ Size extents = Size.Ceiling(g.MeasureString(GetText(value), DataGridTableStyle.DataGrid.Font));
+ extents.Width += xMargin * 2 + DataGridTableStyle.GridLineWidth;
+ extents.Height += yMargin;
+ return extents;
+ }
- protected void EndEdit() { }
+ ///
+ /// Gets the height of a cell in a System.Windows.Forms.DataGridColumnStyle
+ /// .
+ ///
+ protected internal override int GetMinimumHeight()
+ {
+ // why + 3? cause we have to give some way to the edit box.
+ return FontHeight + yMargin + 3;
+ }
- protected void PaintText(
- Graphics g,
- Rectangle bounds,
- string text,
- bool alignToRight)
- { }
+ ///
+ /// Gets the height to be used in for automatically resizing columns.
+ ///
+ protected internal override int GetPreferredHeight(Graphics g, object value)
+ {
+ int newLineIndex = 0;
+ int newLines = 0;
+ string valueString = GetText(value);
+ while (newLineIndex != -1 && newLineIndex < valueString.Length)
+ {
+ newLineIndex = valueString.IndexOf("\r\n", newLineIndex + 1);
+ newLines++;
+ }
- protected void PaintText(
- Graphics g,
- Rectangle textBounds,
- string text,
- Brush backBrush,
- Brush foreBrush,
- bool alignToRight)
- { }
+ return FontHeight * newLines + yMargin;
+ }
- protected internal override Size GetPreferredSize(Graphics g, object value) => throw null;
+ ///
+ /// Initiates a request to interrupt an edit procedure.
+ ///
+ protected internal override void Abort(int rowNum)
+ {
+ RollBack();
+ HideEditBox();
+ EndEdit();
+ }
- protected internal override int GetMinimumHeight() => throw null;
+ // used for Alt0 functionality
+ ///
+ /// Enters a in the column.
+ ///
+ protected internal override void EnterNullValue()
+ {
+ if (ReadOnly)
+ {
+ return;
+ }
- protected internal override int GetPreferredHeight(Graphics g, object value) => throw null;
+ // if the edit box is not visible, then
+ // do not put the edit text in it
+ if (!edit.Visible)
+ {
+ return;
+ }
- protected internal override void Abort(int rowNum) { }
+ // if we are editing, then we should be able to enter alt-0 in a cell.
+ //
+ if (!edit.IsInEditOrNavigateMode)
+ {
+ return;
+ }
- protected internal override bool Commit(CurrencyManager dataSource, int rowNum) => throw null;
+ edit.Text = NullText;
+ // edit.Visible = true;
+ edit.IsInEditOrNavigateMode = false;
+ // tell the dataGrid that there is an edit:
+ if (DataGridTableStyle != null && DataGridTableStyle.DataGrid != null)
+ {
+ DataGridTableStyle.DataGrid.ColumnStartedEditing(edit.Bounds);
+ }
+ }
- protected internal override void Edit(
- CurrencyManager source,
- int rowNum,
- Rectangle bounds,
- bool readOnly,
- string displayText,
- bool cellIsVisible)
- { }
+ ///
+ /// Inititates a request to complete an editing procedure.
+ ///
+ protected internal override bool Commit(CurrencyManager dataSource, int rowNum)
+ {
+ // always hide the edit box
+ // HideEditBox();
+ edit.Bounds = Rectangle.Empty;
- protected internal override void Paint(
- Graphics g,
- Rectangle bounds,
- CurrencyManager source,
- int rowNum,
- bool alignToRight)
- { }
+ if (edit.IsInEditOrNavigateMode)
+ {
+ return true;
+ }
- protected internal override void Paint(Graphics g, Rectangle bounds, CurrencyManager source, int rowNum)
- { }
-}
+ try
+ {
+ object value = edit.Text;
+ if (NullText.Equals(value))
+ {
+ value = Convert.DBNull;
+ edit.Text = NullText;
+ }
+ else if (format != null && format.Length != 0 && parseMethod != null && FormatInfo != null)
+ {
+ // use reflection to get the Parse method on the
+ // type of the propertyDescriptor.
+ value = (object)parseMethod.Invoke(null, new object[] { edit.Text, FormatInfo });
+ if (value is IFormattable)
+ {
+ edit.Text = ((IFormattable)value).ToString(format, formatInfo);
+ }
+ else
+ {
+ edit.Text = value.ToString();
+ }
+ }
+ else if (typeConverter != null && typeConverter.CanConvertFrom(typeof(string)))
+ {
+ value = typeConverter.ConvertFromString(edit.Text);
+ edit.Text = typeConverter.ConvertToString(value);
+ }
+
+ SetColumnValueAtRow(dataSource, rowNum, value);
+ }
+ catch
+ {
+ // MessageBox.Show("There was an error caught setting field \""
+ // + this.PropertyDescriptor.Name + "\" to the value \"" + edit.Text + "\"\n"
+ // + "The value is being rolled back to the original.\n"
+ // + "The error was a '" + e.Message + "' " + e.StackTrace
+ // , "Error commiting changes...", MessageBox.IconError);
+ // Debug.WriteLine(e.GetType().Name);
+ RollBack();
+ return false;
+ }
+
+ DebugOut("OnCommit completed without Exception.");
+ EndEdit();
+ return true;
+ }
+
+ ///
+ /// Prepares a cell for editing.
+ ///
+ protected internal override void Edit(CurrencyManager source,
+ int rowNum,
+ Rectangle bounds,
+ bool readOnly,
+ string displayText,
+ bool cellIsVisible)
+ {
+ DebugOut("Begining Edit, rowNum :" + rowNum.ToString(CultureInfo.InvariantCulture));
+
+ Rectangle originalBounds = bounds;
+
+ edit.ReadOnly = readOnly || ReadOnly || DataGridTableStyle.ReadOnly;
+
+ edit.Text = GetText(GetColumnValueAtRow(source, rowNum));
+ if (!edit.ReadOnly && displayText != null)
+ {
+ // tell the grid that we are changing stuff
+ DataGridTableStyle.DataGrid.ColumnStartedEditing(bounds);
+ // tell the edit control that the user changed it
+ edit.IsInEditOrNavigateMode = false;
+ edit.Text = displayText;
+ }
+
+ if (cellIsVisible)
+ {
+ bounds.Offset(xMargin, 2 * yMargin);
+ bounds.Width -= xMargin;
+ bounds.Height -= 2 * yMargin;
+ DebugOut("edit bounds: " + bounds.ToString());
+ edit.Bounds = bounds;
+
+ edit.Visible = true;
+
+ edit.TextAlign = Alignment;
+ }
+ else
+ {
+ edit.Bounds = Rectangle.Empty;
+ // edit.Bounds = originalBounds;
+ // edit.Visible = false;
+ }
+
+ edit.RightToLeft = DataGridTableStyle.DataGrid.RightToLeft;
+
+ edit.Focus();
+
+ editRow = rowNum;
+
+ if (!edit.ReadOnly)
+ {
+ oldValue = edit.Text;
+ }
+
+ // select the text even if the text box is read only
+ // because the navigation code in the DataGridTextBox::ProcessKeyMessage
+ // uses the SelectedText property
+ if (displayText == null)
+ {
+ edit.SelectAll();
+ }
+ else
+ {
+ int end = edit.Text.Length;
+ edit.Select(end, 0);
+ }
+
+ if (edit.Visible)
+ {
+ DataGridTableStyle.DataGrid.Invalidate(originalBounds);
+ }
+ }
+
+ internal override string GetDisplayText(object value)
+ {
+ return GetText(value);
+ }
+
+ private string GetText(object value)
+ {
+ if (value is DBNull)
+ {
+ return NullText;
+ }
+ else if (format != null && format.Length != 0 && (value is IFormattable))
+ {
+ try
+ {
+ return ((IFormattable)value).ToString(format, formatInfo);
+ }
+ catch
+ {
+ //
+ }
+ }
+ else
+ {
+ // use the typeConverter:
+ if (typeConverter != null && typeConverter.CanConvertTo(typeof(string)))
+ {
+ return (string)typeConverter.ConvertTo(value, typeof(string));
+ }
+ }
+
+ return (value != null ? value.ToString() : "");
+ }
+
+ ///
+ /// Paints the a System.Windows.Forms.DataGridColumnStyle with the specified System.Drawing.Graphics,
+ /// System.Drawing.Rectangle, DataView.Rectangle, and row number.
+ ///
+ protected internal override void Paint(Graphics g, Rectangle bounds, CurrencyManager source, int rowNum)
+ {
+ Paint(g, bounds, source, rowNum, false);
+ }
+
+ ///
+ /// Paints a System.Windows.Forms.DataGridColumnStyle with the specified System.Drawing.Graphics, System.Drawing.Rectangle, DataView, row number, and alignment.
+ ///
+ protected internal override void Paint(Graphics g, Rectangle bounds, CurrencyManager source, int rowNum, bool alignToRight)
+ {
+ string text = GetText(GetColumnValueAtRow(source, rowNum));
+ PaintText(g, bounds, text, alignToRight);
+ }
+
+ ///
+ /// Paints a System.Windows.Forms.DataGridColumnStyle with the specified System.Drawing.Graphics,
+ /// System.Drawing.Rectangle, DataView.Rectangle, row number, background color,
+ /// and foreground color..
+ ///
+ protected internal override void Paint(Graphics g, Rectangle bounds, CurrencyManager source, int rowNum,
+ Brush backBrush, Brush foreBrush, bool alignToRight)
+ {
+ string text = GetText(GetColumnValueAtRow(source, rowNum));
+ PaintText(g, bounds, text, backBrush, foreBrush, alignToRight);
+ }
+
+ ///
+ /// Draws the text and
+ /// rectangle at the given location with the specified alignment.
+ ///
+ protected void PaintText(Graphics g, Rectangle bounds, string text, bool alignToRight)
+ {
+ PaintText(g, bounds, text, DataGridTableStyle.BackBrush, DataGridTableStyle.ForeBrush, alignToRight);
+ }
+
+ ///
+ /// Draws the text and rectangle at the specified location with the
+ /// specified colors and alignment.
+ ///
+ protected void PaintText(Graphics g, Rectangle textBounds, string text, Brush backBrush, Brush foreBrush, bool alignToRight)
+ {
+ /*
+ if (edit.Visible)
+ g.BackColor = BackColor;
+ */
+
+ Rectangle rect = textBounds;
+
+ StringFormat format = new StringFormat();
+ if (alignToRight)
+ {
+ format.FormatFlags |= StringFormatFlags.DirectionRightToLeft;
+ }
+
+ format.Alignment = Alignment == HorizontalAlignment.Left ? StringAlignment.Near : Alignment == HorizontalAlignment.Center ? StringAlignment.Center : StringAlignment.Far;
+
+ // do not wrap the text
+ //
+ format.FormatFlags |= StringFormatFlags.NoWrap;
+
+ g.FillRectangle(backBrush, rect);
+ // by design, painting leaves a little padding around the rectangle.
+ // so do not deflate the rectangle.
+ rect.Offset(0, 2 * yMargin);
+ rect.Height -= 2 * yMargin;
+ g.DrawString(text, DataGridTableStyle.DataGrid.Font, foreBrush, rect, format);
+ format.Dispose();
+ }
+
+ private void RollBack()
+ {
+ Debug.Assert(!edit.IsInEditOrNavigateMode, "Must be editing to rollback changes...");
+ edit.Text = oldValue;
+ }
+
+ protected internal override void ReleaseHostedControl()
+ {
+ if (edit.ParentInternal != null)
+ {
+ edit.ParentInternal.Controls.Remove(edit);
+ }
+ }
+ }
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridToolTip.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridToolTip.cs
new file mode 100644
index 00000000000..a50fcb60a31
--- /dev/null
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/DataGridToolTip.cs
@@ -0,0 +1,89 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma warning disable IDE0040, IDE1006, SA1005, SA1206, SA1400
+
+#nullable disable
+
+using System.Drawing;
+using System.Runtime.InteropServices;
+using DSR = System.Windows.Forms.DataGridStrings;
+
+namespace System.Windows.Forms;
+ // this class is basically a NativeWindow that does toolTipping
+ // should be one for the entire grid
+ internal class DataGridToolTip : MarshalByRefObject
+ {
+ // the toolTip control
+ private NativeWindow tipWindow;
+
+ // the dataGrid which contains this toolTip
+ private readonly DataGrid dataGrid;
+
+ // CONSTRUCTOR
+ public DataGridToolTip(DataGrid dataGrid)
+ {
+ ArgumentNullException.ThrowIfNull(dataGrid);
+ Debug.Assert(dataGrid is not null, "can't attach a tool tip to a null grid");
+ this.dataGrid = dataGrid;
+ }
+
+ // will ensure that the toolTip window was created
+ public void CreateToolTipHandle()
+ {
+ if (tipWindow is null || tipWindow.Handle == IntPtr.Zero)
+ {
+ PInvoke.InitCommonControlsEx(new INITCOMMONCONTROLSEX
+ {
+ dwSize = (uint)Marshal.SizeOf(),
+ dwICC = INITCOMMONCONTROLSEX_ICC.ICC_TAB_CLASSES
+ });
+
+ var cparams = new CreateParams
+ {
+ Parent = dataGrid.Handle,
+ ClassName = PInvoke.TOOLTIPS_CLASS,
+ Style = (int)PInvoke.TTS_ALWAYSTIP,
+ };
+
+ tipWindow = new NativeWindow();
+ tipWindow.CreateHandle(cparams);
+
+ PInvokeCore.SendMessage(tipWindow, PInvoke.TTM_SETMAXTIPWIDTH, 0, SystemInformation.MaxWindowTrackSize.Width);
+ PInvoke.SetWindowPos(
+ tipWindow,
+ HWND.HWND_NOTOPMOST,
+ 0, 0, 0, 0,
+ SET_WINDOW_POS_FLAGS.SWP_NOSIZE | SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE);
+ PInvokeCore.SendMessage(tipWindow, PInvoke.TTM_SETDELAYTIME, (nuint)PInvoke.TTDT_INITIAL, 0);
+ }
+ }
+
+ public void AddToolTip(string toolTipString, IntPtr toolTipId, Rectangle iconBounds)
+ {
+ Debug.Assert(tipWindow is not null && tipWindow.Handle != IntPtr.Zero, "the tipWindow was not initialized, bailing out");
+ if (iconBounds.IsEmpty)
+ {
+ throw new ArgumentNullException(nameof(iconBounds), DSR.DataGridToolTipEmptyIcon);
+ }
+
+ ArgumentNullException.ThrowIfNull(toolTipString);
+
+ var info = new ToolInfoWrapper(dataGrid, toolTipId, TOOLTIP_FLAGS.TTF_SUBCLASS, toolTipString, iconBounds);
+ info.SendMessage(tipWindow, PInvoke.TTM_ADDTOOLW);
+ }
+
+ public void RemoveToolTip(IntPtr toolTipId)
+ {
+ var info = new ToolInfoWrapper(dataGrid, toolTipId);
+ info.SendMessage(tipWindow, PInvoke.TTM_DELTOOLW);
+ }
+
+ // will destroy the tipWindow
+ public void Destroy()
+ {
+ Debug.Assert(tipWindow is not null, "how can one destroy a null window");
+ tipWindow.DestroyHandle();
+ tipWindow = null;
+ }
+ }
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/GridColumnStylesCollection.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/GridColumnStylesCollection.cs
index d9d03901302..f0fddf6eb45 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/GridColumnStylesCollection.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/GridColumnStylesCollection.cs
@@ -1,95 +1,566 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#pragma warning disable IDE0040, IDE1006, SA1005, SA1206, SA1400, CA1510, CSIsNull001, CSIsNull002, IDE0031
+
+#nullable disable
+
using System.Collections;
using System.ComponentModel;
using System.Drawing.Design;
+using System.Globalization;
+using DSR = System.Windows.Forms.DataGridStrings;
namespace System.Windows.Forms;
-
-#nullable disable
-
-///
-/// This type is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code.
-///
-[Obsolete(
- Obsoletions.DataGridMessage,
- error: false,
- DiagnosticId = Obsoletions.UnsupportedControlsDiagnosticId,
- UrlFormat = Obsoletions.SharedUrlFormat)]
-[EditorBrowsable(EditorBrowsableState.Never)]
-[Browsable(false)]
-[Editor($"System.Windows.Forms.Design.DataGridColumnCollectionEditor, {Assemblies.SystemDesign}", typeof(UITypeEditor))]
-[ListBindable(false)]
-public class GridColumnStylesCollection : BaseCollection, IList
-{
- private GridColumnStylesCollection() { }
-
- int IList.Add(object value) => throw null;
-
- void IList.Clear() { }
-
- bool IList.Contains(object value) => throw null;
-
- int IList.IndexOf(object value) => throw null;
-
- void IList.Insert(int index, object value) { }
-
- void IList.Remove(object value) { }
-
- void IList.RemoveAt(int index) { }
-
- bool IList.IsFixedSize => throw null;
-
- bool IList.IsReadOnly => throw null;
-
- object IList.this[int index]
- {
- get => throw null;
- set { }
- }
-
- void ICollection.CopyTo(Array array, int index) { }
-
- int ICollection.Count => throw null;
-
- bool ICollection.IsSynchronized => throw null;
-
- object ICollection.SyncRoot => throw null;
-
- IEnumerator IEnumerable.GetEnumerator() => throw null;
-
- public DataGridColumnStyle this[int index] => throw null;
-
- public DataGridColumnStyle this[string columnName] => throw null;
-
- public DataGridColumnStyle this[PropertyDescriptor propertyDesciptor] => throw null;
-
- public virtual int Add(DataGridColumnStyle column) => throw null;
-
- public void AddRange(DataGridColumnStyle[] columns) { }
-
- public event CollectionChangeEventHandler CollectionChanged
+ ///
+ /// Represents a collection of System.Windows.Forms.DataGridColumnStyle objects in the
+ /// control.
+ ///
+ [
+ Editor("System.Windows.Forms.Design.DataGridColumnCollectionEditor, " + Assemblies.SystemDesign, typeof(UITypeEditor)),
+ ListBindable(false)
+ ]
+ public class GridColumnStylesCollection : BaseCollection, IList
{
- add { }
- remove { }
+ CollectionChangeEventHandler onCollectionChanged;
+ readonly ArrayList items = new ArrayList();
+ readonly DataGridTableStyle owner;
+ private readonly bool isDefault;
+
+ // we have to implement IList for the Collection editor to work
+ //
+ int IList.Add(object value)
+ {
+ return Add((DataGridColumnStyle)value);
+ }
+
+ void IList.Clear()
+ {
+ Clear();
+ }
+
+ bool IList.Contains(object value)
+ {
+ return items.Contains(value);
+ }
+
+ int IList.IndexOf(object value)
+ {
+ return items.IndexOf(value);
+ }
+
+ void IList.Insert(int index, object value)
+ {
+ throw new NotSupportedException();
+ }
+
+ void IList.Remove(object value)
+ {
+ Remove((DataGridColumnStyle)value);
+ }
+
+ void IList.RemoveAt(int index)
+ {
+ RemoveAt(index);
+ }
+
+ bool IList.IsFixedSize
+ {
+ get { return false; }
+ }
+
+ bool IList.IsReadOnly
+ {
+ get { return false; }
+ }
+
+ object IList.this[int index]
+ {
+ get { return items[index]; }
+ set { throw new NotSupportedException(); }
+ }
+
+ void ICollection.CopyTo(Array array, int index)
+ {
+ items.CopyTo(array, index);
+ }
+
+ int ICollection.Count
+ {
+ get { return items.Count; }
+ }
+
+ bool ICollection.IsSynchronized
+ {
+ get { return false; }
+ }
+
+ object ICollection.SyncRoot
+ {
+ get { return this; }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return items.GetEnumerator();
+ }
+
+ internal GridColumnStylesCollection(DataGridTableStyle table)
+ {
+ owner = table;
+ }
+
+ internal GridColumnStylesCollection(DataGridTableStyle table, bool isDefault) : this(table)
+ {
+ this.isDefault = isDefault;
+ }
+
+ ///
+ /// Gets the list of items in the collection.
+ ///
+ protected override ArrayList List
+ {
+ get
+ {
+ return items;
+ }
+ }
+
+ /* implemented in BaseCollection
+ ///
+ /// Gets the number of System.Windows.Forms.DataGridColumnStyle objects in the collection.
+ ///
+ ///
+ /// The number of System.Windows.Forms.DataGridColumnStyle objects in the System.Windows.Forms.GridColumnsStyleCollection .
+ ///
+ ///
+ /// The following example uses the
+ /// property to determine how many System.Windows.Forms.DataGridColumnStyle objects are in a System.Windows.Forms.GridColumnsStyleCollection, and uses that number to iterate through the
+ /// collection.
+ ///
+ /// Private Sub PrintGridColumns()
+ /// Dim colsCount As Integer
+ /// colsCount = DataGrid1.GridColumns.Count
+ /// Dim i As Integer
+ /// For i = 0 to colsCount - 1
+ /// Debug.Print DataGrid1.GridColumns(i).GetType.ToString
+ /// Next i
+ /// End Sub
+ ///
+ ///
+ ///
+ ///
+ public override int Count {
+ get {
+ return items.Count;
+ }
+ }
+ */
+
+ ///
+ /// Gets the System.Windows.Forms.DataGridColumnStyle at a specified index.
+ ///
+ public DataGridColumnStyle this[int index]
+ {
+ get
+ {
+ return (DataGridColumnStyle)items[index];
+ }
+ }
+
+ ///
+ /// Gets the System.Windows.Forms.DataGridColumnStyle
+ /// with the specified name.
+ ///
+ public DataGridColumnStyle this[string columnName]
+ {
+ get
+ {
+ int itemCount = items.Count;
+ for (int i = 0; i < itemCount; ++i)
+ {
+ DataGridColumnStyle column = (DataGridColumnStyle)items[i];
+ // NOTE: case-insensitive
+ if (string.Equals(column.MappingName, columnName, StringComparison.OrdinalIgnoreCase))
+ {
+ return column;
+ }
+ }
+
+ return null;
+ }
+ }
+
+ internal DataGridColumnStyle MapColumnStyleToPropertyName(string mappingName)
+ {
+ int itemCount = items.Count;
+ for (int i = 0; i < itemCount; ++i)
+ {
+ DataGridColumnStyle column = (DataGridColumnStyle)items[i];
+ // NOTE: case-insensitive
+ if (string.Equals(column.MappingName, mappingName, StringComparison.OrdinalIgnoreCase))
+ {
+ return column;
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Gets the System.Windows.Forms.DataGridColumnStyle associated with the
+ /// specified .
+ ///
+ public DataGridColumnStyle this[PropertyDescriptor propertyDesciptor]
+ {
+ get
+ {
+ int itemCount = items.Count;
+ for (int i = 0; i < itemCount; ++i)
+ {
+ DataGridColumnStyle column = (DataGridColumnStyle)items[i];
+ if (propertyDesciptor.Equals(column.PropertyDescriptor))
+ {
+ return column;
+ }
+ }
+
+ return null;
+ }
+ }
+
+ internal DataGridTableStyle DataGridTableStyle
+ {
+ get
+ {
+ return owner;
+ }
+ }
+
+ ///
+ /// Adds a System.Windows.Forms.DataGridColumnStyle to the System.Windows.Forms.GridColumnStylesCollection
+ ///
+ internal void CheckForMappingNameDuplicates(DataGridColumnStyle column)
+ {
+ if (string.IsNullOrEmpty(column.MappingName))
+ {
+ return;
+ }
+
+ for (int i = 0; i < items.Count; i++)
+ {
+ if (((DataGridColumnStyle)items[i]).MappingName.Equals(column.MappingName) && column != items[i])
+ {
+ throw new ArgumentException(DSR.DataGridColumnStyleDuplicateMappingName, nameof(column));
+ }
+ }
+ }
+
+ private void ColumnStyleMappingNameChanged(object sender, EventArgs pcea)
+ {
+ OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Refresh, null));
+ }
+
+ private void ColumnStylePropDescChanged(object sender, EventArgs pcea)
+ {
+ OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Refresh, (DataGridColumnStyle)sender));
+ }
+
+ public virtual int Add(DataGridColumnStyle column)
+ {
+ if (isDefault)
+ {
+ throw new ArgumentException(DSR.DataGridDefaultColumnCollectionChanged);
+ }
+
+ CheckForMappingNameDuplicates(column);
+
+ column.SetDataGridTableInColumn(owner, true);
+ column.MappingNameChanged += new EventHandler(ColumnStyleMappingNameChanged);
+ column.PropertyDescriptorChanged += new EventHandler(ColumnStylePropDescChanged);
+
+ // columns which are not the default should have a default
+ // width of DataGrid.PreferredColumnWidth
+ if (DataGridTableStyle != null && column.Width == -1)
+ {
+ column._width = DataGridTableStyle.PreferredColumnWidth;
+ }
+#if false
+ column.AddOnPropertyChanged(owner.OnColumnChanged);
+#endif
+ int index = items.Add(column);
+ OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Add, column));
+ return index;
+ }
+
+ public void AddRange(DataGridColumnStyle[] columns)
+ {
+ if (columns == null)
+ {
+ throw new ArgumentNullException(nameof(columns));
+ }
+
+ for (int i = 0; i < columns.Length; i++)
+ {
+ Add(columns[i]);
+ }
+ }
+
+ // the dataGrid will need to add default columns to a default
+ // table when there is no match for the listName in the tableStyle
+ internal void AddDefaultColumn(DataGridColumnStyle column)
+ {
+#if DEBUG
+ Debug.Assert(isDefault, "we should be calling this function only for default tables");
+ Debug.Assert(column.IsDefault, "we should be a default column");
+#endif // DEBUG
+ column.SetDataGridTableInColumn(owner, true);
+ items.Add(column);
+ }
+
+ internal void ResetDefaultColumnCollection()
+ {
+ Debug.Assert(isDefault, "we should be calling this function only for default tables");
+ // unparent the edit controls
+ for (int i = 0; i < Count; i++)
+ {
+ this[i].ReleaseHostedControl();
+ }
+
+ // get rid of the old list and get a new empty list
+ items.Clear();
+ }
+
+ ///
+ /// Occurs when a change is made to the System.Windows.Forms.GridColumnStylesCollection.
+ ///
+ public event CollectionChangeEventHandler CollectionChanged
+ {
+ add => onCollectionChanged += value;
+ remove => onCollectionChanged -= value;
+ }
+
+ public void Clear()
+ {
+ for (int i = 0; i < Count; i++)
+ {
+ this[i].ReleaseHostedControl();
+ }
+
+ items.Clear();
+ OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Refresh, null));
+ }
+
+ ///
+ /// Gets a value indicating whether the System.Windows.Forms.GridColumnStylesCollection contains a System.Windows.Forms.DataGridColumnStyle associated with the
+ /// specified .
+ ///
+ public bool Contains(PropertyDescriptor propertyDescriptor)
+ {
+ return this[propertyDescriptor] != null;
+ }
+
+ ///
+ /// Gets a value indicating whether the System.Windows.Forms.GridColumnsStyleCollection contains the specified System.Windows.Forms.DataGridColumnStyle.
+ ///
+ public bool Contains(DataGridColumnStyle column)
+ {
+ int index = items.IndexOf(column);
+ return index != -1;
+ }
+
+ ///
+ /// Gets a value indicating whether the System.Windows.Forms.GridColumnsStyleCollection contains the System.Windows.Forms.DataGridColumnStyle with the specified name.
+ ///
+ public bool Contains(string name)
+ {
+ IEnumerator e = items.GetEnumerator();
+ while (e.MoveNext())
+ {
+ DataGridColumnStyle column = (DataGridColumnStyle)e.Current;
+ // NOTE: case-insensitive
+ if (string.Compare(column.MappingName, name, true, CultureInfo.InvariantCulture) == 0)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /* implemented at BaseCollection
+ ///
+ /// Gets an enumerator for the System.Windows.Forms.GridColumnsStyleCollection.
+ ///
+ ///
+ /// Gets an enumerator for the System.Windows.Forms.GridColumnsStyleCollection.
+ ///
+ ///
+ /// An
+ /// that can be used to iterate through the collection.
+ ///
+ ///
+ /// The following example gets an that iterates through the System.Windows.Forms.GridColumnsStyleCollection. and prints the
+ /// of each
+ /// associated with the object.
+ ///
+ /// Private Sub EnumerateThroughGridColumns()
+ /// Dim ie As System.Collections.IEnumerator
+ /// Dim dgCol As DataGridColumn
+ /// Set ie = DataGrid1.GridColumns.GetEnumerator
+ /// Do While ie.GetNext = True
+ /// Set dgCol = ie.GetObject
+ /// Debug.Print dgCol.DataColumn.Caption
+ /// Loop
+ /// End Sub
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override IEnumerator GetEnumerator() {
+ return items.GetEnumerator();
+ }
+
+ ///
+ /// Gets an enumerator for the System.Windows.Forms.GridColumnsStyleCollection
+ /// .
+ ///
+ ///
+ /// A value that indicates if the enumerator can remove elements. , if removals are allowed; otherwise, . The default is .
+ ///
+ ///
+ /// An that can be used to iterate through the
+ /// collection.
+ ///
+ ///
+ /// An attempt was made to remove the System.Windows.Forms.DataGridColumnStyle through the object's method. Use the System.Windows.Forms.GridColumnsStyleCollection object's method instead.
+ ///
+ ///
+ /// Because this implementation doesn't support the removal
+ /// of System.Windows.Forms.DataGridColumnStyle objects through the
+ /// class's method, you must use the class's
+ /// method instead.
+ ///
+ ///
+ /// The following example gets an for that iterates through the System.Windows.Forms.GridColumnsStyleCollection. If a column in the collection is of type , it is deleted.
+ ///
+ /// Private Sub RemoveBoolColumns()
+ /// Dim ie As System.Collections.IEnumerator
+ /// Dim dgCol As DataGridColumn
+ /// Set ie = DataGrid1.GridColumns.GetEnumerator(true)
+ /// Do While ie.GetNext
+ /// Set dgCol = ie.GetObject
+ ///
+ /// If dgCol.ToString = "DataGridBoolColumn" Then
+ /// DataGrid1.GridColumns.Remove dgCol
+ /// End If
+ /// Loop
+ /// End If
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override IEnumerator GetEnumerator(bool allowRemove) {
+ if (!allowRemove)
+ return GetEnumerator();
+ else
+ throw new NotSupportedException(DSR.DataGridColumnCollectionGetEnumerator);
+ }
+ */
+
+ ///
+ /// Gets the index of a specified System.Windows.Forms.DataGridColumnStyle.
+ ///
+ public int IndexOf(DataGridColumnStyle element)
+ {
+ int itemCount = items.Count;
+ for (int i = 0; i < itemCount; ++i)
+ {
+ DataGridColumnStyle column = (DataGridColumnStyle)items[i];
+ if (element == column)
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ ///
+ /// Raises the System.Windows.Forms.GridColumnsCollection.CollectionChanged event.
+ ///
+ protected void OnCollectionChanged(CollectionChangeEventArgs e)
+ {
+ onCollectionChanged?.Invoke(this, e);
+
+ DataGrid grid = owner.DataGrid;
+ if (grid != null)
+ {
+ grid.checkHierarchy = true;
+ }
+ }
+
+ ///
+ /// Removes the specified System.Windows.Forms.DataGridColumnStyle from the System.Windows.Forms.GridColumnsStyleCollection.
+ ///
+ public void Remove(DataGridColumnStyle column)
+ {
+ if (isDefault)
+ {
+ throw new ArgumentException(DSR.DataGridDefaultColumnCollectionChanged);
+ }
+
+ int columnIndex = -1;
+ int itemsCount = items.Count;
+ for (int i = 0; i < itemsCount; ++i)
+ {
+ if (items[i] == column)
+ {
+ columnIndex = i;
+ break;
+ }
+ }
+
+ if (columnIndex == -1)
+ {
+ throw new InvalidOperationException(DSR.DataGridColumnCollectionMissing);
+ }
+ else
+ {
+ RemoveAt(columnIndex);
+ }
+ }
+
+ ///
+ /// Removes the System.Windows.Forms.DataGridColumnStyle with the specified index from the System.Windows.Forms.GridColumnsStyleCollection.
+ ///
+ public void RemoveAt(int index)
+ {
+ if (isDefault)
+ {
+ throw new ArgumentException(DSR.DataGridDefaultColumnCollectionChanged);
+ }
+
+ DataGridColumnStyle toRemove = (DataGridColumnStyle)items[index];
+ toRemove.SetDataGridTableInColumn(null, true);
+ toRemove.MappingNameChanged -= new EventHandler(ColumnStyleMappingNameChanged);
+ toRemove.PropertyDescriptorChanged -= new EventHandler(ColumnStylePropDescChanged);
+#if false
+ toRemove.RemoveOnPropertyChange(owner.OnColumnChanged);
+#endif
+ items.RemoveAt(index);
+ OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Remove, toRemove));
+ }
+
+ public void ResetPropertyDescriptors()
+ {
+ for (int i = 0; i < Count; i++)
+ {
+ this[i].PropertyDescriptor = null;
+ }
+ }
}
-
- public void Clear() { }
-
- public bool Contains(PropertyDescriptor propertyDescriptor) => throw null;
-
- public bool Contains(DataGridColumnStyle column) => throw null;
-
- public bool Contains(string name) => throw null;
-
- public int IndexOf(DataGridColumnStyle element) => throw null;
-
- protected void OnCollectionChanged(CollectionChangeEventArgs e) { }
-
- public void Remove(DataGridColumnStyle column) { }
-
- public void RemoveAt(int index) { }
-
- public void ResetPropertyDescriptors() { }
-}
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/GridTableStylesCollection.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/GridTableStylesCollection.cs
index 7b1f7bf9c92..33dade8a20d 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/GridTableStylesCollection.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/GridTableStylesCollection.cs
@@ -1,85 +1,342 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#pragma warning disable IDE0040, IDE1006, SA1005, SA1206, SA1400, CA1510, CSIsNull001, CSIsNull002, IDE0031
+
+#nullable disable
+
using System.Collections;
using System.ComponentModel;
+using System.Globalization;
+using DSR = System.Windows.Forms.DataGridStrings;
namespace System.Windows.Forms;
+ ///
+ /// Represents a collection of objects in the
+ /// control.
+ ///
+ [ListBindable(false)]
+ public class GridTableStylesCollection : BaseCollection, IList
+ {
+ CollectionChangeEventHandler onCollectionChanged;
+ readonly ArrayList items = new ArrayList();
+ readonly DataGrid owner;
-#nullable disable
+ int IList.Add(object value)
+ {
+ return Add((DataGridTableStyle)value);
+ }
-///
-/// This type is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code.
-///
-[Obsolete(
- Obsoletions.DataGridMessage,
- error: false,
- DiagnosticId = Obsoletions.UnsupportedControlsDiagnosticId,
- UrlFormat = Obsoletions.SharedUrlFormat)]
-[EditorBrowsable(EditorBrowsableState.Never)]
-[Browsable(false)]
-[ListBindable(false)]
-public class GridTableStylesCollection : BaseCollection, IList
-{
- private GridTableStylesCollection() { }
+ void IList.Clear()
+ {
+ Clear();
+ }
- int IList.Add(object value) => throw null;
+ bool IList.Contains(object value)
+ {
+ return items.Contains(value);
+ }
- void IList.Clear() { }
+ int IList.IndexOf(object value)
+ {
+ return items.IndexOf(value);
+ }
- bool IList.Contains(object value) => throw null;
+ void IList.Insert(int index, object value)
+ {
+ throw new NotSupportedException();
+ }
- int IList.IndexOf(object value) => throw null;
+ void IList.Remove(object value)
+ {
+ Remove((DataGridTableStyle)value);
+ }
- void IList.Insert(int index, object value) { }
+ void IList.RemoveAt(int index)
+ {
+ RemoveAt(index);
+ }
- void IList.Remove(object value) { }
+ bool IList.IsFixedSize
+ {
+ get { return false; }
+ }
- void IList.RemoveAt(int index) { }
+ bool IList.IsReadOnly
+ {
+ get { return false; }
+ }
- bool IList.IsFixedSize => throw null;
+ object IList.this[int index]
+ {
+ get { return items[index]; }
+ set { throw new NotSupportedException(); }
+ }
- bool IList.IsReadOnly => throw null;
+ void ICollection.CopyTo(Array array, int index)
+ {
+ items.CopyTo(array, index);
+ }
- object IList.this[int index]
- {
- get => throw null;
- set { }
- }
+ int ICollection.Count
+ {
+ get { return items.Count; }
+ }
- void ICollection.CopyTo(Array array, int index) { }
+ bool ICollection.IsSynchronized
+ {
+ get { return false; }
+ }
- int ICollection.Count => throw null;
+ object ICollection.SyncRoot
+ {
+ get { return this; }
+ }
- bool ICollection.IsSynchronized => throw null;
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return items.GetEnumerator();
+ }
- object ICollection.SyncRoot => throw null;
+ internal GridTableStylesCollection(DataGrid grid)
+ {
+ owner = grid;
+ }
- IEnumerator IEnumerable.GetEnumerator() => throw null;
+ protected override ArrayList List
+ {
+ get
+ {
+ return items;
+ }
+ }
- public DataGridTableStyle this[int index] => throw null;
+ /* implemented in BaseCollection
+ ///
+ /// Retrieves the number of GridTables in the collection.
+ ///
+ ///
+ /// The number of GridTables in the collection.
+ ///
+ public override int Count {
+ get {
+ return items.Count;
+ }
+ }
+ */
- public DataGridTableStyle this[string tableName] => throw null;
+ ///
+ /// Retrieves the DataGridTable with the specified index.
+ ///
+ public DataGridTableStyle this[int index]
+ {
+ get
+ {
+ return (DataGridTableStyle)items[index];
+ }
+ }
- public virtual int Add(DataGridTableStyle table) => throw null;
+ ///
+ /// Retrieves the DataGridTable with the name provided.
+ ///
+ public DataGridTableStyle this[string tableName]
+ {
+ get
+ {
+ if (tableName == null)
+ {
+ throw new ArgumentNullException(nameof(tableName));
+ }
- public virtual void AddRange(DataGridTableStyle[] tables) { }
+ int itemCount = items.Count;
+ for (int i = 0; i < itemCount; ++i)
+ {
+ DataGridTableStyle table = (DataGridTableStyle)items[i];
+ // NOTE: case-insensitive
+ if (string.Equals(table.MappingName, tableName, StringComparison.OrdinalIgnoreCase))
+ {
+ return table;
+ }
+ }
- public event CollectionChangeEventHandler CollectionChanged
- {
- add { }
- remove { }
- }
+ return null;
+ }
+ }
+
+ internal void CheckForMappingNameDuplicates(DataGridTableStyle table)
+ {
+ if (string.IsNullOrEmpty(table.MappingName))
+ {
+ return;
+ }
+
+ for (int i = 0; i < items.Count; i++)
+ {
+ if (((DataGridTableStyle)items[i]).MappingName.Equals(table.MappingName) && table != items[i])
+ {
+ throw new ArgumentException(DSR.DataGridTableStyleDuplicateMappingName, nameof(table));
+ }
+ }
+ }
+
+ ///
+ /// Adds a to this collection.
+ ///
+ public virtual int Add(DataGridTableStyle table)
+ {
+ // set the rowHeaderWidth on the newly added table to at least the minimum value
+ // on its owner
+ if (owner != null && owner.MinimumRowHeaderWidth() > table.RowHeaderWidth)
+ {
+ table.RowHeaderWidth = owner.MinimumRowHeaderWidth();
+ }
+
+ if (table.DataGrid != owner && table.DataGrid != null)
+ {
+ throw new ArgumentException(DSR.DataGridTableStyleCollectionAddedParentedTableStyle, nameof(table));
+ }
+
+ table.DataGrid = owner;
+ CheckForMappingNameDuplicates(table);
+ table.MappingNameChanged += new EventHandler(TableStyleMappingNameChanged);
+ int index = items.Add(table);
+ OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Add, table));
- public void Clear() { }
+ return index;
+ }
- public bool Contains(DataGridTableStyle table) => throw null;
+ private void TableStyleMappingNameChanged(object sender, EventArgs pcea)
+ {
+ OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Refresh, null));
+ }
- public bool Contains(string name) => throw null;
+ public virtual void AddRange(DataGridTableStyle[] tables)
+ {
+ if (tables == null)
+ {
+ throw new ArgumentNullException(nameof(tables));
+ }
- protected void OnCollectionChanged(CollectionChangeEventArgs e) { }
+ foreach (DataGridTableStyle table in tables)
+ {
+ table.DataGrid = owner;
+ table.MappingNameChanged += new EventHandler(TableStyleMappingNameChanged);
+ items.Add(table);
+ }
- public void Remove(DataGridTableStyle table) { }
+ OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Refresh, null));
+ }
- public void RemoveAt(int index) { }
-}
+ public event CollectionChangeEventHandler CollectionChanged
+ {
+ add => onCollectionChanged += value;
+ remove => onCollectionChanged -= value;
+ }
+
+ public void Clear()
+ {
+ for (int i = 0; i < items.Count; i++)
+ {
+ DataGridTableStyle element = (DataGridTableStyle)items[i];
+ element.MappingNameChanged -= new EventHandler(TableStyleMappingNameChanged);
+ }
+
+ items.Clear();
+ OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Refresh, null));
+ }
+
+ ///
+ /// Checks to see if a DataGridTableStyle is contained in this collection.
+ ///
+ public bool Contains(DataGridTableStyle table)
+ {
+ int index = items.IndexOf(table);
+ return index != -1;
+ }
+
+ ///
+ /// Checks to see if a with the given name
+ /// is contained in this collection.
+ ///
+ public bool Contains(string name)
+ {
+ int itemCount = items.Count;
+ for (int i = 0; i < itemCount; ++i)
+ {
+ DataGridTableStyle table = (DataGridTableStyle)items[i];
+ // NOTE: case-insensitive
+ if (string.Compare(table.MappingName, name, true, CultureInfo.InvariantCulture) == 0)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /*
+ public override IEnumerator GetEnumerator() {
+ return items.GetEnumerator();
+ }
+
+ public override IEnumerator GetEnumerator(bool allowRemove) {
+ if (!allowRemove)
+ return GetEnumerator();
+ else
+ throw new NotSupportedException(DSR.DataGridTableCollectionGetEnumerator);
+ }
+ */
+
+ protected void OnCollectionChanged(CollectionChangeEventArgs e)
+ {
+ onCollectionChanged?.Invoke(this, e);
+
+ DataGrid grid = owner;
+ if (grid != null)
+ {
+ /* FOR DEMO: Microsoft: TableStylesCollection::OnCollectionChanged: set the datagridtble
+ DataView dataView = ((DataView) grid.DataSource);
+ if (dataView != null) {
+ DataTable dataTable = dataView.Table;
+ if (dataTable != null) {
+ if (Contains(dataTable)) {
+ grid.SetDataGridTable(this[dataTable]);
+ }
+ }
+ }
+ */
+ grid.checkHierarchy = true;
+ }
+ }
+
+ public void Remove(DataGridTableStyle table)
+ {
+ int tableIndex = -1;
+ int itemsCount = items.Count;
+ for (int i = 0; i < itemsCount; ++i)
+ {
+ if (items[i] == table)
+ {
+ tableIndex = i;
+ break;
+ }
+ }
+
+ if (tableIndex == -1)
+ {
+ throw new ArgumentException(DSR.DataGridTableCollectionMissingTable, nameof(table));
+ }
+ else
+ {
+ RemoveAt(tableIndex);
+ }
+ }
+
+ public void RemoveAt(int index)
+ {
+ DataGridTableStyle element = (DataGridTableStyle)items[index];
+ element.MappingNameChanged -= new EventHandler(TableStyleMappingNameChanged);
+ items.RemoveAt(index);
+ OnCollectionChanged(new CollectionChangeEventArgs(CollectionChangeAction.Remove, element));
+ }
+ }
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/GridTablesFactory.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/GridTablesFactory.cs
index d1001b33f63..5dc869734a9 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/GridTablesFactory.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/GridTablesFactory.cs
@@ -17,13 +17,14 @@ namespace System.Windows.Forms;
UrlFormat = Obsoletions.SharedUrlFormat)]
[EditorBrowsable(EditorBrowsableState.Never)]
[Browsable(false)]
-public sealed class GridTablesFactory
+public static class GridTablesFactory
{
- private GridTablesFactory() { }
-
public static DataGridTableStyle[] CreateGridTables(
DataGridTableStyle gridTable,
object dataSource,
string dataMember,
- BindingContext bindingManager) => throw new PlatformNotSupportedException();
+ BindingContext bindingManager)
+ {
+ return [gridTable];
+ }
}
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/LegacyDataGridInteropCompat.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/LegacyDataGridInteropCompat.cs
new file mode 100644
index 00000000000..46867211a50
--- /dev/null
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/LegacyDataGridInteropCompat.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Runtime.InteropServices;
+
+namespace System.Windows.Forms;
+
+internal static class LegacyDataGridInteropCompat
+{
+ internal static partial class User32
+ {
+ [DllImport(Libraries.User32, ExactSpelling = true)]
+ private static extern BOOL ScrollWindow(HWND hWnd, int nXAmount, int nYAmount, ref RECT rectScrollRegion, ref RECT rectClip);
+
+ public static BOOL ScrollWindow(IHandle hWnd, int nXAmount, int nYAmount, ref RECT rectScrollRegion, ref RECT rectClip)
+ {
+ BOOL result = ScrollWindow(hWnd.Handle, nXAmount, nYAmount, ref rectScrollRegion, ref rectClip);
+ GC.KeepAlive(hWnd);
+
+ return result;
+ }
+ }
+}
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/Triangle.TriangleDirection.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/Triangle.TriangleDirection.cs
new file mode 100644
index 00000000000..94b4431b73f
--- /dev/null
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/Triangle.TriangleDirection.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma warning disable SA1518
+
+namespace System.Windows.Forms;
+
+internal static partial class Triangle
+{
+ internal enum TriangleDirection
+ {
+ Up,
+ Down,
+ Left,
+ Right
+ }
+}
\ No newline at end of file
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/Triangle.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/Triangle.cs
new file mode 100644
index 00000000000..e7c5ec34984
--- /dev/null
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/DataGrid/Triangle.cs
@@ -0,0 +1,113 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma warning disable SA1518
+
+using System.Drawing;
+
+namespace System.Windows.Forms;
+
+internal static partial class Triangle
+{
+ private const double TriHeightRatio = 2.5;
+ private const double TriWidthRatio = 0.8;
+
+ public static void Paint(
+ Graphics g,
+ Rectangle bounds,
+ TriangleDirection dir,
+ Brush backBr,
+ Pen backPen1,
+ Pen backPen2,
+ Pen backPen3,
+ bool opaque)
+ {
+ Point[] points = BuildTrianglePoints(dir, bounds);
+
+ if (opaque)
+ {
+ g.FillPolygon(backBr, points);
+ }
+
+ g.DrawLine(backPen1, points[0], points[1]);
+ g.DrawLine(backPen2, points[1], points[2]);
+ g.DrawLine(backPen3, points[2], points[0]);
+ }
+
+ private static Point[] BuildTrianglePoints(TriangleDirection dir, Rectangle bounds)
+ {
+ Point[] points = new Point[3];
+
+ int upDownWidth = (int)(bounds.Width * TriWidthRatio);
+ if (upDownWidth % 2 == 1)
+ {
+ upDownWidth++;
+ }
+
+ int upDownHeight = (int)Math.Ceiling((upDownWidth / 2D) * TriHeightRatio);
+
+ int leftRightWidth = (int)(bounds.Height * TriWidthRatio);
+ if (leftRightWidth % 2 == 0)
+ {
+ leftRightWidth++;
+ }
+
+ int leftRightHeight = (int)Math.Ceiling((leftRightWidth / 2D) * TriHeightRatio);
+
+ switch (dir)
+ {
+ case TriangleDirection.Up:
+ points[0] = new Point(0, upDownHeight);
+ points[1] = new Point(upDownWidth, upDownHeight);
+ points[2] = new Point(upDownWidth / 2, 0);
+ break;
+ case TriangleDirection.Down:
+ points[0] = new Point(0, 0);
+ points[1] = new Point(upDownWidth, 0);
+ points[2] = new Point(upDownWidth / 2, upDownHeight);
+ break;
+ case TriangleDirection.Left:
+ points[0] = new Point(leftRightWidth, 0);
+ points[1] = new Point(leftRightWidth, leftRightHeight);
+ points[2] = new Point(0, leftRightHeight / 2);
+ break;
+ case TriangleDirection.Right:
+ points[0] = new Point(0, 0);
+ points[1] = new Point(0, leftRightHeight);
+ points[2] = new Point(leftRightWidth, leftRightHeight / 2);
+ break;
+ default:
+ Debug.Fail("Unexpected triangle direction.");
+ break;
+ }
+
+ switch (dir)
+ {
+ case TriangleDirection.Up:
+ case TriangleDirection.Down:
+ OffsetPoints(
+ points,
+ bounds.X + (bounds.Width - upDownHeight) / 2,
+ bounds.Y + (bounds.Height - upDownWidth) / 2);
+ break;
+ case TriangleDirection.Left:
+ case TriangleDirection.Right:
+ OffsetPoints(
+ points,
+ bounds.X + (bounds.Width - leftRightWidth) / 2,
+ bounds.Y + (bounds.Height - leftRightHeight) / 2);
+ break;
+ }
+
+ return points;
+ }
+
+ private static void OffsetPoints(Point[] points, int xOffset, int yOffset)
+ {
+ for (int i = 0; i < points.Length; i++)
+ {
+ points[i].X += xOffset;
+ points[i].Y += yOffset;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/MainMenu/MainMenu.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/MainMenu/MainMenu.cs
index 5098969a376..20b6985054b 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/MainMenu/MainMenu.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/MainMenu/MainMenu.cs
@@ -1,48 +1,196 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#pragma warning disable IDE1006, CS8625, CS8618, CS8601, RS0036, RS0016
+
using System.ComponentModel;
+using UnsafeNativeMethods = System.Windows.Forms.LegacyMenuUnsafeNativeMethods;
-namespace System.Windows.Forms;
-
-#nullable disable
-
-///
-/// This type is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code.
-///
-[Obsolete(
- Obsoletions.MainMenuMessage,
- error: false,
- DiagnosticId = Obsoletions.UnsupportedControlsDiagnosticId,
- UrlFormat = Obsoletions.SharedUrlFormat)]
-[EditorBrowsable(EditorBrowsableState.Never)]
-[Browsable(false)]
-[ToolboxItemFilter("System.Windows.Forms.MainMenu")]
-public class MainMenu : Menu
+namespace System.Windows.Forms
{
- public MainMenu() : base(items: default) => throw new PlatformNotSupportedException();
+ ///
+ /// Represents a menu structure for a form.
+ ///
+ [ToolboxItemFilter("System.Windows.Forms.MainMenu")]
+ public class MainMenu : Menu
+ {
+ internal Form form;
+ internal Form ownerForm; // this is the form that created this menu, and is the only form allowed to dispose it.
+ private RightToLeft rightToLeft = RightToLeft.Inherit;
+ private EventHandler onCollapse;
- public MainMenu(IContainer container) : this() => throw new PlatformNotSupportedException();
+ ///
+ /// Creates a new MainMenu control.
+ ///
+ public MainMenu()
+ : base(null)
+ {
+ }
- public MainMenu(MenuItem[] items) : base(items: items) => throw new PlatformNotSupportedException();
+ ///
+ /// Initializes a new instance of the class with the specified container.
+ ///
+ public MainMenu(IContainer container) : this()
+ {
+ ArgumentNullException.ThrowIfNull(container);
- public event EventHandler Collapse
- {
- add { }
- remove { }
- }
+ container.Add(this);
+ }
- [Localizable(true)]
- [AmbientValue(RightToLeft.Inherit)]
- public virtual RightToLeft RightToLeft
- {
- get => throw null;
- set { }
- }
+ ///
+ /// Creates a new MainMenu control with the given items to start
+ /// with.
+ ///
+ public MainMenu(MenuItem[] items)
+ : base(items)
+ {
+ }
+
+ public event EventHandler Collapse
+ {
+ add => onCollapse += value;
+ remove => onCollapse -= value;
+ }
+
+ ///
+ /// This is used for international applications where the language
+ /// is written from RightToLeft. When this property is true,
+ /// text alignment and reading order will be from right to left.
+ ///
+ // Add an AmbientValue attribute so that the Reset context menu becomes available in the Property Grid.
+ [
+ Localizable(true),
+ AmbientValue(RightToLeft.Inherit)
+ ]
+ public virtual RightToLeft RightToLeft
+ {
+ get
+ {
+ if (rightToLeft == RightToLeft.Inherit)
+ {
+ if (form is not null)
+ {
+ return form.RightToLeft;
+ }
+ else
+ {
+ return RightToLeft.Inherit;
+ }
+ }
+ else
+ {
+ return rightToLeft;
+ }
+ }
+ set
+ {
+ // valid values are 0x0 to 0x2
+ if (value is < RightToLeft.No or > RightToLeft.Inherit)
+ {
+ throw new InvalidEnumArgumentException(nameof(RightToLeft), (int)value, typeof(RightToLeft));
+ }
+
+ if (rightToLeft != value)
+ {
+ rightToLeft = value;
+ UpdateRtl((value == RightToLeft.Yes));
+ }
+ }
+ }
+
+ internal override bool RenderIsRightToLeft
+ {
+ get
+ {
+ return (RightToLeft == RightToLeft.Yes && (form is null || !form.IsMirrored));
+ }
+ }
- public virtual MainMenu CloneMenu() => throw null;
+ ///
+ /// Creates a new MainMenu object which is a dupliate of this one.
+ ///
+ public virtual MainMenu CloneMenu()
+ {
+ MainMenu newMenu = new MainMenu();
+ newMenu.CloneMenu(this);
+ return newMenu;
+ }
- public Form GetForm() => throw null;
+ protected override IntPtr CreateMenuHandle()
+ {
+ return UnsafeNativeMethods.CreateMenu();
+ }
- protected internal virtual void OnCollapse(EventArgs e) { }
+ ///
+ /// Clears out this MainMenu object and discards all of it's resources.
+ /// If the menu is parented in a form, it is disconnected from that as
+ /// well.
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (form is not null && (ownerForm is null || form == ownerForm))
+ {
+ form.Menu = null;
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ ///
+ /// Indicates which form in which we are currently residing [if any]
+ ///
+ public Form GetForm()
+ {
+ return form;
+ }
+
+ internal Form GetFormUnsafe()
+ {
+ return form;
+ }
+
+ internal override void ItemsChanged(int change)
+ {
+ base.ItemsChanged(change);
+ form?.MenuChanged(change, this);
+ }
+
+ internal virtual void ItemsChanged(int change, Menu menu)
+ {
+ form?.MenuChanged(change, menu);
+ }
+
+ ///
+ /// Fires the collapse event
+ ///
+ protected internal virtual void OnCollapse(EventArgs e)
+ {
+ onCollapse?.Invoke(this, e);
+ }
+
+ ///
+ /// Returns true if the RightToLeft should be persisted in code gen.
+ ///
+ internal virtual bool ShouldSerializeRightToLeft()
+ {
+ if (RightToLeft == RightToLeft.Inherit)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Returns a string representation for this control.
+ ///
+ public override string ToString()
+ {
+ // Removing GetForm information
+ return base.ToString();
+ }
+ }
}
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/StatusBar/StatusBar.StatusBarPanelCollection.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/StatusBar/StatusBar.StatusBarPanelCollection.cs
deleted file mode 100644
index 6238b478caf..00000000000
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/StatusBar/StatusBar.StatusBarPanelCollection.cs
+++ /dev/null
@@ -1,92 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Collections;
-using System.ComponentModel;
-
-namespace System.Windows.Forms;
-
-#nullable disable
-
-public partial class StatusBar
-{
- ///
- /// This type is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code.
- ///
- [Obsolete(
- Obsoletions.StatusBarMessage,
- error: false,
- DiagnosticId = Obsoletions.UnsupportedControlsDiagnosticId,
- UrlFormat = Obsoletions.SharedUrlFormat)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- [Browsable(false)]
- [ListBindable(false)]
- public class StatusBarPanelCollection : IList
- {
- public StatusBarPanelCollection(StatusBar owner) => throw new PlatformNotSupportedException();
-
- public virtual StatusBarPanel this[int index]
- {
- get => throw null;
- set { }
- }
-
- object IList.this[int index]
- {
- get => throw null;
- set { }
- }
-
- public virtual StatusBarPanel this[string key] => throw null;
-
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public int Count => throw null;
-
- object ICollection.SyncRoot => throw null;
-
- bool ICollection.IsSynchronized => throw null;
-
- bool IList.IsFixedSize => throw null;
-
- public bool IsReadOnly => throw null;
-
- public virtual StatusBarPanel Add(string text) => throw null;
-
- public virtual int Add(StatusBarPanel value) => throw null;
-
- int IList.Add(object value) => throw null;
-
- public virtual void AddRange(StatusBarPanel[] panels) { }
-
- public bool Contains(StatusBarPanel panel) => throw null;
-
- bool IList.Contains(object panel) => throw null;
-
- public virtual bool ContainsKey(string key) => throw null;
-
- public int IndexOf(StatusBarPanel panel) => throw null;
-
- int IList.IndexOf(object panel) => throw null;
-
- public virtual int IndexOfKey(string key) => throw null;
-
- public virtual void Insert(int index, StatusBarPanel value) { }
-
- void IList.Insert(int index, object value) { }
-
- public virtual void Clear() { }
-
- public virtual void Remove(StatusBarPanel value) { }
-
- void IList.Remove(object value) { }
-
- public virtual void RemoveAt(int index) { }
-
- public virtual void RemoveByKey(string key) { }
-
- void ICollection.CopyTo(Array dest, int index) { }
-
- public IEnumerator GetEnumerator() => throw null;
- }
-}
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/StatusBar/StatusBar.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/StatusBar/StatusBar.cs
index dac60ecd9bb..656a38a79cc 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/StatusBar/StatusBar.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/StatusBar/StatusBar.cs
@@ -1,23 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#pragma warning disable RS0016, CA1822
+
+using System.Collections;
using System.ComponentModel;
+using System.Drawing;
+using System.Globalization;
using System.Runtime.InteropServices;
+using System.Windows.Forms.VisualStyles;
namespace System.Windows.Forms;
#nullable disable
-///
-/// This type is provided for binary compatibility with .NET Framework and is not intended to be used directly from your code.
-///
-[Obsolete(
- Obsoletions.StatusBarMessage,
- error: false,
- DiagnosticId = Obsoletions.UnsupportedControlsDiagnosticId,
- UrlFormat = Obsoletions.SharedUrlFormat)]
-[EditorBrowsable(EditorBrowsableState.Never)]
-[Browsable(false)]
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDispatch)]
[DefaultEvent(nameof(PanelClick))]
@@ -25,157 +21,1339 @@ namespace System.Windows.Forms;
[Designer($"System.Windows.Forms.Design.StatusBarDesigner, {Assemblies.SystemDesign}")]
public partial class StatusBar : Control
{
- // Adding this constructor to suppress creation of a default one.
- public StatusBar() => throw new PlatformNotSupportedException();
+ private const int SimpleIndex = 0xFF;
+ private const string BadStatusBarPanelMessage = "Value must be a StatusBarPanel.";
+
+ private static readonly object s_panelClickEvent = new();
+ private static readonly object s_drawItemEvent = new();
+
+ private static VisualStyleRenderer s_renderer;
+
+ private readonly List _panels = [];
+ private Point _lastClick;
+ private StatusBarPanelCollection _panelsCollection;
+ private ControlToolTip _tooltips;
+ private int _panelsRealized;
+ private int _sizeGripWidth;
+ private bool _layoutDirty;
+ private bool _showPanels;
+ private bool _sizeGrip = true;
+ private string _simpleText;
+
+ public StatusBar()
+ {
+ SetStyle(ControlStyles.UserPaint | ControlStyles.Selectable, false);
+
+ Dock = DockStyle.Bottom;
+ TabStop = false;
+ }
+
+ private static VisualStyleRenderer VisualStyleRenderer
+ {
+ get
+ {
+ if (!VisualStyleRenderer.IsSupported)
+ {
+ s_renderer = null;
+
+ return null;
+ }
+
+ s_renderer ??= new VisualStyleRenderer(VisualStyleElement.ToolBar.Button.Normal);
+
+ return s_renderer;
+ }
+ }
+
+ private int SizeGripWidth
+ {
+ get
+ {
+ if (_sizeGripWidth != 0)
+ {
+ return _sizeGripWidth;
+ }
+
+ if (Application.RenderWithVisualStyles && VisualStyleRenderer is not null)
+ {
+ using Graphics graphics = Graphics.FromHwndInternal(Handle);
+ VisualStyleRenderer renderer = VisualStyleRenderer;
+
+ renderer.SetParameters(VisualStyleElement.Status.GripperPane.Normal);
+ Size paneSize = renderer.GetPartSize(graphics, ThemeSizeType.True);
+
+ renderer.SetParameters(VisualStyleElement.Status.Gripper.Normal);
+ Size gripSize = renderer.GetPartSize(graphics, ThemeSizeType.True);
+
+ _sizeGripWidth = Math.Max(paneSize.Width + gripSize.Width, 16);
+ }
+ else
+ {
+ _sizeGripWidth = 16;
+ }
+
+ return _sizeGripWidth;
+ }
+ }
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override Color BackColor
{
- get => throw null;
+ get => SystemColors.Control;
set { }
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
- public override Drawing.Image BackgroundImage
+ public new event EventHandler BackColorChanged
{
- get => throw null;
- set { }
+ add => base.BackColorChanged += value;
+ remove => base.BackColorChanged -= value;
+ }
+
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override Image BackgroundImage
+ {
+ get => base.BackgroundImage;
+ set => base.BackgroundImage = value;
+ }
+
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public new event EventHandler BackgroundImageChanged
+ {
+ add => base.BackgroundImageChanged += value;
+ remove => base.BackgroundImageChanged -= value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override ImageLayout BackgroundImageLayout
{
- get => throw null;
- set { }
+ get => base.BackgroundImageLayout;
+ set => base.BackgroundImageLayout = value;
}
- [Localizable(true)]
- [DefaultValue(DockStyle.Bottom)]
- public override DockStyle Dock
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public new event EventHandler BackgroundImageLayoutChanged
{
- get => throw null;
- set { }
+ add => base.BackgroundImageLayoutChanged += value;
+ remove => base.BackgroundImageLayoutChanged -= value;
+ }
+
+ protected override CreateParams CreateParams
+ {
+ get
+ {
+ CreateParams cp = base.CreateParams;
+ cp.ClassName = LegacyStatusBarInterop.StatusClassName;
+ cp.Style |= LegacyStatusBarInterop.CcsNoParentAlign | LegacyStatusBarInterop.CcsNoResize;
+
+ if (_sizeGrip)
+ {
+ cp.Style |= LegacyStatusBarInterop.SbarsSizeGrip;
+ }
+ else
+ {
+ cp.Style &= ~LegacyStatusBarInterop.SbarsSizeGrip;
+ }
+
+ return cp;
+ }
}
+ protected override ImeMode DefaultImeMode => ImeMode.Disable;
+
+ protected override Size DefaultSize => new(100, 22);
+
[EditorBrowsable(EditorBrowsableState.Never)]
protected override bool DoubleBuffered
{
- get => throw null;
- set { }
+ get => base.DoubleBuffered;
+ set => base.DoubleBuffered = value;
}
[Localizable(true)]
- public override Drawing.Font Font
+ [DefaultValue(DockStyle.Bottom)]
+ public override DockStyle Dock
{
- get => throw null;
- set { }
+ get => base.Dock;
+ set => base.Dock = value;
+ }
+
+ [Localizable(true)]
+ public override Font Font
+ {
+ get => base.Font;
+ set
+ {
+ base.Font = value;
+ SetPanelContentsWidths(newPanels: false);
+ }
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override Color ForeColor
{
- get => throw null;
- set { }
+ get => base.ForeColor;
+ set => base.ForeColor = value;
+ }
+
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public new event EventHandler ForeColorChanged
+ {
+ add => base.ForeColorChanged += value;
+ remove => base.ForeColorChanged -= value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new ImeMode ImeMode
{
- get => throw null;
- set { }
+ get => base.ImeMode;
+ set => base.ImeMode = value;
+ }
+
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public new event EventHandler ImeModeChanged
+ {
+ add => base.ImeModeChanged += value;
+ remove => base.ImeModeChanged -= value;
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Localizable(true)]
+ [SRCategory(nameof(SR.CatAppearance))]
[MergableProperty(false)]
- public StatusBarPanelCollection Panels => throw null;
+ public StatusBarPanelCollection Panels => _panelsCollection ??= new StatusBarPanelCollection(this);
+
+ [Localizable(true)]
+ public override string Text
+ {
+ get => _simpleText ?? string.Empty;
+ set
+ {
+ value ??= string.Empty;
+
+ if (string.Equals(Text, value, StringComparison.Ordinal))
+ {
+ return;
+ }
+
+ _simpleText = value.Length == 0 ? null : value;
+ SetSimpleText(_simpleText);
+
+ OnTextChanged(EventArgs.Empty);
+ }
+ }
+ [SRCategory(nameof(SR.CatBehavior))]
[DefaultValue(false)]
public bool ShowPanels
{
- get => throw null;
- set { }
+ get => _showPanels;
+ set
+ {
+ if (_showPanels == value)
+ {
+ return;
+ }
+
+ _showPanels = value;
+ _layoutDirty = true;
+
+ if (!IsHandleCreated)
+ {
+ return;
+ }
+
+ LegacyStatusBarInterop.SendMessageIntPtr(
+ Handle,
+ LegacyStatusBarInterop.SbSimple,
+ (IntPtr)(value ? 0 : 1),
+ IntPtr.Zero);
+
+ if (value)
+ {
+ PerformLayout();
+ RealizePanels();
+ }
+ else if (_tooltips is not null)
+ {
+ foreach (StatusBarPanel panel in _panels)
+ {
+ _tooltips.SetTool(panel, null);
+ }
+ }
+
+ SetSimpleText(_simpleText);
+ }
}
+ [SRCategory(nameof(SR.CatAppearance))]
[DefaultValue(true)]
public bool SizingGrip
{
- get => throw null;
- set { }
+ get => _sizeGrip;
+ set
+ {
+ if (_sizeGrip == value)
+ {
+ return;
+ }
+
+ _sizeGrip = value;
+ _sizeGripWidth = 0;
+ RecreateHandle();
+ }
}
[DefaultValue(false)]
- public new bool TabStop { get => throw null; set { } }
+ public new bool TabStop
+ {
+ get => base.TabStop;
+ set => base.TabStop = value;
+ }
- [Localizable(true)]
- public override string Text { get => throw null; set { } }
+ [SRCategory(nameof(SR.CatBehavior))]
+ public event StatusBarDrawItemEventHandler DrawItem
+ {
+ add => Events.AddHandler(s_drawItemEvent, value);
+ remove => Events.RemoveHandler(s_drawItemEvent, value);
+ }
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public new event EventHandler BackColorChanged
+ [SRCategory(nameof(SR.CatMouse))]
+ public event StatusBarPanelClickEventHandler PanelClick
{
- add { }
- remove { }
+ add => Events.AddHandler(s_panelClickEvent, value);
+ remove => Events.RemoveHandler(s_panelClickEvent, value);
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
- public new event EventHandler BackgroundImageChanged
+ public new event PaintEventHandler Paint
{
- add { }
- remove { }
+ add => base.Paint += value;
+ remove => base.Paint -= value;
}
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public new event EventHandler BackgroundImageLayoutChanged
+ internal bool ArePanelsRealized() => _showPanels && IsHandleCreated;
+
+ internal void DirtyLayout() => _layoutDirty = true;
+
+ internal void ForcePanelUpdate()
{
- add { }
- remove { }
+ if (!ArePanelsRealized())
+ {
+ return;
+ }
+
+ _layoutDirty = true;
+ SetPanelContentsWidths(newPanels: true);
+ PerformLayout();
+ RealizePanels();
}
- public event StatusBarDrawItemEventHandler DrawItem
+ internal void RealizePanels()
{
- add { }
- remove { }
+ int oldCount = _panelsRealized;
+ _panelsRealized = 0;
+
+ if (_panels.Count == 0)
+ {
+ LegacyStatusBarInterop.SendMessageString(Handle, LegacyStatusBarInterop.SbSetText, IntPtr.Zero, string.Empty);
+ }
+
+ int index = 0;
+ for (; index < _panels.Count; index++)
+ {
+ try
+ {
+ _panels[index].Realize();
+ _panelsRealized++;
+ }
+ catch
+ {
+ }
+ }
+
+ for (; index < oldCount; index++)
+ {
+ LegacyStatusBarInterop.SendMessageString(Handle, LegacyStatusBarInterop.SbSetText, IntPtr.Zero, null);
+ }
}
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public new event EventHandler ForeColorChanged
+ internal void RemoveAllPanelsWithoutUpdate()
{
- add { }
- remove { }
+ foreach (StatusBarPanel panel in _panels)
+ {
+ panel.ParentInternal = null;
+ }
+
+ _panels.Clear();
+
+ if (_showPanels)
+ {
+ ApplyPanelWidths();
+ ForcePanelUpdate();
+ }
}
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public new event EventHandler ImeModeChanged
+ internal void SetPanelContentsWidths(bool newPanels)
{
- add { }
- remove { }
+ bool changed = false;
+
+ foreach (StatusBarPanel panel in _panels)
+ {
+ if (panel.AutoSize != StatusBarPanelAutoSize.Contents)
+ {
+ continue;
+ }
+
+ int newWidth = panel.GetContentsWidth(newPanels);
+ if (panel.Width == newWidth)
+ {
+ continue;
+ }
+
+ panel.Width = newWidth;
+ changed = true;
+ }
+
+ if (!changed)
+ {
+ return;
+ }
+
+ DirtyLayout();
+ PerformLayout();
}
- [Browsable(false)]
- [EditorBrowsable(EditorBrowsableState.Never)]
- public new event PaintEventHandler Paint
+ internal void UpdateTooltip(StatusBarPanel panel)
{
- add { }
- remove { }
+ if (_tooltips is null)
+ {
+ if (IsHandleCreated && !DesignMode)
+ {
+ _tooltips = new ControlToolTip(this);
+ }
+ else
+ {
+ return;
+ }
+ }
+
+ if (panel.Parent == this && panel.ToolTipText.Length > 0)
+ {
+ int border = SystemInformation.Border3DSize.Width;
+ ControlToolTip.Tool tool = _tooltips.GetTool(panel) ?? new ControlToolTip.Tool();
+ tool.Text = panel.ToolTipText;
+ tool.Bounds = new Rectangle(panel.Right - panel.Width + border, 0, panel.Width - border, Height);
+ _tooltips.SetTool(panel, tool);
+
+ return;
+ }
+
+ _tooltips.SetTool(panel, null);
}
- public event StatusBarPanelClickEventHandler PanelClick
+ internal void UpdatePanelIndex()
+ {
+ for (int i = 0; i < _panels.Count; i++)
+ {
+ _panels[i].Index = i;
+ }
+ }
+
+ protected override unsafe void CreateHandle()
+ {
+ if (!RecreatingHandle)
+ {
+ using ThemingScope scope = new(Application.UseVisualStyles);
+ PInvoke.InitCommonControlsEx(new INITCOMMONCONTROLSEX
+ {
+ dwSize = (uint)sizeof(INITCOMMONCONTROLSEX),
+ dwICC = INITCOMMONCONTROLSEX_ICC.ICC_BAR_CLASSES
+ });
+ }
+
+ base.CreateHandle();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && _panelsCollection is not null)
+ {
+ StatusBarPanel[] panelCopy = [.. _panels];
+ _panelsCollection.Clear();
+
+ foreach (StatusBarPanel panel in panelCopy)
+ {
+ panel.Dispose();
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+
+ protected virtual void OnDrawItem(StatusBarDrawItemEventArgs sbdievent)
+ => (Events[s_drawItemEvent] as StatusBarDrawItemEventHandler)?.Invoke(this, sbdievent);
+
+ protected override void OnHandleCreated(EventArgs e)
+ {
+ base.OnHandleCreated(e);
+
+ if (!DesignMode)
+ {
+ _tooltips = new ControlToolTip(this);
+ }
+
+ if (!_showPanels)
+ {
+ LegacyStatusBarInterop.SendMessageIntPtr(
+ Handle,
+ LegacyStatusBarInterop.SbSimple,
+ (IntPtr)1,
+ IntPtr.Zero);
+ SetSimpleText(_simpleText);
+
+ return;
+ }
+
+ ForcePanelUpdate();
+ }
+
+ protected override void OnHandleDestroyed(EventArgs e)
+ {
+ base.OnHandleDestroyed(e);
+
+ _tooltips?.Dispose();
+ _tooltips = null;
+ }
+
+ protected override void OnLayout(LayoutEventArgs levent)
+ {
+ if (_showPanels)
+ {
+ LayoutPanels();
+
+ if (IsHandleCreated && _panelsRealized != _panels.Count)
+ {
+ RealizePanels();
+ }
+ }
+
+ base.OnLayout(levent);
+ }
+
+ protected override void OnMouseDown(MouseEventArgs e)
+ {
+ _lastClick = e.Location;
+
+ base.OnMouseDown(e);
+ }
+
+ protected virtual void OnPanelClick(StatusBarPanelClickEventArgs e)
+ => (Events[s_panelClickEvent] as StatusBarPanelClickEventHandler)?.Invoke(this, e);
+
+ protected override void OnResize(EventArgs e)
+ {
+ Invalidate();
+
+ base.OnResize(e);
+ }
+
+ public override string ToString()
+ {
+ string text = base.ToString();
+ text += ", Panels.Count: " + Panels.Count.ToString(CultureInfo.CurrentCulture);
+
+ if (Panels.Count > 0)
+ {
+ text += ", Panels[0]: " + Panels[0];
+ }
+
+ return text;
+ }
+
+ protected override unsafe void WndProc(ref Message m)
+ {
+ switch (m.MsgInternal)
+ {
+ case PInvokeCore.WM_NCHITTEST:
+ WmNCHitTest(ref m);
+ break;
+ case MessageId.WM_REFLECT_DRAWITEM:
+ WmDrawItem(ref m);
+ break;
+ case PInvokeCore.WM_NOTIFY:
+ case MessageId.WM_REFLECT_NOTIFY:
+ NMHDR* note = (NMHDR*)(nint)m.LParamInternal;
+ if (note->code is PInvoke.NM_CLICK or PInvoke.NM_RCLICK or PInvoke.NM_DBLCLK or PInvoke.NM_RDBLCLK)
+ {
+ WmNotifyNmClick(note->code);
+ }
+ else
+ {
+ base.WndProc(ref m);
+ }
+
+ break;
+ default:
+ base.WndProc(ref m);
+ break;
+ }
+ }
+
+ private void ApplyPanelWidths()
+ {
+ if (!IsHandleCreated)
+ {
+ return;
+ }
+
+ if (_panels.Count == 0)
+ {
+ int[] offsets = [Size.Width - (_sizeGrip ? SizeGripWidth : 0)];
+ LegacyStatusBarInterop.SendMessageParts(Handle, LegacyStatusBarInterop.SbSetParts, (IntPtr)1, offsets);
+ LegacyStatusBarInterop.SendMessageIntPtr(Handle, LegacyStatusBarInterop.SbSetIcon, IntPtr.Zero, IntPtr.Zero);
+
+ return;
+ }
+
+ int[] offsets2 = new int[_panels.Count];
+ int currentOffset = 0;
+ for (int i = 0; i < _panels.Count; i++)
+ {
+ StatusBarPanel panel = _panels[i];
+ currentOffset += panel.Width;
+ offsets2[i] = currentOffset;
+ panel.Right = currentOffset;
+ }
+
+ LegacyStatusBarInterop.SendMessageParts(Handle, LegacyStatusBarInterop.SbSetParts, (IntPtr)offsets2.Length, offsets2);
+
+ foreach (StatusBarPanel panel in _panels)
+ {
+ UpdateTooltip(panel);
+ }
+
+ _layoutDirty = false;
+ }
+
+ private void LayoutPanels()
+ {
+ int fixedWidth = 0;
+ List springPanels = [];
+ bool changed = false;
+
+ foreach (StatusBarPanel panel in _panels)
+ {
+ if (panel.AutoSize == StatusBarPanelAutoSize.Spring)
+ {
+ springPanels.Add(panel);
+ }
+ else
+ {
+ fixedWidth += panel.Width;
+ }
+ }
+
+ if (springPanels.Count > 0)
+ {
+ int springPanelsLeft = springPanels.Count;
+ int leftoverWidth = Bounds.Width - fixedWidth;
+ if (_sizeGrip)
+ {
+ leftoverWidth -= SizeGripWidth;
+ }
+
+ int lastLeftoverWidth = int.MinValue;
+
+ while (springPanelsLeft > 0)
+ {
+ int widthOfSpringPanel = leftoverWidth / springPanelsLeft;
+ if (leftoverWidth == lastLeftoverWidth)
+ {
+ break;
+ }
+
+ lastLeftoverWidth = leftoverWidth;
+
+ for (int i = 0; i < springPanels.Count; i++)
+ {
+ StatusBarPanel panel = springPanels[i];
+ if (panel is null)
+ {
+ continue;
+ }
+
+ if (widthOfSpringPanel < panel.MinWidth)
+ {
+ if (panel.Width != panel.MinWidth)
+ {
+ changed = true;
+ }
+
+ panel.Width = panel.MinWidth;
+ springPanels[i] = null;
+ springPanelsLeft--;
+ leftoverWidth -= panel.MinWidth;
+
+ continue;
+ }
+
+ if (panel.Width != widthOfSpringPanel)
+ {
+ changed = true;
+ }
+
+ panel.Width = widthOfSpringPanel;
+ }
+ }
+ }
+
+ if (changed || _layoutDirty)
+ {
+ ApplyPanelWidths();
+ }
+ }
+
+ private void SetSimpleText(string simpleText)
{
- add { }
- remove { }
+ if (_showPanels || !IsHandleCreated)
+ {
+ return;
+ }
+
+ int wParam = SimpleIndex | LegacyStatusBarInterop.SbtNoBorders;
+ if (RightToLeft == RightToLeft.Yes)
+ {
+ wParam |= LegacyStatusBarInterop.SbtRtlReading;
+ }
+
+ LegacyStatusBarInterop.SendMessageString(Handle, LegacyStatusBarInterop.SbSetText, (IntPtr)wParam, simpleText);
}
- protected virtual void OnPanelClick(StatusBarPanelClickEventArgs e) { }
+ private void WmDrawItem(ref Message m)
+ {
+ unsafe
+ {
+ DRAWITEMSTRUCT* dis = (DRAWITEMSTRUCT*)(nint)m.LParamInternal;
+ if (dis->itemID >= (uint)_panels.Count)
+ {
+ Debug.Fail("OwnerDraw item out of range");
+
+ return;
+ }
+
+ StatusBarPanel panel = _panels[(int)dis->itemID];
+
+ using Graphics graphics = Graphics.FromHdcInternal((IntPtr)dis->hDC);
+ Rectangle bounds = Rectangle.FromLTRB(dis->rcItem.left, dis->rcItem.top, dis->rcItem.right, dis->rcItem.bottom);
+ OnDrawItem(new StatusBarDrawItemEventArgs(graphics, Font, bounds, (int)dis->itemID, DrawItemState.None, panel, ForeColor, BackColor));
+ m.ResultInternal = (LRESULT)1;
+ }
+ }
+
+ private void WmNCHitTest(ref Message m)
+ {
+ int x = PARAM.SignedLOWORD(m.LParamInternal);
+ Rectangle bounds = Bounds;
+ bool callSuper = true;
+
+ if (x > bounds.X + bounds.Width - SizeGripWidth)
+ {
+ Control parent = ParentInternal;
+ if (parent is Form form)
+ {
+ FormBorderStyle borderStyle = form.FormBorderStyle;
+ if (borderStyle is not FormBorderStyle.Sizable and not FormBorderStyle.SizableToolWindow)
+ {
+ callSuper = false;
+ }
+
+ if (!form.TopLevel || Dock != DockStyle.Bottom)
+ {
+ callSuper = false;
+ }
+
+ if (callSuper)
+ {
+ foreach (Control child in parent.Controls)
+ {
+ if (child != this && child.Dock == DockStyle.Bottom && child.Top > Top)
+ {
+ callSuper = false;
+
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ callSuper = false;
+ }
+ }
+
+ if (callSuper)
+ {
+ base.WndProc(ref m);
+
+ return;
+ }
+
+ m.ResultInternal = (LRESULT)(nint)PInvoke.HTCLIENT;
+ }
+
+ private void WmNotifyNmClick(uint code)
+ {
+ if (!_showPanels)
+ {
+ return;
+ }
+
+ int currentOffset = 0;
+ int index = -1;
+ for (int i = 0; i < _panels.Count; i++)
+ {
+ StatusBarPanel panel = _panels[i];
+ currentOffset += panel.Width;
+ if (_lastClick.X < currentOffset)
+ {
+ index = i;
+
+ break;
+ }
+ }
+
+ if (index == -1)
+ {
+ return;
+ }
+
+ MouseButtons button = code switch
+ {
+ PInvoke.NM_RCLICK or PInvoke.NM_RDBLCLK => MouseButtons.Right,
+ _ => MouseButtons.Left
+ };
+ int clicks = code switch
+ {
+ PInvoke.NM_DBLCLK or PInvoke.NM_RDBLCLK => 2,
+ _ => 1
+ };
+
+ OnPanelClick(new StatusBarPanelClickEventArgs(_panels[index], button, clicks, _lastClick.X, _lastClick.Y));
+ }
+
+ [ListBindable(false)]
+ public class StatusBarPanelCollection : IList
+ {
+ private readonly StatusBar _owner;
+ private int _lastAccessedIndex = -1;
+
+ public StatusBarPanelCollection(StatusBar owner)
+ {
+ ArgumentNullException.ThrowIfNull(owner);
+
+ _owner = owner;
+ }
+
+ public virtual StatusBarPanel this[int index]
+ {
+ get => _owner._panels[index];
+ set
+ {
+ ArgumentNullException.ThrowIfNull(value);
+
+ _owner._layoutDirty = true;
+
+ if (value.Parent is not null)
+ {
+ throw new ArgumentException(SR.ObjectHasParent, nameof(value));
+ }
+
+ if (index < 0 || index >= _owner._panels.Count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), index, string.Format(SR.InvalidArgument, nameof(index), index));
+ }
+
+ StatusBarPanel oldPanel = _owner._panels[index];
+ oldPanel.ParentInternal = null;
+ value.ParentInternal = _owner;
+ if (value.AutoSize == StatusBarPanelAutoSize.Contents)
+ {
+ value.Width = value.GetContentsWidth(newPanel: true);
+ }
+
+ _owner._panels[index] = value;
+ value.Index = index;
+
+ if (_owner.ArePanelsRealized())
+ {
+ _owner.PerformLayout();
+ value.Realize();
+ }
+ }
+ }
+
+ object IList.this[int index]
+ {
+ get => this[index];
+ set
+ {
+ if (value is not StatusBarPanel panel)
+ {
+ throw new ArgumentException(BadStatusBarPanelMessage, nameof(value));
+ }
+
+ this[index] = panel;
+ }
+ }
+
+ public virtual StatusBarPanel this[string key]
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(key))
+ {
+ return null;
+ }
+
+ int index = IndexOfKey(key);
+
+ return IsValidIndex(index) ? this[index] : null;
+ }
+ }
+
+ [Browsable(false)]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public int Count => _owner._panels.Count;
+
+ public bool IsReadOnly => false;
+
+ object ICollection.SyncRoot => this;
+
+ bool ICollection.IsSynchronized => false;
+
+ bool IList.IsFixedSize => false;
+
+ public virtual StatusBarPanel Add(string text)
+ {
+ StatusBarPanel panel = new()
+ {
+ Text = text
+ };
+
+ Add(panel);
+
+ return panel;
+ }
+
+ public virtual int Add(StatusBarPanel value)
+ {
+ int index = _owner._panels.Count;
+ Insert(index, value);
+
+ return index;
+ }
+
+ int IList.Add(object value)
+ {
+ if (value is not StatusBarPanel panel)
+ {
+ throw new ArgumentException(BadStatusBarPanelMessage, nameof(value));
+ }
+
+ return Add(panel);
+ }
+
+ public virtual void AddRange(StatusBarPanel[] panels)
+ {
+ ArgumentNullException.ThrowIfNull(panels);
+
+ foreach (StatusBarPanel panel in panels)
+ {
+ Add(panel);
+ }
+ }
+
+ public virtual void Clear()
+ {
+ _owner.RemoveAllPanelsWithoutUpdate();
+ _owner.PerformLayout();
+ }
+
+ public bool Contains(StatusBarPanel panel) => IndexOf(panel) != -1;
+
+ bool IList.Contains(object panel) => panel is StatusBarPanel statusBarPanel && Contains(statusBarPanel);
+
+ public virtual bool ContainsKey(string key) => IsValidIndex(IndexOfKey(key));
+
+ void ICollection.CopyTo(Array dest, int index) => ((ICollection)_owner._panels).CopyTo(dest, index);
+
+ public IEnumerator GetEnumerator() => _owner._panels.GetEnumerator();
+
+ public int IndexOf(StatusBarPanel panel)
+ {
+ for (int i = 0; i < Count; i++)
+ {
+ if (this[i] == panel)
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ int IList.IndexOf(object panel) => panel is StatusBarPanel statusBarPanel ? IndexOf(statusBarPanel) : -1;
+
+ public virtual int IndexOfKey(string key)
+ {
+ if (string.IsNullOrEmpty(key))
+ {
+ return -1;
+ }
+
+ if (IsValidIndex(_lastAccessedIndex)
+ && WindowsFormsUtils.SafeCompareStrings(this[_lastAccessedIndex].Name, key, ignoreCase: true))
+ {
+ return _lastAccessedIndex;
+ }
+
+ for (int i = 0; i < Count; i++)
+ {
+ if (WindowsFormsUtils.SafeCompareStrings(this[i].Name, key, ignoreCase: true))
+ {
+ _lastAccessedIndex = i;
+
+ return i;
+ }
+ }
+
+ _lastAccessedIndex = -1;
+
+ return -1;
+ }
+
+ public virtual void Insert(int index, StatusBarPanel value)
+ {
+ ArgumentNullException.ThrowIfNull(value);
+
+ _owner._layoutDirty = true;
+ if (value.Parent != _owner && value.Parent is not null)
+ {
+ throw new ArgumentException(SR.ObjectHasParent, nameof(value));
+ }
+
+ if (index < 0 || index > _owner._panels.Count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), index, string.Format(SR.InvalidArgument, nameof(index), index));
+ }
+
+ value.ParentInternal = _owner;
+ if (value.AutoSize == StatusBarPanelAutoSize.Contents)
+ {
+ value.Width = value.GetContentsWidth(newPanel: true);
+ }
+
+ _owner._panels.Insert(index, value);
+ _owner.UpdatePanelIndex();
+ _owner.ForcePanelUpdate();
+ }
+
+ void IList.Insert(int index, object value)
+ {
+ if (value is not StatusBarPanel panel)
+ {
+ throw new ArgumentException(BadStatusBarPanelMessage, nameof(value));
+ }
+
+ Insert(index, panel);
+ }
+
+ public virtual void Remove(StatusBarPanel value)
+ {
+ ArgumentNullException.ThrowIfNull(value);
+
+ if (value.Parent != _owner)
+ {
+ return;
+ }
+
+ RemoveAt(value.Index);
+ }
+
+ void IList.Remove(object value)
+ {
+ if (value is StatusBarPanel panel)
+ {
+ Remove(panel);
+ }
+ }
+
+ public virtual void RemoveAt(int index)
+ {
+ if (index < 0 || index >= Count)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), index, string.Format(SR.InvalidArgument, nameof(index), index));
+ }
+
+ StatusBarPanel panel = _owner._panels[index];
+ _owner._panels.RemoveAt(index);
+ panel.ParentInternal = null;
+ _owner.UpdateTooltip(panel);
+ _owner.UpdatePanelIndex();
+ _owner.ForcePanelUpdate();
+ }
+
+ public virtual void RemoveByKey(string key)
+ {
+ int index = IndexOfKey(key);
+ if (IsValidIndex(index))
+ {
+ RemoveAt(index);
+ }
+ }
+
+ private bool IsValidIndex(int index) => index >= 0 && index < Count;
+ }
+
+ private sealed class ControlToolTip : IHandle, IDisposable
+ {
+ public sealed class Tool
+ {
+ public Rectangle Bounds = Rectangle.Empty;
+ public string Text;
+ internal nint _id = -1;
+ }
+
+ private readonly Dictionary