Python wxPython: with文でスマートに!wx.BusyInfoの基本的な使い方

Python

PythonのGUIライブラリであるwxPythonを使ってデスクトップアプリを開発していると、ファイルの読み込みやデータの書き出し、ネットワーク通信など、少し時間のかかる処理を実装する場面が必ず出てきます。

そんな時、処理中にアプリの画面が応答しなくなると、ユーザーは「アプリが固まった(フリーズした)のでは?」と不安になってしまいます。最悪の場合、強制終了されてしまうかもしれません。

そうした事態を防ぐために重要なのが、「今、ちゃんと処理中ですよ」とユーザーに伝えることです。

wxPythonには、この「処理中」メッセージを非常に簡単に表示できるwx.BusyInfoという便利な機能が用意されています。

さらに、wx.BusyInfoはPythonのwith文と組み合わせることで、驚くほどコードがスッキリし、安全に実装できます。

この記事では、wxPython初心者から中級者の方に向けて、wx.BusyInfoの基本的な使い方と、with文を使ったスマートな実装方法を、具体的なコード例を交えて詳しく解説します。

この記事を読み終える頃には、あなたのwxPythonアプリのユーザー体験(UX)を一段階アップさせるテクニックが身についているはずです。

wx.BusyInfoとは?

まずはwx.BusyInfoがどのようなものかを知りましょう。

ユーザーに「処理中」を伝えるシンプルな方法

wx.BusyInfoは、その名の通り「ビジー状態(取り込み中)」であることをユーザーに通知するための、一時的なメッセージウィンドウを表示するクラスです。

「データをロード中です…」や「処理を実行しています…」といった短いメッセージを表示するのに特化しています。

似たような機能にwx.ProgressDialog(進捗バー)がありますが、あちらは処理の進捗度(例: 0%〜100%)を視覚的に示したい場合に適しています。

一方、wx.BusyInfoは「処理にかかる時間はわからないけど、とにかく少し待ってほしい」という、比較的短い待機時間(数秒〜十数秒程度)を想定した、よりシンプルな機能です。

(バージョン情報) wx.BusyInfoはwxPythonの基本的なUIコンポーネントの一つであり、wxPython 4系など、現在広く利用されているバージョンで標準的に使用できます。

本記事で解説するwx.BusyInfoの公式ドキュメントはこちらです。

wx.BusyInfoが表示されるタイミングと消えるタイミング

wx.BusyInfoの動作原理は非常にシンプルです。

  1. 表示: wx.BusyInfo("メッセージ") のように、wx.BusyInfoクラスのオブジェクト(インスタンス)が作成された瞬間に、メッセージウィンドウが表示されます。
  2. 非表示: そのオブジェクトが破棄(削除)された瞬間に、メッセージウィンドウが自動的に消えます。

この「オブジェクトの生存期間 = メッセージの表示期間」という仕組みが、後ほど解説するPythonのwith文と非常に相性が良いのです。

wx.BusyInfoの基本的な使い方(with文なしの場合)

with文の便利さを実感していただくために、まずはwith文を使わない基本的な実装方法を見ていきましょう。

ここでは、ボタンを押すと3秒間だけ「処理中」と表示する簡単なサンプルアプリを作成します。

1. インポートとアプリケーションの準備

まずは、wxPythonアプリケーションの最小限の雛形(ウィンドウとボタンを一つ配置)を用意します。

import wx
import time

class MyFrame(wx.Frame):
    def __init__(self, parent, title):
        super(MyFrame, self).__init__(parent, title=title, size=(300, 200))
        
        panel = wx.Panel(self)
        vbox = wx.BoxSizer(wx.VERTICAL)
        
        self.button = wx.Button(panel, label="重い処理を実行")
        self.button.Bind(wx.EVT_BUTTON, self.on_button_click) # ボタンイベントを紐付け
        
        vbox.Add(self.button, proportion=0, flag=wx.ALL | wx.CENTER, border=10)
        panel.SetSizer(vbox)
        
        self.Centre()
        self.Show(True)

    def on_button_click(self, event):
        # ここに処理を書きます
        print("ボタンが押されました。")
        # TODO: BusyInfoを使った処理

if __name__ == '__main__':
    app = wx.App(False)
    frame = MyFrame(None, 'wx.BusyInfo サンプル')
    app.MainLoop()

