GUIプログラミング入門

目次

今回はPythonによるプログラミングを行う.必要に応じて下記のサイトも参照.
参考サイト:情報処理実習1,Python入門

GUI の基礎

GUI とは「Graphical User Interface(グラフィカルユーザインターフェイス)」の略で,画面上のウィンドウやボタン,入力ボックスといった視覚的に操作できる部品で構成されるインターフェイスの総称である.
これにたいして,CUI「Character User Interface」は,文字ベースのコマンドの入力および出力を前提としたユーザーインターフェースである.例:VSCodeのターミナル,Windowsのコマンドプロンプトなど.
一般に,ユーザの操作や,プログラムがユーザに対して情報を提示する機能をユーザーインターフェイス(UI)と呼ぶ.

GUIの概念はプログラミング言語とは独立したものであるが,実際には言語によってその難易度,複雑さは大きく異なる. ここでは,比較的容易にGUIを実装でき,Pythonの標準ライブラリであるtkinterを用いて,GUIの基本概念であるウインドウや,各種ウィジェット(ボタン,テキストボックス,ラジオボタン,コンボボックスなど)を,図とコード例で分かりやすく説明する.
(tkinter = Tk interface)
まずGUIの用語と役割を図で把握し,その後で実際に動く最小のコード例と,GUIを構成する各部品(ウィジェット)の使い方を学ぶ.

図:GUIとCUIの処理の流れの違い

GUI で動作するソフトウエアの構築には,以下のような用語を理解しておくことが重要である.

アプリケーションウィンドウ(root) タイトルバー メニュー メイン領域(ウィジェットを配置) ボタン テキストボックス(Entry) テキスト領域(Text) 代表的なウィジェット Button — 押せるボタン Entry — 1 行入力 Text — 複数行表示/編集 Radiobutton — 単一選択 Combobox — ドロップダウン
アプリケーションウィンドウとウィジェットの配置イメージ

GUI を構成する基本用語

・ウィンドウ(Window / root)
GUI の最上位の枠で,tkinter では tk.Tk() によって生成される. アプリ全体の入れ物にあたり,タイトルバーや,最大化・最小化・閉じるボタンなどが含まれる. すべてのウィジェットはこのウィンドウの中に置かれる.
・ウィジェット(Widget)
GUI の構成要素である部品をウィジェットと呼ぶ. 具体的な例として,ボタン,ラベル,テキストボックス,リスト,チェックボックスなどが挙げられる. ウィジェットを配置していくことで画面が作られ,ユーザーはそれをマウスやキーボードを使って直接操作することができる.
・コンテナ(Frame など)
他のウィジェットをまとめる箱の役割を持つ部品である. ウィンドウ内を領域ごとに区切ったり,レイアウトを整理するときに役立つ.
・レイアウトマネージャ(pack / grid / place)
ウィジェットをどの位置にどう並べるかを決める仕組みである. tkinter では主に 3 種類があり,フォーム形式や段組みレイアウトなど柔軟に画面を構成できる.
・イベント(Event)
「ボタンがクリックされた」,「キーが押された」,「フォーカスが移動した」など,ユーザーまたはシステムによる動作をイベントと呼ぶ. 一般的に GUI でのプログラミングはイベント駆動型(イベントドリブンと呼ぶ)と呼ばれ,イベントが発生したときに,あらかじめ登録しておいた関数(コールバック)が呼ばれる.
・コールバック(Callback)
特定のイベントが発生したときに実行される関数をコールバック関数と呼ぶ. 例:ボタンが押されたときに実行する処理を command=関数名 として指定する.
・メインループ(mainloop)
ウィンドウを表示して,イベントの発生を常に監視するループ処理のことを指す. root.mainloop() を呼ぶことでプログラムが GUI として動作し続ける.

まとめ

GUI アプリは「ウィンドウ → コンテナ → ウィジェット」という階層で構成される.
基本的には,「イベント発生」 → 「コールバック関数が呼ばれる」 → 「画面に変化を提示」という流れで動作.

tkinter プログラム

まずはウィンドウを表示する最小限のコードを示す.
正しくPython がインストールされている環境で実行すれば,画面内のどこかに小さなウィンドウが表示される. これを,ルートウインドウという. このウインドウを閉じると,プログラムが終了する.

実行方法について Pythonプログラムの実行は,VSCodium(またはVSCode)からターミナルを開き,コマンドプロンプトから以下のように入力する.
d:\cprog20\myprog\> python 153R??????-??-?.py
注意:GUIプログラムを実行すると,ウインドウがエディタの後ろに出現する場合があるので,よく探してみよう.
別の実行方法として,VSCodium に Python拡張機能をインストールすれば,メニューから「Run」「Start Debugging」で一発実行できるが,いくつか設定が必要.
import tkinter as tk

