dotnet-winforms-basics について
dotnet-winforms-basicsは、.NET 8+のWinFormsを扱うコーディングエージェントや開発者向けのSkillです。Host builder、依存性注入、高DPI、実験段階のダークモードに加え、適した用途とモダナイズの指針を解説します。
主な機能
- 01.NET 8+のプロジェクトとHost builderの構成
- 02Host builderによる依存性注入
- 03PerMonitorV2の高DPI設定
- 04ダークモードと制約の説明
- 05モダナイズと用途選定の指針
活用シーン
- 01依存性注入を使うWinFormsアプリの作成
- 02複数モニター向けのDPIスケーリング設定
- 03社内ツールにWinFormsが適するかの評価
- 04.NET Frameworkプロジェクトのモダナイズ計画
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill dotnet-winforms-basicsインストール後、Claude Code で/dotnet-winforms-basicsを入力して読み込まれたか確認します。
詳細
dotnet-winforms-basics
.NET 8 以降の WinForms:Host ビルダーと DI を備えた更新版プロジェクトテンプレート、PerMonitorV2 による高 DPI 対応、Application.SetColorMode によるダークモード(.NET 9 では実験的機能で、.NET 11 での最終化を目標)、WinForms を使用する場面、.NET Framework から移行する際のモダナイズのヒント、エージェントが陥りやすい一般的な落とし穴について説明します。
バージョンの前提: .NET 8.0 以降を基準とします(現行の LTS)。TFM は net8.0-windows です。.NET 9 の機能(実験的なダークモード)には明示的に注記します。.NET 11 での最終化目標についても記載します。
対象範囲: このスキルでは、モダン .NET の WinForms パターン(プロジェクトのセットアップ、高 DPI、ダークモード、DI、使用する場面、モダナイズ)を扱います。.NET Framework から .NET 8 以降への移行は [skill:dotnet-wpf-migration] が担当します。デスクトップのテストは [skill:dotnet-ui-testing-core] が担当します。
対象外: WinForms の .NET Framework パターン(レガシー)—このスキルの対象は .NET 8 以降のみです。移行ガイダンスについては [skill:dotnet-wpf-migration] を参照してください。デスクトップのテストについては [skill:dotnet-ui-testing-core] を参照してください。一般的な Native AOT パターンについては [skill:dotnet-native-aot] を参照してください。UI フレームワークの選定については [skill:dotnet-ui-chooser] を参照してください。
相互参照:デスクトップのテストについては [skill:dotnet-ui-testing-core]、WPF のパターンについては [skill:dotnet-wpf-modern]、WinUI 3 のパターンについては [skill:dotnet-winui]、移行ガイダンスについては [skill:dotnet-wpf-migration]、一般的な AOT については [skill:dotnet-native-aot]、フレームワークの選定については [skill:dotnet-ui-chooser] を参照してください。
.NET 8 以降の変更点
.NET 8 以降の WinForms は、SDK スタイルのプロジェクト形式、DI のサポート、更新された API を備え、.NET Framework の WinForms から大幅にモダナイズされています。
新しいプロジェクトテンプレート
<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>
.NET Framework の WinForms からの主な変更点:
- SDK スタイルの
.csproj(packages.configとAssemblyInfo.csは不要) - 既定で有効な Nullable 参照型
- 暗黙的な using の有効化
- NuGet の
PackageReference形式 Program.csでトップレベルステートメントを使用dotnet publishで単一のデプロイ成果物を生成- .NET のサイドバイサイドインストール(コンピューター全体に対するフレームワーク依存なし)
Host ビルダーパターン
モダンな WinForms アプリでは、依存性注入に汎用ホストを使用します。
// 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
.NET 8 以降の WinForms では、複数のレガシーな設定呼び出しをまとめた ApplicationConfiguration.Initialize() をエントリーポイントとして使用します。
// ApplicationConfiguration.Initialize() is equivalent to:
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.SetHighDpiMode(HighDpiMode.SystemAware); // default; override below for PerMonitorV2
高 DPI
.NET 8 以降の WinForms では、高 DPI のサポートが大幅に強化されています。推奨モードは PerMonitorV2 で、モニターごとの DPI 変更を自動的に処理します。
PerMonitorV2 を有効にする
// 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
}
}
}
高 DPI モード:
| モード | 値 | 動作 |
|---|---|---|
DpiUnaware | 0 | 拡大縮小なし。システムがウィンドウのビットマップを引き伸ばします |
SystemAware | 1 | 起動時にプライマリモニターの DPI に合わせて拡大縮小します(.NET 8 の既定値) |
PerMonitor | 2 | モニター間の移動時に調整します(基本機能) |
PerMonitorV2 | 3 | 非クライアント領域のサポートを含む、モニターごとの完全な拡大縮小 (推奨) |
DpiUnawareGdiScaled | 4 | DPI を認識しませんが、GDI+ のテキストはネイティブ解像度でレンダリングされます |
DPI 非対応のデザイナーモード(.NET 9 以降)
.NET 9 では、Visual Studio の WinForms デザイナーでレイアウトの拡大縮小に関する問題を防ぐ、DPI 非対応のデザイナーモードが導入されます。システムの DPI にかかわらずデザイナーは 96 DPI でレンダリングされるため、.Designer.cs ファイルの破損を防げます。
<PropertyGroup>
<ForceDesignerDPIUnaware>true</ForceDesignerDPIUnaware>
</PropertyGroup>
拡大縮小時の注意点
- コントロールに絶対ピクセルサイズを使用しないでください。 フォームで
AutoScaleMode.Dpiを使用し、レイアウトエンジンによるコントロールの自動拡大縮小を利用してください。 - Anchor と Dock によるレイアウトは、絶対位置指定よりも拡大縮小に適しています。
TableLayoutPanelとFlowLayoutPanelは、固定位置のコントロールよりも DPI 変更を確実に処理します。 - カスタム描画(OnPaint)では DPI 対応の座標を使用してください。
OnPaintのオーバーライド内で、描画座標にDeviceDpi / 96.0fを乗じて拡大縮小します。 - 画像リソースには複数の解像度が必要です。 アイコンや画像の 1x、1.5x、2x の各バージョンを用意するか、SVG ベースのレンダリングを使用してください。
// 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);
}
ダークモード
WinForms のダークモードは .NET 9 では実験的であり、.NET 11 での最終化を目指しています。Windows のダークモード API を使用し、WinForms コントロールにシステムと統合されたダークモードを提供します。
ダークモードを有効にする(.NET 9 以降、実験的)
// Program.cs -- set before ApplicationConfiguration.Initialize()
Application.SetColorMode(SystemColorMode.Dark);
ApplicationConfiguration.Initialize();
または、システムのテーマに従います。
// Follow system light/dark preference
Application.SetColorMode(SystemColorMode.System);
SystemColorMode の値:
| モード | 動作 |
|---|---|
Classic | 標準の WinForms の色(ダークモードなし) |
System | Windows のシステムのライト/ダークテーマ設定に従います |
Dark | ダークモードを強制します |
ダークモードに関する注意事項
- 実験的な状態: .NET 11 で最終化されるまでに、API の仕様が変更される可能性があります。本番環境では、特定の色の値や描画動作に依存しないでください。
- コントロールの対応状況: .NET 9 では、すべてのコントロールがダークモードに対応しているわけではありません。標準コントロール(Button、TextBox、Label、ListBox、DataGridView)はダークモードに対応しています。サードパーティ製コントロールや独自描画のコントロールは、正しく描画されない場合があります。
- オーナー描画コントロール:
DrawMode.OwnerDrawFixedまたは独自のOnPaintオーバーライドを使用するコントロールは、ダークモードに対応するためにSystemColorsを手動で読み取る必要があります。ダークモードの色は自動的に継承されません。 - Windows のバージョン: ダークモードには、Windows 10 バージョン 1809(ビルド 17763)以降が必要です。
- .NET 11 での目標: Microsoft は、ダークモードを含む WinForms のビジュアルスタイルについて、.NET 11 での最終化を目指していると表明しています。このリリース以降の API の安定性を見込んで計画してください。
// 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);
}
使用する場面
WinForms は、特定のシナリオに適しています。新しい顧客向けアプリケーションを開発するための汎用 UI フレームワークではありません。
適している用途
- 迅速なプロトタイピング: ドラッグアンドドロップ式のデザイナーを使い、社内ツールや概念実証用 UI をすばやく作成できます
- 社内業務ツール: 業務フォーム、データ入力、DataGridView を使った CRUD アプリケーション
- シンプルな Windows 専用ユーティリティ: システムトレイアプリ、構成ツール、診断ダッシュボード
- 既存の WinForms アプリケーションの保守: 既存の .NET Framework WinForms アプリを .NET 8 以降にモダナイズできます
- 大量のデータを扱う表形式 UI: DataGridView の仮想モードにより、数百万行を効率的に処理できます
適していない用途
- 新しい顧客向けアプリケーション: WPF(Windows 向けの豊富なデスクトップ機能)、WinUI 3(モダンな Windows)、MAUI(クロスプラットフォーム)、または Blazor(Web)を使用してください
- 複雑なカスタム UI: WinForms のコントロールはスタイル設定に制限があります。WPF または WinUI では、豊富なテンプレート機能を利用できます
- クロスプラットフォーム要件: WinForms は Windows 専用です。MAUI または Uno Platform を使用してください
- アクセシビリティを最優先するアプリケーション: WPF と WinUI は、アクセシビリティ API とスクリーンリーダーのサポートが優れています
- タッチ操作に最適化されたインターフェース: WinForms はマウスとキーボード向けに設計されています。タッチ操作には WinUI または MAUI のほうが適しています
選択の指針
| シナリオ | 推奨フレームワーク |
|---|---|
| すばやく作成する社内ツール | WinForms |
| データ入力フォーム(Windows) | WinForms または WPF |
| モダンな Windows デスクトップアプリ | WinUI 3 または WPF(.NET 9 以降の Fluent) |
| クロスプラットフォームのモバイルとデスクトップ | MAUI または Uno Platform |
| クロスプラットフォームと Web | Uno Platform または Blazor |
| 既存の WinForms アプリケーションのモダナイズ | .NET 8 以降の WinForms |
フレームワーク選択の完全な決定ツリーについては、[skill:dotnet-ui-chooser] を参照してください。
モダナイズのヒント
既存の .NET Framework WinForms アプリケーションを .NET 8 以降にモダナイズするためのヒントです。
依存性の注入を追加する
静的参照やシングルトンを、Host ビルダーによるコンストラクターインジェクションに置き換えます(上記の「.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();
}
}
非同期パターンを使用する
同期的に処理をブロックする呼び出しを async/await に置き換え、UI の応答性を維持します。
// 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;
}
}
.NET 8 以降に移行する
自動移行には、.NET Upgrade Assistant を使用します。
# Install upgrade assistant
dotnet tool install -g upgrade-assistant
# Analyze the project
upgrade-assistant analyze MyWinFormsApp.csproj
# Upgrade the project
upgrade-assistant upgrade MyWinFormsApp.csproj
よくある移行時の問題:
App.configの設定は、appsettings.jsonまたは Host ビルダーの構成に手動で移行する必要がありますMy.Settings(VB.NET)とSettings.settingsは手動で移行する必要があります- サードパーティ製コントロールには、.NET 8 対応バージョンがない場合があります
.Designer.csファイル内のデザイナー生成コードは、通常問題なく移行できます- COM 相互運用(
System.Runtime.InteropServices)の構文は異なる場合があります
最新の C# 機能を採用する
// 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") ?? [];
}
エージェント利用時の注意点
- 顧客向けの新しいアプリケーションに WinForms を推奨しないでください。 WinForms は、社内ツール、迅速なプロトタイピング、データ中心のユーティリティに適しています。顧客向けアプリには、要件に応じて WPF、WinUI 3、MAUI、または Blazor を推奨してください。
- 非推奨の WinForms API を使用しないでください。
Menu(MenuStripを使用)、MainMenu(MenuStripを使用)、ContextMenu(ContextMenuStripを使用)、StatusBar(StatusStripを使用)、ToolBar(ToolStripを使用)、DataGrid(DataGridViewを使用)。 - ダークモードが本番環境で使用できる状態だと想定しないでください。
Application.SetColorModeによるダークモードは .NET 9 で実験段階であり、.NET 11 での最終化を目指しています。API のサーフェスとレンダリングは変更される可能性があります。 - マルチモニター環境をテストせずに
HighDpiMode.SystemAwareを使用しないでください。 DPI 設定が異なる複数のモニターで使用するアプリには、PerMonitorV2が推奨されます。 - 同期呼び出しで UI スレッドをブロックしないでください。 イベントハンドラーには
async voidを、その他の非同期メソッドにはasync Taskを使用してください。UI スレッドで.Resultや.Wait()を使用しないでください。 awaitで済む場合はControl.Invokeを使用しないでください。 .NET 8 以降の WinForms では、awaitによってSynchronizationContext経由で UI スレッドに自動的に戻ります。手動のInvoke/BeginInvokeが必要なのは、非同期ではないコード(タイマー、COM コールバック)から呼び出す場合のみです。- ダークモードを有効にした場合、色をハードコードしないでください。 カスタム描画やオーナー描画コントロールでは、
SystemColors.ControlTextやSystemColors.ControlなどのSystemColorsプロパティを使用して、テーマの変更に正しく対応してください。 Application.Runの前にApplicationConfiguration.Initialize()を呼び出すのを忘れないでください。 省略すると、ビジュアルスタイルと高 DPI の構成が無効になります。
前提条件
- Windows デスクトップ ワークロードを含む .NET 8.0 以降
- TFM:
net8.0-windows(WinForms では Windows SDK のバージョン指定は不要) - デザイナーのサポートには、Windows デスクトップ ワークロードを含む Visual Studio 2022 以降
- ダークモードの場合:.NET 9 以降(実験段階)、Windows 10 バージョン 1809 以降
- DPI 非対応のデザイナーの場合:.NET 9 以降