このコードを実行すると、ボタンが一つあるだけのシンプルなウィンドウが表示されます。

2. wx.BusyInfoの表示と非表示(手動)

次に、on_button_clickメソッドを修正し、ボタンが押されたらwx.BusyInfoを表示し、重い処理(のフリ)を行い、最後に非表示にする処理を追加します。

    def on_button_click(self, event):
        print("処理開始...")
        
        # 1. BusyInfoのインスタンスを作成(この瞬間に表示される)
        busy_info = wx.BusyInfo("重い処理を実行中です、お待ちください...")
        
        # 2. 時間のかかる処理のシミュレーション
        time.sleep(3) # 3秒間待機
        
        # 3. BusyInfoのインスタンスを破棄(この瞬間に消える)
        del busy_info 
        # もしくは busy_info = None でもOK
        
        print("処理完了!")

ボタンを押すと、「重い処理を実行中です、お待ちください…」というメッセージウィンドウが表示され、3秒後に自動で消えるはずです。

4. エラー発生時に問題となるケース

上記の方法はシンプルですが、一つ大きな問題点があります。 もし「2. 時間のかかる処理」の途中でエラー(例外)が発生したらどうなるでしょうか?

del busy_infoの行に到達する前にプログラムが停止してしまうため、wx.BusyInfoのオブジェクトが破棄されず、メッセージウィンドウが表示されたまま消えなくなってしまいます。

この問題を避けるため、with文を使わない場合はtry...finally構文を使うのが一般的です。

    def on_button_click(self, event):
        print("処理開始...")
        busy_info = None # 先にNoneで初期化
        try:
            # 1. BusyInfoのインスタンスを作成(表示)
            busy_info = wx.BusyInfo("重い処理を実行中です、お待ちください...")
            
            # 2. 時間のかかる処理のシミュレーション
            time.sleep(3)
            
            # わざとエラーを発生させてみる
            # raise ValueError("何らかのエラーが発生しました!") 
            
            print("処理が正常に完了しました。")
            
        except Exception as e:
            print(f"エラーが発生しました: {e}")
            
        finally:
            # 3. 処理が成功しようがエラーになろうが、必ず実行される
            if busy_info:
                del busy_info # インスタンスを破棄(非表示)
            print("finallyブロックが実行され、BusyInfoが閉じられました。")

try...finallyを使うことで、途中でエラーが発生してもfinallyブロックが必ず実行されるため、メッセージウィンドウが残り続ける事態を防げます。

しかし、コードが冗長になり、busy_info変数の管理も少し面倒に感じませんか?

with文でスマートに!wx.BusyInfoの実装

ここからが本題です。先ほどのtry...finallyを使った冗長なコードは、Pythonのwith文を使うことで、劇的にシンプルかつ安全になります。

なぜwith文が使えるのか?

wx.BusyInfoクラスは、Pythonの「コンテキストマネージャ」という仕組みに対応するように設計されています。

難しい用語はさておき、with文で使えるオブジェクトは、「ブロック(字下げされた範囲)に入る時に特定の前処理を行い、ブロックを抜ける時に(エラーがあってもなくても)必ず後処理を行う」 という動作を保証してくれます。

wx.BusyInfoの場合、これが以下のようにマッピングされます。

  • withブロックに入る時: wx.BusyInfoが作成され、メッセージが表示される
  • withブロックを抜ける時: wx.BusyInfoが自動的に破棄され、メッセージが非表示になる

これは、先ほどのtry...finally構文の動作と全く同じですが、with文がその面倒な処理をすべて裏側で引き受けてくれるのです。

with文を使った具体的なコード例

on_button_clickメソッドをwith文を使って書き換えてみましょう。

    def on_button_click(self, event):
        print("処理開始...")
        
        # with文を使うと、ブロックを抜ける時に自動で消える
        try:
            with wx.BusyInfo("データを処理中です。しばらくお待ちください..."):
                
                # 2. 時間のかかる処理のシミュレーション
                time.sleep(3)
                
                # わざとエラーを発生させてみる
                # raise ValueError("何らかのエラーが発生しました!")
            
            print("処理が正常に完了しました。")

        except Exception as e:
            print(f"エラーが発生しました: {e}")
            # エラーが発生しても、withブロックを抜けているのでBusyInfoは消える

        print("withブロックを抜けました。")

