【Tkinter】ウィジェット変数とは?StringVar含む4つを解説!

Tkinterのウィジェット変数

こんにちは、Youtaです。

今回は、Tkinterのウィジェット変数についてです。

すずか
すずか

また今回も難しそう・・・

ぶっちゃけ知らなくてもOK?

先生
先生

断言します。
TkinterのGUI開発で、ウィジェット変数を知らないと損です。

例えば、次のような動きはウィジェット変数なしには実装が困難です。

ユーザーの操作に応じて、ある変数を自動で変えてくれたら便利ですよね。

なんとなくウィジェット変数が有益だと思ったのではないでしょうか。

本記事では、解説・確認用コード・実行結果の3点をご用意しております。

また必要に応じてコードの解説をしていますので、初心者でも安心です。

記事の信頼性は、確認用コードと実行結果が保証します。

少しでも疑問に感じたら、是非ともコードをコピペしてPCで確かめてみてください。

それでは早速、Tkinterのウィジェット変数を解説します!

ウィジェット変数とは

結論、Tkinterのウィジェット間の橋渡しを行うための変数です。

もう少し正確には、
ウィジェット内部の値を動的に管理・更新するためのツールです。

冒頭の例では、ラベルとエントリーの橋渡しをウィジェット変数が担います。

ウィジェット変数を使うと、ウィジェット同士を連動させられるのです。

ウィジェット変数の利点

ウィジェット変数を利用することで得られる利点は多岐にわたります。

ここでは厳選して\(2\)つほどご紹介します。

ウィジェット間の橋渡し

前述の通り、複数のウィジェットの間で値のやり取りが可能です。

値が変更されると、ウィジェット変数と紐づく他のウィジェットでも、その変更を即時反映できます。

この性質がいかに強力か、実演してみせましょう。

冒頭の例だとインパクトが薄いので、今度はラベルを大量に配置しました。

圧巻ですね。

ウィジェットがいくらあれど、同じウィジェット変数を共有してさえいれば、変更を即時反映できるのです。

変数の値変更をトレース可能

変数の値が変更されるたびに、指定したコールバック関数を呼び出せます。

実質、JavaScriptのonchangeイベントのようなものです。

例えば、オプションメニューに項目を入力する場合。

上の項目が変更された瞬間、下の項目が変わります。

上のオプションメニューに紐づくウィジェット変数に、呼ぶ関数とタイミング(今回は変更時)を指定します。

関数では、上の選択項目の内容に応じて下の選択肢を変えています。

Tkinterではイベントをバインドする方法もあります。

しかし、onchange的なイベントは(なぜか)ないので、こういうときはウィジェット変数の出番です。

ウィジェット変数の種類

Tkinterには主に\(4\)種類のウィジェット変数があります。

操作したいデータの型に応じて選んでください。

ウィジェット変数の種類
  • StringVar: 文字列を扱う
  • IntVar: 整数を扱う
  • DoubleVar:  浮動小数点数を扱う
  • BooleanVar: 真偽値を扱う

いずれもTkinterのVariableクラスを継承するウィジェット変数です。

先生
先生

Variableもウィジェット変数として利用できます。

それぞれの応用例は後の章で解説します。

ウィジェット変数の共通メソッド

すべてのウィジェット変数で使用可能なメソッドを紹介します。

これらを理解すれば、ウィジェット変数を自在に扱えます。

結論、次の\(6\)メソッドが用意されています。

\(1\)つずつ見ていきましょう。

コンストラクタ

ウィジェット変数のコンストラクタです。

引数は下記の\(3\)つです。

変数名データ型必須 / 任意説明
master親ウィジェット(通常はTkクラス)任意親ウィジェット
value(基本は)各ウィジェット変数のデータ型任意ウィジェット変数の初期値
name文字列任意ウィジェット変数に特定の名前を付ける場合に使用
コンストラクタの引数

例えば、文字列を操作するStringVarだと次のような感じです。

import tkinter as tk


root = tk.Tk()

string_var = tk.StringVar(
    root,  # 親ウィジェット
    "aaa",  # 初期値
    "string_var",  # 名前
)

各引数について詳細に見ていきます。

master

masterにはウィジェット変数の親ウィジェットを指定します。

