WPF アプリケーションには NotifyIcon クラスが存在しません。こちら「Windows フォーム コントロールおよび同等の WPF コントロール」を見る限り .NET Framework 4.0 になっても WPF で NotifyIcon を実装しないようです。タスクトレイにアイコンを表示させるためには、Windows Forms アプリケーションの NotifyIcon クラスを使用するしかありません。そのため、非常にやりにくくなっています。

 Microsoft Connect には 2008 年 4 月 28 日に「Implement NotifyIcon in WPF(サインインしないと見れません)」という提案が出ているみたいですが、既に終了となっていて受け入れられていないようです。効果はあるかわかりませんが、実装して欲しいので ▲ をクリックして重要であることを伝えておきました。

System.Windows.Forms 名前空間の参照

 WPF でタスクトレイにアイコンを表示するには System.Windows.Forms.NotifyIcon クラスを使用しなければいけません。これは Windows Forms のクラスのため System.Windows.Forms.dll を参照設定する必要があります。WPF を作成しているのに、この DLL を参照設定するのには抵抗があります。

 NotifyIcon から表示されるコンテキストメニューなどの UI に関するものは、WPF で作成したいです。しかし NotifyIcon は Windows Forms のため WPF の ContextMenu は設定できません。仕方がないので、NotifyIcon を右クリックした時に ContextMenu をコードから表示させようとすると早速問題が発生してしまいました。

コンテキストメニューが自動的に閉じない

 ContextMenu.IsOpen を True にして ContextMenu を表示すると、普通はデスクトップや他のアプリケーションなどをクリックすると ContextMenu は自動的に閉じられます。しかし NotifyIcon を右クリックした時に ContextMenu を表示させると、自動的に閉じられません。では、なぜ普通の場合は閉じられるのでしょうか?

 Window 内から ContextMenu を表示させた時に自動的に閉じるのは、デスクトップなどをクリックした時に Window が非アクティブ状態に変化するからです。この変化を ContextMenu は監視しているので閉じられます。

 NotifyIcon の場合は、右クリックした時点でアプリケーション内の Window は全て非アクティブ状態になっています。その状態でデスクトップなどをクリックしても Window には何も変化はありません。そのため、ContextMenu は表示された状態のままになってしまいます。

コンテキストメニューを自動的に閉じる方法

 ということは ContextMenu を表示した時に、アプリケーション内の Window がアクティブになっていれば、閉じられるということです。

 そこで ContextMenu を表示する時は、一緒にアクティブ状態になる Window も表示しておきます。この Window は ContextMenu を閉じるためだけに表示するので、画面には見えないように透明にしておきます。こうすることで ContextMenu を自動的に閉じられるようになります。またこの方法を使用すれば、ContextMenu だけでなく Popup クラスなど WPF の UI を持ったものを、自由に使用することができるようになります。

ContextMenu を自動的に閉じる例
    ' NotifyIcon の右クリックのイベントで Window を表示
    Private Sub NotifyIcon_MouseClick(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles NotifyIcon.MouseClick
        If e.Button = Forms.MouseButtons.Right Then
            Dim w As New Window1
            w.Show()
        End If
    End Sub

' Window の ContextMenu に trayMenu という名前の ContextMenu を設定済み
Partial Public Class Window1

    Private Sub Window1_Initialized(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Initialized
        Me.WindowStyle = Windows.WindowStyle.None
        Me.AllowsTransparency = True
        Me.Background = Brushes.Transparent
    End Sub

    Private Sub Window1_ContentRendered(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.ContentRendered
        Me.ContextMenu.IsOpen = True
        Me.Activate()
    End Sub

    Private Sub ContextMenu_Closed(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles trayMenu.Closed
        Me.Close()
    End Sub

End Class

 まず NotifyIcon を右クリックした時に ContextMenu を閉じるための Window を表示します。その Window の ContentRendered イベントで ContextMenu を表示します。そのタイミングで Window をアクティブ状態にします。アクティブ化を明示的にやらないとアクティブ状態になりません。なぜ ContentRendered イベントでやっているかというと、Loaded イベントでアクティブ化してもアクティブ状態にならないためです。そして、最後に ContextMenu の Closed イベントで Window を閉じます。

 ちなみに ContentRendered イベントは Window に何かしらのコンテンツがないと発生しないので、Grid などを配置しておきます。また Window から ContextMenu を表示しているのは、その Window の XAML に ContextMenu を定義できるためです。

まとめ

  • WPF には NotifyIcon クラスは存在しない。
  • タスクトレイにアイコンを表示するには System.Windows.Forms.NotifyIcon クラスを使用する。
  • ただし NotifyIcon から WPF の ContextMenu を表示すると自動的に閉じられない問題がある。
  • 自動的に閉じるためには WPF アプリケーション内にアクティブ状態な Window が表示されている必要がある。

 そもそも、なぜ NotifyIcon が WPF に存在しないのかがわかりませんし、今後も実装しそうにないのもわかりません。必要になるアプリケーションはありますので、是非実装して欲しいですね。常駐アプリケーションなどは WPF には向いていないということなのでしょうか?