M.Hiroi's Home Page

Python3 Programming

お気楽 CustomTkinter 超入門

[ PrevPage | Python3 | NextPage ]

CTkToplevel

●複数のウィンドウを作る

CustomTkinter は CTk() でメインウィンドウを生成しますが、このほかにも複数のウィンドウを生成することができます。新しいウィンドウは CTk() でも生成することができますが、この方法では最初のメインウィンドウとは独立したウィンドウ、つまり、新しいメインウィンドウとして扱われます。このため、最初のウィンドウを閉じても、新しいウィンドウはそのまま存在します。メインウィンドウと連動したウィンドウを生成するには CTkToplevel() を使います。

sub_win = ctk.CTkToplevel()

これで新しいウィンドウが生成されます。CTkToplevel() は親ウィンドウの指定を省略してもかまいません。メインウィンドウが閉じられると sub_win も閉じられます。また、メインウィンドウで設定したオプションは sub_win でも有効です。あとはいままでのように、ウィンドウ sub_win にウィジェットを配置します。

それでは簡単な例題として、アプリケーションの情報などを表示するためのウィンドウを作ってみましょう。メインウィンドウのボタン About が押されたら、新しいウィンドウを開いてメッセージを表示します。まず、メインウィンドウとボタンを設定します。

リスト : メインウィンドウとボタン

import customtkinter as ctk

root = ctk.CTk()
my_font = ('Noto Sans CJK JP', 16)

# ボタンの設定
ctk.CTkButton(root, text = 'About', font = my_font, command = message_window).pack()
ctk.CTkLabel(root,  text = 'About を押してね', font = my_font).pack()
root.mainloop()

ここまでは簡単ですね。ウィンドウの生成は関数 message_window() で行います。次のリストを見てください。

リスト : ウィンドウの生成

# メッセージの表示
def message_window():
    sub_win = ctk.CTkToplevel()
    ctk.CTkLabel(sub_win, text = 'TopLevel のサンプル\nプログラムです',
                 justify="center", font = my_font).pack()

最初に CTkToplevel() で新しいウィンドウ sub_win を生成します。複数行のテキストを表示する場合、Tkinter には「メッセージウィジェット」という便利なウィジェットがあるのですが、CustomTkinter にはありません。CTkLabal() のオプション text で改行文字を含む文字列を指定すると、複数行のテキストを表示することができます。この時、オプション justify で文字列の揃えを指定することができます。

これでプログラムは完成です。たったこれだけで、ボタン About を押すとウィンドウが表示されます。

メインウィンドウ メインウィンドウ

サブウィンドウ About をクリックしてサブウィンドウを表示

ところが、このままでは都合の悪いことがあるのです。このウィンドウを表示したまま、もう一度 About をクリックしてみてください。もうひとつ同じウィンドウが表示されてしまいます。それから、ウィンドウに表示されるタイトルが CTk になっています。きちんとしたタイトルをつけた方が良いでしょう。

●ウィジェットの状態を調べる

ウィンドウの状態を調べる場合、Tcl/Tk ではコマンド winfo を使いますが、Tkinter では winfo のサブコマンドに相当するメソッドが多数用意されています。これらのメソッドは CustomTkinter でも使用することができます。ウィンドウの状態を調べるメソッドの一部を表に示します。

表 : ウィジェットの状態を調べるメソッド
widget.winfo_geometry()ウィジェットの位置を文字列 (幅x高さ+x+y) で返す
widget.winfo_width()ウィジェットの幅を返す
widget.winfo_height()ウィジェットの高さを返す
widget.winfo_x()親ウィンドウ内での x 座標を返す
widget.winfo_y()親ウィンドウ内での y 座標を返す
widget.winfo_rootx()ディスプレイ上での x 座標を返す
widget.winfo_rooty()ディスプレイ上での y 座標を返す
widget.winfo_exists()ウィジェットが存在するか

winfo_geometry() でウィジェットを指定した場合、返される座標はディスプレイを基準にした座標ではなく、そのウィジェットが配置されたウィンドウを基準にした座標となります。また、winfo_x(), winfo_y() でメインウィンドウを指定すると、ディスプレイ上での座標を返します。このほかにも多数のメソッドがあるので詳細は Module docs の Tkinter をお読みください。

このプログラムで必要になる、ウィンドウの存在を調べるメソッドは winfo_exists() です。たとえば、ウィンドウ sub_win を調べるには、sub_win.winfo_exists() とすればいいわけです。sub_win が開いていれば真を、そうでなければ偽を返します。