root = tk.Tk()            # ルートウィンドウを作る
root.title('GUI sample')  # ウィンドウのタイトルを設定
root.geometry('400x250')  # ウインドウの幅と高さを指定(pixel単位)

root.mainloop()           # イベントループを開始(ここでウィンドウが表示され続ける)
root.title()などは,クラスのメンバ関数であり,メソッドと呼ばれる.

練習問題

  1. 上記のPythonプログラムを実行し,ウインドウが生成されることを確認しよう.
  2. ウインドウのタイトル,およびサイズを変更してみよう.

ここまででGUIの基盤となるウインドウを無事に開くことができたので,次は,よく用いられるウィジェットについて、ソースコードと実行結果を確認しながら,それぞれの特徴と基本的な使い方を解説する.

Button(ボタン)

多くのGUIプログラムには,以下のような「OK」や「キャンセル」などのボタンが使われている.

クリック → ユーザーの操作でコールバック関数が呼ばれる
図:Button のイメージ.クリックで処理(コールバック)を呼ぶ.
import tkinter as tk

# ボタンが押された時の処理を記したコールバック関数
def on_click():
    print(' You pressed button!')    # ターミナルに文字を表示

# ウインドウを表示
root = tk.Tk()
root.title('Button sample')
root.geometry('400x250')

# ボタンを表示
btn = tk.Button(root, text='Button1', command=on_click)   # ボタン名称や,コールバック関数を指定.
btn.pack(padx=10, pady=10)   # ウイジェットをウインドウ内に配置

root.mainloop()

このプログラムでは,ボタンが押された場合に,on_click()が呼ばれる.
GUIプログラミングでは,ユーザーからの入力に対して何かの動作を開始するようなプログラムとなる. このようなプログラムをイベント駆動型(event-driven)と呼ぶ.

イベントドリブンプログラミングでは,発生したイベントに対して,処理を関数として設定する.上の例では,引数command=の部分がこれに相当する. このような関数をコールバック関数と呼ぶ.
コールバック関数の名前は任意であるが,一般的には on??? という名称をつけることが多い.

btn.pack()は,ボタンをウインドウに配置する関数である.
これとは別に,.place()を用いることで,x座標,y座標,幅や高さなどを細かく指定する方法もある.

上の例では,ターミナルにprint()で文字列を表示するが,GUIプログラムらしくウインドウ内に文字列を表示する場合は,ラベルを用いる.

import tkinter as tk
 
# ボタンが押された時の処理を記したコールバック関数
def on_click():
    print('You pressed button!')   # ターミナルに出力
    label.config(text = 'OMG!')    # ウインドウ内のラベlabelを更新
 
# ウインドウを表示
root = tk.Tk()
root.title('Button sample')
root.geometry('400x250')
 
# ボタンを表示
btn = tk.Button(root, text='Button1', command=on_click)   # コールバック関数を指定.
btn.pack(padx=10, pady=10)   # ウイジェットをウインドウ内に配置

# 文字列を表示(label)
label = tk.Label(root, text='Label here')
label.pack(pady=20)

root.mainloop()

練習問題

  1. 上記のPythonプログラムを実行し,ウインドウ内のボタンをクリックしてみよう.
  2. pack関数の引数,padx, padyは余白のサイズである.この値を変更してどのように変化するか試してみよう.
  3. おまけ:ボタンやラベルの生成時に,オプションfont=('',16)を追加してみよう.この整数値はフォントのサイズである.
    例えば,label = tk.Label(root, font=('',16), text='Label here')
    さらに,第1引数にフォント名を指定するとフォントが変更される.Arial, Times, Symbolなど.

複数のボタンを設置する場合は,tk.Button()を複数回呼び出し,それぞれコールバック関数を用意し,pack(配置)する.

import tkinter as tk

# ボタン 1 が押された時の処理を記したコールバック関数
def on_click1():
    print('Hello')

# ボタン 2 が押された時の処理を記したコールバック関数
def on_click2():
    print('Good Bye')

root = tk.Tk()
root.title('Multiple Button sample')
root.geometry('400x250')

btn1 = tk.Button(root, text='Button1', command=on_click1)
btn1.pack(padx=10, pady=10)

btn2 = tk.Button(root, text='Button2', command=on_click2)
btn2.pack(padx=10, pady=10)

root.mainloop()

練習問題

  1. 上記のPythonプログラムを実行し,ウインドウ内のボタンをクリックしてみよう.

テキスト入力(Entry)

ユーザーから文字・数字などを入力させる(表示もできる)ウィジェットをテキストボックスと呼び,tkinter では Entry と呼ばれる.

ここに入力
図:Entry はテキストボックス(入出力フィールド).文字列を表示したり,ユーザーが文字列を入力できる.
import tkinter as tk
 
