エンジニア ろっきーの探究ノート

"不"を"したい"にひっくり返すモノ/コトづくり(H/W, S/W, IoTシステム, アイデア発想)技術を探究します。

python「TKinter」のウィジェットを指定して画面を操る一例

日経BPさんの「みんなのラズパイコンテスト2020」でアイデア賞をいただきました! その作品で活用したpythonTKinter」について解説します。

やりたいこと

作品ではTKinterウィジェットをラズパイに取り付けたセンサーで操作しましたが、このブログではTKinterに関わる以下の機能だけに絞って書きます。

  1. 好きな画像をウィンドウに配置する
  2. 配置した画像を隠すようにマス目上に正方形を敷き詰める
  3. 消すボタンを押すごとにその正方形がランダムで消えて画像が表れていく
  4. 戻すボタンを押すと正方形が敷き詰められて画像が隠れる
  5. 終わるボタンで終了する

こんな感じ。

f:id:rockhack_design:20211103151009p:plain
プログラム実行時の様子

実行環境

下記に載せるソースコードwindowsでpython3.xをインストールし動作確認したものです。ラズパイで動作させる場合は、(アップデートさせていない限り)python2.xに対応させるべく

import tkinter
import tkinter as tk

の部分を

import Tkinter
import Tkinter as tk

とすればOKです("t"を大文字にする)。

また、ソースコードと同じディレクトリに「picture.gif」ファイルを置けば実行可能です。

ソースコード

import sys,random
import tkinter
import tkinter as tk
from tkinter import messagebox

root = tkinter.Tk()
# 画面タイトル
root.title(u"TKinterで配置したウィジェットを操作する")
# 画面サイズの設定
root.geometry("720x720")
# 使用する背景画像の読み込み
picture = tk.PhotoImage(file="picture.gif")
#キャンバスエリア
canvas = tkinter.Canvas(root, width = 720, height = 540)
canvas.create_image(0, 0, image=picture, tags="picture", anchor=tk.NW)
#キャンバスバインド
canvas.place(x=0, y=0)

yoko = 8
tate = 6
size = 90
list = [[0 for i in range(yoko)] for j in range(tate)]

#「戻す」ボタンが押されたら
def retry(event):
    create_panel()

#「消す」ボタンが押されたら
def delete(event):
    delete_panel()

#「終わる」ボタンが押されたら
def exit(event):
    sys.exit()

#パネル(正方形)をランダムで1つ消す
def delete_panel():
    rand_yoko = random.randint(0,yoko-1)
    rand_tate = random.randint(0,tate-1)
    total = 1

    #すべてのパネルが消えているかチェック
    for i in range(yoko):
        for j in range(tate):
            total = total * list[j][i]

    #変数"total"が0ではなかったときすべてのパネルが消えている
    if total != 0:
        ret = messagebox.showinfo('ミッションコンプリート!', '「戻す」で再開するか「終わる」で終了ください')
    #変数"total"が0のとき1つ以上のパネルが残っている  
    else:
        while True:   #残っているパネルの位置を探す
            if list[rand_tate][rand_yoko] > 1:
                rand_yoko = random.randint(0,yoko-1)
                rand_tate = random.randint(0,tate-1)
            if list[rand_tate][rand_yoko] == 0:
                break
        
        canvas.delete("oval")
        canvas.delete("hide" + str(rand_yoko) + str(rand_tate))
        list[rand_tate][rand_yoko] = 2

#パネル(正方形)を敷き詰めて初期状態に戻す
def create_panel():
    for i in range(yoko):
        for j in range(tate):
            #パネルを生成する(サイズ、位置、色、tagを指定して配置)
            canvas.create_rectangle(size*i, size*j, size*i+size, size*j+size, outline="white", fill="blue", tags="hide" + str(i) + str(j))
    for i in range(yoko):
        for j in range(tate):
            list[j][i] = 0


# 「消す」ボタンを配置
button_draw = tkinter.Button(root, text=u'消す',width=15,height=2)
button_draw.bind("<Button-1>",delete)
button_draw.place(x=10,y=570)

# 「戻す」ボタンを配置
button_draw = tkinter.Button(root, text=u'戻す',width=15,height=2)
button_draw.bind("<Button-1>",retry)
button_draw.place(x=160,y=570)

# 「終わる」ボタンを配置
button_draw = tkinter.Button(root, text=u'終わる',width=15,height=2)
button_draw.bind("<Button-1>",exit)
button_draw.place(x=310,y=570)

create_panel()
root.mainloop()

解説

敷き詰めるパネルは2次元配列"list"と対応させ、例えば

  • 左から3番目、上から1番目のパネルが消えたらlist[2][0]は"0"
  • 左から7番目、上から2番目のパネルが残っていればlist[6][1]が"2"

としてパネルを管理できるようにします。pythonウィジェットはtagsという引数で紐づけられるようになっています。

f:id:rockhack_design:20211103151040p:plain
パネルの2次元配列との対応とtagでの紐づけ

以下のコードで縦と横に対応したタグを付加しながらパネルを生成しています。

canvas.create_rectangle(size*i, size*j, size*i+size, size*j+size, outline="white", fill="blue", tags="hide" + str(i) + str(j))

以下のコードで縦と横に対応したタグを指定してパネルを削除しています。

canvas.delete("hide" + str(rand_yoko) + str(rand_tate))

上記を応用して例えばラズパイに接続したセンサーでウィジェットを生成したり削除したり操作するには、マルチスレッドを起動してそこでセンサー情報を監視、条件に一致したら操作する関数を呼び出します。こうして画面UIとリアルを(ネット接続を介さず)連動させたものを作れました。

おしまい。