Python(tkinterのウィジェット配置とアプリ紹介)

前回の投稿Python(tkinterで〇進数変換アプリ)でラベルやエントリー、ボタン、コンボボックスなどのウィジェットを扱いましたが、今回は、それらのウィジェット配置について詳しく扱いつつ、いくつかのGUIアプリを紹介したいと思います。

Frameとpackによる配置

import tkinter as tk

root = tk.Tk()
tk.Button(root,text="0").pack(fill=tk.X) # オプションfillを指定 tk.Xで枠x幅
# フレーム作成
frame1 = tk.Frame(root,bg="blue")
tk.Button(frame1,text="1").pack(side="left") #オプションside
tk.Button(frame1,text="2").pack(side="left")
frame1.pack(side="left",fill=tk.Y) # tk.Yで枠y幅

frame2 = tk.LabelFrame(root,text="xxxx") # ラベル付きフレーム
tk.Button(frame2,text="3").pack(side="right")
tk.Button(frame2,text="4").pack(side="right")
frame2.config(bg="green",fg="lightgreen") # ラベルのオプションbg,fg
frame2.pack(side="right",fill=tk.Y)

tk.Button(root,text="5").pack(side="bottom")
tk.Button(root,text="6").pack(side="bottom")
w = tk.Button(root,text="7")
w.pack()
# frame1.master
# frame1.master is root
# frame1.master is frame1
# frame1.children

# Buttonのオプションをテストする関数
def test(**kwds):
    root = tk.Tk()
    root.config(bg="white")
    tk.Button(text="Button",bg="yellow").pack(**kwds)
    root.mainloop()

#test()
#test(fill=tk.X)
#test(fill=tk.Y,side="left")
#test(fill=tk.BOTH,expand=1) # オプションexpand 1で引き伸ばし可(default=0)
#test(fill=tk.BOTH,expand=1,padx=50) # オプションpadx
#test(fill=tk.BOTH,expand=1,pady=50) # オプションpady
#test(fill=tk.BOTH,expand=1,padx=50,pady=50)
#test(ipadx=100,ipady=50) # オプションipadx,ipady
#test(anchor=tk.W) # オプションanchor(CENTER+WENS8方位)

tk.Frame(root)
ウィジェットを作成する場合、親のウィジェットを指定して、ラベルやエントリーなどのクラスオブジェクトを生成します。
プログラムではrootを親ウィジェットとして作っていますが、Frameを作成し、そのウィジェットを親とすることでウィジェット内にグループ化して扱うことができるようになります。
tk.LabelFrame(root,text="xxxx")
ラベルをつけたFrameを作成することができます。
.pack(side="left")
ウィジェットを配置することができます。いくつかオプションがあり、オプションを指定することで配置の仕方を帰ることができます。

ウィジェットの配置について以下のサイトがオプション説明を含めて分かりやすかったです。
2. Widget を配置しよう

掲載した紹介コードでコメントアウトしている部分は、IDLEで実行してもらうと挙動の確認ができます。
test関数については、引数を名前付きで指定することで、packについてのオプションを確認ができるようになっています。

.masterや.childrenでそのウィジェットが属している親や子の情報を取得できるので、表示切替をまとめて行う場合などに活用できます。

gridによる配置

import tkinter as tk

root = tk.Tk()
# gridで配置
tk.Button(root,text="B-00",bg="#aa1a00").grid(row=0,column=0)
tk.Button(root,text="B-01",bg="#9a2a00").grid(row=0,column=1)
tk.Button(root,text="B-02",bg="#8a3a00").grid(row=0,column=2)
tk.Button(root,text="B-03",bg="#7a4a00").grid(row=1,column=0,columnspan=3,sticky=tk.W + tk.E)
tk.Button(root,text="B-04",bg="#6a5a00").grid(row=0,column=3,rowspan=2,sticky=tk.N + tk.S)
tk.Button(root,text="B-05",bg="#5a6a00").grid(row=2,column=0,columnspan=2,sticky=tk.W + tk.E)
tk.Button(root,text="B-06",bg="#4a7a00").grid(row=2,column=2,columnspan=2,sticky=tk.W + tk.E)
tk.Button(root,text="B-07",bg="#3a8a00").grid(row=1,column=4,rowspan=2,sticky=tk.N+tk.S)

