【VB.NET】【WPF】通知領域(タスクトレイ)を使う~ WPF NotifyIcon

MEMO

この記事は「Code Project の WPF NotifyIcon の記事」の多くの部分を参考にしています(全ては網羅してません)。詳細については「Code Project の WPF NotifyIcon の記事」を参照してください。

アプリを通知領域(旧名:タスクトレイ)に入れる機能が WPF の標準機能には無いらしい(なぬっ!?)。

で、調べてみると、Forms の NotifyIcon クラスを引っ張ってきてアレコレやる方法がヒットするけれど、Forms の力を借りるのがなんとなく気持ち悪い。

もう少しスッキリした気分で出来る方法は無いものかと探して見つけたのが WPF NotifyIcon(Hardcodet.NotifyIcon.Wpf、by Philipp Sumi)です。WPF NotifyIcon なら、WPF の特性を生かしたメッセージを表示することができます。

インストール

[ツール]>[NuGet パッケージ マネージャー]>[ソリューションの NuGet パッケージの管理]を選択。

[NuGet - ソリューション]タブで[参照]をクリックして検索ボックスに「Hardcodet.NotifyIcon.Wpf」(または「NotifyIcon」など)と入力。「Hardcodet.NotifyIcon.Wpf」が見つかったら選択して、右側のペインでインストール先のプロジェクトにチェックを入れて[インストール]ボタンをクリック。

確認ダイアログが出たら[OK]をクリック。

[出力]ウィンドウにインストール状況が表示されます。

Hello, World

プログラムコード版

Wpf NotifyIcon をインストールしたら、"Hello, World" をやってみましょう。

まず独自のアイコンファイル(.ico)を用意して、Visual Studio の[プロジェクト]>[プロジェクト名 のプロパティ]を選択。

プロパティタブで[リソース]を選択して、リソースの種類として[アイコン]を選択。

アイコンファイル(ここでは conditioner.ico とします)をドラッグ&ドロップして追加。

そして次のコードを実行すると・・・

'Imports Hardcodet.Wpf.TaskbarNotification
'とてもシンプルなシナリオでない限り XAML で行うことをお勧めします
Dim tbi As New TaskbarIcon
tbi.Icon = My.Resources.conditioner '用意したアイコンを指定
tbi.ToolTipText = "hello world"

通知エリアにアイコンが表示されました。アイコンの上にマウスカーソルを重ねるとテキストが表示されます。

XAML 版

前述のことを xaml でやるには次のようにします。

まず xaml のヘッダーに次のネームスペース宣言を追加します。

xmlns:tb="http://www.hardcodet.net/taskbar

そして <tb:TaskbarIcon IconSource="/conditioner.ico" ToolTipText="hello world" /> を追加するだけ(conditioner.ico をプロジェクトファイルと同じ場所に置いてあるという前提)。

xaml 全体は下記のような感じ。

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800" 
        xmlns:tb="http://www.hardcodet.net/taskbar">

    <Grid>
        <tb:TaskbarIcon IconSource="/conditioner.ico" ToolTipText="hello world" />
    </Grid>
</Window>

これだけで、アプリを実行すると通知領域にアイコンが表示されます。アイコンの上にマウスカーソルを重ねるとテキストが表示されます。

アイコンを消す

コードでアイコンを消すときはこう(前述の通りインスタンス tbi を作成済みと想定)。

tbi.Visibility = Visibility.Collapsed

xaml でアイコンを消すときはこう。

<tb:TaskbarIcon IconSource="/conditioner.ico" Visibility="Collapsed" />

 

アイコンはアプリケーションが終了すると自動的に消えるのであまり神経使う必要はないが、実行時に完全に取り除きたいときは TaskbarIcon クラスの Dispose メソッドを使用する。

ツールチップ

TaskbarIcon クラスにはツールチップに関する2つのプロパティがある。

  • TrayToolTip プロパティには任意の UIElement を指定できる。
  • ToolTipText プロパティには String を指定する。これには2通りの使い道がある。
    • TrayToolTip を使用しないとき
    • 古い OS(XP/2003)で TrayToolTip が使えないとき

アプリが古い OS で実行された場合に備えて ToolTipText プロパティは常に設定しておくのが望ましい。

ツールチップ1:インライン宣言

TaskbarIcon 宣言の中に直接、カスタムのツールチップを宣言できる。下記は TextBlock と半透明のボーダーで構成されるツールチップの例。

<tb:TaskbarIcon IconSource="/conditioner.ico" ToolTipText="hello world">
  <tb:TaskbarIcon.TrayToolTip>
    <Border Background="White" BorderBrush="Orange" BorderThickness="2" CornerRadius="4" Opacity="0.8" Width="160" Height="40">
      <TextBlock Text="hello world" HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Border>
  </tb:TaskbarIcon.TrayToolTip>