●ウィンドウの状態を設定する

Tcl/Tk の場合、ウィンドウの設定はコマンド wm (Window Manager) で行いますが、Tkinter では wm のサブコマンドに相当するメソッドが多数用意されています。これらのメソッドも CustomTkinter で使用することができます。ウィンドウの状態を設定するメソッドの一部を表に示します。

表 : ウィンドウの設定を行うメソッド (一部)
window.withdraw()ウィンドウを画面から取り除く
window.deiconify()ウィンドウを見える状態に戻す
window.iconify()ウィンドウをアイコン化する
window.geometry(string)ウィンドウを表示する位置を文字列で (幅x高さ+x+y) で指定する
window.maxsize(幅, 高さ)ウィンドウの最大値を指定
window.minsize(幅, 高さ)ウィンドウの最小値を指定
window.title(タイトル名)ウィンドウのタイトルを指定

このほかにも多数のメソッドがあるので詳細は Module docs の Tkinter をお読みください。

タイトルを設定するにはメソッド title() を使います。ウィンドウ sub_win にタイトルをつけるには、sub_win.title('タイトル') とすればいいわけです。

●プログラムの改良

それではプログラムを改良してみましょう。

リスト : ウィンドウの生成 (改良版)

import customtkinter as ctk

root = ctk.CTk()
root.title('Main')
my_font = ('Noto Sans CJK JP', 16)
sub_win = None

# メッセージの表示
def message_window():
    global sub_win
    if sub_win is None or not sub_win.winfo_exists():
        sub_win = ctk.CTkToplevel()
        sub_win.title('About')
        ctk.CTkLabel(sub_win, text = 'TopLevel のサンプル\nプログラムです',
                     justify = "center", font = my_font).pack()

# ボタンの設定
ctk.CTkButton(root, text = 'About', font = my_font, command = message_window).pack(padx = 4, pady = 4)
ctk.CTkLabel(root,  text = 'About を押してね', font = my_font).pack(padx = 4, pady = 4)
root.mainloop()

sub_win はグローバル変数として定義し、None で初期化しておきます。sub_win が None でなければ、メソッド winfo_exists() でウィンドウ sub_win が開いているかチェックします。まだ開いていないのであれば、Toplevel() でウィンドウを生成します。次に、title() でタイトルを設定します。あとはいままでと同じです。実際にプログラムを実行すると、ウィンドウが開いた状態でボタン About を押しても、新しいウィンドウは開きません。

サブウィンドウ サブウィンドウ (改良版)


ウィジェットの状態

●state オプション

Tk にはいろいろなウィジェットが用意されていますが、場合によっては、ウィジェットの機能を無効にしたいことがあります。たとえば、ボタンやメニューに割り当てた機能が動作しない場合、ボタンやメニューの選択を無効にしなければいけませんが、そのことをユーザーに知らせた方が使いやすいアプリケーションになります。この場合、ウィジェットの状態を制御する state オプションを使うと便利です。

CustomTkinter の場合、Tkinter とは違って state の値は normal (通常の状態) と disabled (無効な状態) の 2 つだけです。state に disabled を設定すると、そのウィジェットは無効な状態になります。ボタンウィジェットであれば、テキストの色が変わりマウスでボタンをクリックしても押すことができなくなります。無効時のテキストの色はオプション text_color_disabled で指定することができます。無効時の背景色は通常の背景色と同じになります。

●ボタンの状態を変更する

それでは簡単な例を示しましょう。ラジオボタンを使ってボタンの状態を設定します。

リスト : ボタンの状態を変更する

import customtkinter as ctk

root = ctk.CTk()
my_font = ('Noto Sans CJK JP', 14)

var = ctk.StringVar()
var.set('normal')

# ボタン
b = ctk.CTkButton(root, text = 'button', font = my_font)
b.pack(padx = 4, pady = 4, fill = 'x')

# 状態の変更
def change_state(): b.configure(state = var.get())

# ラジオボタンの設定
for x in ('normal', 'disabled'):
    ctk.CTkRadioButton(root, text = x, value = x, font = my_font,
                       variable = var, command = change_state).pack(padx = 4, pady = 4, anchor = 'w')

root.mainloop()

ラジオボタンで選択した値はグローバル変数 var に格納し、関数 change_state() でボタンの状態を変更します。変数 var は、あらかじめ normal に初期化しておきます。change_state() では、configure() を使って state に変数 var の値をセットするだけです。これでボタンの状態を変更することができます。

