【WPF】タスクトレイを活用してバックグラウンドアプリを作る

20-20-20ルール1を実践するために、それ専用の Windows のタイマーアプリを WPF で作成することにしました。

仕様として、
・ウィンドウは非表示にしてバックグラウンドで動かしたい
・タスクトレイを活用したい
と思ったのですが、タスクトレイ(通知領域)を使ったことがなく、どう実装すればいいか分かりませんでした。
今回実装するにあたっていろいろ調べた内容をまとめます。

タスクトレイの様子

タスクトレイ:画面の右下、タスクバーの右の方の場所。
時計とか電池残量とか起動しているいろんなアプリのアイコンとかが表示されています。
通知領域の方が正式名称らしいですが、個人的にはタスクトレイの方が馴染みがあるので以降もタスクトレイと呼びます。

全体のコードおよび作成したタイマーアプリはここにあります。

実装

今回具体的にやりたいことは以下です。

・アプリ動作中はタスクトレイにアイコンを表示する。
・アプリウィンドウを×ボタンで閉じてもアプリが終了しないようにする(バックグラウンド動作ができるようにする)。
・タスクトレイのアイコンをクリックすると、閉じたアプリウィンドウが開くようにする。
・タスクトレイのアイコンを右クリックすると、アプリを終了できるようにする。
・タスクトレイのアイコンをホバーして、残り時間を見えるようにする。

タスクトレイを使用するための準備

どうもWPFそのものにはタスクトレイを使用する機能がないっぽいです。
一方で、Windows Forms には NotifyIcon クラス という通知領域にアイコンを作成するコンポーネントの指定クラスがあります。
公式ドキュメント

WPFでもこちらを使用していきます。

WPFプロジェクトは、そのままだとNotifyIcon(System.Windows.Forms)が使用できないので、プロジェクトファイル(.csprojファイル)の<PropertyGroup>に<UseWindowsForms>True を追記します。

<PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <UseWPF>true</UseWPF>
    <UseWindowsForms>true</UseWindowsForms>    <!-- <- この行を追加 -->
</PropertyGroup>

これで System.Windows.Forms を使用できるようになりました。

タスクトレイにアイコンを表示する基礎コード

App.xaml.cs の OnStartup に NotifyIcon の宣言をします。
タスクトレイにアイコンを表示して、アイコンをホバーした時に決まった文字を表示するだけの機能です。

C#
public partial class App : System.Windows.Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        var _notifyIcon = new System.Windows.Forms.NotifyIcon
        {
            Visible = true,
            Icon = アイコン画像,
            Text = "アイコンホバー時に表示される文字列"
        };
    }
}

これからNotifyIconをいろいろいじります。

タスクトレイアイコンの画像を設定する

そもそもアイコン画像がないと表示しようがないので用意します。
フリーアイコンを使うなり、自分で描くなりして、.ico形式の画像を用意します。

自作。遠くを見ることからイメージして望遠鏡にしました。でもアイコンサイズだと何かよくわかんなくなった。

ソリューションを右クリック > 追加 > 既存の項目 を選択し、用意した.icoファイルを追加します。
追加できたらソリューションエクスプローラーから追加した画像のプロパティを表示し、ビルドアクションをリソースに変更します。

先ほどのコードについて、★マークの部分を追加・変更します。
“telescope.ico”の telescope のところは、用意した画像の名前に変更してください。

C#
var icon = GetResourceStream(new Uri("telescope.ico", UriKind.Relative)).Stream; // ★
var _notifyIcon = new System.Windows.Forms.NotifyIcon
{
    Visible = true,
    Icon = new System.Drawing.Icon(icon),  // ★
    Text = "アイコンホバー時に表示される文字列"
};

ウィンドウを×ボタンで閉じてもアプリが終了しないようにする

アプリウィンドウ(ここではMainWindow)で×ボタンを押したとき(= ウィンドウがCloseするとき)に、終了をキャンセルして、ウィンドウを非表示にすれば良さそうです。

MainWindow.xaml の Window に Closing を追加。

<Window x:Class="Timer2020.MainWindow"

        Closing="Window_Closing">

MainWindow.xaml.cs にWindow_Closingメソッドを作成。
アプリの終了をキャンセルし、Hide() で非表示にします。

C#
private void Window_Closing(object sender, CancelEventArgs e)
{
    // ×ボタンを押されてもアプリを終了しない
    e.Cancel = true;
    this.Hide();
}

なお、Alt + F4 をした時も同様にWindow_Closingが呼ばれてアプリは終了せず、ウィンドウが非表示になるだけです。

タスクトレイのアイコンをクリックするとウィンドウを表示するようにする

先ほど×ボタンを押したときに MainWindow を非表示にしたので、タスクトレイのアイコンクリック時に再表示するようにすれば実現できそうです。

タスクトレイのアイコンクリックイベントは、MouseClick で取得できます。
★マークの部分を追加。
NotifyIcon_Click の部分は、アイコンクリック時に実行したい処理を定義したメソッドをApp.xaml.csに定義して、呼ぶようにしてください。