# ボタンが押された時の処理
def on_click():
    print('txt =', e.get())    # Entryの中身を読み取ってターミナルに出力

root = tk.Tk()
root.title('Entry sample')
root.geometry('400x250')

# エントリを生成
e = tk.Entry(root)              # Entryを生成
e.insert(tk.END, 'ここに入力')   # 初期文字列を設定
e.update()                      # 文字表示を更新
e.pack()

# ボタンを生成
btn = tk.Button(root, text='Click me', command=on_click)
btn.pack(padx=10, pady=10)

root.mainloop()

練習問題

  1. 上記のPythonプログラムを実行し,ウインドウ内のボタンをクリックしてみよう.
  2. エントリに数値を入力すると,その2乗を計算してターミナルに出力してみよう.
    ヒント:e.get()は文字列を返すので,これを整数や実数に変換するには int(e.get()) または float(e.get()) とする.

このように,GUIプログラミングにおいては,ユーザからの何らかの入力に応じて,関数(イベントハンドラ)が呼ばれて処理が行われる点に特徴がある.

Radiobutton(ラジオボタン,単一選択)

図のように,複数の選択肢の中から一つだけを選択したい場合がある.これをラジオボタンと呼ぶ.
(これに対して,チェックボックスでは,複数選択が可能.)

選択肢 A 選択肢 B → 複数から 1 つ選ぶときに使う
図:複数の Radiobutton を同じ変数で結び付け,1 つだけ選べるようにする.
import tkinter as tk
 
# イベントハンドラ.
def show_selection():
    label.config(text=f'選択中:{var.get()}')
 
root = tk.Tk()
root.title('Radiobutton Sample')
root.geometry('400x250')

# ウィジェット(ラジオボタン)用の変数
var = tk.StringVar(value='Pizza')

# ラジオボタン.同じイベントハンドラを指定
r1 = tk.Radiobutton(root, text='I like Pizza.', variable=var, value='Pizza', command=show_selection)
r2 = tk.Radiobutton(root, text='I like Pasta.', variable=var, value='Pasta', command=show_selection)
r3 = tk.Radiobutton(root, text='I like Penne.', variable=var, value='Penne', command=show_selection)
 
r1.pack(padx=20, pady=5)
r2.pack(padx=20, pady=5)
r3.pack(padx=20, pady=5)

# ラベル
label = tk.Label(root, text='1つを選択')
label.pack(pady=20)
 
root.mainloop()

ラジオボックスでは,ボタンのようにクリックされたときではなく,選択値が変更されたときに呼ばれるコールバック関数を設定する.
tk.StringVarは,ウィジェット用の特別な文字列変数である. これを用いると,テキスト内容とウィジェットの表示とが自動で連動する.

練習問題

  1. 上記のPythonプログラムを実行し,ウインドウ内のラジオボタンをクリックしてみよう.

コンボボックス

ttk モジュールの Combobox は,あらかじめ用意されたリストから一つを選択するウィジェットである.

import tkinter as tk
from tkinter import ttk

# イベントハンドラ
def show_selected(event):
    label.config(text=f'選択中:{cb.get()}')
    print('You choose ' + cb.get())
 
root = tk.Tk()
root.title('Combobox sample')
root.geometry('400x250')

# コンボボックス
selected_value = tk.StringVar()   # 選択された値を保存する変数
cb = ttk.Combobox(root, values=['Materials', 'Fluid Dynamics', 'Thermodynamics', 'Machine Dynamics'])
cb.current(0)  # 初期の選択肢(0番目)
cb.pack(padx=20, pady=20)
cb.bind('<<ComboboxSelected>>', show_selected)     # イベントハンドラを追加

# ラベル
label = tk.Label(root, text='1つを選択')
label.pack(pady=20)

root.mainloop()

練習問題

  1. 上記のPythonプログラムを実行し,コンボボックスを選択してみよう.
  2. コンボボックスの内容を変更してみよう.

簡単なフォームアプリ

ここまでの要素をまとめて,エントリおよびコンボボックスを選択して,返信の文字列を表示する簡単なフォームを作ってみよう.
このプログラムは,テキストボックスに名前を入力し,コンボボックスから機械工学4力学の科目名を選択し,ボタンをクリックするとメッセージを表示する.

import tkinter as tk
from tkinter import ttk

# ボタンがクリックされた時のイベントハンドラ
def submit():
    name = entry_name.get()
    subject = combo.get()
    if not name:
        label_result.config(text='Please input your name!')
        return
    label_result.config(text=f'Welcome {name}. You like {subject}')

root = tk.Tk()
root.title('Form sample')
root.geometry('300x250')

# 名前を入力(エントリ)
tk.Label(root, text='Name').grid(row=0, column=0, padx=6, pady=6, sticky='w')  # 表示用ラベル
entry_name = tk.Entry(root)
entry_name.grid(row=0, column=1, padx=6, pady=6)