normal button disable button ボタンの状態を変更

ところで、Tkinter のメニューバーはメソッド entryconfigure() を使って state の値を変更することができますが、外部ライブラリ CTkMenuBar にそのような機能はないようです。メニューの状態を変更したい場合は、Tkinter のメニューバーを使ったほうがよさそうです。ご注意くださいませ。


Gridder

メソッド grid() は格子状にウィジェットを配置するジオメトリマネージャです。ウィンドウを M 行 N 列のセルに分割し、そこにウィジェットを配置するのです。x 方: 向の位置はオプション column で指定し、y 方向の位置は row で指定します。

grid() には pack() とは違うオプション columnspan() と rowspan() があります。これは、複数のセルにまたがってウィジェットを配置するために使います。columnspan は x 方向にまたがるセルの数、rowspan は y 方向にまたがるセルの数を指定します。

それから、pack() ではオプション fill でウィジェットを引き伸ばすことができましたが、grid() ではオプション sticky を使います。

sticky は pack のオプション anchor と同じ機能もあわせ持っています。

簡単な使用例と実行結果を示します。

リスト : ボタンを格子状に配置

import customtkinter as ctk

root = ctk.CTk()
my_font = ('Noto Sans CJK JP', 16)

for x in range(5):
    for y in range(5):
        b = ctk.CTkButton(root, text = '{}'.format(y * 5 + x + 1), font = my_font)
        b.grid(column = x, row = y ) # , sticky = 'ew')

root.mainloop()

ボタンを格子状に配置


ウィンドウのリサイズ

●はじめに

今回はウィンドウの大きさを変更してみましょう。もちろん、Tk はデフォルトでウィンドウのリサイズに対応しています。いままでのサンプルプログラムでも、マウスでウィンドウの大きさを変更することができます。ただし、ウィジェットの大きさは変化しません。ウィンドウを小さくしたらウィジェットが表示されなくなった、ということも起こります。

まあ、これはウィンドウの大きさを制限することで回避することができます。ですが、アプリケーションによっては、ウィンドウのリサイズに合わせてウィジェットの大きさを変更した方がよい場合もあるでしょう。

●Packer のリサイズ

ところで、ウィジェットのリサイズは面倒だな、と思われた方はいませんか。まじめに考えると、ウィンドウのサイズからウィジェットのサイズを計算して、大きさを変更する処理が必要になるのですが、Tk ではそんな難しいことをする必要はありません。ジオメトリマネージャーに用意されているオプションを設定するだけで、ウィンドウのサイズに合わせてウィジェットの大きさを変更することができます。Packer を使う場合は、次のオプションを設定します。

余白をウィジェットに割り当てただけでは、ウィジェットは大きくなりません。ウィジェットを引き伸ばすための fill オプションを設定してください。それでは簡単な例を示しましょう。次のプログラムを見てください。

リスト : Packer のリサイズ

import customtkinter as ctk

root = ctk.CTk()
my_font = ('Noto Sans CJK JP', 14)

ctk.CTkButton(root, text = 'button 0', font = my_font).pack(expand = True, fill = 'both')
ctk.CTkButton(root, text = 'button 1', font = my_font).pack(expand = True, fill = 'both')

root.mainloop()

2 つのボタン Packer によるボタンの配置

2 つのボタン(大) ウィンドウを拡大する

ウィンドウ全体に 2 つのボタンが表示されます。マウスでウィンドウの大きさを変えてみてください。ウィンドウに合わせてボタンの大きさも変化します。このように、Tk ではオプションを設定するだけで、ウィンドウのリサイズにも簡単に対応することができるのです。

ウィジェットを配置する順番も大切です。Packer はウィンドウが小さくなるとウィジェットを圧縮しますが、本当にスペースが無くなるとウィジェットは表示されなくなります。このとき、配置された逆順でウィジェットが削除されます。つまり、最初に配置されたウィジェットが最後まで残るのです。大切なウィジェットは最初に配置した方がいいでしょう。

●Gridder のリサイズ

Gridder のリサイズは、マスの状態を設定するメソッド grid_columnconfigure() と grid_rowconfigure() で行います。

window.grid_columnconfigure(column_index, options )
window.grid_rowconfigure(row_index, options )
表 : オプションの種類
minsize最小の幅/高さを数値で指定する
weight余白を配分するときの割合を数値で指定する
pad詰め物を数値で指定する

リサイズに対応するには、オプション weight に 1 以上の整数値を指定します。簡単な使用例を示しましょう。ボタンを 4 つ Gridder で配置します。

