Python | 1 日 120 分 × 7 日アプリ学習:tkinterで作るGUIアプリ(中級編)

Web APP Python
スポンサーリンク

2日目のゴール

2日目のテーマは
「ボタンを“増やす・使い分ける・引数付きで使う”ところまで進む」 ことです。

1日目は、

ウィンドウを出す
ボタンを1つ置く
ボタンを押したら関数が呼ばれる

ここまで行きました。

2日目ではそこから一歩進んで、

複数のボタンを置いて、それぞれ違う動きをさせる
同じ関数を、ボタンごとに“違う動き”で再利用する
ボタンから「値(引数)」を渡して処理する

という、「ボタンイベントの使いこなし」に入っていきます。


複数ボタンを置いてみる

それぞれ別の関数を呼ぶパターン

まずは素直に、「ボタンごとに別の関数」を呼ぶ例から。

import tkinter as tk

def say_hello():
    label.config(text="こんにちは")

def say_goodbye():
    label.config(text="さようなら")

root = tk.Tk()
root.title("複数ボタンの基本")

label = tk.Label(root, text="ボタンを押してください")
label.pack()

button_hello = tk.Button(root, text="あいさつ", command=say_hello)
button_hello.pack()

button_goodbye = tk.Button(root, text="別れのあいさつ", command=say_goodbye)
button_goodbye.pack()

root.mainloop()
Python

ここで押さえたいポイントは、とてもシンプルです。

ボタンごとに command= に渡す関数を変える
ラベルは1つだが、どのボタンからでも書き換えられる

「部品は複数あってもいいし、
同じ部品を複数の関数から触ってもいい」
という感覚を持っておいてください。


同じ関数を“ボタンごとに違う動き”で使う

「関数を増やしすぎない」ための考え方

さっきの例は分かりやすいですが、
ボタンが増えるたびに関数も増えていきます。

例えば、こんなイメージです。

「こんにちは」ボタン → say_hello
「こんばんは」ボタン → say_good_evening
「おはよう」ボタン → say_good_morning

…とやっていると、
関数名がどんどん増えていきます。

ここで出てくるのが、

「同じ関数に、ボタンごとに違う“値”を渡す」

という考え方です。


引数付きでボタンイベントを使いたい問題

そのまま書くと、こうなってしまう

例えば、こう書きたくなります。

button_hello = tk.Button(root, text="こんにちは", command=change_message("こんにちは"))
button_bye = tk.Button(root, text="さようなら", command=change_message("さようなら"))
Python

そして、change_message はこうだとします。

def change_message(text):
    label.config(text=text)
Python

一見よさそうですが、これはダメな書き方です。

理由はシンプルで、

command=change_message("こんにちは")
「ボタンが押されたときに呼ぶ」のではなく
「今すぐ change_message("こんにちは") を実行して、その“結果”を command に渡す」

という意味になってしまうからです。

結果として、

アプリ起動時に change_message("こんにちは") が実行される
command には None が入る(change_message は何も返していないので)
ボタンを押しても何も起きない

という状態になります。


解決策1:lambda を使って“その場で小さな関数を作る”

lambda のイメージ

ここで登場するのが lambda です。

lambda は、

「その場でちょっとだけ使う、小さな無名関数」

だと思ってください。

例えば、

lambda: print("こんにちは")
Python

は、

def _temp():
    print("こんにちは")
Python

という関数を、その場で一瞬だけ作っているようなイメージです。


ボタンに lambda を組み合わせる

さっきの「メッセージを変える」例を、
lambda を使って書き直します。

import tkinter as tk

def change_message(text):
    label.config(text=text)

root = tk.Tk()
root.title("lambdaで引数付きボタン")

label = tk.Label(root, text="ボタンを押してください")
label.pack()

button_hello = tk.Button(root, text="こんにちは", command=lambda: change_message("こんにちは"))
button_hello.pack()

button_bye = tk.Button(root, text="さようなら", command=lambda: change_message("さようなら"))
button_bye.pack()

button_thanks = tk.Button(root, text="ありがとう", command=lambda: change_message("ありがとう"))
button_thanks.pack()

root.mainloop()
Python

ここでの超重要ポイントは、

command=lambda: change_message("こんにちは")

という書き方です。

lambda: ... は「引数なしの小さな関数」です。
その中で change_message("こんにちは") を呼んでいます。

つまり、

「ボタンが押されたときに実行してほしい関数」
として、lambda: change_message("こんにちは") を渡している

という構造になっています。

これで、

関数 change_message は1つだけ
ボタンごとに渡す文字列だけ変える

という、スッキリした形になります。


解決策2:部分適用(応用編の入口)