一応何でも指定できますが、Tkインスタンス(画面)が普通です。

import tkinter as tk

root = tk.Tk()
string_var = tk.StringVar(root)  # OK

frame = tk.Frame(root)
int_var = tk.IntVar(frame)  # OK

spinbox = tk.Spinbox(frame)
double_var = tk.DoubleVar(spinbox)  # OK

とはいえ、内部では自動で共通のTkインスタンスを親とするようです。

(_rootが親でしょうか。)

import tkinter as tk

root = tk.Tk()
string_var = tk.StringVar(root)
print(vars(string_var))
# {'_root': <tkinter.Tk object .>, '_tk': <_tkinter.tkapp object at 0x102447630>, '_name': 'PY_VAR0'}

frame = tk.Frame(root)
int_var = tk.IntVar(frame)
print(vars(int_var))
# {'_root': <tkinter.Tk object .>, '_tk': <_tkinter.tkapp object at 0x102447630>, '_name': 'PY_VAR1'}

spinbox = tk.Spinbox(frame)
double_var = tk.DoubleVar(spinbox)
print(vars(double_var))
# {'_root': <tkinter.Tk object .>, '_tk': <_tkinter.tkapp object at 0x102447630>, '_name': 'PY_VAR2'}

省略すると、最初に作ったウィンドウが親に指定されます。

したがって、ウィンドウを作る前にウィジェット変数を作ると怒られます。

import tkinter as tk

string_var = tk.StringVar()
# RuntimeError: Too early to create variable: no default root window

これを回避するには、先にTkインスタンスを作成します。

import tkinter as tk

root = tk.Tk()

string_var = tk.StringVar()

または、何らかのウィジェットを先に作っておきます。

import tkinter as tk

entry = tk.Entry()

string_var = tk.StringVar()
すずか
すずか

画面なくない?いいのこれ?

画面(Tkインスタンス)がない場合、エントリーを作るタイミングでTkinter内部で自動的に画面が作られます。

ウィジェット変数は、そのTkインスタンスを親とするのです。

value

valueでウィジェット変数の初期値を指定できます。

省略が可能で、\(4\)種類のウィジェット変数のデフォルトは下記です。

ウィジェット変数初期値データ型
StringVar""文字列
IntVar0整数
DoubleVar0.0浮動小数点数
BooleanVarFalseブール値
ウィジェット変数の初期値
先生
先生

ちなみにVariableの初期値はNoneです。

基本的には、valueの型はウィジェット変数の型に合わせます。

import tkinter as tk

root = tk.Tk()

string_var = tk.StringVar(value="1")
int_var = tk.IntVar(value=1)
double_var = tk.DoubleVar(value=1.0)
boolean_var = tk.BooleanVar(value=True)

大抵の場合は、ぶっちゃけ型を合わせなくても問題ありません。

import tkinter as tk

root = tk.Tk()

string_var = tk.StringVar(value=1)
int_var = tk.IntVar(value=1)
double_var = tk.DoubleVar(value=1)
boolean_var = tk.BooleanVar(value=1)

ですが、予期せぬ動作を起こす可能性もあります。

やはりウィジェット変数の型に合わない値を設定しない方が無難でしょう。

import tkinter as tk

root = tk.Tk()

double_var = tk.DoubleVar(value="1.0")  # OK
print(double_var.get())
# 1.0

double_var = tk.DoubleVar(value="")  # NG
print(double_var.get())
# _tkinter.TclError: expected floating-point number but got ""

(getメソッドはウィジェット変数の値を確認できます。詳細は後述。)

name

nameはウィジェット変数の名前です。

ウィジェット変数に名前を付けてみましょう。

import tkinter as tk

root = tk.Tk()

string_var = tk.StringVar(name="named var")
print(string_var)
# named variable

print文でウィジェット変数を出力した際、nameで指定した値が出ます。

ちなみにnameを省略した場合は勝手に名前がつけられます。

import tkinter as tk

root = tk.Tk()

string_var = tk.StringVar()
print(string_var)
# PY_VAR0

int_var = tk.IntVar()
print(int_var)
# PY_VAR1

double_var = tk.DoubleVar()
print(double_var)
# PY_VAR2
先生
先生

わかりやすい名前を付けると、print文を使ってバグを探す際に役立ちます。

