PythonとwxPythonでGUIアプリケーションを開発していると、ユーザーのディスプレイ環境が多様であることに気づかされます。特に、複数のモニター(マルチモニター、デュアルディスプレイ)を使っているユーザーは珍しくありません。
「プレゼンソフトを作りたいけど、2画面目にスライドを全画面表示させたい」 「アプリを起動したとき、セカンダリモニターの中央に配置したい」 「そもそもユーザーがモニターを何台使っているか、どうやって知ればいい?」
これらの課題は、ユーザーの画面環境を正確に把握できなければ解決できません。
この記事では、wxPythonでPCに接続された物理ディスプレイ(モニター)の情報を扱うための wx.Display クラスに焦点を当てます。wx.Display の使い方をマスターすれば、マルチモニター環境を検出し、各画面の解像度や位置情報を取得して、ウィンドウを意図した場所に配置できるようになります。
wx.Display とは?
wx.Display とは、ひと言でいうと**「PCに接続された物理モニターの情報を扱うためのクラス」**です。
wx.Frame がウィンドウという「アプリの部品」を扱うのに対し、wx.Display はそのウィンドウを表示する「土台」である物理的な画面(モニター)そのものを扱います。
このクラスは、特にマルチモニター環境で真価を発揮します。wx.Display を使えば、以下のことが可能になります。
- 接続されているモニターの総数
- 各モニターの解像度(サイズ)
- 各モニターの位置(仮想スクリーン座標系)
- どのモニターがプライマリ(メイン)か
これらの情報を利用することで、プレゼンテーションツールや、複数のウィンドウを異なるモニターに配置するような高度なアプリケーションを作成できます。
注意点として、wx.Display の機能(特に GetCount() など)は、wx.App のインスタンスが作成された後でないと正しく機能しない場合があるため、必ず app = wx.App() を実行した後に呼び出すようにしてください。
wx.Display の基本的な使い方: モニターの数を調べる
wx.Display の情報にアクセスする最初のステップは、モニターが何台接続されているかを調べることです。
これは wx.Display.GetCount() という静的メソッド(クラスから直接呼び出すメソッド)で簡単に取得できます。
import wx
# wx.App のインスタンス化が必須
app = wx.App()
try:
# 接続されているディスプレイの総数を取得
display_count = wx.Display.GetCount()
print(f"接続されているモニターの数: {display_count} 台")
# 取得した数だけループして、各ディスプレイにアクセスできる
for i in range(display_count):
# wx.Display(i) で i 番目のディスプレイオブジェクトを取得
display = wx.Display(i)
print(f"--- {i} 番目のモニター ---")
# (ここで各モニターの情報を取得していく)
except NotImplementedError:
# グラフィカル環境がない場合 (例: SSH接続のみ) など
print("ディスプレイ情報を取得できませんでした。")
# app.MainLoop() などは省略wx.Display.GetCount() は接続されているモニターの数を整数で返します。デュアルモニターなら 2、シングルモニターなら 1 が返ります。
そして、wx.Display(i) のようにインデックス番号(0から始まる)をコンストラクタに渡すことで、個々のディスプレイを表す wx.Display オブジェクトを取得できます。
主要なメソッド: 画面情報の取得
モニターの数がわかったら、次は各モニターの具体的な情報を取得します。wx.Display オブジェクトには、そのための便利なメソッドが用意されています。
GetGeometry() – モニターの解像度と位置を取得する
GetGeometry() は、そのモニターの全体的なサイズ(解像度)と、OSが管理する「仮想スクリーン座標系」における位置を取得する、最も重要なメソッドの一つです。
戻り値は wx.Rect オブジェクトで、x, y, width, height の4つのプロパティを持っています。
rect.width: モニターの幅(解像度)rect.height: モニターの高さ(解像度)rect.x,rect.y: 仮想スクリーン座標系におけるモニターの左上の位置
display = wx.Display(0) # 0番目のモニター
geometry = display.GetGeometry()
print(f"解像度: {geometry.width} x {geometry.height}")
print(f"位置 (X): {geometry.x}")
print(f"位置 (Y): {geometry.y}")重要な注意点: x, y の位置は、必ずしもプライマリモニターが (0, 0) になるとは限りません。例えば、プライマリモニターの「左側」にセカンダリモニターを配置している場合、セカンダリモニターの x がマイナス値(例: -1920)になることもあります。
GetClientArea() – タスクバーを除いた「作業領域」を取得する
GetGeometry() と非常によく似ていますが、GetClientArea() はタスクバーやmacOSのドックなどを除いた、実質的な作業領域を wx.Rect オブジェクトとして返します。
GetGeometry(): 画面全体のピクセル数(例: 1920×1080)GetClientArea(): タスクバーが下にあれば、その分を引いた領域(例: 1920×1040)
ウィンドウを「最大化」させたり、画面いっぱいに表示させたりしたい場合は、GetGeometry() ではなく GetClientArea() を使う方が、タスクバーに隠れることなく安全に配置できます。
display = wx.Display(0) # 0番目のモニター
client_area = display.GetClientArea()
print(f"タスクバー等を除いた作業領域 (幅): {client_area.width}")
print(f"タスクバー等を除いた作業領域 (高さ): {client_area.height}")
print(f"作業領域の開始位置 (X): {client_area.x}")
print(f"作業領域の開始位置 (Y): {client_area.y}")IsPrimary() と wx.Display.GetPrimary() – プライマリモニターを特定する
マルチモニター環境では、OSが「メインディスプレイ(プライマリモニター)」として設定しているモニターが必ず1台あります。
disp.IsPrimary():wx.Display(i)オブジェクトがプライマリモニターかどうかをbool値(True/False)で返します。wx.Display.GetPrimary(): (wxPython 4.1以降推奨) プライマリモニターのwx.Displayオブジェクトを直接返す静的メソッドです。インデックス番号を気にせずプライマリモニターにアクセスしたい場合に便利です。
# プライマリモニターのオブジェクトを直接取得
primary_display = wx.Display.GetPrimary()
print(f"プライマリモニターの解像度: {primary_display.GetGeometry().width}")
# ループで回して判定する
for i in range(wx.Display.GetCount()):
display = wx.Display(i)
if display.IsPrimary():
print(f"{i} 番目のモニターがプライマリです。")
else:
print(f"{i} 番目のモニターはセカンダリです。")実践!マルチモニターでウィンドウを制御する
ここまでの知識を使って、実際のアプリケーションでウィンドウを制御するサンプルコードを見てみましょう。
サンプルコード1: 全モニターの情報を一覧表示する
まずは、PCに接続されている全モニターの情報を網羅的に表示するプログラムです。
import wx
class DisplayInfoFrame(wx.Frame):
def __init__(self):
super().__init__(None, title="モニター情報一覧", size=(500, 300))
self.panel = wx.Panel(self)
# 情報を表示するためのリストボックス
self.listbox = wx.ListBox(self.panel, style=wx.LB_SINGLE)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.listbox, 1, wx.EXPAND | wx.ALL, 10)
self.panel.SetSizer(sizer)
self.ShowInfo()
self.Centre()
def ShowInfo(self):
"""モニター情報を取得してリストボックスに表示する"""
try:
count = wx.Display.GetCount()
self.listbox.Append(f"--- 接続中のモニター: {count} 台 ---")
for i in range(count):
display = wx.Display(i)
geo = display.GetGeometry()
client = display.GetClientArea()
is_primary = display.IsPrimary()
self.listbox.Append(f"【モニター {i}】")
self.listbox.Append(f" 解像度 (Geometry): {geo.width} x {geo.height} (Pos: {geo.x}, {geo.y})")
self.listbox.Append(f" 作業領域 (Client): {client.width} x {client.height} (Pos: {client.x}, {client.y})")
self.listbox.Append(f" プライマリ: {'Yes' if is_primary else 'No'}")
self.listbox.Append(f" PPI (DPI): {display.GetPPI().width}") # PPIも取得
self.listbox.Append("")
except NotImplementedError:
self.listbox.Append("ディスプレイ情報を取得できませんでした。")
# アプリの実行
if __name__ == "__main__":
# wx.App のインスタンス化が必須
app = wx.App()
frame = DisplayInfoFrame()
frame.Show()
app.MainLoop()サンプルコード2: ウィンドウをセカンダリモニターの中央に表示する
次に、セカンダリモニター(もし存在すれば)を検出し、その画面の中央にウィンドウを表示するサンプルです。
import wx
# セカンダリモニター(ここでは1番目のモニター)のインデックス
TARGET_DISPLAY_INDEX = 1
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(None, title="セカンダリモニターに表示", size=(400, 300))
self.panel = wx.Panel(self)
self.panel.SetBackgroundColour(wx.Colour(230, 255, 230))
self.MoveToSecondaryDisplay()
self.Show()
def MoveToSecondaryDisplay(self):
"""ウィンドウをセカンダリモニターの中央に移動させる"""
display_count = wx.Display.GetCount()
# ターゲットのディスプレイ(1番目)が存在するか確認
if display_count > TARGET_DISPLAY_INDEX:
# 1番目のディスプレイオブジェクトを取得
display = wx.Display(TARGET_DISPLAY_INDEX)
# タスクバーを除いた作業領域 (wx.Rect) を取得
client_area = display.GetClientArea()
# ウィンドウのサイズを取得
win_width, win_height = self.GetSize()
# モニターの中央座標を計算
# (モニターの開始X + (モニター幅 / 2)) - (ウィンドウ幅 / 2)
center_x = client_area.x + (client_area.width // 2) - (win_width // 2)
center_y = client_area.y + (client_area.height // 2) - (win_height // 2)
print(f"セカンダリモニター {TARGET_DISPLAY_INDEX} の中央 ({center_x}, {center_y}) へ移動します。")
# ウィンドウの位置を設定
self.SetPosition((center_x, center_y))
else:
print(f"モニター {TARGET_DISPLAY_INDEX} が見つかりません。プライマリモニターの中央に表示します。")
self.Centre() # 見つからなければプライマリの中央に
# アプリの実行
if __name__ == "__main__":
app = wx.App()
frame = MyFrame()
# 注意: Show() は __init__ の中で呼ばれる
app.MainLoop()便利な関連メソッド
wx.Display には、他にも知っておくと便利なメソッドがあります。
GetFromWindow(win) と GetFromPoint(pt)
wx.Display クラスには、特定のウィンドウや座標が「どのモニター上にあるか」を逆引きできる静的メソッドがあります。
wx.Display.GetFromWindow(win):win(通常はself)で指定したウィンドウが、現在表示されているモニターのインデックス(int)を返します。ウィンドウが複数のモニターにまたがっている場合は、最も多く面積を占めているモニターのインデックスを返します。Python# (wx.Frame のメソッド内として) # このウィンドウが今いるモニターのインデックスを取得 current_display_index = wx.Display.GetFromWindow(self) print(f"このウィンドウは {current_display_index} 番目のモニターにいます。")wx.Display.GetFromPoint(pt):pt(wx.Point(x, y))で指定した仮想スクリーン座標が、どのモニターの領域内にあるかのインデックスを返します。
GetPPI() – 画面のDPI (PPI) を取得する
disp.GetPPI() は、そのディスプレイのピクセル密度 (Pixels Per Inch) を wx.Size オブジェクト (X方向のPPI, Y方向のPPI) として返します。
最近の4Kモニターなど、高DPI環境(HiDPI)かどうかを判別するのに使えます。例えば、PPIが一定値(例: 150)を超えていたら、UIのフォントサイズやアイコンサイズを大きくする、といったスケーリング処理を自前で実装する際の重要な参考値となります。
まとめ
この記事では、wxPythonでマルチモニター環境を扱うための wx.Display クラスについて詳しく解説しました。
wx.Displayは、物理モニターの情報を取得するクラスです。wx.Display.GetCount()でモニターの総数を取得できます。wx.Display(i)で個々のモニターオブジェクトにアクセスします。GetGeometry()はモニター全体の解像度と位置(wx.Rect)を返します。GetClientArea()はタスクバーなどを除いた実質的な作業領域(wx.Rect)を返します(ウィンドウ配置にはこちらが推奨)。IsPrimary()やGetPrimary()でメインディスプレイを特定できます。GetFromWindow(self)で、現在のウィンドウがどのモニターにいるかを判別できます。
wx.Display を使いこなすことは、プレゼンテーションツールや多機能なエディタなど、ユーザーのディスプレイ環境に最適化された、プロフェッショナルなwxPythonアプリケーションを開発するための鍵となります。


コメント