※ここは「ふーん」と思えれば十分です。
理解しきれなくても、今は気にしなくてOK。

Pythonには functools.partial という仕組みもあります。

from functools import partial

button_hello = tk.Button(root, text="こんにちは",
                         command=partial(change_message, "こんにちは"))
Python

partial(change_message, "こんにちは") は、

change_message("こんにちは") を呼ぶ関数」

を作ってくれます。

中身のイメージは lambda と似ていますが、
2日目の段階では

「引数付きでボタンに関数を渡したいときは
lambda を使うのが定番」

くらいでOKです。


具体例:簡単な電卓(足し算だけ)を作る

仕様を決める

2日目のミニアプリとして、
「ボタンで数字を押して、合計を表示する」
超シンプル電卓を作ってみます。

ウィンドウにラベルを1つ置く(現在の合計を表示)
「+1」「+5」「+10」の3つのボタンを置く
ボタンを押すたびに合計が増えて、ラベルが更新される

ここで、lambda とボタンイベントの理解を一気に深めます。


コード全体

import tkinter as tk

total = 0

def add_value(amount):
    global total
    total += amount
    label.config(text=f"合計: {total}")

root = tk.Tk()
root.title("かんたん足し算電卓")

label = tk.Label(root, text="合計: 0", font=("Arial", 16))
label.pack()

button1 = tk.Button(root, text="+1", width=10, command=lambda: add_value(1))
button1.pack()

button5 = tk.Button(root, text="+5", width=10, command=lambda: add_value(5))
button5.pack()

button10 = tk.Button(root, text="+10", width=10, command=lambda: add_value(10))
button10.pack()

root.mainloop()
Python

ここでの重要ポイントを整理します。

total という変数で「今の合計」を覚えている
add_value(amount) は「amount だけ total を増やす」関数
ボタンごとに lambda: add_value(1) のように、渡す値を変えている

これで、

「同じ関数を、ボタンごとに違う“意味”で使い回す」

ということができています。


pack の順番と見た目の関係

上から順に積み重なっていく

2日目ではレイアウトはまだ pack() だけで十分ですが、
ひとつだけ意識してほしいことがあります。

pack() は、
「呼んだ順番に、上から下へ積み重ねていく」
という性質があります。

例えば、

label.pack()
button1.pack()
button2.pack()
Python

と書けば、

ラベル
ボタン1
ボタン2

の順に縦に並びます。

もし順番を変えれば、見た目も変わります。

button1.pack()
label.pack()
button2.pack()
Python

なら、

ボタン1
ラベル
ボタン2

という並びになります。

GUIでは、

「コードの書く順番が、そのまま見た目の順番になる」

ことが多いので、
そこも意識しながら書いてみてください。


ちょっと遊ぶ:ON/OFFトグルボタン

ボタンの表示も動的に変えてみる

1日目の「メッセージ切り替え」を、
ボタンの文字も変わる形にしてみます。

import tkinter as tk

is_on = False

def toggle():
    global is_on
    if is_on:
        label.config(text="OFF")
        button.config(text="ON にする")
        is_on = False
    else:
        label.config(text="ON")
        button.config(text="OFF にする")
        is_on = True

root = tk.Tk()
root.title("ON/OFF トグル")

label = tk.Label(root, text="OFF", font=("Arial", 16))
label.pack()

button = tk.Button(root, text="ON にする", command=toggle)
button.pack()

root.mainloop()
Python

ここでのポイントは、

ラベルだけでなく、ボタン自身の textconfig で変えている
ボタンは「押された瞬間だけ何かする」のではなく
「アプリの状態(is_on)を切り替えるスイッチ」になっている

ということです。

GUIアプリでは、

「ボタン = 状態を変えるスイッチ」

というイメージを持っておくと、
設計がしやすくなります。


2日目で絶対に押さえてほしい本質

ボタンイベントは「関数」と「値」の組み合わせ

今日いちばん大事なのは、

ボタンに渡すのは
「今すぐ実行する処理」ではなく
「あとで実行してほしい関数」

そして、その関数に
「ボタンごとに違う値(引数)」を渡したいときは
lambda を使う

という感覚です。

まとめると、

command=関数名 ← 引数なしで呼びたいとき
command=lambda: 関数名(値) ← 引数付きで呼びたいとき

この2パターンが、
tkinter のボタンイベントの“基本形”になります。

3日目以降は、
テキスト入力(Entry)、チェックボックス、レイアウトの工夫などに進んでいきます。
今日のコードをベースに、

ボタンを増やしてみる
増やす値を変えてみる
ラベルのフォントや色を変えてみる

など、少し遊びながら「ボタンと仲良くなる」時間を取ってみてください。

タイトルとURLをコピーしました