PythonのTkinterでウィンドウを画面の中央に表示する

Tkinter

結論のみ知りたい方は、本題までスキップしてください。

はじめに

Tkinterのウィンドウを起動した時に、そのウィンドウがスクリーンの中央に表示されるようにする方法を解説します。

ウィンドウのサイズと表示位置を指定する

まずは、ウィンドウを表示させるだけの簡単なプログラムを見てみましょう。

import tkinter as tk

root = tk.Tk()

root.mainloop()

上記のコードを実行すると、ただのウィンドウが表示されます。

通常、なにも指定せずウィンドウを表示するとスクリーンの左上付近にウィンドウが表示されると思います。

ウィンドウのサイズや表示する位置はgeometry()メソッドで指定することができます。

ウィンドウのサイズを指定

例えば、ウィンドウのサイズを幅500px、高さ400pxにしたいときは次のようにします。

import tkinter as tk

root = tk.Tk()
root.geometry('500x400')

root.mainloop()

これを実行すると幅500px、高さ400pxのウィンドウが起動します。

ここで、geometryの引数には文字列を渡します。また、xはアルファベット(半角英字)のxであることに注意してください。

ウィンドウの表示位置を指定

次に、ウィジェットの表示位置を指定します。

表示位置を指定するときは、スクリーンの左上を基準とし、そこから右に~px, 下に~pxの位置にウィンドウの左上角が来るようにするかを指定します。

スクリーンの左上から、右に300px、下に200pxの位置にウィンドウを表示するには次のようにします。

import tkinter as tk

root = tk.Tk()
root.geometry('+300+200')

root.mainloop()

これを実行すると、スクリーン左上から、右に300px、下に200pxの位置にウィンドウの左上角が来るように起動します。

ウィンドウサイズと表示位置を両方指定

また、ウィンドウのサイズと表示位置は両方指定することもできます。

geometryの引数に'{幅}x{高さ}+{x座標}+{y座標}'を渡します。

import tkinter as tk

root = tk.Tk()
root.geometry('500x400+300+200')

root.mainloop()

これを実行すると、幅500px、高さ400px、右に300px、下に200pxの位置にウィンドウが起動します。

これで、ウィンドウのサイズと表示位置を指定することができました。

ウィンドウのサイズと表示位置を取得する

これまで、ウィンドウを起動する前に、サイズと表示位置を指定して表示させる方法を解説してきましたが、Tkinterのウィンドウは、これらを指定しない場合、ウィンドウ内に配置されたウィジェットの大きさに合わせて自動で幅と高さを決めてくれます。

次は、表示されているウィンドウのサイズと表示位置を取得してみましょう。

いくつか方法はありますが、ここではwinfo系のメソッドを使用します。

width = root.winfo_width() # 幅を取得
height = root.winfo_height() # 高さを取得
x = root.winfo_x() # x座標を取得
y = root.winfo_y() # y座標を取得

Widget.winfo_***()のようにウィンドウもしくはウィジェットに対して、winfo系のメソッドを使うことで、そのオブジェクトの状態を取得することができます。

import tkinter as tk

root = tk.Tk()

root.geometry('500x400+300+200')

root.update_idletasks() # 描画を完成させる

width = root.winfo_width() # 幅を取得
height = root.winfo_height() # 高さを取得
x = root.winfo_x() # x座標を取得
y = root.winfo_y() # y座標を取得

print(width, height, x, y)

root.mainloop()

これを実行すると、ウィンドウが開き、500 400 300 200と出力されます。

ここで、update_idletasks()というメソッドが出てきました。
これは、内部的にウィンドウの描画を完成させるためのものです。

これがないと、mainloopに入る前にwinfo系のメソッドが実行されてしまうので、ウィンドウが作られる前にウィンドウの状態を取得しようとして、1 1 0 0が出力されてしまいます。

それを回避するためにwinfo系メソッドの前にupdate_idletasks()を実行しておきます。

これでサイズと表示位置を取得することができました。

本題

さて、ようやく本題に入ります。

結論としては、スクリーンの幅(高さ)からウィンドウの幅(高さ)を引いた値を半分にした値をx(y)座標に指定することで中央に配置することができます。

あとはスクリーンの幅と高さから計算するだけです。
スクリーンの幅と高さはそれぞれ、winfo_screenwidth(), winfo_screenheight()で取得することができます。

import tkinter as tk

root = tk.Tk()

root.update_idletasks() # 描画を完成させる

width = root.winfo_width() # 幅を取得
height = root.winfo_height() # 高さを取得

screen_width = root.winfo_screenwidth() # スクリーンの幅
screen_height = root.winfo_screenheight() # スクリーンの高さ

x = (screen_width - width) // 2
y = (screen_height - height) // 2

root.geometry(f'+{x}+{y}')


root.mainloop()

実行して確認してみます。

これでウィンドウをスクリーンの中央に表示することができました。

カスタムクラス

ここからはこれを応用したカスタムクラスについての話なので興味がある方は最後まで見てみてください。

Pythonで作成したプログラムを他の人にも使ってもらうことを想定した時、ユーザーからなにかしらの入力を求めるようなものがあるとします。コンソールでinput()を使って済ますこともできますが、それだと少し使いづらいです。

私は、どんなプログラムもできるだけGUIアプリケーションで作成するようにしています。

アプリを作成するたびに、毎回同じコードを書くのは面倒です。