またnameで指定した名前から、そのウィジェット変数の値を取れます。

Tkインスタンスのglobalgetvarメソッドを使います。

import tkinter as tk

root = tk.Tk()

string_var = tk.StringVar(name="named var", value="Hello, World.")

# 名前を使って変数の値を取得
value = root.globalgetvar("named var")

print(value)
# Hello, World.

注意点は、nameで指定する名前はユニークなものにしてください。

別の変数には別の変数名をつけるというプログラミングの原則と同じです。

同名の変数が複数あると、予期せぬ動作が発生する可能性があります。

get()

getメソッドはウィジェット変数の値を取得するときに使います。

import tkinter as tk

root = tk.Tk()

string_var = tk.StringVar(value="Hello, World.")

# 変数の値を取得
value = string_var.get()

print(value)
# Hello, World.

とても簡単です。

戻り値の型はそのウィジェット変数の型に依存します。

整数型の\(1\)を初期値に設定しても、出力はこの通り。

import tkinter as tk

root = tk.Tk()

string_var = tk.StringVar(value=1)
string_value = string_var.get()
print(type(string_value), string_value)
# <class 'str'> 1

int_var = tk.IntVar(value=1)
int_value = int_var.get()
print(type(int_value), int_value)
# <class 'int'> 1

double_var = tk.DoubleVar(value=1)
double_value = double_var.get()
print(type(double_value), double_value)
# <class 'float'> 1.0

boolean_var = tk.BooleanVar(value=1)
boolean_value = boolean_var.get()
print(type(boolean_value), boolean_value)
# <class 'bool'> True

set(value)

setメソッドはウィジェット変数に新しい値を設定します。

import tkinter as tk

root = tk.Tk()
string_var = tk.StringVar()

string_var.set("Hello, World.")
print(string_var.get())
# Hello, World.

string_var.set("Bonjour, le monde.")
print(string_var.get())
# Bonjour, le monde.

string_var.set("Hallo, Welt.")
print(string_var.get())
# Hallo, Welt.

trace_add(mode, callback)

trace_addメソッドは、ウィジェット変数に指定した条件で特定の関数を自動で呼び出します。

先ほどの実演は、実はこのtrace_addメソッドで実装しました。

変数名データ型必須 / 任意説明
mode文字列必須監視モード。"read""write""unset"のいずれかを選択。
callback関数必須コールバック。監視モードでイベントが発生したときに呼び出し。
trace_addメソッドの引数

各引数について詳細に見ていきます。

mode

modeにはコールバック関数を呼ぶ条件を指定します

呼ぶタイミングは\(3\)種類から選択できます。

modeの種類
  • "read": 変数の値が読み取られたとき(getメソッド等)
  • "write": 変数の値が変更されたとき(setメソッド等)
  • "unset":  変数が削除されたとき(del文等)

タプルやリストを使って複数のモード指定も可能です。

variable.trace_add(("read" "write"), callback)

各モードで正しくコールバックを呼べるかは、次のコードで確認できます。

例として、"write"モードの確認です。

import tkinter as tk

def callback(*args):
    print("Callback triggered!")

root = tk.Tk()

# trace_add
string_var = tk.StringVar()
string_var.trace_add("write", callback)  # 確認したいmodeを指定

# 変数の値を読み取る
print("Before reading the value")
string_var.get()
print("After reading the value\n")

# 変数の値を変更
print("Before changing the value")
string_var.set("New Value")
print("After changing the value\n")

# 変数を削除
print("Before removing the variable")
del string_var  # または string_var = None
print("After removing the variable")

出力結果です。

Before reading the value
After reading the value

Before changing the value
Callback triggered!
After changing the value

Before removing the variable
After removing the variable

setメソッドを呼ぶときにのみ、関数が呼ばれるのを確認できました。

ただし、"read"だけはなぜか削除のタイミングでも呼ばれるようです。

Before reading the value
Callback triggered!
After reading the value

Before changing the value
After changing the value

Before removing the variable
Callback triggered!
After removing the variable

残念ながらTkinterの仕様っぽいので、目を瞑りましょう。

一応この挙動について、下記に私が調べたことをまとめました。

各ウィジェット変数の継承元であるVariableクラスの内部コードを確認します。