gridには、packのようにside,fill,expand,anchorオプションがないかわりにrow,columnで座標を指定し、stickyオプションで位置や引き伸ばしの指定を行います。
しかし、このままではウィンドウを引き伸ばした場合の配置可変に対応していないので、ウィジェットの.columnconfigure.rowconfigureweightを指定する必要があります。

from tkinter import *
from tkinter import ttk

root = Tk()

content = ttk.Frame(root, padding=(3,3,12,12))
frame = ttk.Frame(content, borderwidth=5, relief="sunken", width=200, height=100)
namelbl = ttk.Label(content, text="Name")
name = ttk.Entry(content)

onevar = BooleanVar()
twovar = BooleanVar()
threevar = BooleanVar()

onevar.set(True)
twovar.set(False)
threevar.set(True)

one = ttk.Checkbutton(content, text="One", variable=onevar, onvalue=True)
two = ttk.Checkbutton(content, text="Two", variable=twovar, onvalue=True)
three = ttk.Checkbutton(content, text="Three", variable=threevar, onvalue=True)
ok = ttk.Button(content, text="Okay")
cancel = ttk.Button(content, text="Cancel")

content.grid(column=0, row=0, sticky=(N, S, E, W))
frame.grid(column=0, row=0, columnspan=3, rowspan=2, sticky=(N, S, E, W))
namelbl.grid(column=3, row=0, columnspan=2, sticky=(N, W), padx=5)
name.grid(column=3, row=1, columnspan=2, sticky=(N, E, W), pady=5, padx=5)
one.grid(column=0, row=3)
two.grid(column=1, row=3)
three.grid(column=2, row=3)
ok.grid(column=3, row=3)
cancel.grid(column=4, row=3)

root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
content.columnconfigure(0, weight=3)
content.columnconfigure(1, weight=3)
content.columnconfigure(2, weight=3)
content.columnconfigure(3, weight=1)
content.columnconfigure(4, weight=1)
content.rowconfigure(1, weight=1)

root.mainloop()

詳しくはこちら
The Grid Geometry Manager

placeによる配置

import tkinter as Tk

class Frame(Tk.Frame):
    """ Frame with three label """
    def __init__(self, master=None):
        Tk.Frame.__init__(self, master, height=100, width=200)
        self.master.title('Pack Three Labels')

        # First Label
        la = Tk.Label(self, text='Hello everybody. How are you?',  bg='yellow', relief=Tk.RIDGE, bd=2)
        la.place(relx=0.02, rely=0.1, relheight=0.3, relwidth=0.95)
        # Second Label
        lb = Tk.Label(self, text='Oh My God!', bg='red', relief=Tk.RIDGE, bd=2)
        lb.place(relx=0.15, rely=0.45)
        # Third Label
        lc = Tk.Label(self, text='See you tomorrow.', bg='LightSkyBlue', relief=Tk.RIDGE, bd=2)
        lc.place(relx=0.5, rely=0.75)

if __name__ == '__main__':
    f = Frame()
    f.pack()
    f.mainloop()

GUIアプリの紹介

簡易時計アプリ

import time as t
import tkinter as tk

class ClockApp(tk.Frame):
    def __init__(self,master=None):
        super().__init__(master)
        self.master.title('Clock')
        #self.master.geometry("180x30")
        self.clock = tk.StringVar()
        self.counting()
        self.label = tk.Label(self, textvariable=self.clock, relief=tk.SUNKEN,
                              font=("MS Mincho",80),bg='white')
        self.label.pack()

    def clock_set(self):
        self.clock.set(t.strftime("%T"))

    def counting(self):
        self.clock_set()
        self.after(250, self.counting)

if __name__ == '__main__':
    app = ClockApp()
    app.pack()
    app.mainloop()




strftimeの表記については、以前datetimeモジュールを扱った
Python(会員制Webサイト)
をチェックしてみてください。
今回は%Tで、時刻をまとめてstr型で得ることができる表記を利用しています。