そこで、tk.Tkを継承したMyTkクラスを作成し、それを継承したクラスでアプリを作成しています。

また、いくつかオプションや機能を追加して、再利用性をさらに高めています。

カスタムクラスを使用することで、開発が効率的になり、アプリやコード自体の一貫性を保つこともできます。

では、カスタムしたクラスを見てみましょう。

コード全体を表示
import tkinter as tk

class MyTk(tk.Tk):
    def __init__(self, width=None, height=None, x=None, y=None, resizable=True, topmost=False):
        super().__init__()
        self.root = self
        self.width = width
        self.height = height
        self.x = x
        self.y = y
        
        # xボタンでウィンドウを閉じるときの処理
        self.root.protocol("WM_DELETE_WINDOW", self.close_window)
        
        # ウィジェットを作成・配置
        self.create_widgets()
        
        # リサイズを許可するかどうか
        if resizable == False:
            self.root.resizable(False, False)
        else:
            self.root.resizable(True, True)
        
        # 常に最前面にウィンドウを表示するかどうか
        if topmost == False:
            self.root.attributes('-topmost', True) # 最前面にウィンドウを固定
            self.root.after_idle(self.root.attributes, '-topmost', False) # 固定を解除
        else:
            self.root.attributes('-topmost', True)
    
    # ウィジェットを作成・配置するためのメソッド
    def create_widgets(self):
        """ オーバーライドして使用します """
        pass
    
    # ウィンドウを閉じるときの処理
    def close_window(self):
        self.root.destroy()
    
    # ウィンドウのサイズと表示位置を計算しメインループを実行
    def run(self):
        # ウィンドウを一時的に非表示に
        self.root.withdraw()
        
        # ウィンドウの描画を完成させる
        self.root.update_idletasks()
        
        # ウィンドウの幅と高さを取得
        if self.width:
            width = self.width
        else:
            width = self.root.winfo_width()
        if self.height:
            height = self.height
        else:
            height = self.root.winfo_height()
        
        # スクリーンの幅と高さを取得
        screen_width = self.root.winfo_screenwidth()
        screen_height = self.root.winfo_screenheight()
        
        # 座標を計算
        if self.x:
            x = self.x
        else:
            x = (screen_width - width) // 2
        if self.y:
            y = self.y
        else:
            y = (screen_height - height) // 2
        
        # ウィンドウを配置
        self.root.geometry(f'{width}x{height}+{x}+{y}')
        
        # ウィンドウを再表示
        self.root.deiconify()
        self.root.focus_set()
        
        # メインループ
        self.root.mainloop()

カスタムクラスの説明

まず、MyTkクラスは引数に次の6つのオプションを受け取ります。

  • width : 幅
  • height : 高さ
  • x : x座標
  • y : y座標
  • resizable : リサイズの可否
  • topmost : ウィンドウを常に最前面に表示

widthheightxyは任意のサイズ・位置を指定したい場合に使います。

resizabletopmostにはTrueFalseを渡します。
resizable=Falseとした場合、ウィンドウの幅、高さを変更することができなくなります。
topmost=Trueとした場合、ウィンドウが最前面に固定され、他のウィンドウで隠れなくなります。

protocolメソッドは第一引数に"WM_DELETE_WINDOW"、第二引数に実行する<メソッドオブジェクト>を指定することで、ウィンドウの「xボタン」を押したときの処理を変更することができます。

例えば、閉じる前にメッセージボックスを表示させ、ユーザーに本当に閉じるかどうかの確認を行ったりすることができます。

使い方

MyTkクラスを使うときは、これを継承したクラスを定義してアプリを作成します。

実際に、ボタンをクリックするとラベルの表示が変わるだけの簡単なアプリを作ってみました。

import tkinter as tk
from tkinter import ttk

class MyApp(MyTk):
    def __init__(self):
        super().__init__(
            width=250,
            height=200,
            # x=200,
            # y=100,
            # resizable=False,
            # topmost=True
        )
    
    def create_widgets(self): # オーバーライド
        self.root.title("カスタムウィンドウ")
        
        frame = ttk.Frame(self)

        self.label = ttk.Label(frame, text="カスタムウィンドウアプリケーション")
        self.label.pack(expand=True)

        button = ttk.Button(frame, text="クリックしてください", command=self._on_button_click)
        button.pack(expand=True)
        
        frame.pack(expand=True, fill=tk.BOTH)

    def _on_button_click(self):
        self.label.configure(text="ボタンがクリックされました!")
    
    def close_window(self): # オーバーライド
        print("ウィンドウを閉じます")
        return super().close_window()

app = MyApp()
app.run()

ウィジェットの配置はcreate_widgets()メソッド内で行います。create_widgets()MyTkクラスのコンストラクタ内で呼び出されるので、スペルミスなどに気を付けて定義(オーバーライド)してください。

close_window()メソッドをオーバーライドすることで、アプリを終了するときの処理を変更することができます。今回は、"ウィンドウを閉じます"と出力してからウィンドウを閉じるように処理を加えてみました。
return super().close_widow()MyTkクラスのclose_window()メソッドを呼び出すことを忘れないようにしてください。

app = MyApp()でインスタンスを作成し、app.run()でウィンドウの位置を計算し、mainloop()を呼び出します。

run()メソッド内でウィンドウの表示位置を計算し、mainloop()を実行しています。

さいごに

最後まで読んでいただきありがとうございました。

コメント