汎用ホストでWindowsフォームアプリケーションを実行する


## このドキュメントの内容

.NET の汎用ホストを使って Windows フォームアプリケーションを実行する方法をライブラリ化しました。

## 汎用ホストとは

ASP.NET やサービスアプリケーションを開発している方にとってはおなじみですが、アプリケーションの実行に必要なリソースを管理してくれる実行基盤のことで、あらかじめコンフィグやログ出力の機能が組み込まれています。MAUI も汎用ホストに対応したことからも、.NET アプリケーションの標準的なインフラストラクチャであると言えます。これまで Windows フォームアプリケーションやコンソールアプリケーションを中心に開発してきた方も汎用ホストに慣れておくメリットは十分にあると思います。

https://learn.microsoft.com/ja-jp/dotnet/core/extensions/generic-host

https://qiita.com/nozmiz/items/a7c6a7d33c9f67c79336

## 公開先

GitHub と Nuget で公開しています。

https://github.com/mxProject/WindowsFormGenericHost

https://www.nuget.org/packages/mxProject.WindowsFormGenericHost

https://www.nuget.org/packages/mxProject.WindowsFormGenericHost.NETFramework

## 実装例

エントリポイントで Application.Run メソッドを呼ぶ代わりに、汎用ホスト上でアプリケーションを実行するように書き換えます。

```c#:エントリポイントの実装例
using Microsoft.Extensions.Hosting;
using mxProject.WindowFormHosting;

static void Main(string[] args)
{
#if NET6_0
    ApplicationConfiguration.Initialize();
#else
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
#endif

    // 通常の起動方法
    // Application.Run(new Form1());

    // Windwosフォームアプリケーションを実行します
    // このメソッドの実装を以降で説明します
    RunWindowsFormApp(args);
}
```

以降の実装例に現れる MyAppContext クラスは mxProject.WindowFormHosting.IWindowsFormAppContext インターフェースを実装したクラスです。固有の型のコンテキストを利用する場合は、このインターフェースを実装するコンテキストクラスを定義してください。

### メインフォームの型を指定する

```c:Windowsフォームアプリケーションの実装例1
/// <summary>
/// メインフォームの型を指定してWindowsフォームアプリケーションを実行します。
/// </summary>
static void RunWindowsFormApp(string[] args)
{
    void OnStart(IWindowsFormAppContext context) { }
    void OnExit(IWindowsFormAppContext context) { }

    IHostBuilder builder = Host.CreateDefaultBuilder(args)
        .AddWindowsFormApp<Form1>(onStart: OnStart, onExit: OnExit)
        ;

    builder.Build().Run();
}
```

### メインフォームとコンテキストの型を指定する

```c#:Windowsフォームアプリケーションの実装例2
/// <summary>
/// メインフォームとコンテキストの型を指定してWindowsフォームアプリケーションを実行します。
/// </summary>
static void RunWindowsFormApp(string[] args)
{
    void OnStart(MyAppContext context) { }
    void OnExit(MyAppContext context) { }

    IHostBuilder builder = Host.CreateDefaultBuilder(args)
        .AddWindowsFormApp<MyAppContext, Form1>(onStart: OnStart, onExit: OnExit);

    builder.Build().Run();
}
```

### アプリケーション情報を指定する

```c#:Windowsフォームアプリケーションの実装例3
/// <summary>
/// アプリケーション情報を指定してWindowsフォームアプリケーションを実行します。
/// </summary>
static void RunWindowsFormApp(string[] args)
{
    IHostBuilder builder = Host.CreateDefaultBuilder(args)
        .AddWindowsFormApp(new MyWindowsFormAppInfo());

    builder.Build().Run();
}

internal class MyWindowsFormAppInfo : IWindowsFormAppInfo<MyAppContext>
{
    internal MyWindowsFormAppInfo() {}

    // 実行時にメインフォームの型を決定できます
    public Type StartupObjectType => typeof(Form1)

    public void OnStart(MyAppContext context) {}
    public void OnExit(MyAppContext context) {}
}
```

### タスクトレイ常駐アプリケーションとして実行する

```c#:タスクトレイ常駐アプリケーションの実装例1
/// <summary>
/// 通知アイコンの型を指定してタスクトレイ常駐アプリケーションを実行します。
/// </summary>
static void RunWindowsFormApp(string[] args)
{
    void OnStart(IWindowsFormAppContext context) { }
    void OnExit(IWindowsFormAppContext context) { }

    IHostBuilder builder = Host.CreateDefaultBuilder(args)
        .AddTaskTrayApp<MyNotifyIconProvider>(onStart: OnStart, onExit: OnExit);

    builder.Build().Run();
}

/// <summary>
/// 通知アイコンを生成します。
/// </summary>
internal class MyNotifyIconProvider : INotifyIconProvider
{
    public MyNotifyIconProvider(IWindowsFormAppContext context)    {
        m_Context = context;
    }

    private readonly IWindowsFormAppContext m_Context;

    /// <summary>
    /// 通知アイコンを生成します。
    /// </summary>
    public NotifyIcon CreateNotifyIcon()
    {
        return new NotifyIcon
        {
            Icon = new System.Drawing.Icon("sample.ico"),
            ContextMenuStrip = CreateContextMenu(),
            Text = "サンプルアプリケーション",
            Visible = true
        };
    }

    private ContextMenuStrip CreateContextMenu()
    {
        var menu = new ContextMenuStrip();

        menu.Items.Add("フォーム１を表示", null, (sender, e) =>
        {
            using var form = m_Context.FormProvider.CreateForm<Form1>();
            form.ShowDialog();
        });

        menu.Items.Add("フォーム２を表示", null, (sender, e) =>
        {
            using var form = m_Context.FormProvider.CreateForm<Form2>();
            form.ShowDialog();
        });

        menu.Items.Add("終了", null, (sender, e) =>
        {
            Application.Exit();
        });

        return menu;
    }
}
```

### コンテキストの型を指定してタスクトレイ常駐アプリケーションとして実行する

```c#:タスクトレイ常駐アプリケーションの実装例2
/// <summary>
/// 通知アイコンとコンテキストの型を指定してタスクトレイ常駐アプリケーションを実行します。
/// </summary>
static void RunWindowsFormApp(string[] args)
{
    void OnStart(MyAppContext context) { }
    void OnExit(MyAppContext context) { }

    IHostBuilder builder = Host.CreateDefaultBuilder(args)
        .AddTaskTrayApp<MyAppContext, MyNotifyIconProvider>(onStart: OnStart, onExit: OnExit);

    builder.Build().Run();
}

/// <summary>
/// 通知アイコンを生成します。
/// </summary>
internal class MyNotifyIconProvider : INotifyIconProvider
{
    public MyNotifyIconProvider(MyAppContext context)    {
        m_Context = context;
    }

    private readonly MyAppContext m_Context;

    // 以降割愛
}
```