リスト : Gridder のリサイズ

import tkinter as tk

root = tk.Tk()
root.option_add('*font', ('Noto Sans CJK JP', 10))

column_data = (0, 0, 1, 1)
row_data = (0, 1, 0, 1)

for x in range(4):
    b = tk.Button(root, text = f'button {x}')
    b.grid(column = column_data[x], row = row_data[x], sticky = 'nsew')

root.mainloop()

4 つのボタン Gridder によるボタンの配置

grid_columnconfigure() は縦方向に配置されたマスのオプションを設定します。次のように、0 列に weight = 1 を設定します。

root.grid_columnconfigure(0, weight = 1)

ボタンはメインウィンドウに配置されているので、grid_columnconfigure() はメインウィンドウのオブジェクト root のメソッドとして呼び出します。これで、ウィンドウが横に大きくなると、0 列に配置されたボタン button 0 と button 1 も横に大きくなります。

4 つのボタン button 0, 1 は横方向に伸びる

1 列目は weight オプションを設定していないので、余白は割り当てられません。それでは、次のプログラムを追加してみましょう。

root.grid_columnconfigure(1, weight = 2)

4 つのボタン 4 つのボタンが横方向に伸びる

今度は、1 列目にも余白が割り当てられますが、-weight オプションの設定が 2 なので 0 列の 2 倍の余白が割り当てられます。つまり、ボタン button 2 と button 3 の方が大きく伸びるわけです。

このままではウィジェットの縦方向が大きくなりません。これに対応するには grid_rowconfigure() を使います。次のプログラムを追加してください。

root.grid_rowconfigure(0, weight = 1)
root.grid_rowconfigure(1, weight = 2)

4 つのボタン 4 つのボタンが縦横方向に伸びる

縦に増えた余白は、0 行と 1 行に 1 対 2 の割合で配分されます。したがって、ウィンドウを大きくするとボタン button 3 がいちばん大きくなります。縦と横の関係で混乱しそうですが、実際にプログラムを動かしてみてください。納得してもらえると思います。

●キャンバスウィジェットのリサイズ

次に、キャンバスウィジェットのリサイズを行ってみましょう。キャンバスもウィジェットなので、pack() や grid() のオプションを指定することで、ウィンドウのリサイズに対応することができます。次のプログラムを実行してください。

リスト : キャンバスウィジェットのリサイズ (1)

import tkinter as tk

root = tk.Tk()
c0 = tk.Canvas(root, bg = 'green', width = 200, height = 200)
c0.create_rectangle(20, 20, 180, 180, fill = 'red')
c0.pack(fill = tk.BOTH, expand = True)

root.mainloop()

キャンバス キャンバスウィジェットを配置

キャンバス ウィンドウを縮小

キャンバス ウィンドウを拡大

キャンバスウィジェットの背景は green で、その上には赤い四角形が描かれています。ご覧のように、マウスでウィンドウの大きさを変えると、キャンバスウィジェットの大きさは変わりますが、図形の大きさは変わりません。

図形は pack() で配置されているわけではないので、Packer はキャンバスウィジェットを引き伸ばすことはできても、その中の図形を操作することはできないのです。図形はユーザーが定義したものですから、Packer が関知しないのは当然のことですね。したがって、ウィンドウのリサイズに対応するには、ユーザー側で図形を再描画する処理をプログラムする必要があるのです。

●図形の再描画

図形を再描画するには、ウィンドウがリサイズされたときに発生するイベント Configure を使います。このイベントをバインドして、ウィンドウの大きさが変わったら図形を再描画します。バインドはメインウィンドウに対して設定します。

root.bind('<Configure>', change_size)

キャンバスウィジェットは fill と expand を設定して pack されているので、ウィンドウの大きさが変わると、キャンバスの大きさも変わります。このときに図形の大きさを変える関数 change_size() を実行すればいいわけです。

キャンバスウィジェットの大きさですが、これはメソッド cget() では求めることができません。実際、ウィンドウがリサイズされキャンバスウィジェットが引き伸ばされても、最初に設定されたオプションの値そのままになっています。キャンバスウィジェットの大きさを求めるには、ウィジェットの情報を取得するメソッド winfo_width() と winfo_height() を使います。

また、ウィンドウが小さくなると図形が見えなくなるので、ウィンドウの大きさを制限します。これはメソッド minsize() と maxsize() で設定 [*1] することができます。幅と高さはピクセル単位で指定します。

プログラムは次のようになります。