font=("MS Mincho",80)
fontオプションを指定することでフォントの種類とサイズを変更することができます。
MS明朝やMSゴシックは日本語を含むため、そのままでは認識されず、デフォルトのフォントになりますが、"MS Mincho","MS Gothic"と指定することで認識されます。
また、フォントの種類は""空文字列を指定することもでき、この場合はデフォルトのフォントが使用されます。

他の実装例 GUI: Labelとタイマーによる簡単なクロック

タイマーアプリその1

import tkinter as tk
from tkinter import font

class TimerApp(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master)
        self.master.title('Timer')
        self.master.geometry('510x170')
        self.count = None
        self.create_widgets()

    def create_widgets(self):
        tk.Label(self, text="カウントダウンタイマー",bg="lightgreen").pack(fill=tk.X)
        tk.Label(self,text="時間(秒)").pack()
        self.entry = tk.Entry(self)
        self.entry.insert(tk.END,"60")
        self.entry.pack() 
        self.time = tk.StringVar()
        self.time_init()        
        self.label = tk.Label(self, textvariable=self.time, relief=tk.SUNKEN,
                              font=("",20),bg='white')
        self.label.pack(fill=tk.X,pady=10)
        f_button = tk.Frame(self)
        f_button.pack()
        font1 = font.Font(family='Helvetica', size=15, weight='bold')
        self.b_start = tk.Button(f_button, text='Start', font=font1, command=self.start)
        self.b_stop = tk.Button(f_button, text='Stop', font=font1, command=self.stop, state=tk.DISABLED)
        self.b_reset = tk.Button(f_button, text='Reset', font=font1, command=self.reset, state=tk.DISABLED)
        self.b_start.pack(side=tk.LEFT,ipadx=30, padx=10)
        self.b_stop.pack(side=tk.LEFT,ipadx=30, padx=10)
        self.b_reset.pack(side=tk.LEFT,ipadx=30, padx=10)

    def time_init(self):
        self.time.set("セットする時間を秒単位で入力してください。")

    def time_err(self):
        self.time.set("不正な入力値です。")        

    def time_set(self):
        self.h = self.count // 3600
        self.m = (self.count % 3600) // 60
        self.s = (self.count % 3600) % 60        
        self.time.set("のこり %02d時間 %02d分 %02d秒" % (self.h,self.m,self.s))

    def start(self):
        try:
            if not self.count:
                self.count = int(self.entry.get())                                   
        except:
            self.time_err()
            return

        self.started = True
        self.time_set()
        if(0 < self.count <= 10):
            self.label.config(bg="red")
        self.after(1000, self.counting)
        self.entry.config(state=tk.DISABLED)
        self.b_start.config(state=tk.DISABLED)
        self.b_stop.config(state=tk.NORMAL)
        self.b_reset.config(state=tk.DISABLED) 

    def stop(self):
        self.started = False
        self.b_start.config(state=tk.NORMAL)
        self.b_stop.config(state=tk.DISABLED)
        self.b_reset.config(state=tk.NORMAL)

    def reset(self):
        self.count = None
        self.time_init()
        self.label.config(bg="white")
        self.b_reset.config(state=tk.DISABLED)
        self.entry.configure(state=tk.NORMAL)

    def counting(self):
        if self.started:
            self.count -=1
            self.time_set()

            if (0 < self.count <= 10):
                self.label.config(bg="red")

            if self.count <= 0:
                self.stop()
                self.reset()

            self.after(1000, self.counting)

if __name__ == '__main__':
    app = TimerApp()
    app.pack()
    app.mainloop()





実行部分について、詳しくはタイマーアプリその2で説明します。

font.Font(family='Helvetica', size=15, weight='bold')
tkinterでは、フォントオプションの設定で、フォントの種類、サイズ、太字、斜体、下線、取消線の設定ができます。
font=(family[,size,weight,slant,under,overstrike])のようにタプルで設定することもできますが、同じ設定で使いまわす場合は、フォントオブジェクトを作成するとスッキリ書くことが可能です。

