diff --git a/.editorconfig b/.editorconfig
index e7fcfa6c07e..cafbc72a35a 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -184,6 +184,9 @@ csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimenta
dotnet_diagnostic.IDE0090.severity = error
csharp_style_deconstructed_variable_declaration = true:suggestion
+# RS0016: Add public types and members to the declared API
+dotnet_diagnostic.RS0016.severity = silent
+
# Visual Basic files
[*.vb]
diff --git a/.gitignore b/.gitignore
index 610c49c806d..c8a18b3ad19 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,9 @@
*.userosscache
*.sln.docstates
+#Visual Studio creates .bak files when editing resource files. Ignore these
+*.bak
+
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
@@ -29,6 +32,9 @@ bld/
[Oo]bj/
[Ll]og/
+# VSCode directory
+.vscode/
+
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
diff --git a/Build.xml b/Build.xml
new file mode 100644
index 00000000000..74b41ed1b39
--- /dev/null
+++ b/Build.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+ powershell.exe -ExecutionPolicy Bypass .\DeploymentSetup.ps1
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BuildAndPackage.cmd b/BuildAndPackage.cmd
new file mode 100644
index 00000000000..e929307dbaf
--- /dev/null
+++ b/BuildAndPackage.cmd
@@ -0,0 +1,33 @@
+@echo off
+echo Custom NuGet Package Build Script for System.Windows.Forms
+echo =========================================================
+echo.
+
+REM Clean the workspace
+echo Cleaning workspace...
+git clean -dfx
+if errorlevel 1 (
+ echo Error: Failed to clean workspace
+ exit /b 1
+)
+
+REM Build the solution in Release configuration
+echo Building solution in Release configuration...
+dotnet build Winforms.sln -c Release
+if errorlevel 1 (
+ echo Error: Failed to build solution
+ exit /b 1
+)
+
+REM Run the custom packaging script
+echo Running custom packaging...
+powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0CreateCustomPackageStandalone.ps1"
+if errorlevel 1 (
+ echo Error: Failed to create custom package
+ exit /b 1
+)
+
+echo.
+echo Build and packaging completed successfully!
+echo Check the artifacts\packages\Release\Shipping folder for the System.Windows.Forms.*.nupkg file.
+pause
diff --git a/CreateCustomPackage.ps1 b/CreateCustomPackage.ps1
new file mode 100644
index 00000000000..b2e984b9a95
--- /dev/null
+++ b/CreateCustomPackage.ps1
@@ -0,0 +1,213 @@
+# Custom NuGet Package Creation Script for System.Windows.Forms
+# This script automates the manual process described in the requirements
+
+param(
+ [Parameter(Mandatory = $true)]
+ [string]$OriginalPackagePath,
+
+ [Parameter(Mandatory = $true)]
+ [string]$CustomPackagePath,
+
+ [Parameter(Mandatory = $true)]
+ [string]$DesignBinDir,
+
+ [switch]$WhatIf = $false
+)
+
+$ErrorActionPreference = "Stop"
+
+Write-Host "Starting custom NuGet package creation for System.Windows.Forms..." -ForegroundColor Green
+Write-Host "Original package: $OriginalPackagePath" -ForegroundColor Yellow
+Write-Host "Target package: $CustomPackagePath" -ForegroundColor Yellow
+Write-Host "Design bin directory: $DesignBinDir" -ForegroundColor Yellow
+
+if ($WhatIf) {
+ Write-Host "WhatIf mode: Would process package but not make changes" -ForegroundColor Cyan
+ return
+}
+
+# Validate inputs
+if (-not (Test-Path $OriginalPackagePath)) {
+ Write-Error "Original package not found: $OriginalPackagePath"
+ exit 1
+}
+
+# Ensure the Design bin directory exists
+if (-not (Test-Path $DesignBinDir)) {
+ Write-Error "System.Windows.Forms.Design output directory not found at: $DesignBinDir"
+ exit 1
+}
+
+Write-Host "Source directory: $DesignBinDir" -ForegroundColor Cyan
+
+# Validate that the required files exist in the source directory
+$requiredFiles = @(
+ "System.Windows.Forms.Primitives.dll",
+ "System.Windows.Forms.Primitives.xml",
+ "System.Windows.Forms.Design.dll",
+ "System.Windows.Forms.Design.xml"
+)
+
+$missingFiles = @()
+foreach ($file in $requiredFiles) {
+ if (-not (Test-Path (Join-Path $DesignBinDir $file))) {
+ $missingFiles += $file
+ }
+}
+
+if ($missingFiles.Count -gt 0) {
+ Write-Warning "Some required files are missing from the source directory:"
+ foreach ($file in $missingFiles) {
+ Write-Warning " - $file"
+ }
+ Write-Warning "The package will be created but may be incomplete. Ensure System.Windows.Forms.Design project is built correctly."
+}
+
+# Create working directory
+$WorkingDir = Join-Path ([System.IO.Path]::GetDirectoryName($CustomPackagePath)) "temp_package_work"
+$ExtractedPackageDir = Join-Path $WorkingDir "extracted"
+
+if (Test-Path $WorkingDir) {
+ Remove-Item $WorkingDir -Recurse -Force
+}
+New-Item -ItemType Directory -Path $ExtractedPackageDir -Force | Out-Null
+
+try {
+ Write-Host "Extracting original package..." -ForegroundColor Yellow
+
+ # Extract the nupkg (it's a zip file)
+ Add-Type -AssemblyName System.IO.Compression.FileSystem
+ [System.IO.Compression.ZipFile]::ExtractToDirectory($OriginalPackagePath, $ExtractedPackageDir)
+
+ Write-Host "Renaming nuspec file..." -ForegroundColor Yellow
+
+ # Rename the nuspec file
+ $originalNuspec = Join-Path $ExtractedPackageDir "WTG.System.Windows.Forms.nuspec"
+ $newNuspec = Join-Path $ExtractedPackageDir "System.Windows.Forms.nuspec"
+
+ if (Test-Path $originalNuspec) {
+ Move-Item $originalNuspec $newNuspec -Force
+ } else {
+ Write-Error "Original nuspec file not found: $originalNuspec"
+ exit 1
+ }
+
+ Write-Host "Modifying nuspec content..." -ForegroundColor Yellow
+
+ # Modify the nuspec file content
+ $content = Get-Content $newNuspec -Raw -Encoding UTF8
+
+ # Replace the package ID
+ $content = $content -replace 'WTG\.System\.Windows\.Forms', 'System.Windows.Forms'
+
+ # Remove the System.Windows.Forms.Primitives dependency line
+ $content = $content -replace '\s*]*version="[^"]*"[^/]*/>\s*', ''
+
+ # Clean up any extra whitespace
+ $content = $content -replace '\r?\n\s*\r?\n', "`r`n"
+
+ Set-Content $newNuspec -Value $content -Encoding UTF8
+
+ Write-Host "Copying additional files from System.Windows.Forms.Design..." -ForegroundColor Yellow
+
+ $libNetDir = Join-Path $ExtractedPackageDir "lib\net8.0"
+ Write-Host " Target directory: $libNetDir" -ForegroundColor Cyan
+
+ # Copy the main files (these should replace any existing files)
+ $filesToCopy = @(
+ "System.Windows.Forms.Primitives.dll",
+ "System.Windows.Forms.Primitives.xml",
+ "System.Windows.Forms.Design.dll",
+ "System.Windows.Forms.Design.xml"
+ )
+
+ Write-Host " Copying main files..." -ForegroundColor Cyan
+ foreach ($file in $filesToCopy) {
+ $sourcePath = Join-Path $DesignBinDir $file
+ $destPath = Join-Path $libNetDir $file
+
+ if (Test-Path $sourcePath) {
+ $existsAlready = Test-Path $destPath
+ Copy-Item $sourcePath $destPath -Force
+ if ($existsAlready) {
+ Write-Host " Replaced: $file" -ForegroundColor Green
+ } else {
+ Write-Host " Copied: $file" -ForegroundColor Green
+ }
+ } else {
+ Write-Warning " File not found: $sourcePath"
+ }
+ }
+
+ # Copy resource folders (two-character country codes)
+ $resourceFolders = Get-ChildItem $DesignBinDir -Directory
+
+ if ($resourceFolders.Count -gt 0) {
+ Write-Host " Found $($resourceFolders.Count) resource folders to copy" -ForegroundColor Cyan
+ }
+
+ foreach ($folder in $resourceFolders) {
+ $destFolder = Join-Path $libNetDir $folder.Name
+
+ # Create the destination folder
+ New-Item -ItemType Directory -Path $destFolder -Force | Out-Null
+
+ # Copy all files from source folder to destination
+ $sourceFiles = Get-ChildItem $folder.FullName -File
+ if ($sourceFiles.Count -gt 0) {
+ Copy-Item $sourceFiles.FullName $destFolder -Force
+ Write-Host " Copied resource folder: $($folder.Name) ($($sourceFiles.Count) files)" -ForegroundColor Green
+ } else {
+ Write-Host " Resource folder $($folder.Name) is empty, skipping" -ForegroundColor DarkYellow
+ }
+ }
+
+ Write-Host "Creating new package..." -ForegroundColor Yellow
+
+ # Create the new package
+ if (Test-Path $CustomPackagePath) {
+ Remove-Item $CustomPackagePath -Force
+ }
+
+ [System.IO.Compression.ZipFile]::CreateFromDirectory($ExtractedPackageDir, $CustomPackagePath)
+
+ Write-Host "Package created successfully: $CustomPackagePath" -ForegroundColor Green
+
+ # Display package info
+ $packageInfo = Get-ChildItem $CustomPackagePath
+ Write-Host "Package size: $([math]::Round($packageInfo.Length / 1MB, 2)) MB" -ForegroundColor Cyan
+
+ # Show summary of what was included
+ Write-Host ""
+ Write-Host "Package contents summary:" -ForegroundColor Yellow
+ Write-Host " - Original System.Windows.Forms files from WTG package" -ForegroundColor White
+ Write-Host " - Additional files from System.Windows.Forms.Design:" -ForegroundColor White
+ $filesToCopy = @(
+ "System.Windows.Forms.Primitives.dll",
+ "System.Windows.Forms.Primitives.xml",
+ "System.Windows.Forms.Design.dll",
+ "System.Windows.Forms.Design.xml"
+ )
+ foreach ($file in $filesToCopy) {
+ $sourcePath = Join-Path $DesignBinDir $file
+ if (Test-Path $sourcePath) {
+ Write-Host " [OK] $file" -ForegroundColor Green
+ } else {
+ Write-Host " [MISSING] $file (not found)" -ForegroundColor Red
+ }
+ }
+
+ if ($resourceFolders.Count -gt 0) {
+ Write-Host " - Resource folders: $($resourceFolders.Name -join ', ')" -ForegroundColor White
+ } else {
+ Write-Host " - No resource folders found" -ForegroundColor DarkYellow
+ }
+
+} finally {
+ # Clean up working directory
+ if (Test-Path $WorkingDir) {
+ Remove-Item $WorkingDir -Recurse -Force
+ }
+}
+
+Write-Host "Custom NuGet package creation completed!" -ForegroundColor Green
diff --git a/CreateCustomPackageStandalone.ps1 b/CreateCustomPackageStandalone.ps1
new file mode 100644
index 00000000000..fa2f4562976
--- /dev/null
+++ b/CreateCustomPackageStandalone.ps1
@@ -0,0 +1,57 @@
+# Standalone Custom NuGet Package Creation Script for System.Windows.Forms
+# This script can be used independently for testing or manual package creation
+
+param(
+ [string]$Configuration = "Release",
+ [string]$ArtifactsDir = "$PSScriptRoot\artifacts",
+ [switch]$WhatIf = $false
+)
+
+$ErrorActionPreference = "Stop"
+
+Write-Host "Standalone custom NuGet package creation for System.Windows.Forms..." -ForegroundColor Green
+
+# Define paths
+$PackagesOutputDir = Join-Path $ArtifactsDir "packages\$Configuration\Shipping"
+$DesignBinDir = Join-Path $ArtifactsDir "bin\System.Windows.Forms.Design\$Configuration\net8.0"
+
+# Find the original package
+$OriginalPackages = Get-ChildItem $PackagesOutputDir -Filter "WTG.System.Windows.Forms.*.nupkg" -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending
+if ($OriginalPackages.Count -eq 0) {
+ Write-Error "No WTG.System.Windows.Forms package found in $PackagesOutputDir. Make sure the solution is built in $Configuration configuration."
+ exit 1
+}
+
+$OriginalPackage = $OriginalPackages[0]
+$OriginalPackagePath = $OriginalPackage.FullName
+$PackageVersion = $OriginalPackage.Name -replace "WTG\.System\.Windows\.Forms\.(.*?)\.nupkg", '$1'
+$CustomPackageName = "System.Windows.Forms.$PackageVersion.nupkg"
+$CustomPackagePath = Join-Path $PackagesOutputDir $CustomPackageName
+
+Write-Host "Using the main PowerShell script..." -ForegroundColor Yellow
+
+# Call the main PowerShell script
+$mainScriptPath = Join-Path $PSScriptRoot "CreateCustomPackage.ps1"
+if (-not (Test-Path $mainScriptPath)) {
+ Write-Error "Main PowerShell script not found at: $mainScriptPath"
+ exit 1
+}
+
+$params = @{
+ OriginalPackagePath = $OriginalPackagePath
+ CustomPackagePath = $CustomPackagePath
+ DesignBinDir = $DesignBinDir
+}
+
+if ($WhatIf) {
+ $params.WhatIf = $true
+}
+
+& $mainScriptPath @params
+
+if (-not $WhatIf) {
+ Write-Host ""
+ Write-Host "Next steps:" -ForegroundColor Cyan
+ Write-Host "1. To push to proget: dotnet nuget push `"$CustomPackagePath`" --api-key `"your-api-key`" --source `"https://proget.wtg.zone/nuget/WTG-Internal/v3/index.json`"" -ForegroundColor White
+ Write-Host "2. Verify at: https://proget.wtg.zone/feeds/WTG-Internal/System.Windows.Forms/versions" -ForegroundColor White
+}
diff --git a/CustomPackageCreation.md b/CustomPackageCreation.md
new file mode 100644
index 00000000000..f6314ba8dce
--- /dev/null
+++ b/CustomPackageCreation.md
@@ -0,0 +1,173 @@
+# Automated NuGet Package Creation for System.Windows.Forms
+
+This document describes the automated NuGet package creation process that replaces the manual steps previously required.
+
+## Overview
+
+The automated system creates a custom `System.Windows.Forms` NuGet package from the `WTG.System.Windows.Forms` package built by the standard MSBuild process. The automation includes:
+
+1. Extracting the original `WTG.System.Windows.Forms.*.nupkg` package
+2. Renaming the `.nuspec` file from `WTG.System.Windows.Forms.nuspec` to `System.Windows.Forms.nuspec`
+3. Modifying the package ID in the `.nuspec` file to `System.Windows.Forms`
+4. Removing the `System.Windows.Forms.Primitives` dependency
+5. Copying additional files from `System.Windows.Forms.Design` build output
+6. Copying resource folders (localization files)
+7. Creating the final `System.Windows.Forms.*.nupkg` package
+
+## Automated Methods
+
+### Method 1: MSBuild Integration (Recommended)
+
+The automation is integrated into the MSBuild process via custom targets that call a dedicated PowerShell script.
+
+**Files involved:**
+- `eng/CustomPackaging.targets` - MSBuild targets file (calls PowerShell script)
+- `CreateCustomPackage.ps1` - Core PowerShell script with packaging logic
+- `src/System.Windows.Forms/src/System.Windows.Forms.csproj` - Updated to import the custom targets
+
+**How to use:**
+```bash
+# Build the solution in Release configuration
+dotnet build Winforms.sln -c Release
+
+# The custom package will be automatically created in:
+# artifacts/packages/Release/Shipping/System.Windows.Forms.*.nupkg
+```
+
+**What happens:**
+1. Standard MSBuild packaging creates `WTG.System.Windows.Forms.*.nupkg`
+2. Custom target `CreateCustomNuGetPackage` runs after packaging
+3. MSBuild calls `CreateCustomPackage.ps1` with appropriate parameters
+4. Final `System.Windows.Forms.*.nupkg` is created alongside the original
+
+### Method 2: Standalone PowerShell Script
+
+For more control, debugging, or manual package creation from existing build outputs.
+
+**File:** `CreateCustomPackageStandalone.ps1`
+
+**Usage:**
+```powershell
+# Run with default Release configuration (auto-discovers latest package)
+.\CreateCustomPackageStandalone.ps1
+
+# Run with specific configuration
+.\CreateCustomPackageStandalone.ps1 -Configuration Release
+
+# Preview mode (shows what would be done without making changes)
+.\CreateCustomPackageStandalone.ps1 -WhatIf
+```
+
+This script automatically finds the latest `WTG.System.Windows.Forms.*.nupkg` in the artifacts directory and processes it.
+
+### Method 3: Batch File (Simplest)
+
+For users who prefer a simple click-and-run approach.
+
+**File:** `BuildAndPackage.cmd`
+
+**Usage:**
+1. Double-click the `BuildAndPackage.cmd` file, or
+2. Run from command prompt: `BuildAndPackage.cmd`
+
+This will:
+1. Clean the workspace (`git clean -dfx`)
+2. Build the solution in Release configuration
+3. Run the standalone custom packaging script
+
+## Output
+
+After running any of the methods above, you'll find:
+
+- **Original package:** `artifacts/packages/Release/Shipping/WTG.System.Windows.Forms.*.nupkg`
+- **Custom package:** `artifacts/packages/Release/Shipping/System.Windows.Forms.*.nupkg`
+
+The custom package is the one you should push to ProGet.
+
+## Publishing to ProGet
+
+Once the custom package is created, push it to ProGet:
+
+```bash
+dotnet nuget push "artifacts/packages/Release/Shipping/System.Windows.Forms.*.nupkg" \
+ --api-key "your-api-key" \
+ --source "https://proget.wtg.zone/nuget/WTG-Internal/v3/index.json"
+```
+
+Verify the package at: https://proget.wtg.zone/feeds/WTG-Internal/System.Windows.Forms/versions
+
+## Technical Details
+
+### Files Modified
+
+1. **System.Windows.Forms.csproj**: Added import for `CustomPackaging.targets`
+
+### Files Added
+
+1. **eng/CustomPackaging.targets**: MSBuild targets file that calls the PowerShell script
+2. **CreateCustomPackage.ps1**: Core PowerShell script with all packaging logic
+3. **CreateCustomPackageStandalone.ps1**: Standalone wrapper script for manual use
+4. **BuildAndPackage.cmd**: Simple batch file wrapper
+5. **CustomPackageCreation.md**: This documentation
+
+### Architecture
+
+The refactored solution uses a clean separation of concerns:
+
+- **MSBuild targets**: Handle integration with the build process and parameter passing
+- **Core PowerShell script**: Contains all the packaging logic (can be called from MSBuild or standalone)
+- **Standalone wrapper**: Provides auto-discovery and convenience for manual testing
+- **Batch wrapper**: Simple entry point for non-technical users
+
+### Custom Target Details
+
+The `CreateCustomNuGetPackage` target:
+- Only runs in Release configuration
+- Only runs for the `System.Windows.Forms` project
+- Validates that required files exist
+- Calls `CreateCustomPackage.ps1` with the correct parameters
+- Provides detailed logging
+
+### Dependencies
+
+The automation requires:
+- PowerShell (available on Windows by default)
+- .NET SDK (for the build process)
+- Git (for workspace cleaning)
+
+## Troubleshooting
+
+### Common Issues
+
+1. **Package not found**: Ensure the solution builds successfully in Release configuration first
+2. **Design files missing**: Verify that `System.Windows.Forms.Design` project builds correctly
+3. **PowerShell execution policy**: If PowerShell scripts are blocked, run: `Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope CurrentUser`
+
+### Debug Mode
+
+To debug the packaging process:
+1. Use the standalone PowerShell script with `-WhatIf` flag
+2. Check MSBuild output for custom target messages
+3. Look for temporary files in `artifacts/packages/Release/Shipping/temp_package_work` (if cleanup fails)
+
+### Manual Fallback
+
+If automation fails, the original manual process can still be used as documented in the original requirements.
+
+## Maintenance
+
+### Updating Package Dependencies
+
+If the dependencies in the `.nuspec` file need to be updated:
+1. Update the original `WTG.System.Windows.Forms.nuspec` file
+2. Update the PowerShell script in `CustomPackaging.targets` if new dependency removal logic is needed
+
+### Version Updates
+
+The automation automatically uses the version from the built package, so no manual version updates are required.
+
+### Adding New Files
+
+To include additional files in the custom package:
+1. Update the `$filesToCopy` array in the PowerShell script
+2. Ensure the files are available in the `System.Windows.Forms.Design` output directory
diff --git a/Demo/Demo.csproj b/Demo/Demo.csproj
new file mode 100644
index 00000000000..5afe319fe70
--- /dev/null
+++ b/Demo/Demo.csproj
@@ -0,0 +1,24 @@
+
+
+
+ WinExe
+ net8.0-windows;net48
+ enable
+ preview
+ enable
+ SA1400;CS0649
+
+
+
+ true
+
+
+
+ false
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/Directory.Build.props b/Demo/Directory.Build.props
new file mode 100644
index 00000000000..39c1b301948
--- /dev/null
+++ b/Demo/Directory.Build.props
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/Demo/Directory.Build.targets b/Demo/Directory.Build.targets
new file mode 100644
index 00000000000..39c1b301948
--- /dev/null
+++ b/Demo/Directory.Build.targets
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/Demo/Form1.Designer.cs b/Demo/Form1.Designer.cs
new file mode 100644
index 00000000000..e7e66aa1aff
--- /dev/null
+++ b/Demo/Form1.Designer.cs
@@ -0,0 +1,264 @@
+using System.Drawing;
+using System.Windows.Forms;
+
+namespace Demo
+{
+ partial class Form1
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// 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();
+
+ // Set first panel properties and add to StatusBar
+ statusPanel.BorderStyle = StatusBarPanelBorderStyle.Sunken;
+ statusPanel.Text = "Status Bar Example";
+ statusPanel.AutoSize = StatusBarPanelAutoSize.Spring;
+ mainStatusBar.Panels.Add(statusPanel);
+
+ // Set second panel properties and add to StatusBar
+ 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()
+ {
+ this.button1 = new System.Windows.Forms.Button();
+ this.button2 = new System.Windows.Forms.Button();
+ this.myDataGrid = new DataGrid();
+
+ button1.Location = new Point(24, 60);
+ button1.Size = new Size(200, 30);
+ button1.Text = "Change Appearance";
+ button1.Click += new System.EventHandler(Button1_Click);
+
+ button2.Location = new Point(224, 60);
+ button2.Size = new Size(200, 30);
+ button2.Text = "Get Binding Manager";
+ button2.Click += new System.EventHandler(Button2_Click);
+
+ myDataGrid.Location = new Point(24, 100);
+ myDataGrid.Size = new Size(600, 400);
+ myDataGrid.CaptionText = "Microsoft DataGrid Control";
+ myDataGrid.MouseUp += new MouseEventHandler(Grid_MouseUp);
+
+ this.Controls.Add(button1);
+ this.Controls.Add(button2);
+ this.Controls.Add(myDataGrid);
+ }
+
+ private void InitializeMenu()
+ {
+ // Create the main menu
+ mainMenu = new MainMenu();
+
+ // Create menu items
+ fileMenuItem = new MenuItem("File");
+ newMenuItem = new MenuItem("New");
+ openMenuItem = new MenuItem("Open");
+ saveMenuItem = new MenuItem("Save", SaveMenuItem_Click, Shortcut.CtrlS);
+ exitMenuItem = new MenuItem("Exit");
+ viewMenuItem = new MenuItem("View");
+ toolboxMenuItem = new MenuItem("Toolbox");
+ terminalMenuItem = new MenuItem("Terminal");
+ outputMenuItem = new MenuItem("Output");
+
+ newProjectItem = new MenuItem("Project...");
+ newRepositoryItem = new MenuItem("Repository...");
+ newFileItem = new MenuItem("File...");
+
+ newMenuItem.MenuItems.Add(newProjectItem);
+ newMenuItem.MenuItems.Add(newRepositoryItem);
+ newMenuItem.MenuItems.Add(newFileItem);
+
+ openMenuItem.Shortcut = Shortcut.AltF12;
+ openMenuItem.ShowShortcut = true;
+
+ saveMenuItem.Checked = true;
+ saveMenuItem.RadioCheck = true;
+ //saveMenuItem.Shortcut = Shortcut.CtrlS;
+ //saveMenuItem.ShowShortcut = true;
+
+ // Add sub-menu items to the "File" menu item
+ fileMenuItem.MenuItems.Add(newMenuItem);
+ fileMenuItem.MenuItems.Add(openMenuItem);
+ fileMenuItem.MenuItems.Add(saveMenuItem);
+ fileMenuItem.MenuItems.Add(exitMenuItem);
+
+ viewMenuItem.MenuItems.Add(toolboxMenuItem);
+ viewMenuItem.MenuItems.Add(terminalMenuItem);
+ viewMenuItem.MenuItems.Add(outputMenuItem);
+
+ // Add "File" and "View" menu item to the main menu
+ mainMenu.MenuItems.Add(fileMenuItem);
+ mainMenu.MenuItems.Add(viewMenuItem);
+
+ var dynamicMenuItem = new MenuItem("Dynamic");
+ dynamicMenuItem.MenuItems.Add(new MenuItem("Dynamic code has not run yet"));
+ dynamicMenuItem.Popup += DynamicMenuItem_Popup;
+ mainMenu.MenuItems.Add(dynamicMenuItem);
+
+ // Add Owner Draw Demo menu
+ var ownerDrawDemoMenuItem = new MenuItem("Owner Draw Demo");
+
+ // Create owner-draw menu items
+ var ownerDrawItem3 = new OwnerDrawMenuItem();
+ ownerDrawItem3.Text = "Custom Draw Item 1";
+ ownerDrawItem3.Click += (s, e) => MessageBox.Show("Custom Owner Draw Item 1 clicked!");
+
+ // Add submenu items to ownerDrawItem3
+ var subItem1_1 = new OwnerDrawMenuItem();
+ subItem1_1.Text = "Submenu Item 1-1";
+ subItem1_1.Click += (s, e) => MessageBox.Show("Submenu Item 1-1 clicked!");
+
+ var subItem1_2 = new OwnerDrawMenuItem();
+ subItem1_2.Text = "Submenu Item 1-2";
+ subItem1_2.Click += (s, e) => MessageBox.Show("Submenu Item 1-2 clicked!");
+
+ ownerDrawItem3.MenuItems.Add(subItem1_1);
+ ownerDrawItem3.MenuItems.Add(subItem1_2);
+
+ var ownerDrawItem4 = new OwnerDrawMenuItem();
+ ownerDrawItem4.Text = "Custom Draw Item 2";
+ ownerDrawItem4.Click += (s, e) => MessageBox.Show("Custom Owner Draw Item 2 clicked!");
+
+ // Add submenu items to ownerDrawItem4
+ var subItem2_1 = new OwnerDrawMenuItem();
+ subItem2_1.Text = "Nested Custom Item A";
+ subItem2_1.Click += (s, e) => MessageBox.Show("Nested Custom Item A clicked!");
+
+ var subItem2_2 = new OwnerDrawMenuItem();
+ subItem2_2.Text = "Nested Custom Item B";
+ subItem2_2.Click += (s, e) => MessageBox.Show("Nested Custom Item B clicked!");
+
+ ownerDrawItem4.MenuItems.Add(subItem2_1);
+ ownerDrawItem4.MenuItems.Add(subItem2_2);
+
+ // Add a sub-submenu to test deeper nesting
+ var deepSubmenu = new MenuItem("Deep Submenu");
+ var deepSubItem1 = new OwnerDrawMenuItem();
+ deepSubItem1.Text = "Deep Custom Item 1";
+ deepSubItem1.Click += (s, e) => MessageBox.Show("Deep Custom Item 1 clicked!\nThree levels deep with custom drawing!");
+
+ var deepSubItem2 = new OwnerDrawMenuItem();
+ deepSubItem2.Text = "Deep Custom Item 2";
+ deepSubItem2.Click += (s, e) => MessageBox.Show("Deep Custom Item 2 clicked!\nCustom drawing works at any depth!");
+
+ deepSubmenu.MenuItems.Add(deepSubItem1);
+ deepSubmenu.MenuItems.Add(deepSubItem2);
+
+ ownerDrawItem4.MenuItems.Add(subItem2_1);
+ ownerDrawItem4.MenuItems.Add(subItem2_2);
+ ownerDrawItem4.MenuItems.Add(new MenuItem("-")); // Separator
+ ownerDrawItem4.MenuItems.Add(deepSubmenu);
+
+ ownerDrawDemoMenuItem.MenuItems.Add(new MenuItem("Standard Item"));
+ ownerDrawDemoMenuItem.MenuItems.Add(new MenuItem("-"));
+ ownerDrawDemoMenuItem.MenuItems.Add(ownerDrawItem3);
+ ownerDrawDemoMenuItem.MenuItems.Add(ownerDrawItem4);
+
+ mainMenu.MenuItems.Add(ownerDrawDemoMenuItem);
+
+ // Set the form's main menu
+ this.Menu = mainMenu;
+
+ newProjectItem.Click += NewProjectItem_Click;
+ newRepositoryItem.Click += NewRepositoryItem_Click;
+ newFileItem.Click += NewFileItem_Click;
+
+ // Add event handlers for menu items
+ //newMenuItem.Click += NewMenuItem_Click;
+ openMenuItem.Click += OpenMenuItem_Click;
+ //saveMenuItem.Click += SaveMenuItem_Click;
+ exitMenuItem.Click += ExitMenuItem_Click;
+
+ toolboxMenuItem.Click += ToolboxMenuItem_Click;
+ terminalMenuItem.Click += TerminalMenuItem_Click;
+ outputMenuItem.Click += OutputMenuItem_Click;
+ }
+
+ private void DynamicMenuItem_Popup(object sender, EventArgs e)
+ {
+ MenuItem dynamicMenuItem = sender as MenuItem;
+ if (dynamicMenuItem != null)
+ {
+ dynamicMenuItem.MenuItems.Clear();
+ for (int i = 1; i <= 5; i++)
+ {
+ dynamicMenuItem.MenuItems.Add(new MenuItem($"Dynamic Item {i}"));
+ }
+ }
+ }
+
+ private void InitializeMenuStrip()
+ {
+ // Create a MenuStrip
+ MenuStrip menuStrip = new MenuStrip();
+
+ // Create "File" menu item
+ ToolStripMenuItem fileMenuItem = new ToolStripMenuItem("File");
+ fileMenuItem.DropDownItems.Add("New");
+ fileMenuItem.DropDownItems.Add("Open");
+ fileMenuItem.DropDownItems.Add("Save");
+ fileMenuItem.DropDownItems.Add("Exit");
+ menuStrip.Items.Add(fileMenuItem);
+
+ // Create "Edit" menu item
+ ToolStripMenuItem editMenuItem = new ToolStripMenuItem("Edit");
+ editMenuItem.DropDownItems.Add("Cut");
+ editMenuItem.DropDownItems.Add("Copy");
+ editMenuItem.DropDownItems.Add("Paste");
+ menuStrip.Items.Add(editMenuItem);
+
+ // Attach the MenuStrip to the form
+ this.Controls.Add(menuStrip);
+ this.MainMenuStrip = menuStrip;
+ }
+
+ #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()
+ {
+ this.components = new System.ComponentModel.Container();
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(1024, 768);
+ this.Text = "WTG WinForms Demo";
+ }
+
+ #endregion
+ }
+}
diff --git a/Demo/Form1.cs b/Demo/Form1.cs
new file mode 100644
index 00000000000..a84ad6370f3
--- /dev/null
+++ b/Demo/Form1.cs
@@ -0,0 +1,394 @@
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Windows.Forms;
+
+#nullable disable
+
+namespace Demo
+{
+ ///
+ /// Summary description for Form1.
+ ///
+ public partial class Form1 : Form
+ {
+ private DataGrid myDataGrid;
+ private DataSet myDataSet;
+ private bool TablesAlreadyAdded;
+ private Button button1;
+ private Button button2;
+
+ private MainMenu mainMenu;
+ private MenuItem fileMenuItem;
+ private MenuItem newMenuItem;
+ private MenuItem openMenuItem;
+ private MenuItem saveMenuItem;
+ private MenuItem exitMenuItem;
+ private MenuItem newProjectItem;
+ private MenuItem newRepositoryItem;
+ private MenuItem newFileItem;
+ private MenuItem viewMenuItem;
+ private MenuItem toolboxMenuItem;
+ private MenuItem terminalMenuItem;
+ private MenuItem outputMenuItem;
+
+ private ToolBar toolBar;
+ private TreeView treeView;
+
+ ///
+ /// Summary description for Form1.
+ ///
+ public Form1()
+ {
+ InitializeComponent();
+
+ Shown += MainForm_Shown;
+ }
+
+ private void MainForm_Shown(object sender, EventArgs e)
+ {
+ InitializeDataGrid();
+ SetUp();
+ InitializeMenu();
+ InitializeStatusBar();
+ //InitializeMenuStrip();
+
+ InitializeToolBar();
+ InitializeTreeView();
+ }
+
+ private void InitializeTreeView()
+ {
+ this.treeView = new TreeView();
+ this.treeView.Location = new Point(650, 100);
+ this.treeView.Size = new Size(200, 200);
+ this.treeView.CheckBoxes = true;
+
+ TreeNode rootNode = new TreeNode("Root Node")
+ {
+ Nodes =
+ {
+ new TreeNode("Child Node 1")
+ {
+ Nodes =
+ {
+ new TreeNode("Sub Child Node 1"),
+ new TreeNode("Sub Child Node 2")
+ }
+ },
+ new TreeNode("Child Node 2")
+ {
+ Nodes =
+ {
+ new TreeNode("Sub Child Node 3"),
+ new TreeNode("Sub Child Node 4")
+ }
+ },
+ new TreeNode("Child Node 3")
+ }
+ };
+
+ this.treeView.Nodes.Add(rootNode);
+
+ this.treeView.ContextMenu = new ContextMenu(
+ [
+ new MenuItem("Option 1"),
+ new MenuItem("Option 2")
+ ]);
+
+ AddContextMenuToNodes(treeView.Nodes);
+
+ Controls.Add(this.treeView);
+ }
+
+ private static void AddContextMenuToNodes(TreeNodeCollection nodes)
+ {
+ foreach (TreeNode node in nodes)
+ {
+ node.ContextMenu = new ContextMenu(
+ new MenuItem[]
+ {
+ new($"Option for {node.Text}"),
+ new($"Option 2 for {node.Text}")
+ });
+
+ if (node.Nodes.Count > 0)
+ {
+ AddContextMenuToNodes(node.Nodes);
+ }
+ }
+ }
+
+ private void InitializeToolBar()
+ {
+ toolBar = new ToolBar();
+ toolBar.Buttons.Add("1st button");
+ var btn1 = toolBar.Buttons[0];
+ btn1.ToolTipText = "This is the first button";
+
+ var sep1 = new ToolBarButton("sep1");
+ sep1.Style = ToolBarButtonStyle.Separator;
+ toolBar.Buttons.Add(sep1);
+
+ var btn2 = new ToolBarButton("btn2 toggle");
+ btn2.Style = ToolBarButtonStyle.ToggleButton;
+ btn2.ToolTipText = "This is the second button";
+ toolBar.Buttons.Add(btn2);
+
+ var btn3 = new ToolBarButton("btn3 drop-down");
+ btn3.Style = ToolBarButtonStyle.DropDownButton;
+ btn3.ToolTipText = "This is the third button";
+
+ MenuItem menuItem1 = new MenuItem("Wave");
+ menuItem1.Click += (sender, e) => MessageBox.Show("Wave back");
+ ContextMenu contextMenu1 = new ContextMenu(new MenuItem[] { menuItem1 });
+ btn3.DropDownMenu = contextMenu1;
+ toolBar.Buttons.Add(btn3);
+
+ ToolBarButtonClickEventHandler clickHandler = (object sender, ToolBarButtonClickEventArgs e) =>
+ {
+ MessageBox.Show("Button clicked. text = " + e.Button.Text);
+ };
+
+ toolBar.ButtonClick += clickHandler;
+
+ Controls.Add(toolBar);
+ }
+
+ private void SetUp()
+ {
+ // Create a DataSet with two tables and one relation.
+ MakeDataSet();
+ /* Bind the DataGrid to the DataSet. The dataMember
+ specifies that the Customers table should be displayed.*/
+ myDataGrid.SetDataBinding(myDataSet, "Customers");
+ }
+
+ private void Button1_Click(object sender, System.EventArgs e)
+ {
+ if (TablesAlreadyAdded)
+ return;
+ AddCustomDataTableStyle();
+ }
+
+ private void AddCustomDataTableStyle()
+ {
+ DataGridTableStyle ts1 = new DataGridTableStyle
+ {
+ MappingName = "Customers",
+ // Set other properties.
+ AlternatingBackColor = Color.LightGray
+ };
+
+ /* Add a GridColumnStyle and set its MappingName
+ to the name of a DataColumn in the DataTable.
+ Set the HeaderText and Width properties. */
+
+ DataGridColumnStyle boolCol = new DataGridBoolColumn
+ {
+ MappingName = "Current",
+ HeaderText = "IsCurrent Customer",
+ Width = 150
+ };
+ ts1.GridColumnStyles.Add(boolCol);
+
+ // Add a second column style.
+ DataGridColumnStyle TextCol = new DataGridTextBoxColumn
+ {
+ MappingName = "custName",
+ HeaderText = "Customer Name",
+ Width = 250
+ };
+ ts1.GridColumnStyles.Add(TextCol);
+
+ // Create the second table style with columns.
+ DataGridTableStyle ts2 = new DataGridTableStyle
+ {
+ MappingName = "Orders",
+
+ // Set other properties.
+ AlternatingBackColor = Color.LightBlue
+ };
+
+ // Create new ColumnStyle objects
+ DataGridColumnStyle cOrderDate =
+ new DataGridTextBoxColumn();
+ cOrderDate.MappingName = "OrderDate";
+ cOrderDate.HeaderText = "Order Date";
+ cOrderDate.Width = 100;
+ ts2.GridColumnStyles.Add(cOrderDate);
+
+ /* Use a PropertyDescriptor to create a formatted
+ column. First get the PropertyDescriptorCollection
+ for the data source and data member. */
+ PropertyDescriptorCollection pcol = this.BindingContext[myDataSet, "Customers.custToOrders"].GetItemProperties();
+
+ /* Create a formatted column using a PropertyDescriptor.
+ The formatting character "c" specifies a currency format. */
+ DataGridColumnStyle csOrderAmount =
+ new DataGridTextBoxColumn(pcol["OrderAmount"], "c", true);
+ csOrderAmount.MappingName = "OrderAmount";
+ csOrderAmount.HeaderText = "Total";
+ csOrderAmount.Width = 100;
+ ts2.GridColumnStyles.Add(csOrderAmount);
+
+ /* Add the DataGridTableStyle instances to
+ the GridTableStylesCollection. */
+ myDataGrid.TableStyles.Add(ts1);
+ myDataGrid.TableStyles.Add(ts2);
+
+ // Sets the TablesAlreadyAdded to true so this doesn't happen again.
+ TablesAlreadyAdded = true;
+ }
+
+ private void Button2_Click(object sender, System.EventArgs e)
+ {
+ BindingManagerBase bmGrid;
+ bmGrid = BindingContext[myDataSet, "Customers"];
+ MessageBox.Show("Current BindingManager Position: " + bmGrid.Position);
+ }
+
+ private void Grid_MouseUp(object sender, MouseEventArgs e)
+ {
+ // Create a HitTestInfo object using the HitTest method.
+
+ // Get the DataGrid by casting sender.
+ 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);
+ }
+
+ // Create a DataSet with two tables and populate it.
+ private void MakeDataSet()
+ {
+ // Create a DataSet.
+ myDataSet = new DataSet("myDataSet");
+
+ // Create two DataTables.
+ DataTable tCust = new DataTable("Customers");
+ DataTable tOrders = new DataTable("Orders");
+
+ // Create two columns, and add them to the first table.
+ 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);
+
+ // Create three columns, and add them to the second table.
+ 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);
+
+ // Add the tables to the DataSet.
+ myDataSet.Tables.Add(tCust);
+ myDataSet.Tables.Add(tOrders);
+
+ // Create a DataRelation, and add it to the DataSet.
+ DataRelation dr = new DataRelation
+ ("custToOrders", cCustID, cID);
+ myDataSet.Relations.Add(dr);
+
+ /* Populates the tables. For each customer and order,
+ creates two DataRow variables. */
+ DataRow newRow1;
+ DataRow newRow2;
+
+ // Create three customers in the Customers Table.
+ for (int i = 1; i < 4; i++)
+ {
+ newRow1 = tCust.NewRow();
+ newRow1["custID"] = i;
+ // Add the row to the Customers table.
+ tCust.Rows.Add(newRow1);
+ }
+
+ // Give each customer a distinct name.
+ tCust.Rows[0]["custName"] = "Customer1";
+ tCust.Rows[1]["custName"] = "Customer2";
+ tCust.Rows[2]["custName"] = "Customer3";
+
+ // Give the Current column a value.
+ tCust.Rows[0]["Current"] = true;
+ tCust.Rows[1]["Current"] = true;
+ tCust.Rows[2]["Current"] = false;
+
+ // For each customer, create five rows in the Orders table.
+ 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;
+ // Add the row to the Orders table.
+ tOrders.Rows.Add(newRow2);
+ }
+ }
+ }
+
+ //private void NewMenuItem_Click(object sender, EventArgs e)
+ //{
+ // MessageBox.Show("New menu item clicked!");
+ //}
+
+ private void OpenMenuItem_Click(object sender, EventArgs e)
+ {
+ MessageBox.Show("Open menu item clicked!");
+ }
+
+ private void SaveMenuItem_Click(object sender, EventArgs e)
+ {
+ MessageBox.Show("Save menu item clicked!");
+ }
+
+ private void ExitMenuItem_Click(object sender, EventArgs e)
+ {
+ Application.Exit();
+ }
+
+ private void NewProjectItem_Click(object sender, EventArgs e)
+ {
+ newProjectItem.Checked = !newProjectItem.Checked;
+ MessageBox.Show("Project sub-menu item clicked!");
+ }
+
+ private void NewRepositoryItem_Click(object sender, EventArgs e)
+ {
+ newRepositoryItem.Checked = !newRepositoryItem.Checked;
+ MessageBox.Show("Repository sub-menu item clicked!");
+ }
+
+ private void NewFileItem_Click(object sender, EventArgs e)
+ {
+ newFileItem.Checked = !newFileItem.Checked;
+ MessageBox.Show("File sub-menu item clicked!");
+ }
+
+ private void ToolboxMenuItem_Click(object sender, EventArgs e)
+ {
+ MessageBox.Show("Toolbox menu item clicked!");
+ }
+
+ private void TerminalMenuItem_Click(object sender, EventArgs e)
+ {
+ MessageBox.Show("Terminal menu item clicked!");
+ }
+
+ private void OutputMenuItem_Click(object sender, EventArgs e)
+ {
+ MessageBox.Show("Output menu item clicked!");
+ }
+ }
+}
diff --git a/src/System.Windows.Forms/tests/IntegrationTests/ScratchProject/Form1.resx b/Demo/Form1.resx
similarity index 100%
rename from src/System.Windows.Forms/tests/IntegrationTests/ScratchProject/Form1.resx
rename to Demo/Form1.resx
diff --git a/Demo/OwnerDrawMenuItem.cs b/Demo/OwnerDrawMenuItem.cs
new file mode 100644
index 00000000000..5e0fd7b3b9a
--- /dev/null
+++ b/Demo/OwnerDrawMenuItem.cs
@@ -0,0 +1,109 @@
+#if NET
+using System.Drawing;
+using System.Windows.Forms;
+#endif
+
+namespace Demo
+{
+ 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/Demo/Program.cs b/Demo/Program.cs
new file mode 100644
index 00000000000..624a9600932
--- /dev/null
+++ b/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 Form1());
+ }
+ }
+}
+
+#if NETFRAMEWORK
+namespace System.Runtime.Versioning
+{
+ class SupportedOSPlatformAttribute : Attribute
+ {
+ public SupportedOSPlatformAttribute(string platformName) { }
+ }
+}
+#endif
diff --git a/Demo/xlf/Form1.cs.xlf b/Demo/xlf/Form1.cs.xlf
new file mode 100644
index 00000000000..aa40faef86f
--- /dev/null
+++ b/Demo/xlf/Form1.cs.xlf
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/xlf/Form1.de.xlf b/Demo/xlf/Form1.de.xlf
new file mode 100644
index 00000000000..caab0ce76f7
--- /dev/null
+++ b/Demo/xlf/Form1.de.xlf
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/xlf/Form1.es.xlf b/Demo/xlf/Form1.es.xlf
new file mode 100644
index 00000000000..37761f811ce
--- /dev/null
+++ b/Demo/xlf/Form1.es.xlf
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/xlf/Form1.fr.xlf b/Demo/xlf/Form1.fr.xlf
new file mode 100644
index 00000000000..366eb9d5436
--- /dev/null
+++ b/Demo/xlf/Form1.fr.xlf
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/xlf/Form1.it.xlf b/Demo/xlf/Form1.it.xlf
new file mode 100644
index 00000000000..e68d3fe2676
--- /dev/null
+++ b/Demo/xlf/Form1.it.xlf
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/xlf/Form1.ja.xlf b/Demo/xlf/Form1.ja.xlf
new file mode 100644
index 00000000000..2943ecce294
--- /dev/null
+++ b/Demo/xlf/Form1.ja.xlf
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/xlf/Form1.ko.xlf b/Demo/xlf/Form1.ko.xlf
new file mode 100644
index 00000000000..83cb763b8f6
--- /dev/null
+++ b/Demo/xlf/Form1.ko.xlf
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/xlf/Form1.pl.xlf b/Demo/xlf/Form1.pl.xlf
new file mode 100644
index 00000000000..58b6edd6da0
--- /dev/null
+++ b/Demo/xlf/Form1.pl.xlf
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/xlf/Form1.pt-BR.xlf b/Demo/xlf/Form1.pt-BR.xlf
new file mode 100644
index 00000000000..073f6c00557
--- /dev/null
+++ b/Demo/xlf/Form1.pt-BR.xlf
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/xlf/Form1.ru.xlf b/Demo/xlf/Form1.ru.xlf
new file mode 100644
index 00000000000..5d784e2bc56
--- /dev/null
+++ b/Demo/xlf/Form1.ru.xlf
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/xlf/Form1.tr.xlf b/Demo/xlf/Form1.tr.xlf
new file mode 100644
index 00000000000..9ae0ede5336
--- /dev/null
+++ b/Demo/xlf/Form1.tr.xlf
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/xlf/Form1.zh-Hans.xlf b/Demo/xlf/Form1.zh-Hans.xlf
new file mode 100644
index 00000000000..2dbdc8d0ce3
--- /dev/null
+++ b/Demo/xlf/Form1.zh-Hans.xlf
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/xlf/Form1.zh-Hant.xlf b/Demo/xlf/Form1.zh-Hant.xlf
new file mode 100644
index 00000000000..8782e525d98
--- /dev/null
+++ b/Demo/xlf/Form1.zh-Hant.xlf
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DeploymentSetup.ps1 b/DeploymentSetup.ps1
new file mode 100644
index 00000000000..be37a841288
--- /dev/null
+++ b/DeploymentSetup.ps1
@@ -0,0 +1,35 @@
+$pkgId = 'CargoWiseCloudDeployment'
+
+# this uses the latest version of the package, as listed at https://proget.wtg.zone/packages?PackageSearch=CargoWiseCloudDeployment
+# if you need a specific version, you can use:
+#$pkgVersion = "1.0.0.70"
+#url = "https://proget.wtg.zone/nuget/WTG-Internal/package/${pkgId}/$pkgVersion"
+
+$Destination = '.\bin'
+
+$pkgVersion = "1.0.0.362" # Set this to $null for most recent package.
+$pkgRoot = Join-Path $Destination ("$pkgId.$pkgVersion")
+$url = "https://proget.wtg.zone/nuget/WTG-Internal/package/${pkgId}/$pkgVersion"
+
+
+try {
+ Invoke-WebRequest -Uri $url -OutFile "$pkgRoot.zip" -ErrorAction Stop
+ Write-Host "download nuget successfully"
+}
+catch {
+ Write-Error "download nuget failed: $($_.Exception.Message)"
+}
+
+try {
+ Expand-Archive -Path "$pkgRoot.zip" -DestinationPath $pkgRoot -Force
+ Write-Host "unzip successfully" -ForegroundColor Green
+}
+catch {
+ Write-Error "unzip failed: $($_.Exception.Message)"
+}
+
+Copy-Item -Path (Join-Path (Join-Path $pkgRoot '\content') '*') -Destination $Destination -Recurse -Force
+Copy-Item -Path (Join-Path (Join-Path $pkgRoot '\lib\net10.0-windows7.0') '*') -Destination $Destination -Recurse -Force
+
+Remove-Item -Path "$pkgRoot.zip" -Force
+Remove-Item -Path $pkgRoot -Recurse -Force
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
index 3191e3fac7f..9c5d942b758 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -17,19 +17,19 @@
false
preview
enable
- true
-
- false
- true
-
- true
- false
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -44,16 +44,16 @@
Note, any components that aren't exposed as references in the targeting pack (like analyzers/generators) those should rev
so that they can exist SxS, as the compiler relies on different version to change assembly version for caching purposes.
-->
-
- $(MajorVersion).$(MinorVersion).0.0
-
+
+
+
-
- true
+
+
-
- WINFORMS_ANALYZERS
-
+
+
+
diff --git a/Directory.Build.targets b/Directory.Build.targets
index cf183e46bd2..5885fe770b6 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -32,12 +32,12 @@
-
+
x64
diff --git a/NuGet.config b/NuGet.config
index d496e7a4199..ff3297031be 100644
--- a/NuGet.config
+++ b/NuGet.config
@@ -1,29 +1,18 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
diff --git a/README.md b/README.md
index 6f74230874e..3eee7b5285b 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,41 @@
+# WTG Notes
+
+Migrating obsolete winforms controls to compile and run in Net 8 for CW1's ZArchitecture.
+
+# WTG ChangeLog
+
+| Version | What's changed |
+|------------------|-------------|
+| 0.0.6-dev.final | initial version for net7 and net8 |
+| 0.0.7-dev.final | System.Windows.Forms.Design.dll added to package |
+| 0.0.8-dev.final | net8.0: changed version of System.Drawing.Common.dll (v4.0.0.0 -> v8.0.0.0) to fix Dev build |
+| 0.0.9-dev.final | net8.0: changed versions of all libraries in package to v8.0.0.0 |
+| 0.0.10-dev.final | skipped due to error in publishing |
+| 0.0.11-dev.final | net8.0: remove warning WFDEV001 |
+| 0.0.12-dev.final | avoid type issue when instanciated from ZBindingContext (WI00826420) |
+| 0.0.13-dev.final | comment out Debug.Fail in PaintEventArgs (WI00857973) |
+| 0.0.14-dev.final | switched to using Release configuration assemblies instead of Debug, to avoid Microsoft's plentiful Debug.Assert checks (WI00876922) |
+| 0.0.15-dev.final | removed need for forked System.Drawing.Common. We can use the public package listed on nuget.org now. |
+| 0.0.16-dev.final | fixed issue with keyboard shortcuts associated with menus not being captured (WI00895180) |
+| 0.0.17-dev.final | temporarily fixed WebBrowser memory leak (WI00938771) |
+| 0.0.18-dev.final | fixed menu popup events not firing (WI00949199) |
+| 0.0.19-dev.final | fixed menu bar size not taken into account in form sizing (WI00949199) |
+| 0.0.20-dev.final | Menu property change should trigger size update (WI00949199) |
+| 0.0.21-dev.final | fixed ToolBar tooltip moving the position of the toolbar (WI00951596) |
+| 0.0.22-dev.final | fixed control's anchor info (WI00955507) |
+| 0.0.23-dev.final | fixed disappearing Owner Draw MenuItem (WI00948012) |
+| 0.0.24-dev.final | IT Customs - NET8 NRE on Customs Declaration MISC Tab (WI01005730) |
+
+
+# WTG How to publish new version
+
+* Do changes in code you want to be published
+* Increase version in $(LIB_ROOT)\eng\Versions.props
+* CH0 on DAT
+* Go to DAT deployments for the CH0 build and click deploy
+
+# End of WTG part, Microsoft part below
+
# Windows Forms
[](https://github.com/dotnet/winforms/blob/main/LICENSE.TXT)
diff --git a/Tests/System.Windows.Forms.Tests/AdjustWindowRectTests.cs b/Tests/System.Windows.Forms.Tests/AdjustWindowRectTests.cs
new file mode 100644
index 00000000000..e5fdfd57ac9
--- /dev/null
+++ b/Tests/System.Windows.Forms.Tests/AdjustWindowRectTests.cs
@@ -0,0 +1,185 @@
+namespace System.Windows.Forms.Tests
+{
+ using System.Drawing;
+
+ [TestFixture]
+ [SingleThreaded]
+ public class AdjustWindowRectTests
+ {
+ [Test]
+ 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.That(windowSizeChange, Is.EqualTo(clientSizeChange),
+ "Window size changes should match client size changes when menu is present");
+ }
+
+ [Test]
+ 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.That(formWithMenu.Size.Height, Is.GreaterThan(formWithoutMenu.Size.Height),
+ "After handle creation, form with menu should still be taller");
+ }
+
+ [Test]
+ 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.That(textBox1.Size, Is.EqualTo(textBox2.Size),
+ "ToolStripTextBox should not be affected by parent form's menu");
+ }
+
+ [Test]
+ 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;
+
+ var controlTypes = new Func[]
+ {
+ () => 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.That(control1.Size, Is.EqualTo(control2.Size),
+ $"{control1.GetType().Name} controls should have consistent sizing behavior");
+ }
+ }
+
+ [Test]
+ 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.That(form1.Size.Height, Is.EqualTo(form2.Size.Height),
+ "Form with no menu and form with empty menu should have same height");
+
+ // Form 3 should be taller (has menu items)
+ Assert.That(form3.Size.Height, Is.GreaterThan(form1.Size.Height),
+ "Form with menu items should be taller than form without menu");
+
+ Assert.That(form3.Size.Height, Is.GreaterThan(form2.Size.Height),
+ "Form with menu items should be taller than form with empty menu");
+ }
+ }
+}
diff --git a/Tests/System.Windows.Forms.Tests/MainMenuTests.cs b/Tests/System.Windows.Forms.Tests/MainMenuTests.cs
new file mode 100644
index 00000000000..b184260b9b0
--- /dev/null
+++ b/Tests/System.Windows.Forms.Tests/MainMenuTests.cs
@@ -0,0 +1,148 @@
+namespace System.Windows.Forms.Tests
+{
+ using System.Drawing;
+ using System.Runtime.InteropServices;
+
+ [TestFixture]
+ [SingleThreaded]
+ public class MainMenuTests
+ {
+ [Test]
+ public void MainMenu_SetAndGet_ReturnsCorrectValue()
+ {
+ // Arrange
+ var form = new Form();
+ var mainMenu = new MainMenu();
+
+ // Act
+ form.Menu = mainMenu;
+
+ // Assert
+ Assert.That(form.Menu, Is.EqualTo(mainMenu));
+ }
+
+ [Test]
+ public void MainMenu_AddMenuItem_ReturnsCorrectMenuItem()
+ {
+ // Arrange
+ var mainMenu = new MainMenu();
+ var menuItem = new MenuItem("Test Item");
+
+ // Act
+ mainMenu.MenuItems.Add(menuItem);
+
+ // Assert
+ Assert.That(mainMenu.MenuItems.Count, Is.EqualTo(1));
+ Assert.That(mainMenu.MenuItems[0], Is.EqualTo(menuItem));
+ }
+
+ [Test]
+ 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.That(fileMenuItem.MenuItems.Count, Is.EqualTo(0));
+
+ // 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
+ IntPtr result = SendMessage(handle, WM_INITMENUPOPUP, fileMenuItem.Handle, IntPtr.Zero);
+
+ // Assert
+ Assert.That(popupEventFired, Is.True, "Popup event should have been fired");
+ Assert.That(fileMenuItem.MenuItems.Count, Is.EqualTo(1), "File menu should have one item after popup");
+
+ if (addedMenuItem is not null)
+ {
+ Assert.That(fileMenuItem.MenuItems[0], Is.EqualTo(addedMenuItem), "The added item should be in the file menu");
+ Assert.That(fileMenuItem.MenuItems[0].Text, Is.EqualTo("Dynamic Item"), "The added item should have the correct text");
+ }
+
+ // Clean up
+ form.Dispose();
+ }
+
+ [Test]
+ public void MainMenu_FileMenuWithSubmenu_PopupAddsItemToFileMenu()
+ {
+ // Arrange
+ var form = new Form();
+ var mainMenu = new MainMenu();
+ var fileMenuItem = new MenuItem("File");
+ var submenu = new MenuItem("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.That(fileMenuItem.MenuItems.Count, Is.EqualTo(1));
+ Assert.That(fileMenuItem.MenuItems[0], Is.EqualTo(submenu));
+
+ // 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
+ IntPtr result = SendMessage(handle, WM_INITMENUPOPUP, fileMenuItem.Handle, IntPtr.Zero);
+
+ // Assert
+ Assert.That(popupEventFired, Is.True, "Popup event should have been fired");
+ Assert.That(fileMenuItem.MenuItems.Count, Is.EqualTo(2), "File menu should have two items after popup");
+ Assert.That(fileMenuItem.MenuItems[0], Is.EqualTo(submenu), "The original submenu should still be there");
+
+ if (addedMenuItem is not null)
+ {
+ Assert.That(fileMenuItem.MenuItems[1], Is.EqualTo(addedMenuItem), "The added item should be in the file menu");
+ Assert.That(fileMenuItem.MenuItems[1].Text, Is.EqualTo("Dynamic Item Added to File"), "The added item should have the correct text");
+ }
+
+ // Clean up
+ form.Dispose();
+ }
+
+ [DllImport("user32.dll")]
+ private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
+ }
+}
diff --git a/Tests/System.Windows.Forms.Tests/MenuItemTests.cs b/Tests/System.Windows.Forms.Tests/MenuItemTests.cs
new file mode 100644
index 00000000000..9c2b36db8b5
--- /dev/null
+++ b/Tests/System.Windows.Forms.Tests/MenuItemTests.cs
@@ -0,0 +1,139 @@
+namespace System.Windows.Forms.Tests
+{
+ [TestFixture]
+ public class MenuItemTests
+ {
+#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
+ [Test]
+ public void MenuItem_OnDrawItem_Invoke_Success()
+ {
+ var menuItem = new SubMenuItem();
+
+ // No handler.
+ menuItem.OnDrawItem(null);
+
+ // Handler.
+ int callCount = 0;
+ DrawItemEventHandler handler = (sender, e) =>
+ {
+ Assert.That(sender, Is.EqualTo(menuItem));
+ callCount++;
+ };
+
+ menuItem.DrawItem += handler;
+ menuItem.OnDrawItem(null);
+ Assert.That(callCount, Is.EqualTo(1));
+
+ // Should not call if the handler is removed.
+ menuItem.DrawItem -= handler;
+ menuItem.OnDrawItem(null);
+ Assert.That(callCount, Is.EqualTo(1));
+ }
+
+ [Test]
+ public void MenuItem_OnDrawItem_Disposed_ThrowsObjectDisposedException()
+ {
+ var menuItem = new SubMenuItem();
+ menuItem.Dispose();
+ Assert.Throws(() => menuItem.OnDrawItem(null));
+ }
+
+ [Test]
+ 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);
+ }
+
+ [Test]
+ public void MenuItem_OnMeasureItem_Invoke_Success()
+ {
+ var menuItem = new SubMenuItem();
+
+ // No handler.
+ menuItem.OnMeasureItem(null);
+
+ // Handler.
+ int callCount = 0;
+ MeasureItemEventHandler handler = (sender, e) =>
+ {
+ Assert.That(sender, Is.EqualTo(menuItem));
+ callCount++;
+ };
+
+ menuItem.MeasureItem += handler;
+ menuItem.OnMeasureItem(null);
+ Assert.That(callCount, Is.EqualTo(1));
+
+ // Should not call if the handler is removed.
+ menuItem.MeasureItem -= handler;
+ menuItem.OnMeasureItem(null);
+ Assert.That(callCount, Is.EqualTo(1));
+ }
+
+ [Test]
+ public void MenuItem_OnMeasureItem_Disposed_ThrowsObjectDisposedException()
+ {
+ var menuItem = new SubMenuItem();
+ menuItem.Dispose();
+ Assert.Throws(() => menuItem.OnMeasureItem(null));
+ }
+
+ [Test]
+ 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/Tests/System.Windows.Forms.Tests/MenuSizeCalculationTests.cs b/Tests/System.Windows.Forms.Tests/MenuSizeCalculationTests.cs
new file mode 100644
index 00000000000..5fa9ecdc7e3
--- /dev/null
+++ b/Tests/System.Windows.Forms.Tests/MenuSizeCalculationTests.cs
@@ -0,0 +1,299 @@
+namespace System.Windows.Forms.Tests
+{
+ using System.Drawing;
+
+ [TestFixture]
+ [SingleThreaded]
+ public class MenuSizeCalculationTests
+ {
+ [Test]
+ 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.That(formWithMenu.ClientSize, Is.EqualTo(clientSize), "Form with menu should have correct client size");
+ Assert.That(formWithoutMenu.ClientSize, Is.EqualTo(clientSize), "Form without menu should have correct client size");
+
+ // The key test: form with menu should be taller due to menu bar
+ Assert.That(formWithMenu.Size.Height, Is.GreaterThan(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.That(formWithMenu.Size.Width, Is.EqualTo(formWithoutMenu.Size.Width),
+ "Form width should not be affected by menu presence");
+ }
+
+ [Test]
+ 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.That(formWithEmptyMenu.Size.Height, Is.EqualTo(formWithoutMenu.Size.Height),
+ "Form with empty menu should have same height as form without menu");
+ }
+
+ [Test]
+ 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.That(form.Size.Height, Is.GreaterThan(initialHeight),
+ "Form height should increase when menu with items is added");
+ Assert.That(form.ClientSize, Is.EqualTo(clientSize),
+ "Client size should remain consistent after menu addition");
+ }
+
+ [Test]
+ 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.That(form.Size.Height, Is.LessThan(heightWithMenu),
+ "Form height should decrease when menu is removed");
+ Assert.That(form.ClientSize, Is.EqualTo(clientSize),
+ "Client size should remain consistent after menu removal");
+ }
+
+ [Test]
+ 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.That(childForm.Size.Height, Is.EqualTo(childFormNoMenu.Size.Height),
+ "Non-top-level forms should not be affected by menu presence");
+ }
+
+ [Test]
+ 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.That(mdiChild1.Size.Height, Is.EqualTo(mdiChild2.Size.Height),
+ "MDI child forms should not be affected by menu presence");
+ }
+
+ [Test]
+ 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.That(formWithMultipleMenuItems.Size.Height, Is.EqualTo(formWithOneMenuItem.Size.Height),
+ "Forms should have same height regardless of number of menu items");
+ }
+
+ [Test]
+ 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.That(formWithSubmenu.Size.Height, Is.EqualTo(formWithoutSubmenu.Size.Height),
+ "Forms should have same height regardless of submenu complexity");
+ }
+
+ [Test]
+ 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.That(form.ClientSize, Is.EqualTo(clientSize1), "First client size setting should work correctly");
+ var height1 = form.Size.Height;
+
+ form.ClientSize = clientSize2;
+ Assert.That(form.ClientSize, Is.EqualTo(clientSize2), "Second client size setting should work correctly");
+ 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.That(windowHeightDiff, Is.EqualTo(clientHeightDiff),
+ "Window height difference should equal client height difference (menu bar height is constant)");
+ }
+
+ [Test]
+ 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.That(sizeWithoutMenu.Height, Is.LessThan(sizeWithMenu.Height),
+ "Form height should decrease immediately when menu is removed before handle creation");
+
+ Assert.That(form.ClientSize, Is.EqualTo(targetClientSize),
+ "Client size should remain the target size after menu removal");
+
+ // Width should remain the same
+ Assert.That(sizeWithoutMenu.Width, Is.EqualTo(sizeWithMenu.Width),
+ "Form width should not change when menu is removed");
+ }
+ }
+}
diff --git a/Tests/System.Windows.Forms.Tests/MouseOperations.cs b/Tests/System.Windows.Forms.Tests/MouseOperations.cs
new file mode 100644
index 00000000000..cfb7c1dac39
--- /dev/null
+++ b/Tests/System.Windows.Forms.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/Tests/System.Windows.Forms.Tests/System.Windows.Forms.Tests.csproj b/Tests/System.Windows.Forms.Tests/System.Windows.Forms.Tests.csproj
new file mode 100644
index 00000000000..e46948ac6e1
--- /dev/null
+++ b/Tests/System.Windows.Forms.Tests/System.Windows.Forms.Tests.csproj
@@ -0,0 +1,31 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+ $([System.IO.Path]::GetFullPath('$(RepoRoot)Bin\$(OutDirName)\'))
+
+ ECMA
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/System.Windows.Forms.Tests/TabControlTests.cs b/Tests/System.Windows.Forms.Tests/TabControlTests.cs
new file mode 100644
index 00000000000..77b91517390
--- /dev/null
+++ b/Tests/System.Windows.Forms.Tests/TabControlTests.cs
@@ -0,0 +1,48 @@
+namespace System.Windows.Forms.Tests
+{
+ [TestFixture]
+ [SingleThreaded]
+ public class TabControlTests
+ {
+ [Test]
+ public void TabControl_ClearTabsWhileSelected_DoesNotThrowNullReferenceException()
+ {
+ Exception? capturedException = 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.That(control.TabPages.Count, Is.EqualTo(0));
+ Assert.That(control.SelectedTab, Is.Null);
+ }
+ finally
+ {
+ Application.ThreadException -= handler;
+ if (capturedException is not null)
+ {
+ Assert.Fail($"Unhandled exception: {capturedException.GetType().Name}\n{capturedException.Message}\n{capturedException.StackTrace}");
+ }
+ }
+ }
+ }
+}
diff --git a/Tests/System.Windows.Forms.Tests/ToolBarToolTipTests.cs b/Tests/System.Windows.Forms.Tests/ToolBarToolTipTests.cs
new file mode 100644
index 00000000000..f834c08f47a
--- /dev/null
+++ b/Tests/System.Windows.Forms.Tests/ToolBarToolTipTests.cs
@@ -0,0 +1,260 @@
+using System;
+using System.Drawing;
+using System.Runtime.InteropServices;
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.UI.WindowsAndMessaging;
+
+namespace System.Windows.Forms.Tests
+{
+ [TestFixture]
+ [SingleThreaded]
+ [Apartment(System.Threading.ApartmentState.STA)]
+ 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,
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct NMTBHOTITEM
+ {
+ public NativeMethods.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 DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = new IntPtr(-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 */
+ }
+ }
+ }
+ }
+
+ [Test]
+ public void ToolBar_ToolTip_Show_DoesNotMove_ToolBar()
+ {
+ using var form = new Form
+ {
+ StartPosition = FormStartPosition.Manual,
+ Location = new Point(100, 100),
+ Size = new Size(400, 200)
+ };
+
+ var toolBar = new ToolBar
+ {
+ ShowToolTips = true,
+ Dock = DockStyle.None,
+ Location = new Point(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.That(toolBar.Location, Is.EqualTo(new Point(0, 0)));
+
+ // Get the native tooltip HWND created by the toolbar
+ var lres = PInvoke.SendMessage(toolBar, (MessageId)NativeMethods.TB_GETTOOLTIPS);
+ HWND tooltipHwnd = (HWND)lres;
+ Assert.That((IntPtr)tooltipHwnd.Value, Is.Not.EqualTo(IntPtr.Zero), "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 NativeMethods.NMHDR { hwndFrom = toolBar.Handle, idFrom = IntPtr.Zero, code = NativeMethods.TBN_HOTITEMCHANGE },
+ idOld = -1,
+ idNew = 0,
+ dwFlags = HICF.ENTERING
+ };
+ PInvoke.SendMessage(toolBar, (MessageId)MessageId.WM_REFLECT_NOTIFY, default, ref hot);
+
+ Application.DoEvents();
+
+ // 2) Simulate TTN_SHOW from the tooltip window
+ var nmhdr = new NativeMethods.NMHDR { hwndFrom = (IntPtr)tooltipHwnd.Value, idFrom = IntPtr.Zero, code = NativeMethods.TTN_SHOW };
+ PInvoke.SendMessage(toolBar, (MessageId)MessageId.WM_REFLECT_NOTIFY, default, 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.That(toolBar.Location, Is.EqualTo(new Point(0, 0)), "ToolBar moved unexpectedly during TTN_SHOW processing");
+
+ form.Close();
+ }
+
+ [Test]
+ public void ToolBar_ToolTip_Show_Returns_1_When_Tooltip_Is_At_0_0()
+ {
+ using var form = new Form
+ {
+ StartPosition = FormStartPosition.Manual,
+ Location = new Point(200, 200),
+ Size = new Size(400, 200)
+ };
+
+ var toolBar = new ToolBar
+ {
+ ShowToolTips = true,
+ Dock = DockStyle.None,
+ Location = new Point(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.That(toolBar.Location, Is.EqualTo(new Point(10, 10)));
+
+ // Acquire native tooltip HWND
+ var lres = PInvoke.SendMessage(toolBar, (MessageId)NativeMethods.TB_GETTOOLTIPS);
+ HWND tooltipHwnd = (HWND)lres;
+ Assert.That((IntPtr)tooltipHwnd.Value, Is.Not.EqualTo(IntPtr.Zero));
+
+ // 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 NativeMethods.NMHDR { hwndFrom = toolBar.Handle, idFrom = IntPtr.Zero, code = NativeMethods.TBN_HOTITEMCHANGE },
+ idOld = -1,
+ idNew = 0,
+ dwFlags = HICF.ENTERING
+ };
+ PInvoke.SendMessage(toolBar, (MessageId)MessageId.WM_REFLECT_NOTIFY, default, ref hot);
+
+ Application.DoEvents();
+
+ // Simulate TTN_SHOW and capture the return value from WndProc
+ var nmhdr = new NativeMethods.NMHDR { hwndFrom = (IntPtr)tooltipHwnd.Value, idFrom = IntPtr.Zero, code = NativeMethods.TTN_SHOW };
+ var retL = PInvoke.SendMessage(toolBar, (MessageId)MessageId.WM_REFLECT_NOTIFY, default, ref nmhdr);
+ IntPtr ret = (IntPtr)(nint)retL;
+
+ // 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.That(ret, Is.EqualTo(new IntPtr(1)), "TTN_SHOW did not signal reposition; expected m.Result==1 when tooltip at (0,0)");
+
+ form.Close();
+ }
+
+ [Test]
+ 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(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
+
+ using var form = new Form
+ {
+ StartPosition = FormStartPosition.Manual,
+ Location = new Point(300, 300),
+ Size = new Size(500, 300)
+ };
+
+ var toolBar = new ToolBar
+ {
+ ShowToolTips = true,
+ Dock = DockStyle.None,
+ Location = new Point(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
+ var lres = PInvoke.SendMessage(toolBar, (MessageId)NativeMethods.TB_GETTOOLTIPS);
+ HWND tooltipHwnd = (HWND)lres;
+ Assert.That((IntPtr)tooltipHwnd.Value, Is.Not.EqualTo(IntPtr.Zero));
+
+ // 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 NativeMethods.NMHDR { hwndFrom = toolBar.Handle, idFrom = IntPtr.Zero, code = NativeMethods.TBN_HOTITEMCHANGE },
+ idOld = -1,
+ idNew = 0,
+ dwFlags = HICF.ENTERING
+ };
+ PInvoke.SendMessage(toolBar, (MessageId)MessageId.WM_REFLECT_NOTIFY, default, ref hot);
+
+ Application.DoEvents();
+
+ // Simulate TTN_SHOW and capture return value
+ var nmhdr = new NativeMethods.NMHDR { hwndFrom = (IntPtr)tooltipHwnd.Value, idFrom = IntPtr.Zero, code = NativeMethods.TTN_SHOW };
+ var retL = PInvoke.SendMessage(toolBar, (MessageId)MessageId.WM_REFLECT_NOTIFY, default, ref nmhdr);
+ IntPtr ret = (IntPtr)(nint)retL;
+
+ Application.DoEvents();
+
+ // Assertions: TTN_SHOW is handled (ret==1) and the toolbar itself does not move
+ Assert.That(ret, Is.EqualTo(new IntPtr(1)), "TTN_SHOW should be handled under Per-Monitor V2 context");
+ Assert.That(toolBar.Location, Is.EqualTo(originalLocation), "ToolBar moved unexpectedly during TTN_SHOW under Per-Monitor V2 context");
+
+ form.Close();
+ }
+ }
+}
diff --git a/Tests/System.Windows.Forms.Tests/TreeNodeTests.cs b/Tests/System.Windows.Forms.Tests/TreeNodeTests.cs
new file mode 100644
index 00000000000..7a4121a3280
--- /dev/null
+++ b/Tests/System.Windows.Forms.Tests/TreeNodeTests.cs
@@ -0,0 +1,155 @@
+namespace System.Windows.Forms.Tests
+{
+ using System.Drawing;
+
+ [TestFixture]
+ [SingleThreaded]
+ public class TreeNodeTests
+ {
+ [Test]
+ public void Text_SetAndGet_ReturnsCorrectValue()
+ {
+ // Arrange
+ var treeNode = new TreeNode();
+ var text = "Test Node";
+
+ // Act
+ treeNode.Text = text;
+
+ // Assert
+ Assert.That(treeNode.Text, Is.EqualTo(text));
+ }
+
+ [Test]
+ public void Nodes_AddAndGet_ReturnsCorrectNodes()
+ {
+ // Arrange
+ var parentNode = new TreeNode();
+ var childNode = new TreeNode("Child Node");
+
+ // Act
+ parentNode.Nodes.Add(childNode);
+
+ // Assert
+ Assert.That(parentNode.Nodes.Count, Is.EqualTo(1));
+ Assert.That(parentNode.Nodes[0], Is.EqualTo(childNode));
+ }
+
+ [Test]
+ 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.That(childNode.Parent, Is.EqualTo(parentNode));
+ }
+
+ [Test]
+ 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.That(parentNode.Nodes.Count, Is.EqualTo(0));
+ Assert.That(childNode.Parent, Is.Null);
+ }
+
+ [Test]
+ public void TreeView_SetAndGet_ReturnsCorrectTreeView()
+ {
+ // Arrange
+ var treeView = new TreeView();
+ var treeNode = new TreeNode("Test Node");
+
+ // Act
+ treeView.Nodes.Add(treeNode);
+
+ // Assert
+ Assert.That(treeNode.TreeView, Is.EqualTo(treeView));
+ }
+
+ [Test]
+ public void ContextMenu_SetAndGet_ReturnsCorrectValue()
+ {
+ // Arrange
+ var treeNode = new TreeNode();
+ var contextMenu = new ContextMenu();
+
+ // Act
+ treeNode.ContextMenu = contextMenu;
+
+ // Assert
+ Assert.That(treeNode.ContextMenu, Is.EqualTo(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 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/Tests/System.Windows.Forms.Tests/TreeViewTests.cs b/Tests/System.Windows.Forms.Tests/TreeViewTests.cs
new file mode 100644
index 00000000000..c6413d3cd38
--- /dev/null
+++ b/Tests/System.Windows.Forms.Tests/TreeViewTests.cs
@@ -0,0 +1,96 @@
+namespace System.Windows.Forms.Tests
+{
+ using System.Drawing;
+
+ [TestFixture]
+ [SingleThreaded]
+ public class TreeViewTests
+ {
+ [Test]
+ public void ContextMenu_SetAndGet_ReturnsCorrectValue()
+ {
+ // Arrange
+ var treeView = new TreeView();
+ var contextMenu = new ContextMenu();
+
+ // Act
+ treeView.ContextMenu = contextMenu;
+
+ // Assert
+ Assert.That(treeView.ContextMenu, Is.EqualTo(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();
+ //}
+
+ [Test]
+ 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.That(items.Count, Is.EqualTo(1));
+ Assert.That(items[0].Text, Is.EqualTo("Test Item"));
+ }
+ }
+}
diff --git a/WTG.System.Windows.Forms.Tests/Common/CommonMemberDataAttribute.cs b/WTG.System.Windows.Forms.Tests/Common/CommonMemberDataAttribute.cs
new file mode 100644
index 00000000000..a4ba915c24f
--- /dev/null
+++ b/WTG.System.Windows.Forms.Tests/Common/CommonMemberDataAttribute.cs
@@ -0,0 +1,33 @@
+using System.Reflection;
+
+namespace WTG.System.Windows.Forms.Tests.Common
+{
+ ///
+ /// A custom MemberData attribute that is specialized for the CommonTestHelper type.
+ /// Useful to remove the need to suffix all attributes with "MemberType = ..."
+ /// We cannot inherit from MemberDataAttribute as it is sealed, so we have to reimplement
+ /// ConvertDataItem inheriting from MemberDataAttributeBase.
+ ///
+ public sealed class CommonMemberDataAttribute : MemberDataAttributeBase
+ {
+ public CommonMemberDataAttribute(string memberName, params object[] parameters) : base(memberName, parameters)
+ {
+ MemberType = typeof(CommonTestHelper);
+ }
+
+ protected override object[] ConvertDataItem(MethodInfo testMethod, object item)
+ {
+ if (item is null)
+ {
+ return null;
+ }
+
+ if (!(item is object[] array))
+ {
+ throw new ArgumentException($"Property {MemberName} on {MemberType ?? testMethod.DeclaringType} yielded an item that is not an object[]");
+ }
+
+ return array;
+ }
+ }
+}
diff --git a/WTG.System.Windows.Forms.Tests/CommonTestHelper.cs b/WTG.System.Windows.Forms.Tests/CommonTestHelper.cs
new file mode 100644
index 00000000000..25b75c62de3
--- /dev/null
+++ b/WTG.System.Windows.Forms.Tests/CommonTestHelper.cs
@@ -0,0 +1,532 @@
+using System.ComponentModel.Design.Serialization;
+using System.Drawing;
+using System.Windows.Forms;
+
+namespace WTG.System.Windows.Forms.Tests
+{
+ public static class CommonTestHelper
+ {
+ // helper method to generate theory data from all values of an enum type
+ internal static TheoryData GetEnumTheoryData() where T : Enum
+ {
+ var data = new TheoryData();
+ foreach (T item in Enum.GetValues(typeof(T)))
+ {
+ data.Add(item);
+ }
+
+ return data;
+ }
+
+ public static TheoryData GetEnumTypeTheoryData(Type enumType)
+ {
+ var data = new TheoryData();
+ foreach (Enum item in Enum.GetValues(enumType))
+ {
+ data.Add(item);
+ }
+
+ return data;
+ }
+
+ // helper method to generate invalid theory data for an enum type
+ // This method assumes that int.MinValue and int.MaxValue are not in the enum
+ internal static TheoryData GetEnumTheoryDataInvalid() where T : Enum
+ {
+ var data = new TheoryData
+ {
+ // This boxing is necessary because you can't cast an int to a generic,
+ // even if the generic is guaranteed to be an enum
+ (T)(object)int.MinValue,
+ (T)(object)int.MaxValue
+ };
+ return data;
+ }
+
+ public static TheoryData GetEnumTypeTheoryDataInvalid(Type enumType)
+ {
+ var data = new TheoryData();
+ IEnumerable values = Enum.GetValues(enumType).Cast().OrderBy(p => p);
+
+ // Assumes that the enum is sequential.
+ data.Add((Enum)Enum.ToObject(enumType, Convert.ToInt32(values.Min()) - 1));
+ data.Add((Enum)Enum.ToObject(enumType, Convert.ToInt32(values.Max()) + 1));
+ return data;
+ }
+
+ #region Primitives
+
+ // helper method to generate theory data for all values of a boolean
+ public static TheoryData GetBoolTheoryData()
+ {
+ var data = new TheoryData
+ {
+ true,
+ false
+ };
+ return data;
+ }
+
+ // helper method to generate theory data for some values of a int
+ public static TheoryData GetIntTheoryData()
+ {
+ var data = new TheoryData
+ {
+ int.MinValue,
+ int.MaxValue,
+ 0,
+ 1,
+ -1,
+ int.MaxValue / 2
+ };
+ return data;
+ }
+
+ public static TheoryData GetNonNegativeIntTheoryData()
+ {
+ var data = new TheoryData
+ {
+ int.MaxValue,
+ 0,
+ 1,
+ int.MaxValue / 2
+ };
+ return data;
+ }
+
+ // helper method to generate theory data for some values of a int
+ internal static TheoryData GetUIntTheoryData()
+ {
+ var data = new TheoryData
+ {
+ int.MaxValue,
+ 0,
+ 1,
+ int.MaxValue / 2
+ };
+ return data;
+ }
+
+ // helper method to generate theory data for some values of a int
+ internal static TheoryData GetNIntTheoryData()
+ {
+ var data = new TheoryData
+ {
+ int.MinValue,
+ -1,
+ int.MinValue / 2
+ };
+ return data;
+ }
+
+ // helper method to generate theory data for some values of a int
+ internal static TheoryData GetFloatTheoryData()
+ {
+ var data = new TheoryData
+ {
+ float.MaxValue,
+ float.MinValue,
+ float.Epsilon,
+ float.Epsilon * -1,
+ float.NegativeInfinity, // not sure about these two
+ float.PositiveInfinity, // 2
+ 0,
+ -1,
+ 1,
+ float.MaxValue / 2
+ };
+ return data;
+ }
+
+ // helper method to generate theory data for some values of a int
+ internal static TheoryData GetUFloatTheoryData()
+ {
+ var data = new TheoryData
+ {
+ float.MaxValue,
+ float.Epsilon,
+ float.PositiveInfinity, // not sure about this one
+ 0,
+ 1,
+ float.MaxValue / 2
+ };
+ return data;
+ }
+
+ // helper method to generate theory data for a span of string values
+ private const string reasonable = nameof(reasonable);
+
+ public static TheoryData GetStringTheoryData()
+ {
+ var data = new TheoryData
+ {
+ string.Empty,
+ reasonable
+ };
+ return data;
+ }
+
+ public static TheoryData GetStringWithNullTheoryData()
+ {
+ var data = new TheoryData
+ {
+ null,
+ string.Empty,
+ reasonable
+ };
+ return data;
+ }
+
+ public static TheoryData GetNullOrEmptyStringTheoryData()
+ {
+ var data = new TheoryData
+ {
+ null,
+ string.Empty
+ };
+ return data;
+ }
+
+ public static TheoryData GetStringNormalizedTheoryData()
+ {
+ var data = new TheoryData
+ {
+ { null, string.Empty },
+ { string.Empty, string.Empty },
+ { reasonable, reasonable }
+ };
+ return data;
+ }
+
+ public static TheoryData GetCtrlBackspaceData()
+ {
+ var data = new TheoryData
+ {
+ { "aaa", "", 0 },
+ { "---", "", 0 },
+ { " aaa", "", 0 },
+ { " ---", "", 0 },
+ { "aaa---", "", 0 },
+ { "---aaa", "---", 0 },
+ { "aaa---aaa", "aaa---", 0 },
+ { "---aaa---", "---", 0 },
+ { "a-a", "a-", 0 },
+ { "-a-", "", 0 },
+ { "--a-", "--", 0 },
+ { "abc", "c", -1 },
+ { "a,1-b", "a,b", -1 }
+ };
+ return data;
+ }
+
+ public static TheoryData GetCtrlBackspaceRepeatedData()
+ {
+ var data = new TheoryData
+ {
+ { "aaa", "", 2 },
+ { "---", "", 2 },
+ { "aaa---aaa", "", 2 },
+ { "---aaa---", "", 2 },
+ { "aaa bbb", "", 2 },
+ { "aaa bbb ccc", "aaa ", 2 },
+ { "aaa --- ccc", "", 2 },
+ { "1 2 3 4 5 6 7 8 9 0", "1 ", 9 }
+ };
+ return data;
+ }
+
+ public static TheoryData GetCharTheoryData()
+ {
+ var data = new TheoryData
+ {
+ '\0',
+ 'a'
+ };
+ return data;
+ }
+
+ public static TheoryData GetIntPtrTheoryData()
+ {
+ var data = new TheoryData
+ {
+ (IntPtr)(-1),
+ IntPtr.Zero,
+ (IntPtr)1
+ };
+ return data;
+ }
+
+ public static TheoryData GetGuidTheoryData()
+ {
+ var data = new TheoryData
+ {
+ Guid.Empty,
+ Guid.NewGuid()
+ };
+ return data;
+ }
+
+ public static TheoryData GetColorTheoryData()
+ {
+ var data = new TheoryData
+ {
+ Color.Red,
+ Color.Blue,
+ Color.Black
+ };
+ return data;
+ }
+
+ public static TheoryData GetColorWithEmptyTheoryData()
+ {
+ var data = new TheoryData
+ {
+ Color.Red,
+ Color.Empty
+ };
+ return data;
+ }
+
+ public static TheoryData GetBackColorTheoryData()
+ {
+ return new TheoryData
+ {
+ { Color.Red, Color.Red },
+ { Color.Empty, Control.DefaultBackColor }
+ };
+ }
+
+ public static TheoryData GetForeColorTheoryData()
+ {
+ return new TheoryData
+ {
+ { Color.Red, Color.Red },
+ { Color.FromArgb(254, 1, 2, 3), Color.FromArgb(254, 1, 2, 3) },
+ { Color.Empty, Control.DefaultForeColor }
+ };
+ }
+
+ public static TheoryData GetImageTheoryData()
+ {
+ var data = new TheoryData
+ {
+ new Bitmap(10, 10),
+ null
+ };
+ return data;
+ }
+
+ public static TheoryData GetFontTheoryData()
+ {
+ var data = new TheoryData
+ {
+ SystemFonts.MenuFont,
+ null
+ };
+ return data;
+ }
+
+ public static TheoryData GetTypeWithNullTheoryData()
+ {
+ var data = new TheoryData
+ {
+ null,
+ typeof(int)
+ };
+ return data;
+ }
+
+ public static TheoryData GetRightToLeftTheoryData()
+ {
+ var data = new TheoryData
+ {
+ { RightToLeft.Inherit, RightToLeft.No },
+ { RightToLeft.Yes, RightToLeft.Yes },
+ { RightToLeft.No, RightToLeft.No }
+ };
+ return data;
+ }
+
+ public static TheoryData GetPointTheoryData() => GetPointTheoryData(TestIncludeType.All);
+
+ public static TheoryData GetPointTheoryData(TestIncludeType includeType)
+ {
+ var data = new TheoryData();
+ if (!includeType.HasFlag(TestIncludeType.NoPositives))
+ {
+ data.Add(new Point());
+ data.Add(new Point(10));
+ data.Add(new Point(1, 2));
+ }
+
+ if (!includeType.HasFlag(TestIncludeType.NoNegatives))
+ {
+ data.Add(new Point(int.MaxValue, int.MinValue));
+ data.Add(new Point(-1, -2));
+ }
+
+ return data;
+ }
+
+ public static TheoryData GetSizeTheoryData() => GetSizeTheoryData(TestIncludeType.All);
+
+ public static TheoryData GetSizeTheoryData(TestIncludeType includeType)
+ {
+ var data = new TheoryData();
+ if (!includeType.HasFlag(TestIncludeType.NoPositives))
+ {
+ data.Add(new Size());
+ data.Add(new Size(new Point(1, 1)));
+ data.Add(new Size(1, 2));
+ }
+
+ if (!includeType.HasFlag(TestIncludeType.NoNegatives))
+ {
+ data.Add(new Size(-1, 1));
+ data.Add(new Size(1, -1));
+ }
+
+ return data;
+ }
+
+ public static TheoryData GetPositiveSizeTheoryData()
+ {
+ var data = new TheoryData
+ {
+ new(),
+ new(1, 2)
+ };
+ return data;
+ }
+
+ public static TheoryData GetRectangleTheoryData()
+ {
+ var data = new TheoryData
+ {
+ new(),
+ new(1, 2, 3, 4),
+ new(-1, -2, -3, -4)
+ };
+ return data;
+ }
+
+ public static TheoryData GetPaddingTheoryData()
+ {
+ var data = new TheoryData
+ {
+ new(),
+ new(1, 2, 3, 4),
+ new(1),
+ new(-1, -2, -3, -4)
+ };
+ return data;
+ }
+
+ public static TheoryData GetPaddingNormalizedTheoryData()
+ {
+ var data = new TheoryData
+ {
+ { new Padding(), new Padding() },
+ { new Padding(1, 2, 3, 4), new Padding(1, 2, 3, 4) },
+ { new Padding(1), new Padding(1) },
+ { new Padding(-1, -2, -3, -4), Padding.Empty }
+ };
+ return data;
+ }
+
+ public static TheoryData GetConvertFromTheoryData()
+ {
+ var data = new TheoryData
+ {
+ { typeof(bool), false },
+ { typeof(InstanceDescriptor), true },
+ { typeof(int), false },
+ { typeof(double), false },
+ { null, false }
+ };
+ return data;
+ }
+
+ public static TheoryData GetCursorTheoryData()
+ {
+ var data = new TheoryData
+ {
+ null,
+ new((IntPtr)1)
+ };
+ return data;
+ }
+
+ public static TheoryData GetEventArgsTheoryData()
+ {
+ var data = new TheoryData
+ {
+ null,
+ new()
+ };
+ return data;
+ }
+
+ public static TheoryData GetPaintEventArgsTheoryData()
+ {
+ var image = new Bitmap(10, 10);
+ Graphics graphics = Graphics.FromImage(image);
+ return new TheoryData
+ {
+ null,
+ new(graphics, Rectangle.Empty)
+ };
+ }
+
+ public static TheoryData GetKeyEventArgsTheoryData()
+ {
+ var data = new TheoryData
+ {
+ null,
+ new(Keys.Cancel)
+ };
+ return data;
+ }
+
+ public static TheoryData GetKeyPressEventArgsTheoryData()
+ {
+ var data = new TheoryData
+ {
+ null,
+ new('1')
+ };
+ return data;
+ }
+
+ public static TheoryData GetLayoutEventArgsTheoryData()
+ {
+ var data = new TheoryData
+ {
+ null,
+ new(null, null),
+ new(new Control(), "affectedProperty")
+ };
+ return data;
+ }
+
+ public static TheoryData GetMouseEventArgsTheoryData()
+ {
+ return new TheoryData
+ {
+ null,
+ new(MouseButtons.Left, 1, 2, 3, 4),
+ new HandledMouseEventArgs(MouseButtons.Left, 1, 2, 3, 4)
+ };
+ }
+
+ #endregion
+ }
+
+ [Flags]
+ public enum TestIncludeType
+ {
+ All,
+ NoPositives,
+ NoNegatives
+ }
+}
diff --git a/WTG.System.Windows.Forms.Tests/ContextMenuTests.cs b/WTG.System.Windows.Forms.Tests/ContextMenuTests.cs
new file mode 100644
index 00000000000..a158fc5e873
--- /dev/null
+++ b/WTG.System.Windows.Forms.Tests/ContextMenuTests.cs
@@ -0,0 +1,299 @@
+using System.ComponentModel;
+using System.Drawing;
+using System.Windows.Forms;
+using WTG.System.Windows.Forms.Tests.Common;
+
+namespace WTG.System.Windows.Forms.Tests
+{
+ public class ContextMenuTests
+ {
+ [Fact]
+ public void ContextMenu_Ctor_Default()
+ {
+ var menu = new ContextMenu();
+ Assert.Empty(menu.MenuItems);
+ Assert.False(menu.IsParent);
+ Assert.Equal(RightToLeft.No, menu.RightToLeft);
+ Assert.Null(menu.SourceControl);
+ Assert.Empty(menu.Name);
+ Assert.Null(menu.Site);
+ Assert.Null(menu.Container);
+ Assert.Null(menu.Tag);
+ }
+
+ public static IEnumerable