リスト : キャンバスウィジェットのリサイズ (2)

import customtkinter as ctk
import tkinter as tk

# メインウィンドウの設定
root = ctk.CTk()
root.minsize(100, 100)
root.maxsize(400, 400)

# キャンバスの設定
c0 = tk.Canvas(root, bg = 'green', width = 200, height = 200)
id = c0.create_rectangle(20, 20, 180, 180, fill = 'red')
c0.pack(fill = 'both', expand = True)

# 図形の大きさを変更
def change_size(event):
    w = c0.winfo_width()
    h = c0.winfo_height()
    c0.coords(id, 20, 20, w - 20, h - 20)

# バインディングの設定
root.bind('<Configure>', change_size)

root.mainloop()

関数 change_size() の処理は簡単です。メソッド winfo_width() と winfo_height() でキャンバスの大きさを求めたら、メソッド coords() で図形の位置を変更するだけです。とても簡単ですね。たったこれだけの処理で、ウィンドウの大きさに合わせて図形の大きさを変更することができます。

キャンバス ウィンドウを縮小

キャンバス ウィンドウを拡大

-- note --------
M.Hiroi の実行環境 (Python 3.12.3, WSL2 + WSLg, Windows 11) では、minsize(), maxsize() の制限は機能しないようです。

CTkSwitch

「スイッチウイジェット」は、従来の Tkinter にある Checkbutton や CustomTkinter の CTkCheckBox の役割を、トグルスイッチ (ON / OFF ボタン) のような UI で実現したものです。設定の 有効 / 無効 の切り替えなど、2 つの状態を選択させる際に使用します。

スイッチウィジェットは CTkSwitch() で生成します。主なオプションを以下に示します。

よく用いられるメソッドを以下に示します。

簡単な使用例と実行結果を示します。

リスト : スイッチウィジェットの使用例

import customtkinter as ctk

root = ctk.CTk()
my_font = ('Noto Sans CJK JP', 14)

sw_var = ctk.IntVar()
sw_var.set(0)

def switch_event():
    print(f"スイッチの状態: {sw_var.get()}")

sw = ctk.CTkSwitch(root, text = "設定を有効にする", command=switch_event,
                   variable = sw_var, font = my_font)
sw.pack(padx=20, pady=10)

root.mainloop()

スイッチ OFF の状態

スイッチ ON の状態


CTkSegmentedButton

CTkSegmentedButton は、複数の選択肢から 1 つを選ぶためのモダンなウィジェットです。従来のラジオボタンを洗練させたような外観で、直感的なタブ切り替えやモード選択に適しています。基本的な使い方は簡単で、値をリスト形式で渡し、command でクリック時の動作を指定します。

ウィジェットは CTkSegmentedButton() で生成します。主なオプションとメソッドを以下に示します。

簡単な使用例と実行結果を示します。

リスト : CTkSegmentedButton の使用例

import customtkinter as ctk

def callback(value):
    print("選択された値:", value)

root = ctk.CTk()
my_font = ('Noto Sans CJK JP', 14)

seg = ctk.CTkSegmentedButton(root, font = my_font,
                             values = ["Option 1", "Option 2", "Option 3"],
                             command = callback)
seg.pack(pady=20)
seg.set("Option 1") # 初期値を設定

root.mainloop()

初期状態

Option 3 を選択


CTkComboBox

コンボボックスウィジェットはエントリー (CTkEntry) とドロップダウンリスト (CTkOptionMenu) を合わせたようなウィジェットです。ドロップダウンリストから項目を選択することができるとともに、エントリーからテキストを直接入力することもできます。

コンボボックスウィジェットは CTkComboBox() で生成します。主なオプションとメソッドを以下に示します。

簡単な使用例と実行結果を示します。

リスト : コンボボックスの使用例

import customtkinter as ctk

def combobox_callback(choice):
    print("選択された値:", choice)

root = ctk.CTk()
my_font = ('Noto Sans CJK JP', 14)

# コンボボックスの作成
cb = ctk.CTkComboBox(
    root,
    values = ["Option 1", "Option 2", "Option 3"],
    command = combobox_callback,
    font = my_font
)
cb.pack(padx=20, pady=20)

# 初期値を設定する場合
cb.set("Option 2")

# バインディング
cb.bind('<Return>', lambda event: combobox_callback(cb.get()))

root.mainloop()

初期状態

Option 3 を選択

Option 999 を入力


Copyright (C) 2026 Makoto Hiroi
All rights reserved.

[
PrevPage | Python3 | NextPage ]