フォントオブジェクトについて詳しくは以下のサイトを確認してください。
python tkinter フォント(font)の設定方法

StringVar()after(250, self.counting)といったtkinterのクラスやメソッドについては、以下のサイトに分かりやすいチュートリアルがあります。

紫藤のページ On Python

こちらで扱っているアプリを少しアレンジしたverコードを紹介します。
(主にPython2系から3系への変換)

タイマーアプリその2

import tkinter as Tk
import os

BLUE = '#99CCFF'
YELLOW = '#FFCC00'
RED = '#FF00FF'
CLOCK = os.path.dirname(__file__)+"/meza-bl-2.gif"

class Frame(Tk.Frame):
    def __init__(self, master=None):
        Tk.Frame.__init__(self, master)

        self.echo = Tk.StringVar()
        self.m = 3
        self.echo_set()

        self.master.title('Alarm')
        f_display = Tk.Frame(self, relief=Tk.RIDGE, bd=4)
        f_display.pack(fill=Tk.X, expand=1)

        self.image= Tk.PhotoImage(file=CLOCK)
        self.icon=Tk.Label(f_display, image=self.image, bg=BLUE)
        self.icon.grid(row=0,column=0, rowspan=2)
        display = Tk.Label(f_display, textvariable=self.echo, width=5, relief=Tk.SUNKEN, bd=2, anchor = Tk.E, 
                             font=('Helvetica', '24'), bg='white')
        display.grid(row=0, column=1, rowspan=2, sticky=Tk.N+Tk.S)
        self.b_inc = Tk.Button(f_display, font=('Helvetica', '6'), text='+', command=self.inc_time)
        self.b_inc.grid(row=0, column=2, sticky=Tk.W + Tk.E + Tk.S, pady=1)
        self.b_dec = Tk.Button(f_display, font=('Helvetica', '6'), text='-', command=self.dec_time)
        self.b_dec.grid(row=1, column=2, sticky=Tk.W + Tk.E + Tk.N, pady=1)

        f_button = Tk.Frame(self)
        f_button.pack(pady=2)
        self.b_start = Tk.Button(f_button, text='Start', command=self.start)
        self.b_stop = Tk.Button(f_button, text='Stop', command=self.stop, state=Tk.DISABLED)
        self.b_reset = Tk.Button(f_button, text='Reset', command=self.reset, state=Tk.DISABLED)
        self.b_start.pack(side=Tk.LEFT, padx=1)
        self.b_stop.pack(side=Tk.LEFT, padx=1)
        self.b_reset.pack(side=Tk.LEFT, padx=1)

    def echo_set(self):
        self.timer = 60 * self.m
        self.echo.set('%02d:00' % (self.m))

    def inc_time(self):
        self.m += 1
        self.echo_set()

    def dec_time(self):
        self.m -= 1
        self.echo_set()

    def start(self):
        self.started = True
        if 0<self.timer<=20:
            self.icon.configure(bg=YELLOW)
        elif(0 >= self.timer):
            self.icon.configure(bg=RED)
        self.after(1000, self.counting)
        self.b_start.configure(state=Tk.DISABLED)
        self.b_stop.configure(state=Tk.NORMAL)
        self.b_reset.configure(state=Tk.DISABLED)
        self.b_inc.configure(state=Tk.DISABLED)
        self.b_dec.configure(state=Tk.DISABLED)

    def stop(self):
        self.icon.configure(bg=BLUE)
        self.started = False
        self.b_start.configure(state=Tk.NORMAL)
        self.b_stop.configure(state=Tk.DISABLED)
        self.b_reset.configure(state=Tk.NORMAL)

    def reset(self):
        self.echo_set()
        self.b_reset.configure(state=Tk.DISABLED)
        self.b_inc.configure(state=Tk.NORMAL)
        self.b_dec.configure(state=Tk.NORMAL)

    def counting(self):
            if self.started:
                self.timer -=1
                self.echo.set('%02d:%02d' % (self.timer/60, self.timer%60))
                if self.timer == 20:
                    self.icon.configure(bg=YELLOW)

                if self.timer <= 0:
                    self.bell()
                    t= -1 * self.timer
                    self.icon.configure(bg=RED)
                    self.echo.set('-%02d:%02d' % (t/60, t%60))
                    self.after(500, self.yellow)

                self.after(1000, self.counting)

    def yellow(self):
        if self.started:
            self.icon.configure(bg=YELLOW)

