PythonでGUIアプリケーションを作成できるライブラリ「wxPython」を使っていて、「ウィンドウが移動された瞬間」を検知して、その「移動後の座標」を取得したいと思ったことはありませんか?
この記事では、wxPythonのイベント処理の中でも**wx.MoveEvent**に焦点を当て、ウィンドウの移動を検知し、そのX座標とY座標を取得する具体的な方法を、初心者から中級者の方にもわかりやすく解説します。
サンプルコードを交えながら、イベント処理の基本的な流れから座標の具体的な取得方法までを丁寧に説明しますので、ぜひ最後までご覧ください。
本記事で解説するwx.MoveEventについての公式ドキュメントはこちらです。
はじめに:wx.MoveEventとは?
wx.MoveEventとは、その名の通り、ウィンドウが移動したときに発生するイベントを扱うためのクラスです。
ユーザーがマウスでウィンドウのタイトルバーを掴んでドラッグした際や、プログラムによってウィンドウの位置が変更された際に、wxPythonは「ウィンドウが動いたよ!」という合図(イベント)を送出します。この合図こそがwx.MoveEventです。
この記事を読めば、以下のことがわかります。
wx.MoveEventを使ってウィンドウの移動を検知する方法- 移動後のウィンドウの左上のX座標、Y座標を取得する方法
- イベント処理の基本的な書き方(
Bind()メソッドの使い方)
wxPythonでイベント処理の基礎を学びたい方や、特定のウィンドウ操作(移動)に応じて何か処理を行いたい方にとって、必須の知識となります。
wx.MoveEventで「移動座標」を取得する基本的な流れ
wx.MoveEvent を使ってウィンドウの移動座標を取得する処理は、主に3つのステップで実現できます。
これはwxPythonにおける多くのイベント処理で共通する、基本的な流れでもあります。
ステップ1:イベントハンドラ(関数)を準備する
まず、「ウィンドウが移動したときに、何をするか」という処理内容を関数(メソッド)として定義します。この関数をイベントハンドラと呼びます。
このハンドラは、wx.MoveEvent オブジェクトを引数として受け取るように定義する必要があります。
ステップ2:Bind() メソッドで wx.EVT_MOVE とハンドラを紐付ける
次に、作成したイベントハンドラを、「どのイベントが発生したときに呼び出すか」をwxPythonに教える必要があります。
これを行うのが Bind() メソッドです。wx.Window クラス(や、それを継承する wx.Frame などのウィジェット)がこのメソッドを持っています。
wx.EVT_MOVE という定数が「ウィンドウが移動したイベント」を示しており、これをステップ1で作成したハンドラと紐付け(バインド)します。
ステップ3:イベントオブジェクトから座標を取得する
最後に、イベントハンドラの中で、引数として受け取った wx.MoveEvent オブジェクト(慣習的に event という変数名が使われます)から、座標情報を取り出します。
event.GetPosition() というメソッドを使うことで、移動後の座標を簡単に取得できます。
【サンプルコード】ウィンドウの移動座標をリアルタイムで取得する
それでは、上記の3ステップを踏まえた具体的なサンプルコードを見ていきましょう。 このコードは、ウィンドウを作成し、そのウィンドウが移動するたびに、移動後の左上のX座標とY座標をコンソール(ターミナル)に出力します。
必要なモジュール(wx)のインポート
まずは、wxPythonライブラリ(wx)をインポートします。
import wxメインウィンドウ(wx.Frame)の準備
次に、アプリケーションの土台となる wx.App と、メインウィンドウとなる wx.Frame を定義します。wx.Frame は、wx.Window クラスを継承しており、イベント処理の仕組み(wx.EvtHandler の機能)を持っています(wx.EvtHandlerについての詳しい記事はこちら)。
class MyFrame(wx.Frame):
def __init__(self, parent, title):
# wx.Frameのコンストラクタを呼び出す
super(MyFrame, self).__init__(parent, title=title, size=(300, 200))
# ここでイベントをバインドします(後述)
self.setup_event_binding()
# ウィンドウを表示
self.Centre()
self.Show(True)
def setup_event_binding(self):
# このフレーム(self)で発生する wx.EVT_MOVE を
# self.on_move メソッドに紐付ける
pass # 後ほどここでBindしますwx.MoveEvent を受け取るイベントハンドラの定義
MyFrame クラス内に、ステップ1で説明したイベントハンドラ on_move を定義します。 引数として event を受け取る点に注目してください。この event には wx.MoveEvent オブジェクトが渡されます。
def on_move(self, event):
""" ウィンドウが移動したときに呼び出されるハンドラ """
print("ウィンドウが移動しました。")
# ステップ3: イベントオブジェクトから座標を取得
# GetPosition() は wx.Point オブジェクトを返します
pos = event.GetPosition()
# wx.Point オブジェクトから x と y を取り出す
print(f"現在の座標: X={pos.x}, Y={pos.y}")
# (参考)イベントが親ウィジェットに伝播するのを許可
event.Skip()wx.EVT_MOVE を使ってイベントをバインドする
ステップ2です。setup_event_binding メソッド(または __init__ 内)で、Bind() メソッドを使って wx.EVT_MOVE と on_move ハンドラを紐付けます。
def setup_event_binding(self):
# このフレーム(self)で発生する wx.EVT_MOVE を
# self.on_move メソッドに紐付ける
self.Bind(wx.EVT_MOVE, self.on_move)全体のサンプルコードと実行方法
これまでに作成した各パーツと、アプリケーションを実行するためのお決まりのコードを組み合わせた、全体のサンプルコードです。
import wx
class MyFrame(wx.Frame):
"""
メインウィンドウ(フレーム)を定義するクラス
wx.Frame は wx.Window を継承しており、イベント処理機能を持つ
"""
def __init__(self, parent, title):
# 親クラス(wx.Frame)のコンストラクタを呼び出す
super(MyFrame, self).__init__(parent, title=title, size=(350, 250))
# ウィンドウ(フレーム)の移動イベントをバインド
self.setup_event_binding()
# ウィンドウを中央に表示
self.Centre()
self.Show(True)
def setup_event_binding(self):
"""
イベントとイベントハンドラを紐付ける(バインドする)
"""
# ステップ2:
# self (このフレーム) で wx.EVT_MOVE (移動イベント) が発生したら、
# self.on_move メソッドを呼び出すように設定
self.Bind(wx.EVT_MOVE, self.on_move)
def on_move(self, event):
"""
ステップ1: ウィンドウが移動したときに呼び出されるイベントハンドラ
引数 event には wx.MoveEvent オブジェクトが渡される
"""
# ステップ3: イベントオブジェクトから座標を取得
# event.GetPosition() は、移動後のウィンドウの左上隅の
# スクリーン座標(wx.Point)を返します。
pos = event.GetPosition()
# wx.Point オブジェクトは .x と .y 属性を持つ
print(f"ウィンドウが移動しました。 現在の座標: X={pos.x}, Y={pos.y}")
# 他のイベントハンドラも実行されるようにする
event.Skip()
# アプリケーションのエントリポイント
if __name__ == '__main__':
# wx.App オブジェクトを作成
app = wx.App(False)
# MyFrame オブジェクトを作成(これがウィンドウの実体)
frame = MyFrame(None, 'wx.MoveEvent サンプル')
# イベントループを開始
app.MainLoop()このコードを実行し、表示されたウィンドウをマウスでドラッグして移動させてみてください。 コードを実行したコンソール(ターミナル)に、移動後の座標がリアルタイムで次々と出力されるはずです。
ウィンドウが移動しました。 現在の座標: X=100, Y=101
ウィンドウが移動しました。 現在の座標: X=100, Y=102
ウィンドウが移動しました。 現在の座標: X=101, Y=102
...wx.MoveEventオブジェクトの活用方法
サンプルコードの on_move ハンドラで使った event オブジェクト(wx.MoveEvent のインスタンス)について、もう少し詳しく見てみましょう。
event.GetPosition():移動後の座標(wx.Point)を取得する
wx.MoveEvent の中で最も重要なメソッドが GetPosition() です。
これは、移動が完了した後のウィンドウのクライアント領域ではない、ウィンドウ全体の左上隅のスクリーン座標を返します。 戻り値の型は wx.Point という専用のクラスオブジェクトです。
wx.Point オブジェクトから X座標とY座標を取り出す方法
GetPosition() によって返された wx.Point オブジェクト(サンプルコードでは pos 変数に格納)は、x と y という2つの属性を持っています。
pos.x: ウィンドウ左上隅のX座標(整数)pos.y: ウィンドウ左上隅のY座標(整数)
これにより、print(f"X={pos.x}, Y={pos.y}") のようにして、それぞれの座標値に簡単にアクセスできます。
(参考)event.Skip() の役割について
サンプルコードの最後に event.Skip() という記述がありました。
これは、「このイベント(wx.MoveEvent)に対する処理はこれで完了したので、もし親ウィジェット(この場合は特にないですが)や、他のデフォルト処理がこのイベントを待っているなら、そちらでも処理を続けてください」とwxPythonに伝えるためのメソッドです。
wx.MoveEvent のような単純な通知イベントでは、Skip() を呼び出さなくても問題なく動作することが多いですが、イベント処理のお作法として記述しておくことが推奨されます。
wx.MoveEvent利用時の注意点
最後に、wx.MoveEvent を利用する上での注意点を2つ紹介します。
イベントが連続して(大量に)発生することへの留意
サンプルコードを実行してウィンドウをドラッグするとわかりますが、wx.MoveEvent は移動中に連続して(ほぼ1ピクセル動くごとに)発生します。
もし on_move ハンドラの中でファイル保存やネットワーク通信、複雑な描画更新といった重い処理を実行してしまうと、移動のたびにその重い処理が呼び出され、アプリケーション全体の動作がカクついたり、固まったりする原因になります。
on_move ハンドラ内では、座標を記録するなど、ごく軽量な処理に留めるのが鉄則です。
wx.MoveEvent が発生しない場合のチェックポイント
「サンプルコードを試したのに on_move が呼び出されない」という場合、Bind() メソッドの記述を確認してください。
self.Bind(wx.EVT_MOVE, self.on_move)Bind している対象(self)が、実際に移動するウィンドウ(この例では wx.Frame)であることを確認してください。もし wx.Panel の上でイベントを取りたい場合は、そのパネルのインスタンスで Bind する必要があります(ただし、wx.MoveEvent は通常、wx.Frame などのトップレベルウィンドウで取得します)。
まとめ
この記事では、PythonのwxPythonライブラリを使用して、ウィンドウの移動を検知する wx.MoveEvent の使い方と、移動後の座標を取得する方法について解説しました。
本記事の重要ポイント:
- ウィンドウの移動を検知するには
wx.EVT_MOVEを使います。 - イベント処理の基本は「ハンドラ定義」と「
Bindでの紐付け」です。 wx.MoveEventは、移動イベントの詳細情報を持つオブジェクトです。- 移動後の座標は
event.GetPosition()で取得できます。 GetPosition()はwx.Pointオブジェクトを返し、.xと.yで座標にアクセスできます。
wx.MoveEvent をマスターすれば、「ウィンドウが特定の位置に来たら色を変える」「ウィンドウが画面外に出ないように制御する」といった、より高度なGUIアプリケーションの制御が可能になります。
まずはこの記事のサンプルコードを基本として、ご自身のアプリケーションに応用してみてください。


コメント