その中に、ウィジェット変数を削除すると発火するメソッドを発見しました。

    def __del__(self):
        """Unset the variable in Tcl."""
        if self._tk is None:
            return
        if self._tk.getboolean(self._tk.call("info", "exists", self._name)):
            self._tk.globalunsetvar(self._name)
        if self._tclCommands is not None:
            for name in self._tclCommands:
                #print '- Tkinter: deleted command', name
                self._tk.deletecommand(name)
            self._tclCommands = None

\(2\)つ目のif文をご覧ください。

        if self._tk.getboolean(self._tk.call("info", "exists", self._name)):

どうやらこの部分が悪さをしているようです。

                               self._tk.call("info", "exists", self._name)

というのも、これによりtrace_addで設定した関数が自動で呼ばれてしまうのです。

その証拠をお見せします。

先ほどのselfは今回string_varなので、書き換えて実行します。

import tkinter as tk

def callback(*args):
    print("Callback triggered!")

root = tk.Tk()

# readモードでtrace_add
string_var = tk.StringVar()
string_var.trace_add("read", callback)

for i in range(3):
    string_var._tk.call("info", "exists", string_var._name)

for文で\(3\)回呼んだので間違いないですね。

Callback triggered!
Callback triggered!
Callback triggered!

callはTkinterの元となるtclのコマンドを実行するためのメソッドです。

是非とも中身を確認したいところでしたが、それは叶わず・・・。

Tkinterの最深部・tclに詳しい方はご連絡いただけると幸いです。

callback

callbackには呼び出されるべき関数を指定します

関数は必ず下記の\(3\)つの引数をセットする必要があります。

def callback(var_name, index, mode):
先生
先生

変数名は説明のために便宜上付けただけです。
\(3\)つあればx, y, zとかでもOKです。

各引数の詳細は下記をご覧ください。

変数名データ型必須 / 任意説明
var_name文字列必須コールバックに紐づくウィジェット変数の名前。
index(文字列)必須空文字列
mode文字列必須イベントの種類。"read""write""unset"のいずれか。
callbackメソッドの引数

百聞は一見にしかずなので、実際に引数を出力してみました。

import tkinter as tk

def callback(var_name, index, mode):
    print(f"var_name: {var_name}")
    print(f"index: {index}")
    print(f"mode: {mode}")

root = tk.Tk()

string_var = tk.StringVar()
string_var.trace_add("write", callback)

string_var.set("Hello, World.")
# var_name: PY_VAR0
# index: 
# mode: write

引数が不要であれば、可変長引数でスッキリさせましょう。

def callback(*args):

また関数であれば良いので、lambda式でも通用します。

variable.trace_add("write", lambda *args: 処理)

最後に第\(2\)引数のindexに関して補足します。

信頼できる情報源によると、var_nameがリスト形式で渡る場合にindexにはインデックスが入るようです。

(何のリストなのか、何のインデックスなのかは不明。)

しかし、その可能性は極めて低く、基本は空文字列と考えていいでしょう。

trace_remove(mode, cbname)

trace_removeメソッドは、ウィジェット変数からtrace_addで設定したトレースを削除します。

引数の一覧です。

変数名データ型必須 / 任意説明
mode文字列必須削除するトレースタイプ。"read""write""unset"のいずれかを選択。
cbname文字列必須trace_addメソッドが返すコールバック識別子。
trace_removeメソッドの引数

各引数について詳細に見ていきます。

mode

modeにはトレースが発生する条件を指定します。

trace_addメソッドと同じく\(3\)種類あります。

modeの種類
  • "read": 変数の値が読み取られたとき
  • "write": 変数の値が変更されたとき
  • "unset":  変数が削除されたとき

タプルやリストを使うと、複数のモードの同時削除が可能です。

variable.trace_remove(["write" "unset"], cbname)

事前にtrace_addメソッドで指定したモード以外をtrace_removeで指定しても、エラーにはなりません。

callback_id = variable.trace_add("write", lambda *args: None)
variable.trace_remove("read", callback_id)  # OK

cbname

cbnameには削除したいコールバックの識別子を指定します。

実はtrace_addメソッドには戻り値があり、登録したコールバックの情報を返します。

import tkinter as tk

