PythonのGUI、tkinterとついに向き合ってみる
非エンジニアの僕が約3年前に初めてプログラミングをはじめた時に、まず読んだのが以下の本でした。
解説は分かりやすいものの、tkinterというPythonのGUIライブラリを駆使して、色々作って動かしてみましょうよ的な内容。
プログラムの機能的な部分のコードと、途中から語られるtkinterのGUI部分のコードは結構毛色が違うので戸惑いました。
見よう見まねで書くことはできても、全く身にならない気がしたためです。
もうそろそろいい加減逃げ回るのはやめて、PythonのGUIと向き合うべきではないか、ということでここ数日学び、咀嚼した内容のおすそ分けです。
次回は、作ってみたものをもとに解説する予定です。
1. GUIって何?
GUIとは、グラフィカルユーザーインターフェースの略で、いわゆるキーボード・マウスで操作ができるような画面だと思ってください。
今まで僕がnoteに書いてきたのは、殆どコマンドプロンプトを駆使してユーザーが入力・出力結果を見る前提です。
上記3つは全部、コマンドプロンプトをユーザーインターフェースとして記載しています。今回は、例えば以下のWindowsの設定メニューの画面のように、より「操作画面」ぽい感じを追求していく感じです。
2. tkinterって何?
tkinterはPythonの標準ライブラリに存在する、GUI作成ができるモジュールです。
予め、画面作成のためのウインドウ・ラベル・入力欄・ボタン等々、様々なパーツ(ウィジェット)を階層でくみ上げていくことで、画面が作成できます。
自分が初心者(今も中級者を名乗れるのか分かりませんが)だった時は、上記の構造が理解できなかったのと、「コードは全て暗記せねばならぬのだ」という前提があったため、拒否反応を覚えました。
上記記事にも書いた通り、「コードなんて書き方さえ知ってれば、暗記する必要なんてなし」と今は割り切り、乗り越えましたが、初心者の方にはハードルが高く感じるかもしれません。・・・が、覚える必要なので、パーツごとの書き方等だけ理解して、後は組み合わせればいいのねってとこまでで十分だと思います。
(それなら、そんなに難しい話ではないかと)
3. tkinterでウインドウとラベルとボタンを表示
まずは、何事も実行あるのみです。早速、tkinterを使ってみましょう。
以下のコードを書いてみましょう。
#インポート
from tkinter import *
from tkinter import ttk
#ウインドウ作成
root = Tk()
root.title("テスト")
#フレームを作成
frame1 = ttk.Frame(root)
frame1.grid(column = 0, row = 0, rowspan=2, sticky=(N,W,E,S))
frame2 = ttk.Frame(root)
frame2.grid(column = 1, row = 0,sticky = (N,W,E,S))
frame3 = ttk.Frame(root)
frame3.grid(column = 1, row = 1,sticky = (N,W,E,S))
frame4 = ttk.Frame(root)
frame4.grid(column = 0, row = 2, columnspan=2)
#テキストラベルを作成
label1 = ttk.Label(frame1, text="これはフレーム1のラベルです")
label1.grid(column=0, row=0)
label2 = ttk.Label(frame2, text="これはフレーム2のラベルです")
label2.grid(column=0, row=0)
label3 = ttk.Label(frame3, text="これはフレーム3のラベルです")
label3.grid(column=0, row=0)
label4 = ttk.Label(frame4, text="これはフレーム4のラベルです")
label4.grid()
#ボタンを作成
button1 = ttk.Button(frame1, text="ボタン1")
button1.grid(column=1, row=0)
button2 = ttk.Button(frame2, text="ボタン2")
button2.grid(column=1, row=0)
button3 = ttk.Button(frame3, text="ボタン3")
button3.grid(column=1, row=0)
#処理実行
root.mainloop()
これを実行すると以下のようになります。
4. 各ウィジェットと配置(grid)の説明
簡単に順を追って説明します。
(1) インポート
from tkinter import *
from tkinter import ttk
まず、tkinterのインポートですが、ttkをインポートしてるのってなぜ?tkinterじゃないの?と引っかかる方が多数と思います。
tkinterのバージョン8.5から登場したttkというモジュールは、tkinterには存在しないコンボボックス等のウィジェットが利用可能なのと、見た目が良化しており、tkinterのウィジェットを上書き処理するイメージです。
なので、1行目でtkinterをインポートしつつ、2行目でtkkもインポートしています。
(2) ウインドウを作成する
まずは、ウインドウ作成です。以下コードを実行した時点ですでにウインドウは作成されます。
#ウインドウ作成
root = Tk()
root.title("テスト")
rootという変数にTkクラスのインスタンスを格納しています。
つまり、以下の記事でも解説したclassに用意された設計書をもとに実体化したイメージです。
そして、classが保有するtitleというメソッドでウインドウのタイトルを設定しています。
※分からない方は以下をさらっと読んでください。
変数もrootである必要はありません。試しに、変数名をmainに変えて、インスタンスを格納し、print(type(main))で型を確認してみます。
上記のとおり、classであることが分かりました。
(3) フレームを作成する(グリッド配置を利用)
次にフレーム作成の部分に触れていきます。また、避けて通れない配置についての話も同時に触れます。
#フレームを作成
frame1 = ttk.Frame(root)
frame1.grid(column = 0, row = 0, rowspan=2, sticky=(N,W,E,S))
frame2 = ttk.Frame(root)
frame2.grid(column = 1, row = 0,sticky = (N,W,E,S))
frame3 = ttk.Frame(root)
frame3.grid(column = 1, row = 1,sticky = (N,W,E,S))
frame4 = ttk.Frame(root)
frame4.grid(column = 0, row = 2, columnspan=2)
それぞれ、frame1~frame4の変数に格納しているのは、ttkのFrameというウィジェットです。(変数名はもちろん適当です)
引数にrootを置いているのは、各フレームの親パーツをrootとしたいためです。
この時点で、構造は以下のようなイメージです。
そして、配置について触れます。tkinterは、pack, grid, placeと3通りの配置方法があり、今回はgrid(グリッド)で配置しています。
gridの配置の仕組みは以下の通りです。
frame1は2行、frame4は2列に渡って使用したいため、rowspanあるいは、columnspanという引数を書いています。
ウィジェットは作成して、そして配置するという2つの記述が必要となるので、そこだけ覚えておきましょう。
(4) ラベルを作成・配置する
次にラベル作成です。ラベルにはtext等の引数でどんな文字列を表示するのかを記述し、同様に配置していきます。
#テキストラベルを作成
label1 = ttk.Label(frame1, text="これはフレーム1のラベルです")
label1.grid(column=0, row=0)
label2 = ttk.Label(frame2, text="これはフレーム2のラベルです")
label2.grid(column=0, row=0)
label3 = ttk.Label(frame3, text="これはフレーム3のラベルです")
label3.grid(column=0, row=0)
label4 = ttk.Label(frame4, text="これはフレーム4のラベルです")
label4.grid()
なお、後述のボタンと併せて解説してしまうと、frame内で更にcolumn,rowで配置を指定している感じです。(入れ子のイメージ)
(5) ボタンを作成・配置する
最後にボタンを作成について。本来ボタンは押したら、なんかアクションがあるのでそこは、後述します。
#ボタンを作成
button1 = ttk.Button(frame1, text="ボタン1")
button1.grid(column=1, row=0)
button2 = ttk.Button(frame2, text="ボタン2")
button2.grid(column=1, row=0)
button3 = ttk.Button(frame3, text="ボタン3")
button3.grid(column=1, row=0)
なお、root.mainloop()は一旦割愛します。
5. 機能を追加し、ボタンと紐づけてみる
当然、表示だけでなく入出力、ボタン操作の結果プログラムで何かを算出するのが一般的な画面です。
今回は3つラベルを作っているので、おはよう・こんにちわ・こんばんわの3つの挨拶をframe4に表示する機能を追加してみましょう。
少しレイアウトも修正します。
#インポート
from tkinter import *
from tkinter import ttk
#あいさつ機能
def greet_gm(*args):
greet.set("おはようございます")
def greet_ga(*args):
greet.set("こんにちわ")
def greet_gn(*args):
greet.set("こんばんわ")
#ウインドウ作成
root = Tk()
root.title("テスト")
#フレームを作成
frame1 = ttk.Frame(root)
frame1.grid(column = 0, row = 0,sticky=(W),padx=10, pady = 5)
frame2 = ttk.Frame(root)
frame2.grid(column = 1, row = 0,sticky=(N,W),padx=10, pady = 5)
frame3 = ttk.Frame(root)
frame3.grid(column = 2, row = 0,sticky=(N,W),padx=10, pady = 5)
frame4 = ttk.Frame(root, borderwidth=5, relief="sunken")
frame4.grid(column = 0, row = 2, columnspan=3,padx=20, pady = 10)
#テキストラベルを作成
label1 = ttk.Label(frame1, text="おはよう")
label1.grid(sticky=(W))
label2 = ttk.Label(frame2, text="こんにちわ")
label2.grid(sticky=(W))
label3 = ttk.Label(frame3, text="こんばんわ")
label3.grid(sticky=(W))
greet = StringVar()
greet.set("挨拶待ち中")
label4 = ttk.Label(frame4, textvariable=greet, foreground="blue", background="white",
font = ('MS ゴシック', 12))
label4.grid()
#ボタンを作成
button1 = ttk.Button(frame1, text="挨拶", command=greet_gm,width=4)
button1.grid(column=0, row=1, sticky=(N,E,W,S),padx=10,pady=5,ipadx=5, ipady=3)
button2 = ttk.Button(frame2, text="挨拶", command=greet_ga, width=4)
button2.grid(column=0, row=1, sticky=(N,E,W,S),padx=10,pady=5,ipadx=5, ipady=3)
button3 = ttk.Button(frame3, text="挨拶",command=greet_gn, width=4)
button3.grid(column=0, row=1,sticky=(N,E,W,S),padx=10,pady=5,ipadx=5, ipady=3)
#処理実行
root.mainloop()
やや見栄えも整えていますので、オプションは「あーそうなんだー」くらいで一旦見てください。
ポイントは・・・
#あいさつ機能
def greet_gm(*args):
greet.set("おはようございます")
def greet_ga(*args):
greet.set("こんにちわ")
def greet_gn(*args):
greet.set("こんばんわ")の
の部分です。
3つ関数を作成して、greetという変数に.setというメソッドでそれぞれ挨拶の文字列を渡す単純な機能で作成しました。
では、greetという変数をどう扱えばいいかというと・・・
#~抜粋
greet = StringVar()
greet.set("挨拶待ち中")
label4 = ttk.Label(frame4, textvariable=greet, foreground="blue", background="white",
font = ('MS ゴシック', 12))
label4.grid()
#ボタンを作成
button1 = ttk.Button(frame1, text="挨拶", command=greet_gm,width=4)
button1.grid(column=0, row=1, sticky=(N,E,W,S),padx=10,pady=5,ipadx=5, ipady=3)
button2 = ttk.Button(frame2, text="挨拶", command=greet_ga, width=4)
button2.grid(column=0, row=1, sticky=(N,E,W,S),padx=10,pady=5,ipadx=5, ipady=3)
button3 = ttk.Button(frame3, text="挨拶",command=greet_gn, width=4)
button3.grid(column=0, row=1,sticky=(N,E,W,S),padx=10,pady=5,ipadx=5, ipady=3)
#~抜粋
greetにStringVar()というオブジェクトを格納しています。
そして、まずは「挨拶待ち中」という文字列をセットし、それをラベル4のtextvariableオプションで、greet変数を渡しています。
これにより、起動時は以下のように表示されます。
そして、各ボタンにはcommandオプションで作成した機能を割り当てています。ボタンを押すと、greet変数に格納された、StringVarに各挨拶がセットされるので・・・
表示がそれぞれ
おはようございます
こんにちわ
こんばんわ
と表示されます。
6. 応用編
defから始まる関数部分は、いわゆる普通のPythonです。
なので、色々なことが可能です。
例えば、リスト形式で用意した名前に対し挨拶する機能を少し追加します。
#あいさつ機能
def random_name(*args):
names = ['一郎','二郎','三郎']
name = random.choice(names)
return name
def greet_gm(*args):
name = random_name()
greet.set("おはようございます"+name+"さん")
def greet_ga(*args):
name = random_name()
greet.set("こんにちわ"+name+"さん")
def greet_gn(*args):
name = random_name()
greet.set("こんばんわ"+name+"さん")
そうすると・・・表示は以下のようになります。
ランダムで選ばれた名前に挨拶がされます。
次回は、これをベースにちょっと違うGUIを作ってみましょう。