Loading...
Loading...
Building WinForms on .NET 8+. High-DPI, dark mode (experimental), DI patterns, modernization tips.
npx skill4agent add wshaddix/dotnet-skills dotnet-winforms-basicsPerMonitorV2Application.SetColorModenet8.0-windows<!-- MyWinFormsApp.csproj (SDK-style) -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.*" />
</ItemGroup>
</Project>.csprojpackages.configAssemblyInfo.csPackageReferenceProgram.csdotnet publish// Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
ApplicationConfiguration.Initialize();
var host = Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
// Services
services.AddSingleton<IProductService, ProductService>();
services.AddSingleton<ISettingsService, SettingsService>();
// HTTP client
services.AddHttpClient("api", client =>
{
client.BaseAddress = new Uri("https://api.example.com");
});
// Forms
services.AddTransient<MainForm>();
services.AddTransient<ProductDetailForm>();
})
.Build();
var mainForm = host.Services.GetRequiredService<MainForm>();
Application.Run(mainForm);// MainForm.cs -- constructor injection
public partial class MainForm : Form
{
private readonly IProductService _productService;
private readonly IServiceProvider _serviceProvider;
public MainForm(IProductService productService, IServiceProvider serviceProvider)
{
_productService = productService;
_serviceProvider = serviceProvider;
InitializeComponent();
}
private async void btnLoad_Click(object sender, EventArgs e)
{
var products = await _productService.GetProductsAsync();
dataGridProducts.DataSource = products.ToList();
}
private void btnDetails_Click(object sender, EventArgs e)
{
var detailForm = _serviceProvider.GetRequiredService<ProductDetailForm>();
detailForm.ShowDialog();
}
}ApplicationConfiguration.Initialize()// ApplicationConfiguration.Initialize() is equivalent to:
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.SetHighDpiMode(HighDpiMode.SystemAware); // default; override below for PerMonitorV2PerMonitorV2// Program.cs -- set before ApplicationConfiguration.Initialize()
Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
ApplicationConfiguration.Initialize();
// Note: SetHighDpiMode() called before Initialize() takes precedence
// over the default SystemAware mode set by Initialize().runtimeconfig.json{
"runtimeOptions": {
"configProperties": {
"System.Windows.Forms.ApplicationHighDpiMode": 3
}
}
}| Mode | Value | Behavior |
|---|---|---|
| 0 | No scaling; system bitmap-stretches the window |
| 1 | Scales to primary monitor DPI at startup (default in .NET 8) |
| 2 | Adjusts when moved between monitors (basic) |
| 3 | Full per-monitor scaling with non-client area support (recommended) |
| 4 | DPI-unaware but GDI+ text renders at native resolution |
.Designer.cs<!-- .csproj: opt in to DPI-unaware designer (.NET 9+) -->
<PropertyGroup>
<ForceDesignerDPIUnaware>true</ForceDesignerDPIUnaware>
</PropertyGroup>AutoScaleMode.DpiTableLayoutPanelFlowLayoutPanelDeviceDpi / 96.0fOnPaint// DPI-aware custom drawing
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
float scale = DeviceDpi / 96.0f;
float fontSize = 12.0f * scale;
using var font = new Font("Segoe UI", fontSize);
e.Graphics.DrawString("Scaled text", font, Brushes.Black, 10 * scale, 10 * scale);
}// Program.cs -- set before ApplicationConfiguration.Initialize()
Application.SetColorMode(SystemColorMode.Dark);
ApplicationConfiguration.Initialize();// Follow system light/dark preference
Application.SetColorMode(SystemColorMode.System);| Mode | Behavior |
|---|---|
| Standard WinForms colors (no dark mode) |
| Follow Windows system light/dark theme setting |
| Force dark mode |
DrawMode.OwnerDrawFixedOnPaintSystemColors// Owner-drawn controls must use SystemColors for dark mode compatibility
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// Use SystemColors instead of hardcoded colors
using var textBrush = new SolidBrush(SystemColors.ControlText);
using var bgBrush = new SolidBrush(SystemColors.Control);
e.Graphics.FillRectangle(bgBrush, ClientRectangle);
e.Graphics.DrawString("Text", Font, textBrush, 10, 10);
}| Scenario | Recommended Framework |
|---|---|
| Quick internal tool | WinForms |
| Data entry form (Windows) | WinForms or WPF |
| Modern Windows desktop app | WinUI 3 or WPF (.NET 9+ Fluent) |
| Cross-platform mobile + desktop | MAUI or Uno Platform |
| Cross-platform + web | Uno Platform or Blazor |
| Existing WinForms modernization | WinForms on .NET 8+ |
// Anti-pattern: static service references
public partial class MainForm : Form
{
private void btnLoad_Click(object sender, EventArgs e)
{
var products = ProductService.Instance.GetProducts();
dataGridProducts.DataSource = products;
}
}// Modern: constructor injection
public partial class MainForm : Form
{
private readonly IProductService _productService;
public MainForm(IProductService productService)
{
_productService = productService;
InitializeComponent();
}
private async void btnLoad_Click(object sender, EventArgs e)
{
var products = await _productService.GetProductsAsync();
dataGridProducts.DataSource = products.ToList();
}
}// Before: blocks UI thread
private void btnSave_Click(object sender, EventArgs e)
{
var client = new HttpClient();
var result = client.PostAsync(url, content).Result; // BLOCKS UI
MessageBox.Show("Saved!");
}
// After: async keeps UI responsive
private async void btnSave_Click(object sender, EventArgs e)
{
btnSave.Enabled = false;
try
{
var result = await _httpClient.PostAsync(url, content);
result.EnsureSuccessStatusCode();
MessageBox.Show("Saved!");
}
catch (HttpRequestException ex)
{
MessageBox.Show($"Error: {ex.Message}");
}
finally
{
btnSave.Enabled = true;
}
}# Install upgrade assistant
dotnet tool install -g upgrade-assistant
# Analyze the project
upgrade-assistant analyze MyWinFormsApp.csproj
# Upgrade the project
upgrade-assistant upgrade MyWinFormsApp.csprojApp.configappsettings.jsonMy.SettingsSettings.settings.Designer.csSystem.Runtime.InteropServices// File-scoped namespaces
namespace MyApp.Forms;
// Null-conditional event invocation
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
// Collection expressions
var columns = new[] { "Name", "Price", "Category" };
// Primary constructors for services (C# 12)
public class ProductService(HttpClient httpClient) : IProductService
{
public async Task<List<Product>> GetProductsAsync()
=> await httpClient.GetFromJsonAsync<List<Product>>("/products") ?? [];
}MenuMenuStripMainMenuMenuStripContextMenuContextMenuStripStatusBarStatusStripToolBarToolStripDataGridDataGridViewApplication.SetColorModeHighDpiMode.SystemAwarePerMonitorV2async voidasync Task.Result.Wait()Control.InvokeawaitawaitSynchronizationContextInvokeBeginInvokeSystemColorsSystemColors.ControlTextSystemColors.ControlApplicationConfiguration.Initialize()Application.Runnet8.0-windows