WPF/Windowsフォーム:タスクバーのアイコンに進捗表示を出すには?[C#、VB].NET TIPS

WindowsフォームとWPFアプリではタスクバーのアイコンに処理の進捗状況を表示することがよくある。本稿ではこの機能を実装する方法を解説する。

» 2015年08月26日 05時00分 公開
[山本康彦BluewaterSoft/Microsoft MVP for Windows Platform Development]
.NET TIPS
Insider.NET

 

「.NET TIPS」のインデックス

連載目次

対象:.NET 4.0以降、Windows 7以降


 デスクトップ用のプログラムで長い処理を行うとき、Windows 7以降ではタスクバーのアイコンに進捗(しんちょく)状態を表示できる。これを実装するのは難しそうだと思っていないだろうか? 長い処理の途中で画面上に進捗を表示できているのなら、タスクバーのアイコンに表示するのは意外と簡単なのである。本稿では、.NET 4.0以降のWindowsフォームとWPFでタスクバーのアイコンに進捗表示を出す方法を解説する。

進捗表示の種類

 タスクバーのアイコンに表示される進捗表示は、正式には「プログレスインジケーター」と呼ばれる。「進行状況バー」と呼ぶこともあるようだ。プログレスインジケーターには5種類の状態がある(次の画像)。その種類を表す列挙体のメンバー名と意味は、次のようになっている。

  • None: 進捗表示なし
  • Normal: 緑色(正常に処理中)
  • Paused: 黄色(中断状態、再開待ち)
  • Error: 赤色(エラーで中断、再開不能)
  • Indeterminate: 緑色(正常に処理中、ただし進捗割合は不明)
プログレスインジケーターの表示例(上:Windows 7/下:Windows 10)
プログレスインジケーターの表示例(上:Windows 7/下:Windows 10) プログレスインジケーターの表示例(上:Windows 7/下:Windows 10)
左から順に、None/Normal/Paused/Error/Indeterminate(意味は上記参照)。

利用するAPI

 Windowsフォームには標準の機能としては備わっていない。「Windows API Code Pack」を利用する*1

 WPFでは、.NET Framework 4.0で追加されたTaskbarItemInfoクラス(System.Windows.Shell名前空間)を利用する。

*1 Windows API Code Packは、以前はcode.msdn.microsoft.comやarchive.msdn.microsoft.comでソースコードと共に公開されていた。現在は、マイクロソフトからの公開は終了している。しかし、有志の手によってソースコードは引き続きメンテされており、そのバイナリはNuGetから入手できる。なお、当初のWindows API Code Packは.NET Framework 3.0から利用できたが、現在NuGetで公開されているバイナリを利用するには.NET Framework 4.0以上が必要なようである。


Windowsフォームで進捗表示を出すには?

 まず、進捗表示を出したいWindowsフォームのプロジェクトごとに、Windows API Code PackをNuGetから導入しておく(次の画像)。

NuGetからWindows API Code Packをインストールする(Visual Studio 2012) NuGetからWindows API Code Packをインストールする(Visual Studio 2012)
ソリューションエクスプローラーでプロジェクトを選択し、その右クリックメニューから[NuGet パッケージの管理]を選ぶと、この画像のような[NuGet パッケージの管理]ダイアログが出てくる。
[NuGet パッケージの管理]ダイアログで、左側の[オンライン]を選び((1))、右上の検索ボックスに「Windows 7 Api Code Pack w/ xml documentation」と入力して検索する((2))。見つかったパッケージを選ぶと[インストール]ボタンが表示されるので((3))、それをクリックしてインストールする。プログレスインジケーターを表示するには、「Windows 7 Api Code Pack w/ xml documentation - Core」と「Windows 7 Api Code Pack w/ xml documentation - Shell」の二つのパッケージを順に入れる必要がある。
なお、右側の[ライセンス条項の表示]リンク((4))でライセンス条件を確認できるので、利用に先立って確認しておいてほしい。

 上の画像のようにしてWindows API Code Packをインストールすると、プロジェクトの参照設定に次の三つが追加されているはずだ。確認しておいてほしい(C#のプロジェクトではソリューションエクスプローラーの[参照設定]フォルダー、VBのプロジェクトではプロジェクトのプロパティの[参照]タブ)。

  • Microsoft.WindowsAPICodePack
  • Microsoft.WindowsAPICodePack.Shell
  • Microsoft.WindowsAPICodePack.ShellExtensions

 準備が整ったら、メインのフォーム(=プログラムの起動時に表示されるフォーム)のコードビハインドの冒頭部分に名前空間の指定を追加する(次のコード)。

using Microsoft.WindowsAPICodePack.Taskbar;

Imports Microsoft.WindowsAPICodePack.Taskbar

Windows API Code Packを利用するための名前空間を追加する(上:C#、下:VB)

 そうしたら、メインのフォームが完全に表示された後の好きなタイミングで*2、次に示すようなコードを記述するだけである。

// プログレスインジケーターを緑色にする
TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.Normal);
// プログレスインジケーターの進捗割合を30%にする
TaskbarManager.Instance.SetProgressValue(30, 100);

' プログレスインジケーターを緑色にする
TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.Normal)
' プログレスインジケーターの進捗割合を30%にする
TaskbarManager.Instance.SetProgressValue(30, 100)

Windows API Code Packを使ってプログレスインジケーターを表示する例(上:C#、下:VB)
SetProgressStateメソッドとSetProgressValueメソッドを呼び出すだけである。
SetProgressStateメソッドには、前述したプログレスインジケーターの5種類の状態のいずれかを指定する。
SetProgressValueメソッドは、進捗率の更新があるときだけ呼び出せばよい。この例の引数は、100分の30(=30%)を表している。

// プログレスインジケーターを元に戻す
TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.NoProgress);

' プログレスインジケーターを元に戻す
TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.NoProgress)