C#
var icon = GetResourceStream(new Uri("telescope.ico", UriKind.Relative)).Stream;
var _notifyIcon = new System.Windows.Forms.NotifyIcon
{
    Visible = true,
    Icon = new System.Drawing.Icon(icon),
    Text = "アイコンホバー時に表示される文字列"
};
// タスクトレイのアイコンがクリックされたときの処理
_notifyIcon.MouseClick += new System.Windows.Forms.MouseEventHandler(NotifyIcon_Click);  // ★

NotifyIcon_Click = MainWindow を再表示するための処理は以下です。

C#
private void NotifyIcon_Click(object? sender, System.Windows.Forms.MouseEventArgs e)
{
    if (e.Button == System.Windows.Forms.MouseButtons.Left)
    {
        var wnd = System.Windows.Application.Current.MainWindow;
        if (wnd != null)
        {
            wnd.Show();
            wnd.WindowState = WindowState.Normal;
            wnd.Activate();
        }
    }
}

タスクトレイのアイコンに右クリックメニューを追加する

右クリック時に表示されるメニュー = コンテキストメニューは、NotifiIcon 作成時にContextMenuStrip プロパティを設定することで使用できます。

ContextMenuStrip を作成し、メニュー項目をAddで追加。
NotifyIconのプロパティに設定します。
★マークの部分を追加。

C#
var icon = GetResourceStream(new Uri("telescope.ico", UriKind.Relative)).Stream;
var menu = new System.Windows.Forms.ContextMenuStrip();  // ★
menu.Items.Add("終了", null, Exit_Cilck);  // ★
var _notifyIcon = new System.Windows.Forms.NotifyIcon
{
    Visible = true,
    Icon = new System.Drawing.Icon(icon),
    Text = "アイコンホバー時に表示される文字列",
    ContextMenuStrip = menu  // ★
};
_notifyIcon.MouseClick += new System.Windows.Forms.MouseEventHandler(NotifyIcon_Click); 

終了機能を追加したいので、Exit_Cilck では Shutdown() をするようにします。

C#
private void Exit_Cilck(object? sender, EventArgs e)
{
    Shutdown();
}

タスクトレイのアイコンホバー時の表示内容を変更する

タスクトレイのアイコンホバー時に表示されるテキストは、Text プロパティに設定した文字列なのですが、これだけだと動的に変更ができません。

表示したい内容が変わるたびに、NotifyIconのTextの値を変えるようにすればいいのですが、ここまでのコードだと、_notifyIcon が OnStartup 外では使用できません。
そのため、_notifyIcon の宣言をメソッドの外でして、App クラス内で使用できるようにします。

今回はタイマーで1秒経過するごとにメソッドを呼ぶようにし、そのメソッド内で残り時間を Text にセットするようにしました。
(完全なコードはここを見てください)

C#
public partial class App : System.Windows.Application
{
    private System.Windows.Forms.NotifyIcon? _notifyIcon;  // ★
    
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        var icon = GetResourceStream(new Uri("telescope.ico", UriKind.Relative)).Stream;
        var menu = new System.Windows.Forms.ContextMenuStrip();
        menu.Items.Add("終了", null, Exit_Cilck);
        _notifyIcon = new System.Windows.Forms.NotifyIcon  // ★
        {
            Visible = true,
            Icon = new System.Drawing.Icon(icon),
            Text = "未実行",
            ContextMenuStrip = menu
        };
        _notifyIcon.MouseClick += new System.Windows.Forms.MouseEventHandler(NotifyIcon_Click);
    }

    // タイマーで定期的に呼ばれる処理
    private void Timer_Tick()
    {
        if (_notifyIcon != null)
            // ここが呼ばれるたびにアイコンホバー時のテキストが変わる
            _notifyIcon.Text = "新たに表示したい文字列";
    }
}

終わりに

実現したかったことが一通りできたので満足しています。
簡単に実装できることが分かったので、機会があれば積極的にタスクトレイを活用していきたいと思いました。

作ったタイマー(20分毎に20秒カウントダウンする、20-20タイマー)も使用感が個人的には気に入っていて2、ついつい集中して画面を見てしまっても、定期的に20秒のポップアップが出てくるので以前よりも目を休められるようになったと感じます。眼精疲労や視力低下が改善して欲しいです。
Githubにはアプリ実行用の.exeや関連ファイルも置いてますので、もしよかったら使った感想とかご意見とかいただけると喜びます。

参考

WPFでタスクトレイ常駐アプリを作る - Qiita
WPFでタスクトレイ(通知領域)常駐アプリを作ろうとしたら想像以上に手間がかかったので備忘録として初投稿です。 動作環境 Visual Studio 2019 .NET Core3.1(.NET5.0でも可) 事前準備 新しいプロジェクトの...
【C#/WinForms実践入門編(9)】通知領域(タスクトレイ) ~バックグラウンド実行可能なタイマーアプリへ~|C#/.NETプログラミング入門 ~初心者向けガイドから最新AI活用まで~【2025年最新】
今回のテーマは「通知領域(タスクトレイ)」です。アプリをバックグラウンドで実行させ、ユーザに通知を送る機能を実装します。
  1. 眼精疲労防止などのために「20分ごとに、20秒間、20フィート(約6メートル)以上離れたところを見る」というもので、アメリカ眼科学会が推奨しています。 ↩︎
  2. 20秒のポップアップの表示位置を指定できるようにはしたいなあと思ってます。 ↩︎

コメント

タイトルとURLをコピーしました