def callback(*args):
    print("Callback triggered!")

root = tk.Tk()

string_var = tk.StringVar()
callback_id = string_var.trace_add("write", callback)

print(type(callback_id), callback_id)
# <class 'str'> 4347542208callback

この情報こそが第\(2\)引数cbnameで、トレースを取り消す際に必要です。

string_var.trace_remove("write", callback_id)

trace_addメソッドは、関数が直接引数になりました。

しかし、trace_removeメソッドは関数でなくidですのでご注意ください。

string_var.trace_remove("write", callback)
# TypeError: deletecommand() argument must be str, not function

trace_info()

trace_infoメソッドはウィジェット変数に設定された全てのトレース情報を取得します。

情報には、trace_addメソッドで設定された各トレースのmodecbnameが含まれます。

import tkinter as tk

def callback1(*args):
    print("Callback1 triggered!")

def callback2(*args):
    print("Callback2 triggered!")

root = tk.Tk()
string_var = tk.StringVar()

string_var.trace_add(("write", "unset",), callback1)
string_var.trace_add("read", callback2)

info = string_var.trace_info()
print(info)
# [(('read',), '4334190144callback2'), (('write', 'unset'), '4334189952callback1')]

後から登録した関数の情報ほど、リストの先に入ります。

ウィジェット変数の応用例

\(4\)種類あるウィジェット変数が、具体的にどのような場面で用いられるかをご紹介します。

種類ごとに、下記の順で解説します。

StirngVar × 項目のフィルタリング

StringVarは文字列を扱います。

そのため、文字を入力・表示するエントリーやラベルとの相性が抜群です。

具体例をあげます。

StringVarを利用して商品名のフィルタリング機能を実装してみました。

import tkinter as tk

# リストボックスの内容を検索語に基づいてフィルタリングする関数
def update_listbox(*args):
    search_term = search_var.get().lower()  # 検索語を小文字に変換
    filtered_items = [item for item in items if search_term in item.lower()]  # フィルタリング
    listbox.delete(0, tk.END)  # リストボックスをクリア
    for item in filtered_items:
        listbox.insert(tk.END, item)  # フィルタリングされた項目をリストボックスに追加

# ウィンドウの作成
root = tk.Tk()

# 適当な製品名リスト
items = [
    "Apple iPhone 13", "Samsung Galaxy S21", "Google Pixel 6", "Sony WH-1000XM4",
    "Bose QuietComfort 35", "Dell XPS 13", "Apple MacBook Pro", "Microsoft Surface Pro 7",
    "Amazon Echo Dot", "Google Nest Hub", "Roku Streaming Stick+", "Apple Watch Series 6",
    "Fitbit Charge 5", "Canon EOS R5", "Nikon Z6 II", "Sony Alpha 7C", "Panasonic Lumix GH5",
    "GoPro HERO9 Black", "DJI Mavic Air 2", "Samsung QLED TV", "LG OLED TV", "TCL Roku TV",
    "HP Envy x360", "Asus ROG Zephyrus", "Lenovo ThinkPad X1", "Acer Predator Helios 300",
    "Alienware m15", "Nintendo Switch", "Sony PlayStation 5", "Xbox Series X",
    "Apple AirPods Pro", "Samsung Galaxy Buds Pro", "Sony WF-1000XM4", "Jabra Elite 85t",
    "Anker Soundcore Liberty Air 2 Pro", "Logitech MX Master 3", "Razer DeathAdder V2",
    "Corsair K95 RGB Platinum", "SteelSeries Apex Pro", "HyperX Alloy Elite 2",
    "Netgear Nighthawk AX12", "TP-Link Archer AX6000", "Asus RT-AX88U", "Google Nest Wifi",
    "Eero Pro 6", "Ring Video Doorbell Pro", "Arlo Pro 4", "Wyze Cam v3",
    "Nest Learning Thermostat", "Ecobee SmartThermostat", "August Wi-Fi Smart Lock",
    "SimpliSafe Home Security System", "Philips Hue White and Color Ambiance",
    "LIFX A19 LED Light", "Sengled Smart LED Multicolor", "Nanoleaf Shapes",
    "Govee Immersion LED TV Backlights", "Dyson V11 Torque Drive", "Shark Navigator Lift-Away",
    "iRobot Roomba i7+", "Eufy RoboVac 11S", "Bissell CrossWave", "Black+Decker Dustbuster",
    "Instant Pot Duo", "Ninja Foodi", "Breville Smart Oven", "KitchenAid Stand Mixer",
    "Vitamix 5200", "Cuisinart Food Processor", "Nespresso VertuoPlus", "Keurig K-Elite",
    "Hamilton Beach FlexBrew", "Black+Decker 12-Cup Coffee Maker", "Whirlpool WRF535SMHZ",
    "LG LFXS26973S", "Samsung RF28R7351SG", "GE Profile PFE28KYNFS", "Bosch 800 Series SHXM78Z55N",
    "Miele Classic G4228SCU", "Frigidaire FGID2466QF", "Maytag MDB4949SHZ", "Dyson Supersonic",
    "Revlon One-Step Hair Dryer", "Conair InfinitiPro", "BaBylissPRO Nano Titanium",
    "Panasonic EH-NA65-K", "Remington Pro Pearl Ceramic", "Beats Solo Pro", "Marshall Major IV",
    "Bose SoundLink II", "JBL Flip 5", "Ultimate Ears WONDERBOOM 2", "Anker Soundcore Motion+",
]