Windows API Code Packを使ってプログレスインジケーターを元に戻す例(上:C#、下:VB)
プログレスインジケーターを元に戻すには、SetProgressStateメソッドを呼び出すだけである。

*2 それ以外のフォーム(=メインのフォームから表示した別のフォーム)からプログレスインジケーターを表示することも可能だが、そのときはウィンドウハンドルを引数として渡す必要がある。また、(どのフォームにおいても)フォームが完全に表示される前に呼び出すと、例外が発生する。例えば、フォームのLoadイベントでは例外となり、Shownイベントならば正しく表示される。


 ただし、上のコードは、UIスレッドで呼び出さねばならない(そうしないと例外が発生する)。時間のかかる処理は別スレッドで非同期に動作させるのが普通であるから、UIの表示を更新する部分をUIスレッドに戻して実行するようなコーディングが必要だ。

 簡単な例を紹介しておこう。次の画像のようなUIを作る。カウントを表示しているラベルの名前は「label1」、[START]ボタンの名前は「button1」とする。

非同期処理からプログレスインジケーターを更新するフォームの例 非同期処理からプログレスインジケーターを更新するフォームの例
Windows 7で実行しているところである。非同期処理で10から0までカウントダウンしていく。カウントダウンに合わせて、フォーム上のラベルとプログレスインジケーターの表示を更新する。

 「button1」ボタンのクリックイベントで、10から0までカウントダウンする処理を非同期に実行させよう。そして、カウントが進むたびに、「label1」ラベルの表示とプログレスインジケーターを更新しよう。.NET 4.0のTaskクラス(System.Threading.Tasks名前空間)を使って書くと、次のコードのようになる。

private void button1_Click(object sender, EventArgs e)
{
  // ボタンをディスエーブルに
  this.button1.Enabled = false;

  // プログレスインジケーターを緑色にする
  TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.Normal);

  // 進捗を表示するためのデリゲートを定義する(カウントダウン中に呼び出される)
  Action<Object> callUpdateProgress = (o) =>
  {
    int number = (int)o;
    // ラベルの表示を更新する
    this.label1.Text = number.ToString();
    // プログレスインジケーターに値をセットする
    TaskbarManager.Instance.SetProgressValue(10 - number, 10);
  };

  // 別スレッドで実行するタスクを定義し、非同期実行を開始する
  Task.Factory.StartNew(() =>
      {
        // 別スレッドで行う処理の本体
        for (int i = 10; i >= 0; i--)
        {
          // UIスレッドで進捗表示を呼び出す
          this.BeginInvoke(callUpdateProgress, i);
          // 1秒間待機
          System.Threading.Thread.Sleep(1000);
        }
      })
  .ContinueWith((t) =>
    // 非同期実行が完了した後の処理
    {
      // プログレスインジケーターを通常表示に戻す
      TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.NoProgress);
      // ボタンをイネーブルに戻す
      this.button1.Enabled = true;
    },
    // 完了時の処理をUIスレッドで実行するための指定
    TaskScheduler.FromCurrentSynchronizationContext()
  );
}