</tb:TaskbarIcon>

アプリを実行してアイコンにマウスカーソルを重ねるとこんな感じ。

ツールチップ2:ユーザーコントロールの使用

「SimpleUserControl」という名前のユーザーコントロールを作成してそれを表示してみる。

Visual Studio 上部メニューの[プロジェクト]>[ユーザー コントロールの追加]を選択。

ユーザーコントロールの名前を SimpleUserControl.xaml にして追加。

ユーザーコントロールの xaml を(たとえば)下記のようにする。

<UserControl x:Class="SimpleUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApp1"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Border Background="White" BorderBrush="Orange" BorderThickness="2" CornerRadius="4" Opacity="0.8" Width="160" Height="40">
            <TextBlock Text="hello world, UserControl" HorizontalAlignment="Center" VerticalAlignment="Center" />
        </Border>            
    </Grid>
</UserControl>

アプリのメインウィンドウの xaml のタグを下記のように変更。

<tb:TaskbarIcon IconSource="/conditioner.ico" ToolTipText="hello world">
    <tb:TaskbarIcon.TrayToolTip>
        <local:SimpleUserControl />
    </tb:TaskbarIcon.TrayToolTip>
</tb:TaskbarIcon>

そうすると、こんな感じのツールチップになる。

MEMO

エイリアスの local のところでエラーが出る場合は後述の「はまりやすい点」を参照してみてください。

ツールチップ3:リソースにリンク

ツールチップコントロールをリソースに宣言している場合はこんな感じらしい(当サイトの管理人は未検証)。

<tb:TaskbarIcon IconSource="/conditioner.ico" ToolTipText="hello world" TrayToolTip="{StaticResource TheKeyOfMyToolTipControl}" />

ポップアップ

ツールチップと違って、ポップアップはユーザーが NotifyIcon をクリックしたときだけ表示され、ユーザーがトレイエリアからマウスを離しても開いたままになる。

ただし、ユーザーがどこか違う場所をクリックすると、閉じる。

というわけで、ポップアップにはインタラクティブなコンテンツを含めることができる。

詳細は「Code Project の WPF NotifyIcon の記事」を参照してください(当サイトの管理人は未検証)。

コンテキストメニュー

NotifyIcon をクリックしたときに表示されるコンテキストメニュー。これは FrameworkElement のベースクラスから直接派生した WPF 標準の ContextMenu である。

xaml でコンテキストメニューを定義した例。

<tb:TaskbarIcon IconSource="/conditioner.ico" ToolTipText="hello world">
  <tb:TaskbarIcon.ContextMenu>
    <ContextMenu>
      <MenuItem Name="WindowMinimizeMenu" Header="Window Minimize" />
      <MenuItem Name="WindowNormalMenu" Header="Window Normal" />
    </ContextMenu>
  </tb:TaskbarIcon.ContextMenu>
</tb:TaskbarIcon>

メニューの項目がクリックされたときの動作をコードで記述した例。

Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
  AddHandler Me.WindowMinimizeMenu.Click, AddressOf WindowMinimize
  AddHandler Me.WindowNormalMenu.Click, AddressOf WindowNormal
End Sub

Private Sub WindowMinimize
  Me.WindowState = WindowState.Minimized
End Sub

Private Sub WindowNormal
  Me.WindowState = WindowState.Normal
End Sub

 

コンテキストメニューはデフォルトでは右クリックで開くが、その挙動は MenuActivation プロパティで変えることができる(左クリックやダブルクリックで開くようにもできる)。

ちなみに PopupActivation と MenuActivation で干渉しあう値を設定した場合、MenuActivation が優先される。

バルーンチップ(通知バナー)

標準のバルーンチップ(通知バナー)

コード

Dim title As String = "WPF NotifyIcon"
Dim text As String = "This is a standard balloon"

MsgBox("組み込みのアイコン")
Me.tbi.ShowBalloonTip(title, text, BalloonIcon.Error)

MsgBox("カスタムのアイコン")
Me.tbi.ShowBalloonTip(title, text, My.Resources.conditioner, True)

MsgBox("バルーンを隠す")
Me.tbi.HideBalloonTip() 'すぐに消したいとき。実行しなくても自動的に消える。

表示例(組み込みのアイコンを使用)

表示例(カスタムのアイコンを使用)

注意

カスタムのアイコンを使うメソッドの第4引数(True)は本来は省略できるオプションのはずですが、省略したり False にしたりするとバルーンが表示されませんでした。

バグかな?