# 好きな科目を選択(コンボボックス)
tk.Label(root, text='Subject').grid(row=1, column=0, padx=6, pady=6, sticky='w')  # 表示用ラベル
combo = ttk.Combobox(root, values=['Materials', 'Fluid Dynamics', 'Thermodynamics', 'Machine Dynamics'])
combo.current(0)
combo.grid(row=1, column=1, padx=6, pady=6)

# 送信ボタン
btn = tk.Button(root, text='Submit', command=submit)
btn.grid(row=2, column=0, columnspan=2, pady=10)

# メッセージ表示用(ラベル)
label_result = tk.Label(root, text='')
label_result.grid(row=3, column=0, columnspan=2, pady=6)

root.mainloop()

このサンプルでは,ウィジェットの配置に pack()よりもより細かなコントロールが可能なgrid() を使っている. 詳細については,公式ドキュメントおよび下記のウィジェット配置方法の説明を参照.

ウィジェット配置方法,3種類
ウィジェット配置方法

tkinterでは,ウィジェットを画面上に配置するために,主に packgridplace の3つの方法が用意されている. それぞれの特徴と使い分けを以下に示す.

方法 特徴 主な用途 難易度
pack 追加順に上下・左右へ配置 簡単なGUI,縦並びレイアウト
grid 行・列で表形式に配置 入力フォーム,設定画面
place 座標を指定して配置 微調整,実験用

pack による配置

pack は最も簡単な配置方法で,ウィジェットを追加した順に自動的に配置する.

import tkinter as tk

root = tk.Tk()
root.title('pack の例')

tk.Label(root, text='Label 1').pack(pady=5)   # packをまとめて書く方法
tk.Label(root, text='Label 2').pack(pady=5)
tk.Button(root, text='Button').pack(pady=5)

root.mainloop()

packの主なオプション:

grid による配置

grid は行 (row) と列 (column) を指定して配置する方法で,フォーム形式のGUIに適している.

import tkinter as tk

root = tk.Tk()
root.title('grid sample')

tk.Label(root, text='名前').grid(row=0, column=0, padx=5, pady=5)
tk.Entry(root).grid(row=0, column=1, padx=5, pady=5)

tk.Label(root, text='学生番号').grid(row=1, column=0, padx=5, pady=5)
tk.Entry(root).grid(row=1, column=1, padx=5, pady=5)

tk.Button(root, text='送信').grid(row=2, column=0, columnspan=2, pady=10)

root.mainloop()

注意: 同一コンテナ内で packgrid は併用できない.どちらかのみを使用.

place による配置

place は座標値を直接指定してウィジェットを配置する. 細かい制御が可能だが,ウィンドウサイズ変更に弱いという欠点がある.

import tkinter as tk

root = tk.Tk()
root.geometry('300x200')

tk.Button(root, text='左上').place(x=20, y=20)
tk.Button(root, text='中央').place(relx=0.5, rely=0.5, anchor=tk.CENTER) # ウインドウ中央に固定
tk.Button(root, text='右下').place(x=200, y=150)

root.mainloop()

まとめ

GUIプログラミングでは,画面構成に応じて適切な配置方法を選ぶことが重要で,ユーザーインターフェースの設計(各ウィジェットの配置,直感的なレイアウト,ウィジェットのサイズなど)が,そのアプリケーションの使いやすさに直結する.

実習課題

課題1

ウインドウ内に[ADD]と[SUB]ボタンを設置し,それぞれをクリックすると変数の数値を +1, -1 してターミナルに表示せよ.

ヒント:ADDボタンの設置とコールバック関数の実装
import tkinter as tk

sum = 0  # global 変数

# ADDボタンが押された時のコールバック関数
def on_click_add():
    global sum        # global 変数を変更するためのおまじない 
    sum = sum+1
    print('sum =', sum)
 
# SUBボタンが押された時のコールバック関数
# ここに追加(1)

root = tk.Tk()
root.title('Add/Sub sample')
root.geometry('400x250')

# ボタンの生成と配置
btn1 = tk.Button(root, text='ADD', command=on_click_add)
btn1.pack(padx=10, pady=10)

# ここに追加(2)

root.mainloop()


実行例
sum = 1   [ADD をクリック]
sum = 2   [ADD をクリック]
sum = 3   [ADD をクリック]
sum = 2   [SUB をクリック]

課題2

実数a, bをテキストボックス(エントリ)で入力し,ボタンをクリックすると,$\sqrt{a^2+b^2}$ を計算し,label として表示するプログラムを作成せよ.

課題3

テキストボックス2個に身長と体重を入力し,「計算」ボタンをクリックすると,BMIの計算をしてウインドウ内に表示するプログラムを作成せよ.
参考:WHO Body mass index (BMI)東京都保健医療局