1
3

More than 1 year has passed since last update.

[Python][tkinter] 1次元配列のループでn行×m列のgrid表示をさせる方法

Last updated at Posted at 2023-02-10

作ったプログラムの備忘録

はじめに

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()

FC14C67D-9A18-4930-82B0-0DCDD57E8011.png

  • 上記は4×3のグリッド表示させる際のコードです。

  • for文でtk.Button()を生成して、grid配置を繰り返しています。

  • self.button = [] はボタンでは必須ではない表記ですが、EntryCheckboxではあとからインデックスで個別に入力内容を取得できるので、配列に格納する方がよいと考えています。

  • ここで行×列に配置する際の行番号は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)を返してしまうので、それを回避するにはfunctoolspartial関数を使う必要があります。

        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()

308B2DE7-76E9-46B8-B45F-DE5EA31F2B58.png

具体例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()

D642439B-4189-477A-B765-10ECC2C6CC9C.png

具体例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

カレンダー表示もできます

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3