# StringVarのインスタンス
search_var = tk.StringVar()
# search_varが変更されるたびにupdate_listbox関数を呼び出す
search_var.trace("w", update_listbox)

# 検索ボックス
search_entry = tk.Entry(root, textvariable=search_var, width=50)
search_entry.pack(pady=10)

# リストボックス
listbox = tk.Listbox(root, width=50, height=20)
listbox.pack(pady=10)
update_listbox()  # 初期表示

# メインループの開始
root.mainloop()

ポイントは、エントリーに紐付けたStringVartrace_addメソッドです。

# search_varが変更されるたびにupdate_listbox関数を呼び出す
search_var.trace("w", update_listbox)

# 検索ボックス
search_entry = tk.Entry(root, textvariable=search_var, width=50)

キー入力が起こるたびに、リストボックスの項目を更新するupdate_listbox関数を呼べます。

def update_listbox(*args):
    """
    search_varが変更されるたびに呼び出される関数。
    リストボックスの内容を検索語に基づいてフィルタリングする。
    """
    search_term = search_var.get().lower()  # 検索語を小文字に変換
    filtered_items = [item for item in items if search_term in item.lower()]  # フィルタリング
    listbox.delete(0, tk.END)  # リストボックスをクリア
    for item in filtered_items:
        listbox.insert(tk.END, item)  # フィルタリングされた項目をリストボックスに追加

実行例です。

IntVar × 割り勘の計算

IntVarは整数を扱います。

そのため、数値管理に長けたスケールやスピンボックスと相性が抜群です。

また、選択状態を\(1\)・\(0\)の整数とすることで、チェックボックスの状態管理もIntVarで行えます。

具体例をあげます。

IntVarを使って割り勘計算機能を実装しました。

import tkinter as tk

def calculate():
    try:
        total = total_var.get()
    except:
        return None

    people = people_var.get()

    return total / people

def show_result(per_person):
    try:
        text = f"一人当たりの金額: {per_person:.2f}円"
    except:
        text = "エラー"

    result_label.config(text=text)

# ウィンドウの作成
root = tk.Tk()

# 合計金額の入力
total_label = tk.Label(root, text="合計金額 (円):")
total_label.grid(column=0, row=0, padx=5, pady=5)

total_var = tk.IntVar()
total_spinbox = tk.Spinbox(root, from_=0, to=100000, textvariable=total_var)
total_spinbox.grid(column=1, row=0, padx=5, pady=5)

# 人数の入力
people_label = tk.Label(root, text="人数 (人):")
people_label.grid(column=0, row=1, padx=5, pady=5)

people_var = tk.IntVar()
people_spinbox = tk.Spinbox(root, from_=1, to=100, textvariable=people_var, state="readonly")
people_spinbox.grid(column=1, row=1, padx=5, pady=5)

# 結果表示用のラベル
result_label = tk.Label(root, text="一人当たりの金額: ")
result_label.grid(column=0, row=2, columnspan=2, padx=5, pady=5)

