C#で常駐アプリなどを作り、タスクトレイにアイコンを出す場合には、Win Formsで用意されているNotifyIconというクラスを使います。
以前、このクラスを使って常駐アプリを作る方法を↓に書きました。
この方法だと、使うたびにWinFormsのデザイナを利用して設定したりする必要があり面倒です。
ということで、NotifyIconに関する処理を1ファイルにまとめ、簡単に再利用できるようにしてみました。
今回は、以前のサンプルと比べ、以下のような点を修正してます。
- Formsのコンポーネントクラスではなく、NotifyIconExという普通のクラスとして実装
- Win Formsのデザイナをわざわざ使いたくなかったので、すべてC#のコードで書くように修正しました。
- タスクトレイ関係の処理はすべてこのクラスの中に閉じ込めました。
- メニューを、WPFのContextMenuクラスで作れるように修正
- バルーン表示に関わる各種プロパティ/メソッドを利用できるようにラップ
- 各種イベント類もラップ
コード一式は以下のリポジトリに上げています。
WPFSamples/NotifyIconSample at master · sourcechord/WPFSamples · GitHub
使い方
準備
NotifyIconExクラス
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace NotifyIconSample { publicenum ToolTipIconEx { None = 0, Info = 1, Warning = 2, Error = 3 } publicclass NotifyIconEx : IDisposable { private System.Windows.Forms.NotifyIcon _notify; publicstring Text { get { returnthis._notify.Text; } set { this._notify.Text = value; } } publicbool Visible { get { returnthis._notify.Visible; } set { this._notify.Visible = value; } } public Uri IconPath { set { if (value == null) { return; } var iconStream = System.Windows.Application.GetResourceStream(value).Stream; this._notify.Icon = new System.Drawing.Icon(iconStream); } } public System.Windows.Controls.ContextMenu ContextMenu { get; set; } public ToolTipIconEx BalloonTipIcon { get { return (ToolTipIconEx)this._notify.BalloonTipIcon; } set { this._notify.BalloonTipIcon = (System.Windows.Forms.ToolTipIcon)value; } } publicstring BalloonTipTitle { get { returnthis._notify.BalloonTipTitle; } set { this._notify.BalloonTipTitle = value; } } publicstring BalloonTipText { get { returnthis._notify.BalloonTipText; } set { this._notify.BalloonTipText = value; } } publicvoid ShowBalloonTip(int timeout) { this._notify.ShowBalloonTip(timeout); } publicvoid ShowBalloonTip(int timeout, string tipTitle, string tipText, ToolTipIconEx tipIcon) { var icon = (System.Windows.Forms.ToolTipIcon)tipIcon; this._notify.ShowBalloonTip(timeout, tipTitle, tipText, icon); } public NotifyIconEx() : this(null) { } public NotifyIconEx(Uri iconPath) : this(iconPath, null) { } public NotifyIconEx(Uri iconPath, string text) : this(iconPath, text, null) {} public NotifyIconEx(Uri iconPath, string text, System.Windows.Controls.ContextMenu menu) { // 各種プロパティを初期化this._notify = new System.Windows.Forms.NotifyIcon(); this.IconPath = iconPath; this.Text = text; this.ContextMenu = menu; // マウス右ボタンUpのタイミングで、ContextMenuの表示を行う// ダミーの透明ウィンドウを表示し、このウィンドウのアクティブ状態を用いてContextMenuの表示/非表示を切り替えるthis._notify.MouseUp += (s, e) => { if (e.Button != System.Windows.Forms.MouseButtons.Right) { return; } var win = new System.Windows.Window() { WindowStyle = System.Windows.WindowStyle.None, ShowInTaskbar = false, AllowsTransparency = true, Background = System.Windows.Media.Brushes.Transparent, Content = new System.Windows.Controls.Grid(), ContextMenu = this.ContextMenu }; var isClosed = false; win.Activated += (_, __) => { if (win.ContextMenu != null) { win.ContextMenu.IsOpen = true; } }; win.Closing += (_, __) => { isClosed = true; }; win.Deactivated += (_, __) => { if (win.ContextMenu != null) { win.ContextMenu.IsOpen = false; } if (!isClosed) { win.Close(); } }; // ダミーウィンドウ表示&アクティブ化をする。// ⇒これがActivatedイベントで、ContextMenuが表示される win.Show(); win.Activate(); }; this._notify.Visible = true; } #region NotifyIconクラスの各種イベントをラップするpublicevent EventHandler BalloonTipClicked { add { this._notify.BalloonTipClicked += value; } remove { this._notify.BalloonTipClicked -= value; } } publicevent EventHandler BalloonTipClosed { add { this._notify.BalloonTipClosed += value; } remove { this._notify.BalloonTipClosed -= value; } } publicevent EventHandler BalloonTipShown { add { this._notify.BalloonTipShown += value; } remove { this._notify.BalloonTipShown -= value; } } publicevent EventHandler Click { add { this._notify.Click += value; } remove { this._notify.Click -= value; } } publicevent EventHandler Disposed { add { this._notify.Disposed += value; } remove { this._notify.Disposed -= value; } } publicevent EventHandler DoubleClick { add { this._notify.DoubleClick += value; } remove { this._notify.DoubleClick -= value; } } #endregion #region IDisposable Supportprivatebool disposedValue = false; protectedvirtualvoid Dispose(bool disposing) { if (!disposedValue) { if (disposing) { this._notify.Dispose(); } disposedValue = true; } } publicvoid Dispose() { Dispose(true); } #endregion } }
タスクトレイにアイコン表示
private NotifyIconEx _notify; public MainWindow() { InitializeComponent(); var iconPath = new Uri("pack://application:,,,/NotifyIconSample;component/Icon1.ico", UriKind.Absolute); this._notify = new NotifyIconEx(iconPath, "Notify Title"); }
タスクトレイ登録時には、アイコンが必要になります。
プロジェクトに、「ビルドアクション:Resource」に設定したicoファイルを追加しておき、Uri指定することでアイコンを設定できます。
お片付け
Disposeメソッドを呼び出すと、タスクトレイアイコンを破棄します。
protectedoverridevoid OnClosed(EventArgs e) { base.OnClosed(e); // ウィンドウを閉じる際に、タスクトレイのアイコンを削除する。this._notify.Dispose(); }
Disposeをせずにアプリを終了すると、タスクトレイにアイコンが残るので注意してください。
(タスクトレイのアイコン上にマウスカーソルを乗せると消えますが・・・)
タスクトレイのアイコンからContextMenu表示
以下のようにすると、XAML上で定義したContextMenuを表示できます。
MainWindow.xaml
<Window.Resources><ContextMenu x:Key="sampleWinMenu"><MenuItem Header="ShowBalloon"Click="MenuItem_Show_Balloon_Click" /><Separator /><MenuItem Header="Show"Click="MenuItem_Show_Click" /><MenuItem Header="Exit"Click="MenuItem_Exit_Click" /></ContextMenu></Window.Resources>
MainWindow.xaml.cs
public MainWindow() { InitializeComponent(); var iconPath = new Uri("pack://application:,,,/NotifyIconSample;component/Icon1.ico", UriKind.Absolute); var menu = (ContextMenu)this.FindResource("sampleWinMenu"); this._notify = new NotifyIconEx(iconPath, "Notify Title", menu); }
ContextMenuは、タスクトレイのアイコン右クリックで表示されます。
バルーン表示
以下のメソッドでバルーン表示できます。
this._notify.ShowBalloonTip(1000, "tipTitle", "tipText", ToolTipIconEx.Info);
ちなみに、Windows10ではバルーンはこんな風に表示されます。
以前のものより、だいぶカッコよくなってますね!!
タスクトレイのメニューについて
FormsのNotifyIconクラスでメニューを出す場合、FormsのContextMenuStripクラスを使ってメニューを構築します。
ですが、WPFでの開発に慣れてくると、Formsのデザイナで作るこのContextMenuStripクラスは使いにくく感じます。
ContextMenuもXAML上で定義したいですよね。。。
ということで、NotifyIconのMouseUpイベントなどを使い、WPFのContextMenuを表示するようにしました。
しかし、ここで問題が、、、
タスクトレイ上から、WPFのContextMenuを表示すると、メニューが非アクティブ状態になっても消えずに残り続けてしまいます。
原因や対処方法などは、以下のリンクを参考にやってみました。
http://blogs.wankuma.com/youryella/archive/2009/11/01/182630.aspx
http://copycodetheory.blogspot.jp/2012/07/notify-icon-in-wpf-applications.html
ContextMenuはアプリケーションの持つウィンドウのアクティブ状態などを監視して、自動で閉じる処理を行っているようです。
なので、アクティブなウィンドウがない状態では、ContextMenuが非アクティブになっても閉じることができないようです。
ということで、上記リンクなどを参考に以下のような処理を行っています。
- ContextMenu表示時に透明なダミーウィンドウを作りアクティブ化
- 上記ウィンドウが非アクティブになったらContextMenuを閉じる(& ダミーのウィンドウも閉じる)
だいぶトリッキーですね・・・orz
その他
ここで作ったサンプルコードでは、タスクトレイのコンテキストメニューを自動で閉じるためにトリッキーな処理をしています。
手軽にタスクトレイやトレイのメニューを使うにはいいかもしれませんが、
この手のタスクトレイ関係の機能をガッツリと作りこみたい場合には、↓のライブラリのようなものを別途使用したほうがよいかもしれません。
http://www.hardcodet.net/wpf-notifyicon
https://www.nuget.org/packages/Hardcodet.NotifyIcon.Wpf/