if __name__ == '__main__':
    f = Frame()
    f.pack()
    f.mainloop()

CLOCK = os.path.dirname(__file__)+"/meza-bl-2.gif"
画像ファイルの指定でosモジュールを利用してファイルが保存されているディレクトリのパス名を得て、保存してある画像ファイルの絶対パスを指定しています。
"./"のカレントディレクトリからの相対パスでファイル指定した場合の画像読み込みができませんでした。
プログラムと同じディレクトリに画像ファイルを保存している場合は、画像ファイル名だけで設定可能です。
self.image= Tk.PhotoImage(file=CLOCK)
Tk.PhotoImage 関数で、イメージを読み取り、それを、 self.image に代入します。 イメージは、必ずインスタンス変数に代入する必要があります。そうしないと、関数を抜けたとたん変数のメモリーが 回収され、イメージが消えてしまうからです。
Tk.Label(f_display, image=self.image, bg=BLUE)でラベルのウィジェットに、imageオプションで、先ほどのイメージを渡して表示させています。
w.after(n, func)
動作をするメソッドを 再帰的に呼び出します。このメソッドは2つの引数をとり、2番目の引数は、 動作を定義したコールバック関数(func)、最初の引数はミリ秒 (n) で、n ミリ秒後に func を呼び出します。
Tk.Button(f_button, text='Stop', command=self.stop, state=Tk.DISABLED)
Buttonのstateオプションでボタンの状態を指定することができます。デフォルトは NORMAL. ボタンが利かない状態にするには DISABLED を指定します。

花の画像と背景色を選んで表示するプログラム

import tkinter as Tk
import os

FLOWERPATH = os.path.dirname(__file__)+"/flower/"
FLOWERS = ["suisencut4.gif", "tanpopotouka1.gif", "odamakitouka.gif", "rengetouka2.gif",              \
           "ajisaicatmurasaki-a.gif", "cosmos-s.gif", "fujimurasakitouka.gif", "minaesi-5.gif",       \
           "hotarubukuro3ko.gif", "burudejinarabi.gif", "aoaji3touka.gif", "sakurasou.gif",           \
           "rengetakusann.gif", "nanohana3.gif", "suzurantouka2.gif", "liradai.gif",                  \
           "cosmostouka-b.gif", "syunrantouka2.gif", "sakuraeda9.gif", "syoubu-s.gif",                \
           "suirenpink-a.gif", "yuri2.gif", "aoasagaoline.gif", "asagaoyoujiro2.gif",                 \
           "himawari-l.gif", "yagurumagikutouka1.gif", "susukistouka.gif"]

BGS = [('aliceblue', '#F0F8FF'), ('azure', '#F0FFFF'), ('beige', '#F5F5DC'),              \
       ('cornsilk', '#FFF8DC'), ('khaki', '#F0E68C'), ('lightgreen', '#90EE90'),          \
       ('lightpink', '#FFB6C1'), ('lightskyblue', '#87CEFA'), ('palegreen', '#98FB98')]

