mcpField
D
dotnet-winforms-basics

dotnet-winforms-basics

github.com/wshaddix開発86289 回インストール35 週間前に更新
リポジトリ

dotnet-winforms-basics について

dotnet-winforms-basicsは、.NET 8+のWinFormsを扱うコーディングエージェントや開発者向けのSkillです。Host builder、依存性注入、高DPI、実験段階のダークモードに加え、適した用途とモダナイズの指針を解説します。

主な機能

  1. 01.NET 8+のプロジェクトとHost builderの構成
  2. 02Host builderによる依存性注入
  3. 03PerMonitorV2の高DPI設定
  4. 04ダークモードと制約の説明
  5. 05モダナイズと用途選定の指針

活用シーン

  1. 01依存性注入を使うWinFormsアプリの作成
  2. 02複数モニター向けのDPIスケーリング設定
  3. 03社内ツールにWinFormsが適するかの評価
  4. 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 モード:

モード値動作
DpiUnaware0拡大縮小なし。システムがウィンドウのビットマップを引き伸ばします
SystemAware1起動時にプライマリモニターの DPI に合わせて拡大縮小します(.NET 8 の既定値)
PerMonitor2モニター間の移動時に調整します(基本機能)
PerMonitorV23非クライアント領域のサポートを含む、モニターごとの完全な拡大縮小 (推奨)
DpiUnawareGdiScaled4DPI を認識しませんが、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 の色(ダークモードなし)
SystemWindows のシステムのライト/ダークテーマ設定に従います
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
クロスプラットフォームと WebUno 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") ?? [];
}

エージェント利用時の注意点

  1. 顧客向けの新しいアプリケーションに WinForms を推奨しないでください。 WinForms は、社内ツール、迅速なプロトタイピング、データ中心のユーティリティに適しています。顧客向けアプリには、要件に応じて WPF、WinUI 3、MAUI、または Blazor を推奨してください。
  2. 非推奨の WinForms API を使用しないでください。 Menu(MenuStrip を使用)、MainMenu(MenuStrip を使用)、ContextMenu(ContextMenuStrip を使用)、StatusBar(StatusStrip を使用)、ToolBar(ToolStrip を使用)、DataGrid(DataGridView を使用)。
  3. ダークモードが本番環境で使用できる状態だと想定しないでください。 Application.SetColorMode によるダークモードは .NET 9 で実験段階であり、.NET 11 での最終化を目指しています。API のサーフェスとレンダリングは変更される可能性があります。
  4. マルチモニター環境をテストせずに HighDpiMode.SystemAware を使用しないでください。 DPI 設定が異なる複数のモニターで使用するアプリには、PerMonitorV2 が推奨されます。
  5. 同期呼び出しで UI スレッドをブロックしないでください。 イベントハンドラーには async void を、その他の非同期メソッドには async Task を使用してください。UI スレッドで .Result や .Wait() を使用しないでください。
  6. await で済む場合は Control.Invoke を使用しないでください。 .NET 8 以降の WinForms では、await によって SynchronizationContext 経由で UI スレッドに自動的に戻ります。手動の Invoke/BeginInvoke が必要なのは、非同期ではないコード(タイマー、COM コールバック)から呼び出す場合のみです。
  7. ダークモードを有効にした場合、色をハードコードしないでください。 カスタム描画やオーナー描画コントロールでは、SystemColors.ControlText や SystemColors.Control などの SystemColors プロパティを使用して、テーマの変更に正しく対応してください。
  8. 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 以降

参考資料

コメント