# 計算ボタン
calc_button = tk.Button(root, text="計算", command=lambda: show_result(calculate()))
calc_button.grid(column=0, row=3, columnspan=2, padx=5, pady=5)

# メインループの開始
root.mainloop()

ポイントはIntVarをスピンボックスに紐付けている点です。

total_var = tk.IntVar()
total_spinbox = tk.Spinbox(root, from_=0, to=100000, textvariable=total_var)
total_spinbox.grid(column=1, row=0, padx=5, pady=5)

…

people_var = tk.IntVar()
people_spinbox = tk.Spinbox(root, from_=1, to=100, textvariable=people_var,  state=“readonly”)
people_spinbox.grid(column=1, row=1, padx=5, pady=5)

これにより、calculate関数からtotal_varpeople_varを介してスピンボックスの値にアクセスできます。

def calculate():
    try:
        total = total_var.get()
    except:
        return None

    people = people_var.get()

    return total / people

居酒屋のタブレットで見たことありませんか?

DoubleVar × BMIの計算

DoubleVarは浮動小数点数を扱います。

そのため、スケールやスピンボックスの増減量を、それぞれresolutionincrementオプションで小数点以下にまで細かくした場合、値の管理はDoubleVarが最適です。

具体例をあげます。

体重と身長を元にしてBMIを計算する機能を作りました。

import tkinter as tk

# BMIを計算する関数
def calculate_bmi():
    weight = weight_var.get()
    height_cm = height_var.get()
    height_m = height_cm / 100  # cmをmに変換

    bmi = weight / (height_m ** 2)
    bmi_var.set(round(bmi, 2))

    if bmi < 18.5:
        category_var.set("低体重(痩せ)")
    elif 18.5 <= bmi < 25:
        category_var.set("普通体重")
    elif 25 <= bmi < 30:
        category_var.set("肥満(1度)")
    elif 30 <= bmi < 35:
        category_var.set("肥満(2度)")
    elif 35 <= bmi < 40:
        category_var.set("肥満(3度)")
    else:
        category_var.set("肥満(4度)")

# メインウィンドウの設定
root = tk.Tk()

# デフォルト値
default_height = 170.0  # cm
default_weight = 60.0  # kg

# DoubleVarオブジェクトの設定
height_var = tk.DoubleVar(value=default_height)
weight_var = tk.DoubleVar(value=default_weight)
bmi_var = tk.DoubleVar()
category_var = tk.StringVar()

# ウィジェットの配置
tk.Label(root, text="身長 (cm)").grid(row=1, column=0)
tk.Spinbox(root, from_=100, to=220, increment=0.1, textvariable=height_var, state="readonly").grid(row=0, column=1)

tk.Label(root, text="体重 (kg)").grid(row=0, column=0)
tk.Spinbox(root, from_=30, to=150, increment=0.1, textvariable=weight_var, state="readonly").grid(row=1, column=1)

tk.Button(root, text="BMI計算", command=calculate_bmi).grid(row=2, columnspan=2)

tk.Label(root, text="BMI").grid(row=3, column=0)
tk.Entry(root, textvariable=bmi_var, state='readonly').grid(row=3, column=1)

tk.Label(root, text="カテゴリー").grid(row=4, column=0)
tk.Entry(root, textvariable=category_var, state='readonly').grid(row=4, column=1)

# メインループの開始
root.mainloop()

ポイントは体重・身長の数値管理をDoubleVarが担う点です。

(ちゃっかりStringVarも使っています。)

weight_var = tk.DoubleVar(value=default_weight)
height_var = tk.DoubleVar(value=default_height)
bmi_var = tk.DoubleVar()
category_var = tk.StringVar()

入力欄のスピンボックスの増減量は\(0.1\)刻みにし、より正確な数値の入力を促します。

tk.Label(root, text="体重 (kg)").grid(row=0, column=0)
tk.Spinbox(root, from_=30, to=150, increment=0.1, textvariable=weight_var, state="readonly").grid(row=0, column=1)