Private Sub button1_Click(sender As Object, e As EventArgs) Handles button1.Click
  ' ボタンをディスエーブルに
  Me.button1.Enabled = False

  ' プログレスインジケーターを緑色にする
  TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.Normal)

  ' 進捗を表示するためのデリゲートを定義する(カウントダウン中に呼び出される)
  Dim callUpdateProgress As Action(Of Object) _
    = Sub(o)
        Dim number As Integer = CInt(o)
        ' ラベルの表示を更新する
        Me.label1.Text = number.ToString()
        ' プログレスインジケーターに値をセットする
        TaskbarManager.Instance.SetProgressValue(10 - number, 10)
      End Sub

  ' 別スレッドで実行するタスクを定義し、非同期実行を開始する
  Task.Factory.StartNew(
    Sub()
      ' 別スレッドで行う処理の本体
      For i As Integer = 10 To 0 Step -1
        ' UIスレッドで進捗表示を呼び出す
        Me.BeginInvoke(callUpdateProgress, i)
        ' 1秒間待機
        System.Threading.Thread.Sleep(1000)
      Next
    End Sub) _
    .ContinueWith(
      Sub(t)
        ' 非同期実行が完了した後の処理
        ' プログレスインジケーターを通常表示に戻す
        TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.NoProgress)
        ' ボタンをイネーブルに戻す
        Me.button1.Enabled = True
      End Sub,
      TaskScheduler.FromCurrentSynchronizationContext()
    )
    ' TaskScheduler.〜 は、完了時の処理をUIスレッドで実行するための指定
End Sub