どうでしょうか? try...finallyを使ったコードと比べて、busy_info変数の管理やdelの呼び出しが一切不要になり、非常にスッキリしました。

withブロックの中に時間のかかる処理を書くだけで、処理の開始時に自動で表示され、処理の終了時(またはエラー発生時)に自動で消えてくれます。これこそがwx.BusyInfoの最もスマートな使い方です。

メッセージを途中で変更する方法

処理が複数のステップに分かれている場合、進捗に合わせてメッセージを変更したいこともあります。 with文のasキーワードを使えば、wx.BusyInfoのインスタンスを受け取ることができ、UpdateMessageメソッドでメッセージを更新できます。

    def on_button_click(self, event):
        print("処理開始...")
        
        # 'as info' でインスタンスを受け取る
        with wx.BusyInfo("ステップ1/3: データの準備中...", parent=self) as info:
            
            time.sleep(2) # ステップ1の処理
            
            # メッセージを更新
            info.UpdateMessage("ステップ2/3: データの処理中...")
            time.sleep(2) # ステップ2の処理

            # メッセージを更新
            info.UpdateMessage("ステップ3/3: データの保存中...")
            time.sleep(2) # ステップ3の処理
        
        print("全ての処理が完了しました。")

これにより、ユーザーは処理が止まっているのではなく、順調に進んでいることを視覚的に理解でき、より安心して待つことができます。

wx.BusyInfoの便利なオプション

最後に、wx.BusyInfoをさらに便利に使うための代表的なオプションを2つ紹介します。

親ウィンドウを指定する

wx.BusyInfoのコンストラクタ(オブジェクトを作るところ)でparent引数を指定すると、そのウィンドウの中央にメッセージが表示されるようになります。

# parent を指定しない場合(デフォルト)
# 画面(スクリーン)の中央に表示される
wx.BusyInfo("処理中です...")

# parent を指定する場合
# self.frame (MyFrameインスタンス) の中央に表示される
with wx.BusyInfo("処理中です...", parent=self): # self は MyFrame のこと
    time.sleep(2)

親ウィンドウを指定することで、ユーザーは「どのアプリが処理中なのか」を明確に認識できるため、特別な理由がなければ指定することが推奨されます。

wx.BusyCursorとの違い

wx.BusyInfoとよく似た名前にwx.BusyCursorという機能があります。

  • wx.BusyInfo: メッセージウィンドウを表示します(この記事で解説したもの)。
  • wx.BusyCursor: マウスカーソルの形状を、砂時計や回転する円などの「ビジー状態」のアイコンに変更します。

wx.BusyCursorは、処理中であることを示しつつも、ユーザーが他の操作(ウィンドウの移動など)をできる(場合もある)ことを示唆します。一方、wx.BusyInfoは「操作を止めて待ってほしい」という意図がより強いです。

wx.BusyCursorwith文に対応しているため、使い方は非常に似ています。

# 0.5秒程度の本当に一瞬の処理の場合
with wx.BusyCursor():
    # 何か非常に軽い処理
    time.sleep(0.5)

数秒以上かかる処理の場合はwx.BusyInfoでメッセージを明示し、1秒未満のほんの一瞬だけ待たせる場合はwx.BusyCursorでカーソルを変えるだけにする、といった使い分けが考えられます。

まとめ: wx.BusyInfoはwith文で使おう!

今回は、wxPythonで時間のかかる処理中にユーザーを待たせるためのwx.BusyInfoの使い方を解説しました。

  • wx.BusyInfoは、処理中メッセージを簡単に表示できる機能です。
  • インスタンスの作成で表示され、破棄で消えます。
  • try...finallyでも実装できますが、コードが冗長になりがちです。
  • wx.BusyInfoはコンテキストマネージャに対応しているため、with文が使えます。
  • with wx.BusyInfo(...)構文を使うのが、最もシンプルで、安全(閉じ忘れがない)、かつスマートな実装方法です。

重い処理を実装する際は、ユーザーを不安にさせない「ひと工夫」として、ぜひwith wx.BusyInfo(...)を活用してみてください。あなたのwxPythonアプリの品質がきっと向上するはずです。

コメント

タイトルとURLをコピーしました