class Frame(Tk.Frame):    
    def __init__(self, master=None):
        Tk.Frame.__init__(self, master)
        self.master.title('flower image and background')
        intro = Tk.Label(self, font=('Helvetica', '12'),  justify=Tk.LEFT, wraplength='15c', 
                         text = u"リストボックスから画像ファイル(マウス左ダブルクリック)を\n"
                                u"ボタンから背景色を選択してください。\n" 
                                u"右側のラベルに画像が表示されます。\n")
        intro.pack()
        f = Tk.Frame(self, bd=3, relief=Tk.RIDGE)
        f.pack(fill=Tk.BOTH, expand=1)

        f_lb = Tk.Frame(f)
        f_lb.pack(side=Tk.LEFT, padx=5, pady=5, fill=Tk.BOTH, expand=1)

        self.listbox = Tk.Listbox(f_lb)
        self.listbox.pack(side=Tk.LEFT, fill=Tk.Y, expand=1)
        self.listbox.bind("<Double-Button-1>", self.change_flower)
        self.listbox.insert(Tk.END, *FLOWERS)

        self.scrollbar = Tk.Scrollbar(f_lb, orient=Tk.VERTICAL,command=self.listbox.yview)
        self.listbox['yscrollcommand'] = self.scrollbar.set
        self.scrollbar.pack(side=Tk.RIGHT, fill=Tk.Y, expand=1)

        f_button = Tk.Frame(f)
        f_button.pack(side=Tk.LEFT, padx=5, pady=5)
        self.flower = Tk.PhotoImage(file=FLOWERPATH+FLOWERS[0])
        self.label = Tk.Label(f, image=self.flower, relief=Tk.RAISED, bd=3)
        self.label.pack(side=Tk.RIGHT, padx =5, fill=Tk.BOTH, expand=1)

        for name, code in BGS:
            b = Tk.Button(f_button, text=name, bg=code, command=self.bgchange(code))
            b.pack(fill=Tk.X)

    def bgchange(self,color):
        def f():
            self.label.configure(bg=color)
        return f

    def change_flower(self, event):
        self.flower = Tk.PhotoImage(file=FLOWERPATH+self.listbox.get(Tk.ACTIVE))
        self.label.configure(image=self.flower)

if __name__ == '__main__':
    f = Frame()
    f.pack()
    f.mainloop()

Tk.Listbox(master, **option)
画像データ一覧表示のためにリストボックスのウィジェットを使っています。
.insert(Tk.END, *FLOWERS)メソッドで、リストの末尾に可変引数としてデータ名リストを渡すことで一覧表示のリストができます。
self.listbox.bind("", self.change_flower)メソッドで、マウスボタンをダブルクリックしたときに、 アプリケーションが反応するようにしています。
Tk.Scrollbar(f_lb, orient=Tk.VERTICAL,command=self.listbox.yview)
リストボックスと同じFrame を親として作成し、スクロールバーを動かしたらリストボックスも動くように、commandオプションにlistbox.yview設定しています。
リストボックスを動かすとスクロールバーが動くようにself.listbox['yscrollcommand'] = self.scrollbar.setを追加しています。
b = Tk.Button(f_button, text=name, bg=code, command=self.bgchange(code))
色の名前とカラーコードをタプルで作成したリストBGSの要素をループ処理で1つずつ取り出しながら複数のボタンを作成しています。
commandオプションではコールバック関数として関数オブジェクトを返すbgchange(code)メソッドを定義することでボタンを押したときの処理をスマートに記述しています。

複数の花の画像を選んで表示するプログラム

import tkinter as Tk
from PIL import Image as I
from PIL import ImageTk as Itk
import math
import os

FLOWERPATH = os.path.dirname(__file__)+"/flower/"
FLOWERS = ["suisencut4.gif", "tanpopotouka1.gif", "odamakitouka.gif", "rengetouka2.gif",              \
           "ajisaicatmurasaki-a.gif", "cosmos-s.gif", "fujimurasakitouka.gif", "minaesi-5.gif",       \
           "hotarubukuro3ko.gif", "burudejinarabi.gif", "aoaji3touka.gif", "sakurasou.gif",           \
           "rengetakusann.gif", "nanohana3.gif", "suzurantouka2.gif", "liradai.gif",                  \
           "cosmostouka-b.gif", "syunrantouka2.gif", "sakuraeda9.gif", "syoubu-s.gif",                \
           "suirenpink-a.gif", "yuri2.gif", "aoasagaoline.gif", "asagaoyoujiro2.gif",                 \
           "himawari-l.gif", "yagurumagikutouka1.gif", "susukistouka.gif"]

SIZE = 100

def get_size(tup):
    """ It returns the size of images on the summary"""
    x, y = tup
    if (x<=100 and y<=100):
        return (x, y)
    elif x > y:
        r = float(SIZE) / float(x)
        return (100, int(y*r))
    else:
        r = float(SIZE) / float(y)
        return (int(x*r), 100)