カウントダウンする非同期処理の例(上:C#、下:VB)
これはフォームのコードビハインドに記述している。そのため、UIスレッドで処理を呼び出すには「this.BeginInvoke」メソッドが使える。フォームと切り離して非同期処理を書く場合には、非同期処理側でイベントを発生させたり、.NET 4.5で導入されたIProgressインターフェース(System名前空間)を利用したりなどと、さまざまな工夫をすることになる。

WPFで進捗表示を出すには?

 Windowsフォームと同様にWindows API Code Packを使ってもよいが、.NET 4.0のWPFからは標準でサポートされている。本稿では、WPFの機能を使う。

 WPF(.NET 4.0以降)のWindowオブジェクト(System.Windows名前空間)にはTaskbarItemInfoプロパティがあって、これを通じてプログレスインジケーターを操作できる。ただし、このプロパティはデフォルトではnullになっている(利用しないときに無用な負荷を与えないためであろう)。

 そこで、プログレスインジケーターを利用するには、メインのウィンドウが作られたときに、TaskbarItemInfoオブジェクト(System.Windows.Shell名前空間)を生成してウィンドウに設定してやる(次のコード)。

public MainWindow()
{
  InitializeComponent();

  // WindowのTaskbarItemInfoプロパティにTaskbarItemInfoインスタンスを設定する
  this.TaskbarItemInfo = new System.Windows.Shell.TaskbarItemInfo();
  // これはXAMLで次のように書いてもよい
  // <Window.TaskbarItemInfo>
  //   <TaskbarItemInfo />
  // </Window.TaskbarItemInfo>
}

Public Sub New()

  ' この呼び出しはデザイナーで必要です。
  InitializeComponent()

  ' InitializeComponent() 呼び出しの後で初期化を追加します。

  ' WindowのTaskbarItemInfoプロパティにTaskbarItemInfoインスタンスを設定する
  Me.TaskbarItemInfo = New System.Windows.Shell.TaskbarItemInfo()
  ' これはXAMLで次のように書いてもよい
  ' <Window.TaskbarItemInfo>
  '   <TaskbarItemInfo />
  ' </Window.TaskbarItemInfo>
End Sub

プログレスインジケーターを利用できるようにする(上:C#、下:VB)
太字の部分を追加する。
なお、コメントに書いたように、XAMLコードで記述することもできる。

 そうしたら、TaskbarItemInfoプロパティにオブジェクトをセットした後の好きなタイミングで、次に示すようなコードを記述するだけである。

// プログレスインジケーターを緑色にする
this.TaskbarItemInfo.ProgressState
  = System.Windows.Shell.TaskbarItemProgressState.Normal;
// プログレスインジケーターの進捗割合を30%にする
this.TaskbarItemInfo.ProgressValue = 0.3;

' プログレスインジケーターを緑色にする
Me.TaskbarItemInfo.ProgressState _
  = System.Windows.Shell.TaskbarItemProgressState.Normal
' プログレスインジケーターの進捗割合を30%にする
Me.TaskbarItemInfo.ProgressValue = 0.3

WPFの機能を使ってプログレスインジケーターを表示する例(上:C#、下:VB)
ProgressStateプロパティとProgressValueプロパティをセットするだけである。
ProgressStateプロパティには、前述したプログレスインジケーターの5種類の状態のいずれかを指定する。
ProgressValueプロパティは、進捗率の更新があるときだけセットすればよい。

// プログレスインジケーターを元に戻す
this.TaskbarItemInfo.ProgressState
  = System.Windows.Shell.TaskbarItemProgressState.None;

' プログレスインジケーターを元に戻す
Me.TaskbarItemInfo.ProgressState _
  = System.Windows.Shell.TaskbarItemProgressState.None

WPFの機能を使ってプログレスインジケーターを元に戻す例(上:C#、下:VB)
ProgressStateプロパティにセットするだけである。

 ただし、上のコードは、UIスレッドで呼び出さねばならない(そうしないと例外が発生する)。時間のかかる処理は別スレッドで非同期に動作させるのが普通であるから、UIの表示を更新する部分をUIスレッドに戻して実行するようなコーディングが必要だ。

 簡単な例を紹介しておこう。次の画像のようなUIを作る。カウントを表示しているテキストブロックの名前は「textBlock1」、[START]ボタンの名前は「button1」とする。

非同期処理からプログレスインジケーターを更新するウィンドウの例 非同期処理からプログレスインジケーターを更新するウィンドウの例
Windows 10で実行しているところである。非同期処理で10から0までカウントダウンしていく。カウントダウンに合わせて、ウィンドウ上のテキストブロックとプログレスインジケーターの表示を更新する。

 「button1」ボタンのクリックイベントで、10から0までカウントダウンする処理を非同期に実行させよう。そして、カウントが進むたびに、「textBlock1」テキストブロックの表示とプログレスインジケーターを更新しよう。.NET 4.5のTaskクラス(System.Threading.Tasks名前空間)とVisual Studio 2012で導入されたasync/awaitキーワードを使って書くと、次のコードのようになる。

private async void Button_Click(object sender, RoutedEventArgs e)
{
  // ボタンをディスエーブルに
  this.button1.IsEnabled = false;

  // プログレスインジケーターを緑色にする
  this.TaskbarItemInfo.ProgressState
    = System.Windows.Shell.TaskbarItemProgressState.Normal;

  // 進捗を表示するためのデリゲートを定義する(カウントダウン中に呼び出される)
  Action<Object> callUpdateProgress = (o) =>
  {
    int number = (int)o;
    // テキストブロックの表示を更新する
    this.textBlock1.Text = number.ToString();
    // プログレスインジケーターに値をセットする
    this.TaskbarItemInfo.ProgressValue = (10 - number) / 10.0;
  };

  // 別スレッドで実行するタスクを定義し、非同期実行を開始する(実行開始と同時に制御が戻る)
  await Task.Run(() =>
    {
      // 別スレッドで行う処理の本体
      for (int i = 10; i >= 0; i--)
      {
        // UIスレッドで進捗表示を呼び出す
        this.Dispatcher.BeginInvoke(callUpdateProgress, i);
        // 1秒間待機
        System.Threading.Thread.Sleep(1000);
      }
    });

  // 非同期実行が完了した後の処理(上のタスクが終了した後にUIスレッドで呼び出される)
  // プログレスインジケーターを通常表示に戻す
  this.TaskbarItemInfo.ProgressState
    = System.Windows.Shell.TaskbarItemProgressState.None;
  // ボタンをイネーブルに戻す
  this.button1.IsEnabled = true;
}

Private Async Sub Button_Click(sender As Object, e As RoutedEventArgs)
  ' ボタンをディスエーブルに
  Me.button1.IsEnabled = False

  ' プログレスインジケーターを緑色にする
  Me.TaskbarItemInfo.ProgressState _
      = System.Windows.Shell.TaskbarItemProgressState.Normal

  ' 進捗を表示するためのデリゲートを定義する(カウントダウン中に呼び出される)
  Dim callUpdateProgress As Action(Of Object) _
    = Sub(o)
        Dim number As Integer = CInt(o)
        ' テキストブロックの表示を更新する
        Me.textBlock1.Text = number.ToString()
        ' プログレスインジケーターに値をセットする
        Me.TaskbarItemInfo.ProgressValue = (10 - number) / 10.0
      End Sub

  ' 別スレッドで実行するタスクを定義し、非同期実行を開始する(実行開始と同時に制御が戻る)
  Await Task.Run(
    Sub()
      ' 別スレッドで行う処理の本体
      For i As Integer = 10 To 0 Step -1
        ' UIスレッドで進捗表示を呼び出す
        Me.Dispatcher.BeginInvoke(callUpdateProgress, i)
        ' 1秒間待機
        System.Threading.Thread.Sleep(1000)
      Next
    End Sub)

  ' 非同期実行が完了した後の処理(上のタスクが終了した後にUIスレッドで呼び出される)
  ' プログレスインジケーターを通常表示に戻す
  Me.TaskbarItemInfo.ProgressState _
      = System.Windows.Shell.TaskbarItemProgressState.None
  ' ボタンをイネーブルに戻す
  Me.button1.IsEnabled = True
End Sub

カウントダウンする非同期処理の例(上:C#、下:VB)
これはウィンドウのコードビハインドに記述している。そのため、UIスレッドで処理を呼び出すには「this.Dispatcher.BeginInvoke」メソッドが使える。ウィンドウと切り離して非同期処理を書く場合には、非同期処理側でイベントを発生させたり、.NET 4.5で導入されたIProgressインターフェース(System名前空間)を利用したりなどと、さまざまな工夫をすることになる。

まとめ

 プログレスインジケーターを表示すること自体は簡単である。実際には、非同期処理の途中で行うことになるので、そこがちょっと難しい*3。とはいえ、非同期処理の途中で画面を更新するコードが書けるならば、プログレスインジケーターの表示を更新するコードも同様である。

利用可能バージョン:.NET Framework 4.0以降
カテゴリ:オープンソース・ライブラリ 処理対象:タスクバー
カテゴリ:Windowsフォーム 処理対象:ウィンドウ
カテゴリ:WPF/XAML 処理対象:タスクバー
使用ライブラリ:TaskbarItemInfoクラス(System.Windows.Shell名前空間)
関連TIPS:タスクバー上のボタンやウィンドウのタイトルバーを点滅させるには?[C#、VB]
関連TIPS:タスクバーにアイコンを表示させないようにするには?


「.NET TIPS」のインデックス

.NET TIPS

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。