カスタムのバルーンチップ(通知バナー)

カスタムのバルーンを表示するには TaskbarIcon クラスの ShowCustomBalloon メソッドを使う。ShowCustomBalloon は任意の UIElement のインスタンスを表示できるだけでなく、第2引数でバルーンの表示方法(アニメーション)を指定したり、第3引数でバルーンのタイムアウトの時間を指定することもできる。

Dim balloon As New NotifyBaloon 'UserControl で作成
Dim SecondsToClose As Integer = 10
Me.tbi.ShowCustomBalloon(balloon, Primitives.PopupAnimation.Slide, SecondsToClose*1000) ' Nothing = バルーンを自動的に閉じない

下図は、私(当サイトの管理人)が UserControl で作ったカスタムのバルーンを表示したところ。

MEMO

アニメーションの選択肢は Fade、Scroll、Slide の3種類ありますが、表示速度が速いせいか、あまり違いを感じませんでした。

コマンド(ICommand)

TaskbarIcon では現在のところ次の2つのプロパティにコマンドを割り当てることができる(割り当てる必要がある)。

  • LeftClickCommand
  • DoubleClickCommand

コマンド定義

まずコマンドを定義します。

'Imports System.Windows.Input

'コマンドパラメータを表示する単純なコマンド
Public Class ShowMessageCommand : Implements ICommand
    '実行可能かどうかを定義。とりあえず True を返しておく。
    Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
        Return True
    End Function
    '実行可能かどうかの状態が変化したときのイベント。
    Public Event CanExecuteChanged(sender As Object, e As EventArgs) Implements ICommand.CanExecuteChanged
    '実行内容を定義。
    Public Sub Execute(parameter As Object) Implements ICommand.Execute
        MessageBox.Show(parameter.ToString())
    End Sub
End Class

割り当て方法1:XAML でバインド

xaml でバインドするときはこんな感じ(下記のハイライト個所については後述の「はまりやすい点」で解説)。

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800" xmlns:tb="http://www.hardcodet.net/taskbar">

    <Grid>
        <!-- ローカルリソースとしてコマンドを宣言 -->
        <Grid.Resources>
          <local:ShowMessageCommand x:Key="MessageCommand" />
        </Grid.Resources>

        <!-- NotifyIcon の宣言とパラメータ付きコマンドの定義 -->
        <tb:TaskbarIcon Name="tbi" IconSource="/conditioner.ico" 
            LeftClickCommand="{StaticResource MessageCommand}"
            LeftClickCommandParameter="Single left mouse button click."
            DoubleClickCommand="{StaticResource MessageCommand}"
            DoubleClickCommandParameter="Double click on NotifyIcon." />
    </Grid>

</Window>

アプリを実行して、通知領域のアイコンを左クリックまたはダブルクリックすると下図のメッセージボックスが表示される。

はまりやすい点

上記のコードを試してみて「あれ? 動かないよ。エラーになるよ!」ということがあるかもしれないので、ポイントを解説します。

最初のポイントは、上の xaml の6行目に xmlns:local="clr-namespace:WpfApp1" という記述がある点です。プロジェクトのルート名前空間(WpfApp1。デフォルトはプロジェクト名と同じ)を local というエイリアスに割り当てています。

2番目のポイントですが、今回、コマンドパラメータを記述したのは、下図の場所です。メインウィンドウのクラスの外に定義しました。

で、xaml に戻って3番目のポイントです。下図のところ(前述の xaml の13行目)で正しいコマンドパラメータの名前を記述しても最初はエラーが出るかもしれません。

xaml のエラーって、現在の状態をリアルタイムに反映してないんですよね。。。記述が正しければ、リビルドしたり「デバッグの開始(F5)」を実行すると、エラーが消えます。

割り当て方法2:コードビハインド

コードでコマンドを割り当てる場合は下記のようにします。

'xaml の TaskbarIcon に "tbi" という名前を付けている場合
Me.tbi.LeftClickCommand = New ShowMessageCommand
Me.tbi.LeftClickCommandParameter = "Single left mouse button click.(by code)"
Me.tbi.DoubleClickCommand = New ShowMessageCommand
Me.tbi.DoubleClickCommandParameter = "Double click on NotifyIcon.(by code)"

Window.ShowInTaskbar

基本的すぎて書くのを忘れるとこでしたけど、念のため書いておきます。

WPF のウィンドウには .ShowInTaskbar プロパティ(タスクバーにアイコンを表示するかどうか)があるので、通知領域(タスクトレイ)を使うときは、きっとこのプロパティも活用することになりますね。

購読する
通知を受け取る対象
guest
0 Comments
Inline Feedbacks
View all comments