tk.Label(root, text="身長 (cm)").grid(row=1, column=0)
tk.Spinbox(root, from_=100, to=220, increment=0.1, textvariable=height_var, state="readonly").grid(row=1, colum

実行例です。

BooleanVar × フォーム画面

BooleanVarは真偽値を扱います。

そのため、二択の選択を受け取るウィジェットと相性が良いです。

その代表例はチェックやラジオボタンなどでしょう。

具体例をあげます。

「プライバシーポリシーに同意」にチェックしないと送信ボタンを押せないフォームです。

import tkinter as tk

def update_submit_button():
    if agree_var.get():
        submit_button.config(state=tk.NORMAL)
    else:
        submit_button.config(state=tk.DISABLED)

def clear_placeholder(event, placeholder):
    entry = event.widget

    if entry.get() == placeholder:
        entry.delete(0, tk.END)
        entry.config(fg='black')

def add_placeholder(event, placeholder):
    entry = event.widget

    if entry.get() == '':
        entry.insert(0, placeholder)
        entry.config(fg='grey')

root = tk.Tk()

# 名前の入力
tk.Label(root, text="名前").grid(row=0, column=0, padx=10, pady=5, sticky='e')
default_name = "山田 太郎"
name_entry = tk.Entry(root, fg='grey')
name_entry.insert(0, default_name)
name_entry.bind("<FocusIn>", lambda event: clear_placeholder(event, default_name))
name_entry.bind("<FocusOut>", lambda event: add_placeholder(event, default_name))
name_entry.grid(row=0, column=1, padx=10, pady=5)

# メールの入力
tk.Label(root, text="メールアドレス").grid(row=1, column=0, padx=10, pady=5, sticky='e')
default_email = "example@example.com"
email_entry = tk.Entry(root, fg='grey')
email_entry.insert(0, default_email)
email_entry.bind("<FocusIn>", lambda event: clear_placeholder(event, default_email))
email_entry.bind("<FocusOut>", lambda event: add_placeholder(event, default_email))
email_entry.grid(row=1, column=1, padx=10, pady=5)

# 電話番号の入力
tk.Label(root, text="電話番号").grid(row=2, column=0, padx=10, pady=5, sticky='e')
default_phone = "080-1234-5678"
phone_entry = tk.Entry(root, fg='grey')
phone_entry.insert(0, default_phone)
phone_entry.bind("<FocusIn>", lambda event: clear_placeholder(event, default_phone))
phone_entry.bind("<FocusOut>", lambda event: add_placeholder(event, default_phone))
phone_entry.grid(row=2, column=1, padx=10, pady=5)

# 住所の入力
tk.Label(root, text="住所").grid(row=3, column=0, padx=10, pady=5, sticky='e')
default_address = "東京都新宿区1-2-3"
address_entry = tk.Entry(root, fg='grey')
address_entry.insert(0, default_address)
address_entry.bind("<FocusIn>", lambda event: clear_placeholder(event, default_address))
address_entry.bind("<FocusOut>", lambda event: add_placeholder(event, default_address))
address_entry.grid(row=3, column=1, padx=10, pady=5)

# プライバシーポリシーへの同意
agree_var = tk.BooleanVar()
agree_check = tk.Checkbutton(root, text="プライバシーポリシーに同意する", variable=agree_var, command=update_submit_button)
agree_check.grid(row=4, columnspan=2, padx=10, pady=10)

# 送信ボタン
submit_button = tk.Button(root, text="送信", state=tk.DISABLED)
submit_button.grid(row=5, columnspan=2, padx=10, pady=20)

# メインループの開始
root.mainloop()

ポイントはチェックボタンをBooleanVarと連動させている点です。

agree_var = tk.BooleanVar()
agree_check = tk.Checkbutton(root, text="プライバシーポリシーに同意する", variable=agree_var, command=update_submit_button)

これにより、update_submit_button関数内でBooleanVarの状態に応じて送信ボタンの状態を制御できます。

def update_submit_button():
    if agree_var.get():
        submit_button.config(state=tk.NORMAL)
    else:
        submit_button.config(state=tk.DISABLED)

実行例です。

まとめ

今回は、Tkinterのウィジェット変数を解説しました。

最後に軽く復習して締めましょう。

ウィジェット変数を使うメリットです。

ウィジェット変数のメソッドの使い方です。

ウィジェット変数は、扱うデータ型に応じて下記のような応用ができます。

この記事が少しでも皆様のお役に立ったら幸いです。

最後までお付き合いいただきありがとうございました。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA