WPFでタスクトレイアイコンを使う【令和7年最新版】

タイトルは某ECサイトを参考にしました。なので中身は胡散臭いです:-)

令和7(2025)年になってからも.NET10に向けに若干の変更があったようですが、いまだMicrosoft Copilotですら「ソリューションを右クリックして参照の追加で云々...」とぬかしやがるので[^1]将来でもなるべくコード修正しないで済むようにまとめておきます。
以下の記事を元にさせていただきました。ありがとうございます。
[^1]:最新のVisualStudio2022でWPFの場合にはそんな右クリックメニューすら出ない

https://qiita.com/TiggeZaki/items/aa17edbef0cc5f4736d9

## アイコンの準備
変更ありません。いつものようにリソースとして使えるようにしておいてください。
## Formクラスの準備
これも変更ありません。`.csproj`ファイルに`<UseWindowsForms>True</UseWindowsForms>`を追加してください。
``` foobar.csproj
<PropertyGroup>
    :
    <UseWPF>true</UseWPF>
    <UseWindowsForms>True</UseWindowsForms>
</PropertyGroup>
```
## 「あいまいな参照」の解決
ここに変更というか追加があります。.csprojに上記を追加した後WPFとWindows Formsに同名のクラスがあってもいままではいい感じに解決してくれていたようですが、今では参照の解決方法が変わったのか同名のクラスは「XXXとYYY間のあいまいな参照です」と言われまくります[^2]。
[^2]: Point型やColor型、MessageBoxを使っただけで言われます。

[破壊的変更 - WPF と WinForms の両方を参照するアプリケーションは、MenuItem 型と ContextMenu 型のあいまいさを解消する必要があります](https://learn.microsoft.com/ja-jp/dotnet/core/compatibility/windows-forms/10.0/menuitem-contextmenu)

エラーが出たところひとつひとつ使いたい方で完全修飾してあげればいいのですが、ソース本体が非常に見づらくなるので数が多い場合はusingを使ってエリアスを設定してあげます。
``` MainWindow.cs
using Application = System.Windows.Application;
//System.Windows.Forms.Applicationとバッティングするため。どちらを使うのかエリアスを使って明示する
//以下必要なだけ追加
:
```
## タスクトレイアイコンを作る
Formsのコントロール部品ですのでXAMLには書けないしFormsアプリのようにデザイナーは使えないのでコードビハインドにゴリゴリ記述します。
自分の場合、(起動時から最小化でスタートすることを想定していないため?)コンストラクタから設定するようにしました。
### 各メニューアイテムの作成
後でメニューの追加・削除が楽なように`Dictionaly`、`List`を使用して連続的に登録しています。Listに格納しているMenuItem名は例ですので適宜お好きな名前(表示)に変更してください。`"-"`はセパレータを表示します。

```MainWindow.cs
:
using System.Reflection;

public partial class MainWindow : Window
{
    private NotifyIcon notifyIcon = new();
    private readonly Dictionary<string, ToolStripMenuItem> toolStripMenuItemsDict = [];

    public MainWindow()
    {
        InitializeComponent();

        SetNotifyIcon();
        :
    }
        
    private void SetNotifyIcon()
    {
        List<string> toolStripMenuItemsList = ["Config", "-", "Spectrum", "Level Meter", "Level Stream", "-", "Reload Device", "-", "Exit"];
        var toolStripMenu = new ContextMenuStrip();
        foreach (var item in toolStripMenuItemsList)
        {
            if (item != "-")
            {
                toolStripMenuItemsDict[item] = new ToolStripMenuItem()
                {
                    Text = item,
                    Image = null,
                };
                toolStripMenu.Items.Add(toolStripMenuItemsDict[item]);
                :
```
メニューを表示したときチェックマークを付けるところは`toolStripMenuItemsDict["Spectrum"].Checked = true;`のように追加しておいてください。もちろん後でやってもOKです[^3]。
[^3]:当然ですがFormsなので`.IsChecked`ではありません。

同時に`Refrection`で`item`を使って各メニューのClickのイベントハンドラを登録します。その際、表示名から不要なスペースを削除してメソッド名を作成し、`private`でも検索できるよう`BindingFlags.Instance | BindingFlags.NonPublic`フラグを立てます。
```MainWindow.cs
                :
                toolStripMenuItemsDict[item].Click += (s, e) =>
                {
                    var method = GetType().GetMethod(item.Replace(" ", "") + "ToolStripMenuItem_Click", BindingFlags.Instance | BindingFlags.NonPublic);
                    method?.Invoke(this, [s, e]);
                };
            }
            else
            {
                toolStripMenu.Items.Add(new ToolStripSeparator());
            }
        }
        :
```
### コンテキストメニューへの登録
`toolStripMenu`に各アイテムが追加出来たら`NotifyIcon`をいつもどおり登録します[^4]。
[^4]:もしNotifyIcon自体にWindowサイズの復元などのイベント処理が必要な場合は適宜イベントハンドラも追加します。
```MainWindow.cs
        :
        var icon = GetResourceStream(new Uri("icon.ico", UriKind.Relative)).Stream;
        notifyIcon = new NotifyIcon()
        {
            Text = "Test",
            Visible = true,
            Icon = new Icon(icon),
            ContextMenuStrip = toolStripMenu,
        };
    }
```
### 対応するメソッドの追加
ここは中身がそれぞれ違うのでひとつひとつ手で作成しますが、メソッドの名前は追加したときのものに合わせてください。
終了時は念のため`NotifyIcon`も`Dispose()`しておきます。
``` MainWindow.cs
private void ConfigToolStripMenuItem_Click(object? sender, EventArgs e)
{
    ...
}

private void SpectrumToolStripMenuItem_Click(object? sender, EventArgs e)
{
    toolStripMenuItemsDict["Spectrum"].Checked = !toolStripMenuItemsDict["Spectrum"].Checked;
}
:
private void ExitToolStripMenuItem_Click(object? sender, EventArgs e)
{
    notifyIcon?.Dispose();
    Application.Current.Shutdown();
}
```
### 
ちゃんと対応するメソッドに飛ぶか確認して、これで完成です。
![スクリーンショット 2025-04-20 113602.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2986568/0ab05997-1203-4f93-8399-4007d76c9705.png)