作ったプログラムの備忘録
はじめに
tkinterでボタンなどを配置する際に似たようなコードを列挙するのが嫌で、ループで表示させる方法について検討した際のメモ
動作テスト環境
OS: Windows 10 Pro 64bit
言語: Python 3.9.13
ソースコード
4×3のグリッド表示例
import tkinter as tk
from tkinter import messagebox as mb
from functools import partial
class Application(tk.Frame):
def __init__(self, master = None):
super().__init__(master)
self.master.title("grid example app")
self.master.geometry("300x400")
self.master.grid_rowconfigure((0, 1, 2, 3), weight=1)
self.master.grid_columnconfigure((0, 1, 2), weight=1)
clr = ['#ffb6b9', '#bbded6', '#8ac6d1', '#ffeead', '#fae3d9', '#96ceb4',
'#d9534f', '#ffad60', '#a696c8', '#2470a0', '#b689b0', '#fe5f55']
self.button = []
for i in range(12):
tmp = tk.Button(self.master, text=f'{i+1}', bg=f'{clr[i]}',
command=partial(self.click_button, i+1))
tmp.grid(row=i//3, column=i%3, sticky=tk.NSEW)
self.button.append(tmp)
def click_button(self, num):
mb.showinfo(title='click', message=f'click: {num}')
if __name__ == "__main__":
root = tk.Tk()
app = Application(master = root)
app.mainloop()
-
上記は
4×3
のグリッド表示させる際のコードです。 -
for文で
tk.Button()
を生成して、grid配置を繰り返しています。 -
self.button = []
はボタンでは必須ではない表記ですが、Entry
やCheckbox
ではあとからインデックスで個別に入力内容を取得できるので、配列に格納する方がよいと考えています。 -
ここで行×列に配置する際の行番号は
i//列数
、列番号はi%列数
でそれぞれ取得できます。書いている内容は以下と同じですが、行番号の規則性が切り捨て除算
、列番号の規則性が剰余
になっているのがわかるかと思います。
for文を使わないで配置する際のコード
self.button[0].grid(row=0, column=0, sticky=tk.NSEW)
self.button[1].grid(row=0, column=1, sticky=tk.NSEW)
self.button[2].grid(row=0, column=2, sticky=tk.NSEW)
self.button[3].grid(row=1, column=0, sticky=tk.NSEW)
self.button[4].grid(row=1, column=1, sticky=tk.NSEW)
self.button[5].grid(row=1, column=2, sticky=tk.NSEW)
self.button[6].grid(row=2, column=0, sticky=tk.NSEW)
self.button[7].grid(row=2, column=1, sticky=tk.NSEW)
self.button[8].grid(row=2, column=2, sticky=tk.NSEW)
self.button[9].grid(row=3, column=0, sticky=tk.NSEW)
self.button[10].grid(row=3, column=1, sticky=tk.NSEW)
self.button[11].grid(row=3, column=2, sticky=tk.NSEW)
- またループで回してボタンを配置する際に
command
で引数を持たせたいときにcommand=lambda: click_button(i)
としても、どのボタンを押しても変数i
は回りきった最後の値(今回なら11)を返してしまうので、それを回避するにはfunctools
のpartial
関数を使う必要があります。
self.button = [tk.Button(self.master, text=f'{i+1}', bg=f'{clr[i]}',
command=partial(self.click_button, i+1)) for i in range(12)]
for i in range(12):
self.button[i].grid(row=i//3, column=i%3, sticky=tk.NSEW)
- ちなみにwidgetsの配列への入れ方は内包表記も可能です(1行が長くなりすぎるので可読性が低いですが)
self.button = [tk.Button(self.master, text=f'{i+1}', bg=f'{clr[i]}',
command=partial(self.click_button, i+1)) for i in range(12)]
self.button = [self.button[i].grid(row=i//3, column=i%3, sticky=tk.NSEW) for i in range(12)]
- さすがに
.grid()
を内包表記すると挙動の意味合いがよくわからなくなるので良くないと思いますが、一応表示はされます。
一部を変更する場合
- またgrid表示を一部結合させたい場合は、for文中にif文をかいてもいいと思いますし、後から上書きと
grid_forget()
やgrid_remove()
で修正してもいいと思います。
for文の後に変更する場合の例
self.button = []
for i in range(12):
tmp = tk.Button(self.master, text=f'{i+1}', bg=f'{clr[i]}',
command=partial(self.click_button, i+1))
tmp.grid(row=i//3, column=i%3, sticky=tk.NSEW)
self.button.append(tmp)
self.button[0].grid(row=0, column=0, columnspan=2, sticky=tk.NSEW)
self.button[1].grid_remove()
self.button[7].grid(row=2, column=1, columnspan=2, sticky=tk.NSEW)
self.button[8].grid_forget()
具体例1
いまいち何に使うんだ?という感じかもしれませんが、電卓っぽいGUIはすっきり書けます。
import tkinter as tk
from tkinter import messagebox as mb
from functools import partial
class Application(tk.Frame):
def __init__(self, master = None):
super().__init__(master)
self.master.title("grid example app")
self.master.geometry("300x400")
self.master.grid_rowconfigure((0, 1, 2, 3, 4), weight=1)
self.master.grid_columnconfigure((0, 1, 2, 3), weight=1)
btn = ['7', '8', '9', '÷', '4', '5', '6', '×', '1', '2', '3', '-', '0', '.', '=', '+']
self.entry = tk.Entry(self.master, text='')
self.entry.grid(row=0, column=0, columnspan=4, sticky=tk.NSEW)
self.button = []
for i in range(16):
tmp = tk.Button(self.master, text=f'{btn[i]}', command=partial(self.click_button, btn[i]))
tmp.grid(row=i//4+1, column=i%4, sticky=tk.NSEW)
self.button.append(tmp)
def click_button(self, num):
self.entry.insert(tk.END, f'{num}')
if __name__ == "__main__":
root = tk.Tk()
app = Application(master = root)
app.mainloop()
具体例2
あとはマインスイーパーぽいGUIもすっきり書けます。
import random
import tkinter as tk
from tkinter import messagebox as mb
from functools import partial
class Application(tk.Frame):
def __init__(self, master = None):
super().__init__(master)
self.master.title("grid example app")
self.master.geometry("400x400")
self.master.grid_rowconfigure(tuple(range(20)), weight=1)
self.master.grid_columnconfigure(tuple(range(20)), weight=1)
self.mine = [0 if random.random() < 0.85 else 1 for i in range(400)]
self.label = []
for i in range(400):
tmp = tk.Label(self.master, text=' ', relief=tk.RAISED)
tmp.grid(row=i//20, column=i%20, sticky=tk.NSEW)
self.label.append(tmp)
self.label[i].bind('<Button-1>', partial(self.click_left, i))
self.label[i].bind('<Button-3>', partial(self.click_right, i))
def click_left(self, num, button):
self.label[num].config(relief=tk.SUNKEN, text=f'{self.mine[num]}')
def click_right(self, num, button):
self.label[num].config(text='F')
if __name__ == "__main__":
root = tk.Tk()
app = Application(master = root)
app.mainloop()
具体例3
カレンダー表示もできます