Python(tkinterの各種ウィジェットとクラス化)
Python(tkinterで〇進数変換アプリ)で紹介した2進数→10進数変換ツールのコードは、クラス化せずに、tkinterのrootを作成後、そのままラベルやエントリーなどのウィジェットを配置して作成していました。
今回はアプリケーション単位でフレームを作成するようにクラス化した方法で記述してみました。
import tkinter as tk
class App(tk.Frame):
def __init__(self,master=None):
super().__init__(master)
self.master.title("2進数→10進数変換")
self.master.geometry("250x100")
self.create_widgets()
def create_widgets(self):
tk.Label(self, text="2進数を入力してください").pack()
self.entry = tk.Entry(self)
self.entry.pack()
btn=tk.Button(self,text="10進数に変換",command=self.convert)
btn.pack()
self.result_label = tk.Label(self,text="2進数を入力してボタンを押してください")
self.result_label.pack()
def convert(self):
src=self.entry.get()
try:
self.result_label.config(text=f"変換結果は {eval('0b'+src)} です。")
except:
self.result_label.config(text="不正な形式です。")
if __name__ == '__main__':
app = App()
app.pack()
app.mainloop()
実行結果は以前と変わりませんが、クラス化して1つのアプリケーションとしてまとめることで、コードも見やすくなり、機能別にクラス化することで複数の機能を有したアプリケーションが作りやすくなります。
沢山のウィジェットを有した画像表示アプリ
import re
import os
import os.path as P
import tkinter as Tk
from PIL import Image as I
from PIL import ImageTk as Itk
import tkinter.filedialog as D
import tkinter.messagebox as M
SIZE = 100
IMAGR_TYPES = ['gif', 'png', 'bmp', 'jpg', 'tif', 'ppm']
MAM_COLN = 6
GEO_MAIN = '+20+20'
GEO_SATE = '+750+50'
GEO_SCAL = '+750+400'
BGS = [('aliceblue', '#F0F8FF'), ('azure', '#F0FFFF'), ('beige', '#F5F5DC'), \
('cornsilk', '#FFF8DC'), ('khaki', '#F0E68C'), ('lightgreen', '#90EE90'), \
('lightpink', '#FFB6C1'), ('lightskyblue', '#87CEFA'), ('palegreen', '#98FB98')]
## functions ------------------------------------------------
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)
def make_regexp(types):
""" It returns the regular expression of the image file type"""
str = "\.("
for k, v in types.items():
if v.get():
str += k + '|'
str = str[0:-1] + ')$'
return re.compile(str, re.I)
## classes ------------------------------------------------------------------
class ImageLabel(Tk.Frame):
""" A Label class to show an image """
# 一覧表示する画像のクラス.クリックされると原寸大のイメージを別窓で表示.
id_original_size = None # Label showing an original size image
bg_var = None # variable for background color
def __init__(self, master, image_file, img):
Tk.Frame.__init__(self, master)
self.image = img # 一覧で表示する Tk.Image
self.image_file = image_file # もとのイメージファイル
img_label=Tk.Label(self, image=self.image)
img_label.pack()
txt_label=Tk.Label(self, text=P.basename(self.image_file), font=('Helvetica', '8'))
txt_label.pack()
img_label.bind('<Double-Button-1>', self.show)
def show(self, event):
label = ImageLabel.id_original_size
if (label and label.winfo_exists()):
top = label.winfo_toplevel()
top.destroy()
top = Tk.Toplevel(self)
top.title(P.basename(self.image_file))
top.geometry(GEO_SATE)
img = I.open(P.abspath(self.image_file))
self.timg = Itk.PhotoImage(img)
label=Tk.Label(top, image=self.timg, bg=ImageLabel.bg_var.get())
label.pack()
ImageLabel.id_original_size = label
class ImageFrame:
""" A Class to show summary of images """
def __init__(self, master, **key):
self.master=master
self.key = key
self.frame=Tk.Frame(master, **key)
self.frame.pack(padx=5, pady=5)
self.once = False
def update(self, dir, types):
if self.once:
self.frame.destroy()
self.frame=Tk.Frame(self.master, **self.key)
self.frame.pack(padx=5, pady=5)
self.once = True
pat = make_regexp(types)
files = [P.join(dir, file) for file in os.listdir(dir) if pat.search(file)]
for i, file in enumerate(files):
img_temp = I.open(file)
img = img_temp.resize(get_size(img_temp.size), I.NEAREST)
tkimg = Itk.PhotoImage(img)
la = ImageLabel(self.frame, file, tkimg)
la.grid(row = int(i/MAM_COLN), column=int(i%MAM_COLN), sticky=Tk.S+Tk.W+Tk.E)
class Frame(Tk.Frame):
""" The main class of this program. """
def __init__(self, master=None):
Tk.Frame.__init__(self, master)
self.master.title('Image Viewer')
self.master.geometry(GEO_MAIN)
self.cus_top = None # 背景色調節 window をあらわす内部変数
f_dir = Tk.LabelFrame(self, text='Directory')
f_dir.pack(anchor=Tk.W, padx=10, pady=2)
self.e_dir = Tk.Entry(f_dir, width=50)
self.e_dir.pack(side=Tk.LEFT)
self.e_dir.bind('<Return>', self.show_sum)
self.b_dir = Tk.Button(f_dir, text='Browse', command=self.browse)
self.b_dir.pack(side=Tk.LEFT, padx=2)
f=Tk.Frame(self)
f.pack(fill=Tk.X)
b_info=Tk.Button(f, bitmap='info', width=25, command=self.show_info)
b_info.pack(side=Tk.RIGHT, padx=20, pady=10, anchor=Tk.CENTER)
f_type = Tk.LabelFrame(f, text='File Type')
f_type.pack(side=Tk.LEFT, padx=10, pady=2)
b_custom_bg = Tk.Button(f, text='Customize Background', command=self.cus_bg)
b_custom_bg.pack(side=Tk.LEFT, anchor=Tk.S, padx=10)
self.var_type = dict()
for image_type in IMAGR_TYPES:
self.var_type[image_type] = Tk.IntVar()
cb = Tk.Checkbutton(f_type, text=image_type, variable=self.var_type[image_type],
relief=Tk.FLAT, justify = Tk.LEFT)
cb.select()
cb.pack(side=Tk.LEFT, padx=5)
ImageLabel.bg_var = Tk.StringVar()
ImageLabel.bg_var.set('#FFFFFF')
self.f_bg = Tk.LabelFrame(self, text='Background')
self.f_bg.pack(anchor=Tk.W, padx=10, pady=2)
for name, code in BGS:
rb = Tk.Radiobutton(self.f_bg, text=name, variable=ImageLabel.bg_var, value=code,
bg=code, command=self.change_bg, bd=1, relief=Tk.GROOVE)
rb.pack(side=Tk.LEFT, padx=5)
self.image_frame = ImageFrame(self, relief=Tk.RIDGE, border=2)
def change_bg(self):
bg1 = ImageLabel.bg_var.get()
label = ImageLabel.id_original_size
if label and label.winfo_exists():
label.configure(bg=bg1)
top = label.winfo_toplevel()
top.focus_set()
def browse(self):
dir = D.askdirectory()
if dir:
self.e_dir.delete(0, Tk.END)
self.e_dir.insert(0, dir)
self.image_frame.update(dir, self.var_type)
def show_sum(self, event):
self.image_frame.update(self.e_dir.get(), self.var_type)
def show_info(self):
M.showinfo(u"使い方", u"Entry か Browse ボタンでディレクトリを選択してください。\n"
u"そのディレクトリに含まれる画像ファイルの一覧が表示されます。\n"
u"一覧にある画像をクリックすると、別窓でオリジナルサイズの画像が表示されます。\n"
u"透過型 GIF の場合は背景色を変えることができます。\n"
u"ラジオボタンにある背景色が気に入らない場合は、\n"
u"Customize Background ボタンを押して現れる\n赤、緑、青用の3つのスケールで"
u"背景色を調節してください。")
def cus_bg(self):
if not (self.cus_top and self.cus_top.winfo_exists()):
self.cus_top = Tk.Toplevel(self)
self.cus_top.title('Create Back Ground')
self.cus_top.geometry(GEO_SCAL)
cusf = Tk.Frame(self.cus_top)
cusf.pack(fill=Tk.BOTH, padx=10, pady=10)
self.scale = dict()
for i, color in enumerate(('red', 'green', 'blue')):
l=Tk.Label(cusf, text=color+': ', anchor=Tk.W, fg=color, font=('Helvetica', '10', 'bold'))
l.grid(row=i, column=0, sticky=Tk.W)
self.scale[color] = Tk.Scale(cusf, orient=Tk.HORIZONTAL, length=300, from_=0, to=255,
command=self.customize_bg, tickinterval=50)
self.scale[color].set(255)
self.scale[color].grid(row=i, column=1)
self.cus_top.bind('<FocusIn>', self.customize_bg)
def customize_bg(self, event):
ImageLabel.bg_var.set('#%02X%02X%02X' %
(self.scale['red'].get(), self.scale['green'].get(), self.scale['blue'].get()))
self.change_bg()
##------------------------------------------------
if __name__ == '__main__':
f = Frame()
f.pack()
f.mainloop()
このアプリケーションは、以下のサイトで紹介されていたものをPython3系に修正したものになります。
詳しい解説についても記載されていますので、各コードについて知りたい人はチェックしてみてください。
<2019/12/03追記>
今までの投稿で扱っていなかったウィジェットはCheckbutton、Scale、Spinbox、Toplevelです。
また、tkinter.messageboxのようにダイアログを表示する方法で、 いろいろな質問ができるダイアログを作成するtkinter.dialogについて説明があります。
Menu,tkinter.scrolledtextを用いた画像表示アプリ
import re
import os
import os.path as P
import tkinter as Tk
from PIL import Image as I
from PIL import ImageTk as Itk
from PIL import ImageColor as Ic
import tkinter.scrolledtext as S
import tkinter.filedialog as D
import tkinter.messagebox as M
SIZE = 100
IMAGR_TYPES = ['gif', 'png', 'bmp', 'jpg', 'tif', 'ppm']
MAM_COLN = 6
GEO_MAIN = '680x600+20+20'
GEO_SATE = '+730+50'
GEO_SCAL = '+730+400'
## functions ------------------------------------------------
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)
def make_regexp(types):
""" It returns the regular expression of the image file type"""
str = "\.("
for k, v in types.items():
if v.get():
str += k + '|'
str = str[0:-1] + ')$'
return re.compile(str, re.I)
## classes ------------------------------------------------------------------
class ImageLabel(Tk.Frame):
""" A Label class to show an image """
# 一覧表示する画像のクラス.クリックされると原寸大のイメージを別窓で表示.
id_original_size = None # Label showing an original size image
bg_var = None # variable for background color
image_file_now = None
def __init__(self, stxt, image_file, img):
self.image = img # 一覧で表示する Tk.Image
self.image_file = image_file # もとのイメージファイル
frame = Tk.Frame(stxt, height=115, width=100)
frame.pack_propagate(0)
txt_label=Tk.Label(frame, text=P.basename(self.image_file), font=('Helvetica', '8'))
txt_label.pack(side=Tk.BOTTOM)
self.img_label=Tk.Label(frame, image=self.image)
self.img_label.pack(side=Tk.BOTTOM)
stxt.window_create(Tk.END, align=Tk.BASELINE, padx=5, pady=5, window=frame)
self.img_label.bind('<Double-Button-1>', self.show)
def show(self, event):
label = ImageLabel.id_original_size
if (label and label.winfo_exists()):
top = label.winfo_toplevel()
top.destroy()
top = Tk.Toplevel(self.img_label)
top.title(P.basename(self.image_file))
top.geometry(GEO_SATE)
img = I.open(P.abspath(self.image_file))
self.timg = Itk.PhotoImage(img)
label=Tk.Label(top, image=self.timg, bg=ImageLabel.bg_var.get())
label.pack()
ImageLabel.id_original_size = label
ImageLabel.image_file_now = self.image_file
class Frame(Tk.Frame):
""" The main class of this program. """
def __init__(self, master=None):
Tk.Frame.__init__(self, master)
self.master.title('BackGrounder')
self.master.geometry(GEO_MAIN)
self.cus_top = None # 背景色調節 window をあらわす内部変数
self.fout = None
### Menu
menu_bar = Tk.Menu(self, tearoff=0)
# File
menu_file = Tk.Menu(menu_bar, tearoff=0)
menu_bar.add_cascade(label="File", menu=menu_file, underline=0)
menu_file.add_command(label="Browse Dir.", command=self.browse, underline=0, accelerator = 'Ctrl-O')
menu_file.add_command(label="ReLoad", command=self.load_dir, underline=0, accelerator = 'Ctrl-R')
menu_file.add_command(label="Save As", command=self.save_image, underline=0, accelerator = 'Ctrl-S')
menu_file_type = Tk.Menu(menu_file, tearoff=0)
menu_file.add_cascade(label="File Type", menu=menu_file_type, underline=0)
menu_file.add_separator()
menu_file.add_command(label="Exit", command=self.exit, underline=0 , accelerator = 'Ctrl-Q')
menu_bar.add_command(label="Back Ground", command=self.cus_bg, underline=0)
menu_bar.add_command(label="Help", command=self.show_info, underline=0)
# short-cuts
self.master.bind('<Control-KeyPress-o>', self.browse)
self.master.bind('<Control-KeyPress-s>', self.save_image)
self.master.bind('<Control-KeyPress-r>', self.load_dir)
self.master.bind('<Control-KeyPress-q>', self.exit)
# check buttons in menu_file_type
self.var_type = dict()
for i, image_type in enumerate(IMAGR_TYPES):
self.var_type[image_type] = Tk.IntVar()
menu_file_type.add_checkbutton(label=image_type, variable=self.var_type[image_type])
menu_file_type.invoke(i)
ImageLabel.bg_var = Tk.StringVar()
ImageLabel.bg_var.set('#FFFFFF')
# add menu bar
try:
self.master.config(menu=menu_bar) # this required to show the menu bar
except AttributeError:
self.master.Tk.call(master, "config", "-menu", menu_bar)
self.once = False
self.stxt = S.ScrolledText(self, bg=self.cget('bg'), cursor=self.cget('cursor'), state=Tk.DISABLED)
self.stxt.pack(fill=Tk.BOTH, expand=1)
self.pack(fill=Tk.BOTH, expand=1)
def update(self, dir, types):
self.stxt.configure(state=Tk.NORMAL)
if self.once:
self.stxt.delete('1.0', Tk.END)
self.once=True
pat = make_regexp(types)
for f in os.listdir(dir):
if pat.search(f):
file = P.join(dir, f)
img = I.open(file)
ImageLabel(self.stxt, file,
Itk.PhotoImage(img.resize(get_size(img.size), I.NEAREST)))
self.stxt.configure(state=Tk.DISABLED)
def browse(self, event=None):
self.dir = D.askdirectory()
if self.dir:
self.load_dir()
def load_dir(self, event=None):
self.update(self.dir, self.var_type)
def show_info(self):
M.showinfo(u"使い方", u"メニューバーの File->Browse Dir. でディレクトリを選択してください。\n"
u"そのディレクトリに含まれる画像ファイルの一覧が表示されます。\n"
u"一覧にある画像をクリックすると、別窓でオリジナルサイズの画像が表示されます。\n\n"
u"透過型 GIF の場合は背景色をつけることができます。\n"
u"メニューバーの Back Ground をクリックすると\n"
u"赤、緑、青用の3つのスケールがある Window が現れるので、\n"
u"それで背景色を調節できます。\n\n"
u"背景色をつけた画像は File->SaveAs により保存することができます。"
)
def cus_bg(self):
if not (self.cus_top and self.cus_top.winfo_exists()):
self.cus_top = Tk.Toplevel(self)
self.cus_top.title('Create Back Ground')
self.cus_top.geometry(GEO_SCAL)
cusf = Tk.Frame(self.cus_top)
cusf.pack(fill=Tk.BOTH, padx=10, pady=10)
self.scale = dict()
for i, color in enumerate(('red', 'green', 'blue')):
l=Tk.Label(cusf, text=color+': ', anchor=Tk.W, fg=color, font=('Helvetica', '10', 'bold'))
l.grid(row=i, column=0, sticky=Tk.W)
self.scale[color] = Tk.Scale(cusf, orient=Tk.HORIZONTAL, length=300, from_=0, to=255,
command=self.customize_bg, tickinterval=50)
self.scale[color].set(255)
self.scale[color].grid(row=i, column=1)
#self.cus_top.bind('<FocusIn>', self.customize_bg)
def customize_bg(self, event):
ImageLabel.bg_var.set('#%02X%02X%02X' %
(self.scale['red'].get(), self.scale['green'].get(), self.scale['blue'].get()))
self.change_bg()
def change_bg(self):
bg1 = ImageLabel.bg_var.get()
label = ImageLabel.id_original_size
if label and label.winfo_exists():
label.configure(bg=bg1)
top = label.winfo_toplevel()
top.focus_set()
def save_image(self, event=None):
self.fout = D.asksaveasfilename(initialdir=self.dir, initialfile=self.fout and P.basename(self.fout) or None)
if self.fout:
img=I.open(ImageLabel.image_file_now)
imgc = img.mode == 'RGB' and img or img.convert('RGB')
bg1 = Ic.getrgb(ImageLabel.bg_var.get()) # background color
ls=[] # a sequence to store image data
for c0 in imgc.getdata():
if(c0==(255,255,255)):
ls.append(bg1)
else:
ls.append(c0)
imgc.putdata(ls)
imgc.save(self.fout)
def exit(self, event=None):
self.master.destroy()
##------------------------------------------------
if __name__ == '__main__':
f = Frame()
f.pack()
f.mainloop()
このアプリケーションは、以下のサイトで紹介されていたものをPython3系に修正したものになります。
詳しい解説についても記載されていますので、各コードについて知りたい人はチェックしてみてください。
今まで扱っていなかったウィジェットはMenu、ScrolledTextです。
Menuウィジェットについて詳しくはこちら
The Tkinter Menu Widget
また、ImageColorモジュールをインポートして、画像の背景色を変更したデータを元のデータに書き込んで保存できるようにしています。
- Ic.getrgb(ImageLabel.bg_var.get())
- pixel ごとの色の配列を返すメソッド
背景色のデータを読み込み、元の画像のpixcelが白部分は背景色を、白以外の部分は元のpixcelの色をリストとして保存するようにしています。 - imgc.putdata(ls)
- imgc に変換したデータ ls を putdata メソッドで書き込んでいます。
imgcは'RGB' and img or img.convert('RGB')で元のデータ形式がRGBであればそのままの形式で、RGBAなど異なる形式であればconvertメソッドでRGBに変換したデータです。
lsは背景色を書き込んだ画像のpixcelごとの色配列です。
ただし、α情報をもった画像データをRGB形式で保存する場合、αの情報がなくなるため、コンソールには以下のような警告表示がされます。
これは元のThe Python Imaging Libraryで定義されている仕様のようです。