CodeZine(コードジン)

特集ページ一覧

時間のかかる処理で「処理中」を表現する(前編)

Windowsアプリケーションで処理中を表現する様々な方法

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2010/07/30 14:00

ダウンロード WaitSample.zip (46.5 KB)

目次

反応できないことを明示的に意思表示する

 アプリケーションが処理に反応できない場合、待機カーソル(WaitCursor:XPまでは砂時計、Vista/Windows7では回転する緑のドーナツ状のマウスカーソル)にするという決まりがあります。まずは、先ほどのループにこれを挟み込んで、応答なしが表示される場合でも頑張って作業していることをユーザーに伝えるコードを追加しましょう。

 待機カーソルにするには、LoadCursor APIでシステム定義のIDC_WAITカーソルをロードし、それをSetCursorで一時的にセットします。マウスカーソルは、マウスメッセージが処理されるタイミングでリセットされてしまうため、メッセージループが回るともとのカーソルに戻されてしまうので、注意が必要です。

待機カーソル設定(API)
void BusyLoop( DWORD dwTime )
{
  HCURSOR hWait = LoadCursor( NULL, IDC_WAIT );
  HCURSOR hPrev = SetCursor( hWait );

  DWORD dwStart = GetTickCount();
  while( (GetTickCount()-dwStart) < dwTime );

  SetCursor( hPrev );
}

 これで、ループ中はカーソルが待機カーソルになります。このサンプルは例外が発生したり、途中でリターンしてしまうなどはありませんが、プログラムが大きくなると、例外処理やリターン処理が入ることがあります。そのような場合、マウスカーソルを戻すためだけに例外をキャッチするのはあまり効率がよいとは言えません。C++ではこういう場合RAII(Resource Acquisition Is Initialization)と呼ばれる想定外の事態にも安全に対応できる記述をする手法があります。そちらで記述したものが下記のものです。

待機カーソル設定(RAII)
void BusyLoop( DWORD dwTime )
{
  CSimpleWaitCursor	waitCursor;
  DWORD dwStart = GetTickCount();
  while( (GetTickCount()-dwStart) < dwTime );
}
CSimpleWaitCursorクラス
class CSimpleWaitCursor
{
public:
  CSimpleWaitCursor()
  {
    HCURSOR hWait = ::LoadCursor( NULL, IDC_WAIT );
    if( hWait != NULL ){
      m_hPrev = ::SetCursor( hWait );
    }
    else{
      m_hPrev = NULL;
    }
  }
  ~CSimpleWaitCursor()
  {
    if( m_hPrev != NULL ){
      ::SetCursor( m_hPrev );
    }
    m_hPrev = NULL;
  }
private:
  HCURSOR  m_hPrev;
};

 単純なカーソル設定ですが、たったこれだけで、どのタイミングでBusyLoopから抜けても元のカーソルに戻ります。実際の処理時間がせいぜい2秒程度で終わるようなものであれば、これでも十分と言えます。

作業中にメッセージを処理する

 待機カーソルはマウスカーソルの見た目を変えることで、ユーザーに時間がかかる作業をしているというフィードバックを与えます。しかし、プログラムがしばらくメッセージを処理できないことをシステムに通知するものではありません。そのため、待機カーソルに変えただけでは一定時間ののちに「(応答なし)」状態になってしまうという状況に変化はありません(サンプルプログラムでは実際にマウスカーソルを変えています)。

 冒頭でも書いていたように「(応答なし)」が出る条件は一定時間メッセージを処理しなかった場合です。従って、時間のかかる処理中も何らかの形でメッセージを処理するようにすれば、基本的には問題が解決できるはずです。

 では、そのメッセージを処理するとはいったいどういうことなのでしょうか。答えはいたって簡単で、処理中にメッセージポンプと呼ばれる一時的なメッセージ処理ループを回すことで解決します。具体的には、次のようなごく単純なメッセージループを用意しておき、必要に応じて呼び出すだけです。

メッセージポンプ
void PumpMessage()
{
  MSG msg;
  while( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ){
    TranslateMessage( &msg );
    DispatchMessage( &msg );
  }
}

 さっそく最初のビジーループでも、応答なしが出ないように1秒ごとにメッセージを処理するように修正したものを用意、実行してみましょう。

ビジーループV2
void BusyLoop2( DWORD dwTime )
{
  while( dwTime > 0 ){
    PumpMessage();
    // メッセージを処理してから待機カーソルにする
    CSimpleWaitCursor  waitCursor;
    DWORD	dwWait = ( dwTime > 1000 ) ? 1000 : dwTime ;
    DWORD dwStart = GetTickCount();
    while( (GetTickCount()-dwStart) < dwWait );
    dwTime -= dwWait;
  }
}

 サンプルプログラムの[体験メニュー]-[ビジーループV2]で体験できます。これで、応答なしが出なくなりました。しかも、嬉しい副作用として、リサイズなどのウィンドウ操作も行うことができます。まずはいろいろ試してみてください。V2では環境によらず30秒間の処理となっています(実際には1秒×30のループ)。


  • LINEで送る
  • このエントリーをはてなブックマークに追加

バックナンバー

連載:Windowsアプリケーションで「処理中」を表現する

著者プロフィール

あなたにオススメ

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5