class Frame(Tk.Frame):    
    def __init__(self, master=None):
        Tk.Frame.__init__(self, master)
        self.master.title('show flower images')
        intro = Tk.Label(self, font=('Helvetica', '12'),  justify=Tk.LEFT, wraplength='15c', width=50, 
                         text = u"リストボックスから画像ファイルを選択してください。\n"
                                u"左ボタンのドラッグ、Shift + 左ボタン、Ctrl + 左ボタン などで複数の画像の選択が可能です。\n"
                                u"選択が終わったら、マウス右ボタンをクリックしてください。\n" 
                                u"選択された画像が右側に表示されます。\n")
        intro.pack()
        self.f = Tk.Frame(self, bd=3, relief=Tk.RIDGE)
        self.f.pack(fill=Tk.BOTH, expand=1)

        f_lb = Tk.Frame(self.f)
        f_lb.pack(side=Tk.LEFT, padx=5, pady=5, fill=Tk.BOTH, expand=1)

        self.listbox = Tk.Listbox(f_lb,selectmode=Tk.EXTENDED)
        self.listbox.pack(side=Tk.LEFT, fill=Tk.Y, expand=1)
        self.listbox.bind("<3>", self.show_flowers)
        self.listbox.insert(Tk.END, *FLOWERS)

        self.scrollbar = Tk.Scrollbar(f_lb, orient=Tk.VERTICAL,command=self.listbox.yview)
        self.listbox['yscrollcommand'] = self.scrollbar.set
        self.scrollbar.pack(side=Tk.RIGHT, fill=Tk.Y, expand=1)

        self.renew()

    def show_flowers(self, event):
        if self.images:
            self.ff.destroy()
            self.renew()

        selected = [ int(x) for x in self.listbox.curselection()]
        span = int(math.ceil(math.sqrt(len(selected))))
        if span == 0:
            return None

        for i, j in  enumerate(selected):
            img = I.open(FLOWERPATH+FLOWERS[j])
            img = img.resize(get_size(img.size))
            tkimg = Itk.PhotoImage(img)
            la = Tk.Label(self.ff, image=tkimg)
            la.grid(row=int(i/span), column=int(i%span), sticky=Tk.SW)
            self.images.append(tkimg)
        self.listbox.selection_clear(min(selected), max(selected))

    def renew(self):
        self.images = []
        self.ff = Tk.Frame(self.f, border=3, relief=Tk.RAISED)
        self.ff.pack(side=Tk.RIGHT, fill=Tk.BOTH, expand=1, padx =5)

if __name__ == '__main__':
    f = Frame()
    f.pack()
    f.mainloop()

Tk.Listbox(f_lb,selectmode=Tk.EXTENDED)
selectmode=Tk.EXTENDED のオプションを用いて複数のアイテムが選択できるようにしています。
selected = [ int(x) for x in self.listbox.curselection()]
self.listbox の .curselection() メソッドを使って、選択されているアイテムを取り出しています。
このメソッドが返す値は文字列の選択された行番号のタプルなので、 int() を使って整数に変換しています。
span = int(math.ceil(math.sqrt(len(selected))))
一覧表示の列数を計算するために、全体数の平方根を求め、切り上げて整数を得ています。
また、span == 0のチェックを行うことで、何も選択されてないときに右クリックした場合のエラーを回避しています。
img = I.open(FLOWERPATH+FLOWERS[j])
enumerate 関数を適用して、選択されているファイル名を番号付きで1つずつ取り出し、Image モジュールの open 関数で画像イメージを取得しています。
resizeメソッドでサイズ調整し、ImageTk モジュールの PhotoImage 関数を用いて img を TkImage の tkimg に変換して、Labelウィジェットを作成しています。
.selection_clear ( first, last=None )
first から last までの行を選択解除します。
min(selected), max(selected)を引数に渡すことで、選択されている箇所を選択解除できます。

今回はここまで、次回は、まだ扱っていないtkinterのウィジェットを確認していきます。

Python(tkinterのウィジェット配置とアプリ紹介)” に対して1件のコメントがあります。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください