PythonでGUIアプリケーションを作成できるライブラリ「wxPython」を使っていると、ウィンドウのサイズが変更されたときに特定の処理を実行したい、という場面に遭遇します。
「ウィンドウをリサイズしたら、中の部品(ウィジェット)の配置が崩れてしまった…」 「ウィンドウの現在のサイズを取得して、ステータスバーに表示したい」 「サイズ変更をきっかけに、描画内容を更新したい」
このような悩みは、wxPythonの**wx.SizeEvent**を理解することで解決できます。
この記事では、wxPython初心者から中級者の方に向けて、wx.SizeEventとは何か、基本的な使い方(EVT_SIZE)、そしてGUI開発で非常に重要なレイアウト管理クラス**wx.Sizer**との関係性まで、サンプルコードを交えて分かりやすく解説します。
この記事を読み終えれば、ウィンドウのリサイズイベントを自由に扱えるようになります。
本記事で解説するwx.SizeEventの公式ドキュメントはこちらです。
wx.SizeEventとは?
ウィンドウのサイズ変更を知らせる「合図」
wx.SizeEventとは、その名の通り、ウィンドウのサイズ(大きさ)が変更されたときに発生するイベントの一種です。
wxPythonでは、ウィンドウやボタン、テキストボックスなど、画面に表示されるほぼすべての部品が**wx.Windowクラスを親(基底クラス)として作られています。例えば、アプリのメインウィンドウであるwx.Frameや、部品を配置する土台となるwx.Panel**もwx.Windowの一種です。
これらのwx.Window(や、その派生クラス)のサイズが変更されたことをプログラムに知らせる「合図」が、wx.SizeEventです(wx.Windowの詳しい記事はこちら)。
wx.SizeEventが発生する主なタイミング
このイベントは、主に以下のタイミングで発生します。
- ユーザーが手動でリサイズした時: ユーザーがマウスでウィンドウの端をドラッグして、サイズを変更した(し終わった)時。
- プログラム側でサイズを変更した時: コードの中で
SetSize()メソッドなどを呼び出し、強制的にウィンドウサイズを変更した時。 - (補足)ウィンドウの初期表示時: ウィンドウが最初に画面に表示される際にも、サイズが確定する過程で発生することがあります。
この「合図」をキャッチすることで、私たちは「サイズが変わった瞬間に、この処理を実行する」というプログラムを書くことができるようになります。
wx.SizeEventの基本的な使い方 (EVT_SIZE)
wx.SizeEventを利用するには、大きく分けて2つのステップが必要です。
- イベント発生時に実行したい処理(関数)を定義する。
- 「サイズが変わったら、その関数を呼び出す」ように紐付け(バインド)する。
この「イベント処理」の仕組みは、wxPythonの**wx.EvtHandler**クラスによって提供されています(wx.Windowはwx.EvtHandlerを継承しています)。
ステップ1:イベントハンドラ(処理する関数)を定義する
まず、イベントが発生したときに呼び出される関数(メソッド)を定義します。これをイベントハンドラと呼びます。
イベントハンドラは、引数として「発生したイベントの詳細情報」を受け取る必要があります。wx.SizeEventの場合、この引数(慣習的にeventと名付けられます)には、変更後のサイズなどの情報が格納されたwx.SizeEventオブジェクトが渡されます。
# クラスのメソッドとして定義する例
class MyFrame(wx.Frame):
def __init__(self, parent, title):
super().__init__(parent, title=title, size=(300, 200))
# ... ここにウィジェットの配置など ...
# ステップ1: イベントハンドラを定義
def on_size_changed(self, event):
# この中身が、サイズ変更時に実行される
print("ウィンドウサイズが変更されました!")
# イベントオブジェクトをスキップさせ、
# 他のデフォルト処理(レイアウト調整など)も実行されるようにする
event.Skip()ポイント: イベントハンドラの最後でevent.Skip()を呼び出すのが一般的です。 これを忘れると、wxPythonが標準で行うはずだったリサイズ処理(例えばwx.Sizerによるレイアウトの自動調整)が停止してしまう可能性があるため、意図的に停止させる場合以外は記述しておきましょう。
ステップ2:イベントをバインド(紐付け)する
次に、ステップ1で定義したon_size_changedメソッドを、「wx.SizeEventが発生した時」に呼び出してもらうよう登録します。この作業をバインドと呼びます。
バインドにはBind()メソッドを使います。wx.SizeEventを監視するための専用の目印として**EVT_SIZE**が用意されています。
class MyFrame(wx.Frame):
def __init__(self, parent, title):
super().__init__(parent, title=title, size=(300, 200))
# ...
# ステップ2: イベントをバインドする
# self (このフレーム自身) で EVT_SIZE (サイズ変更イベント) が発生したら、
# self.on_size_changed メソッドを呼び出す
self.Bind(wx.EVT_SIZE, self.on_size_changed)
# ... (on_size_changed メソッドの定義は省略) ...これで、MyFrame(このメインウィンドウ)のサイズが変更されるたびに、on_size_changedメソッドが自動的に呼び出されるようになりました。
【実践】wx.SizeEventを使ったサンプルコード
言葉だけでは分かりにくいので、実際に動作するコードを見てみましょう。
サンプル1:リサイズを検知してサイズをコンソールに出力する
最もシンプルな例として、ウィンドウサイズが変更されたら、その新しいサイズ(幅と高さ)をコンソ(ターミナル)に出力するプログラムを作成します。
import wx
class MyFrame(wx.Frame):
def __init__(self, parent, title):
# wx.Frameを初期化
super().__init__(parent, title=title, size=(350, 250))
# パネルを作成
panel = wx.Panel(self)
panel.SetBackgroundColour("#EFEFEF") # パネルに色を付けておく
# ステップ2: イベントのバインド
# フレーム(self)のサイズ変更(EVT_SIZE)を監視
self.Bind(wx.EVT_SIZE, self.on_size_changed)
self.Centre()
self.Show()
# ステップ1: イベントハンドラの定義
def on_size_changed(self, event):
""" ウィンドウサイズ変更時に呼び出される """
# eventオブジェクトから新しいサイズを取得
# GetSize()メソッドは wx.Size オブジェクトを返します
new_size = event.GetSize()
# wx.Size オブジェクトから幅(width)と高さ(height)を取得
width = new_size.GetWidth()
height = new_size.GetHeight()
print(f"サイズ変更検知: 幅={width}, 高さ={height}")
# 他のイベント処理も実行されるようにSkip()を呼ぶ
event.Skip()
if __name__ == '__main__':
app = wx.App()
MyFrame(None, title='wx.SizeEvent サンプル1')
app.MainLoop()このプログラムを実行し、ウィンドウの端をドラッグしてリサイズしてみてください。ドラッグを離すたびに、コンソールに「サイズ変更検知: 幅=XXX, 高さ=XXX」と出力されるはずです。
ここで重要なのがevent.GetSize()メソッドです。これは、変更後のウィンドウサイズを**wx.Size**オブジェクトとして返します。wx.Sizeオブジェクトは、幅(Width)と高さ(Height)の情報をペアで保持する便利なクラスで、GetWidth()やGetHeight()(または.width, .heightプロパティ)でそれぞれの値にアクセスできます。
サンプル2:ウィンドウサイズ情報をステータスバーに表示する
もう少し実用的な例として、現在のウィンドウサイズをリアルタイムでステータスバーに表示してみましょう。
import wx
class MyFrame(wx.Frame):
def __init__(self, parent, title):
super().__init__(parent, title=title, size=(400, 300))
# ステータスバーを作成
self.CreateStatusBar()
self.SetStatusText("ウィンドウをリサイズしてみてください")
# パネルを作成
panel = wx.Panel(self)
panel.SetBackgroundColour("#EFEFEF")
# イベントのバインド
self.Bind(wx.EVT_SIZE, self.on_size_changed)
self.Centre()
self.Show()
# イベントハンドラ
def on_size_changed(self, event):
""" ウィンドウサイズ変更時に呼び出される """
# eventオブジェクトから wx.Size オブジェクトを取得
new_size = event.GetSize()
# ステータスバーに現在のサイズを表示
status_text = f"現在のサイズ: 幅={new_size.width}, 高さ={new_size.height}"
self.SetStatusText(status_text)
# イベントをスキップ
event.Skip()
if __name__ == '__main__':
app = wx.App()
MyFrame(None, title='wx.SizeEvent サンプル2')
app.MainLoop()この例では、on_size_changedハンドラの中で、取得したwx.Sizeオブジェクト(new_size)を使い、ステータスバーのテキストをSetStatusText()で更新しています。
これにより、ウィンドウサイズが変わるたびに、ステータスバーの表示がリアルタイム(※)で更新されます。 (※OSにもよりますが、EVT_SIZEは通常、リサイズ操作が完了した時点で発生します)
wx.SizeEventとレイアウト管理 (wx.Sizer) の関係
ここまでwx.SizeEventのキャッチ方法を説明してきましたが、ここでwxPythonにおける非常に重要な注意点があります。
基本:レイアウト調整はwx.Sizerに任せるのが最適解
wx.SizeEventの使い道として、「ウィンドウサイズに合わせてボタンの位置を動かしたい」といったレイアウト調整を想像するかもしれません。
しかし、wxPythonでウィジェットのレイアウト調整を行う場合、wx.SizeEventを自分で処理するのは悪手となるケースがほとんどです。
なぜなら、wxPythonには**wx.Sizer**(wx.BoxSizerやwx.GridSizerなど)という、非常に強力なレイアウト管理専用のクラスが用意されているからです。
wx.Sizerは、ウィンドウ(wx.Frameやwx.Panel)のサイズが変更されたこと(=wx.SizeEventが発生したこと)を自動的に検知し、自身が管理しているウィジェット(ボタンやテキストボックスなど)のサイズと位置を、設定されたルールに基づいて自動的に再配置してくれます。
Sizerを使っていればEVT_SIZEは不要?
結論:ウィジェットの「レイアウト調整」が目的ならば、EVT_SIZEのバインドは不要です。
wx.Sizerを使うのがwxPythonの標準的な設計方法であり、最も効率的です。wx.Sizerにwx.Panelを管理させ、そのパネルの上にウィジェットを配置し、最後にSetSizer()メソッドでフレームやパネルにSizerをセットするだけで、レスポンシブな(サイズ変更に追従する)レイアウトが完成します。
開発者はwx.SizeEventの存在を意識する必要すらありません。
wx.SizeEventをあえて使うケース
では、wx.SizeEventを手動でバインドするのはどのような時でしょうか? それは、wx.Sizerによる自動レイアウト調整以外の処理を行いたい場合です。
- サンプル2のようなサイズ情報の表示: 現在のサイズを取得してステータスバーやタイトルバーに表示する場合。
- カスタム描画(グラフィック)の更新:
wx.DC(デバイスコンテキスト)を使って独自の図形などを描画している場合、ウィンドウサイズが変わったら描画内容(座標や大きさ)を再計算して、Refresh()メソッドで再描画を促す場合。 Sizerでは実現が難しい特殊なレイアウト: 非常に動的で複雑な、Sizerのルールだけでは対応しきれないカスタムレイアウトを実装する場合。(ただし、これは上級者向けです)
(補足) EVT_SIZING と EVT_SIZE の違い
wx.SizeEventに関連するイベントとして、EVT_SIZINGというものもあります。この2つは似ていますが、発生するタイミングが明確に異なります。
EVT_SIZING:サイズ変更の「最中」に連続発生
EVT_SIZINGは、ユーザーがマウスでウィンドウをドラッグしている最中に、連続して何度も発生します。
用途:
- リサイズ中のサイズをリアルタイムで取得したい場合(ただし高頻度で発生するため処理負荷に注意)。
- ウィンドウの最小サイズや最大サイズを動的に制限したい場合。
EVT_SIZE:サイズ変更が「完了した後」に発生
EVT_SIZEは、基本的にユーザーがドラッグを離すなどして、サイズ変更操作が完了(確定)した後に発生します。
用途:
- 最終的に確定したサイズに基づいて、レイアウトの再計算やウィジェットの再配置を行いたい場合(
wx.Sizerが内部で行っているのはこちらです)。 - サイズ確定後にステータスバーを更新するなど、1回の処理で済ませたい場合。
ほとんどの場合、私たちが使いたいのは「サイズが確定した後」の処理、すなわちEVT_SIZEです。
まとめ
今回は、wxPythonのwx.SizeEventについて詳しく解説しました。
wx.SizeEventは、wx.Window(wx.Frameやwx.Panelなど)のサイズが変更されたときに発生するイベントです。- イベント処理は**
wx.EvtHandler**の仕組み(Bind()メソッド)を利用します。 wx.SizeEventをキャッチするには**EVT_SIZEを使い、イベントハンドラをBind()**メソッドで紐付けます。- ハンドラでは引数
eventを受け取り、event.GetSize()で変更後のサイズを**wx.Size**オブジェクトとして取得できます。 - ハンドラの最後では
event.Skip()を呼び出すのが安全です。 - 最重要: GUIのレイアウト調整は、
wx.SizeEventを直接処理するのではなく、**wx.Sizer**クラスに任せるのがwxPythonの基本です。Sizerがwx.SizeEventを自動で処理してくれます。
wx.SizeEventを直接扱うのは、Sizerの自動レイアウト以外の目的(サイズの表示やカスタム描画の更新など)が主となります。
wx.Sizerの便利さとwx.SizeEventの役割を正しく理解し、柔軟なGUIアプリケーション開発に役立ててください。


コメント