【Python GUI tkinter実用サンプル】tkinterのWidget群を使用して、UIを直感的に作成するソフトウェアのサンプル
概要&使い方
直感的にUIの作成ができるソフトウェアのtkinterのサンプルコード。
①widgetsから配置したいパーツを選択
②canvas内でクリックして配置したい箇所へ移動
③必要があればoptionで編集しupdate
④作成メニュー>outputでcanvas内に配置したWidgetを持つアプリケーション(test.py)が作成される
ソースコード説明
ソースコード内に記載されているコメントを参照ください
サンプル画像
①uiBuilderControl.py実行時
②複数Widgetを配置
③作成メニュー>output押下
④スクリプトが置かれているフォルダにtest.pyが作成される
⑤作成されたtest.py実行
サンプルコード
実行方法
view用スクリプト、コントロール&ロジック用スクリプトどちらも取得ください
※.pyファイルは可能であればフルパスで指定すると良い
python uiBuilderControl.py
View用スクリプト
・uiBuilderView.py
import tkinter.ttk as ttk
from tkinter import *
class LabelEntryWidget(ttk.Frame):
"""
LabelとEntryがくっついたWidget
"""
def __init__(self, master,text="property"):
super().__init__(master)
self.value = StringVar()
self.createWidgets(text)
def createWidgets(self,text="property"):
self.label = ttk.Label(self,text=text)
self.label.pack(side="left")
self.entry = ttk.Entry(self,textvariable=self.value)
self.entry.pack(side="left")
def getVar(self):
"""
値を取得するためのWidget変数の取得
"""
return self.value
def setLabelOption(self,key_dict):
"""
Labelのオプション指定(オプションはdictで渡す)
"""
for k in key_dict.keys():
self.label[k] = key_dict[k]
def setEntryOption(self,key_dict):
"""
Entryのオプション指定(オプションはdictで渡す)
"""
for k in key_dict.keys():
self.entry[k] = key_dict[k]
class WidgetsParts():
"""
Widget群とCanvasFrameサイズを指定するパーツ
"""
def __init__(self, parent):
self.parent = parent
self.width_var = None
self.height_var = None
self.command = None
self.add_frame = None
self.option_parts = None
self.start_xy =None
self.x_y=None
self.createWidgets()
def createWidgets(self):
self.inputWHWidgets()
self.inputWidgets()
def inputWHWidgets(self):
"""
widthとheightを入力する
"""
option = {"width":"5"}
frame = ttk.LabelFrame(self.parent,text="windowsize")
frame.pack()
input_frame = ttk.Frame(frame)
input_frame.pack()
width_parts = LabelEntryWidget(input_frame,text="width")
width_parts.pack(side="left")
width_parts.setEntryOption(option)
height_parts = LabelEntryWidget(input_frame,text="height")
height_parts.pack(side="left")
height_parts.setEntryOption(option)
self.update_button = ttk.Button(frame,text="update")
self.update_button.pack()
self.width_var = width_parts.getVar()
self.height_var = height_parts.getVar()
def getWidthVar(self):
return self.width_var
def getHeightVar(self):
return self.height_var
def setUpdateCommand(self,command):
self.update_button["command"] = command
def inputWidgets(self):
"""
Widget毎にボタンを作成
commandには自身のクラスを引数にaddWidgetを登録
"""
ttk.Button(self.parent,text= "Label",command = lambda : self.addWidget(ttk.Label)).pack()
ttk.Button(self.parent,text= "Button",command = lambda : self.addWidget(ttk.Button)).pack()
ttk.Button(self.parent,text= "Entry",command = lambda : self.addWidget(ttk.Entry)).pack()
ttk.Button(self.parent,text= "CheckBox",command = lambda : self.addWidget(ttk.Checkbutton)).pack()
ttk.Button(self.parent,text= "RadioButton",command = lambda : self.addWidget(ttk.Radiobutton)).pack()
ttk.Button(self.parent,text= "ComboBox",command = lambda : self.addWidget(ttk.Combobox)).pack()
def addWidget(self,widget):
"""
widgetは作成するWidgetのクラス
ボタン押下時にWidgetのクラスをオブジェクト化する
"""
if self.add_frame is None:
print("none")
return
widget = widget(self.add_frame)
widget.place(x=50,y=50)
if "text" in widget.keys():
widget["text"] = "sample"
widget.bind("<Button-1>",self.move_start)
widget.bind("<B1-Motion>",self.move_now)
widget.bind("<ButtonRelease-1>",self.move_end)
def move_start(self,event):
"""
Widgetが選択されたときの処理
①プロパティパーツの更新をする
②マウスカーソルの座標取得(スクリーン位置)
③位置情報取得(Canvas内の位置)
"""
self.option_parts.make_child(event.widget)
self.start_xy = (event.x_root,event.y_root)
place_info = event.widget.place_info()
x = int(place_info['x'])
y = int(place_info['y'])
self.x_y = (x,y)
print(self.add_frame.winfo_reqwidth())
def move_now(self,event):
"""
移動中処理
①move_startで取得したマウスカーソル位置と現在のマウスカーソル位置で距離を計算
②計算した距離を対象のWidget位置に加算
③x、yの移動後の座標を検査(Canvas内からはみ出る場合は調整)
④再配置
"""
if self.start_xy is None:
return
# 移動距離を調べる
distance = (event.x_root-self.start_xy[0],event.y_root-self.start_xy[1])
# 再度座標を設定する
place_info = event.widget.place_info()
self.option_parts.setPlaceinfo(place_info)
x = self.x_y[0] + distance[0]
y = self.x_y[1] + distance[1]
if x < 5:
x = 5
elif x >self.add_frame.winfo_reqwidth()-event.widget.winfo_reqwidth() - 10:
x = self.add_frame.winfo_reqwidth()-event.widget.winfo_reqwidth() - 10
if y < 5:
y = 5
elif y>self.add_frame.winfo_reqheight()-event.widget.winfo_reqheight() - 20:
y = self.add_frame.winfo_reqheight()-event.widget.winfo_reqheight() - 20
place_info['x'] = x
place_info['y'] = y
event.widget.place_configure(place_info)
def move_end(self,event):
"""
移動処理が終わったら座標類を初期化
"""
self.start_xy = None
self.x_y = None
def setAddFrame(self,add_frame):
self.add_frame = add_frame
self.width_var.set(self.add_frame["width"])
self.height_var.set(self.add_frame["height"])
def setOptionParts(self,option_parts):
self.option_parts = option_parts
class CanvasParts(ttk.Frame):
"""
Widgetを配置するCanvas
"""
def __init__(self, master,**kw):
super().__init__(master,**kw)
self.pack()
class OptionParts():
"""
各Widgetのオプション値を編集するパーツ
"""
def __init__(self, parent):
self.parent = parent
self.widget = None
self.x = None
self.y = None
def make_child(self,widgets):
"""
①以前のパーツを削除
②与えられたWidgetによって編集可能なオプション値を探す。
③WidgetのCanvas内位置を変更する編集可能Widgetに座標を入力
④target_keyに当てはまるオプションの編集可能Widgetを作成
"""
self.delete(destroy=False)
self.widget = widgets
target_key = ("width","height","text","state")
option_dict={"width":"7"}
itemdict = {}
place_info = widgets.place_info()
self.createWidgets()
self.x.set(place_info['x'])
self.y.set(place_info['y'])
for key in widgets.keys():
if key in target_key:
label = LabelEntryWidget(self.parent,text=key)
label.pack()
label.setLabelOption(option_dict)
itemdict[key] = label.getVar()
itemdict[key].set(widgets[key])
def _addCommand():
"""
update用コマンド
"""
for item in itemdict.keys():
widgets[item] = itemdict[item].get()
place_info['x']=self.x.get()
place_info['y']=self.y.get()
widgets.place_configure(place_info)
update = ttk.Button(self.parent,text = "update",command = _addCommand)
update.pack()
delete = ttk.Button(self.parent,text = "destroy",command = self.delete)
delete.pack()
def setPlaceinfo(self,place_info):
"""
Widgetの座標位置を更新(マウスで動かされたときに同期する用)
"""
self.x.set(place_info['x'])
self.y.set(place_info['y'])
def createWidgets(self):
"""
操作(編集)対象のWidget共通項目
Widgetの座標位置を編集するWidget
"""
option_dict={"width":"7"}
xlabel = LabelEntryWidget(self.parent,text="x")
xlabel.pack()
xlabel.setLabelOption(option_dict)
self.x = xlabel.getVar()
ylabel = LabelEntryWidget(self.parent,text="y")
ylabel.pack()
ylabel.setLabelOption(option_dict)
self.y = ylabel.getVar()
def delete(self,destroy=True):
"""
現在編集対象Widgetの編集項目を削除する
"""
children = self.parent.winfo_children()
for child in children:
child.destroy()
if destroy:
self.widget.destroy()
class UIBuilderApp(ttk.Frame):
"""
各パーツを組み立てるメインView
"""
def __init__(self, master):
super().__init__(master)
self.widgets_parts = None
self.canvs_parts = None
self.option_parts = None
self.output_command = lambda : print("none")
self.setupMenu()
self.createWidgets()
self.pack()
def createWidgets(self):
widgets_frame = ttk.Labelframe(self,text = "widgets",width="190",height="580")
widgets_frame.propagate(False)
widgets_frame.pack(side = "left")
self.widgets_parts = widgetparts = WidgetsParts(widgets_frame)
canvs_frame = ttk.Labelframe(self,text = "canvas",width="400",height="580")
canvs_frame.propagate(False)
canvs_frame.pack(side = "left")
self.canvs_parts = CanvasParts(canvs_frame,width="400",height="580")
option_frame = ttk.Labelframe(self,text = "option",width="190",height="580")
option_frame.propagate(False)
option_frame.pack(side = "left")
self.option_parts = OptionParts(option_frame)
self.widgets_parts.setAddFrame(self.canvs_parts)
self.widgets_parts.setOptionParts(self.option_parts)
self.setUpdateCommand()
def getWidgetParts(self):
return self.widgets_parts
def getCsanvasParts(self):
return self.canvs_parts
def setUpdateCommand(self):
def command():
width = self.widgets_parts.getWidthVar().get()
height = self.widgets_parts.getHeightVar().get()
self.canvs_parts["width"] = width
self.canvs_parts["height"] = height
# 親のFrameもサイズを変更する
parent = self.canvs_parts.master
parent["width"] = width
parent["height"] = height
self.widgets_parts.setUpdateCommand(command)
def setupMenu(self):
menu = Menu(self.master)
createMenu = Menu(menu,tearoff = 0)
createMenu.add_command(label="output",command=lambda:self.output_command())
exitMenu = Menu(menu,tearoff = 0)
exitMenu.add_command(label="exit",command=lambda:self.master.destroy())
menu.add_cascade(label="作成",menu = createMenu)
menu.add_cascade(label="終了",menu = exitMenu)
self.master.config(menu=menu)
def getCanvasParts(self):
"""
Canvasパーツを取得する
"""
return self.canvs_parts
def setOutputCommand(self,command):
"""
ファイル出力コマンドの設定
"""
print("set")
self.output_command = command
if __name__ == '__main__':
master = Tk()
master.title("UIBuilder")
master.geometry("800x600")
UIBuilderApp(master)
master.mainloop()
コントロール&ロジック用スクリプト
・uiBuilderControl.py
import tkinter.ttk as ttk
from tkinter import *
from uiBuilderView import UIBuilderApp
import os
import tkinter.messagebox as messagebox
class UIBuilderLogic():
"""
UIBuilderで組み立てたUIのサンプルコードを出力するロジッククラス
"""
def __init__(self,canvas_parts):
"""
初期化
出力ディレクトリはこのファイルと同じ階層
出力ファイル名はtest.pyとして出力する
canvas_parts:パーツを配置した親フレーム
"""
self.output_dir = os.path.dirname(__file__)
self.output_file_name = os.path.join(self.output_dir,"test.py")
self.canvas_parts = canvas_parts
def outputCommand(self):
"""
実際に出力させるメインメソッド
①インポート文出力
②UISampleクラス出力
③main文出力
"""
ret = False
try:
fo = open(self.output_file_name,"w",newline= "\n")
self.writeImport(fo)
self.writeUISample(fo)
self.writeMain(fo)
fo.close()
ret = True
except IOError as e:
print(e)
print("ファイルの出力に失敗しました。")
ret = False
return ret
def writeImport(self,file):
"""
import文の出力
file:ファイルオブジェクト
"""
import_list =[
"import tkinter.ttk as ttk",
"from tkinter import *",
]
for item in import_list:
print(item,file=file)
def writeUISample(self,file):
"""
実際のWidget作成
UISampleクラスを作成しCanvasに配置したWidgetを組み立てる
フレームサイズは指定サイズで作成
file:ファイルオブジェクト
"""
width = self.canvas_parts["width"]
height = self.canvas_parts["height"]
print("class UISample(ttk.Frame):",file=file)
print("\tdef __init__(self, master):",file=file)
print("\t\tsuper().__init__(master,width='{}',height='{}')".format(width,height),file=file)
print("\t\tself.createWidgets()",file=file)
print("\t\tself.propagate(False)",file=file)
print("\t\tself.pack()",file=file)
print("\tdef createWidgets(self):",file=file)
child_idx = 0
button_command = []
for child in self.canvas_parts.winfo_children():
place_info = child.place_info()
x = place_info['x']
y = place_info['y']
class_name = child.__class__.__name__
option = ""
target_key = ("width","height","text","state","command")
for ck in child.keys():
if ck in target_key:
if ck == "command":
command_function = "self.commandFunction{}".format(child_idx)
button_command.append(command_function)
option = option + "{} = {},".format(ck,command_function)
else:
option =option + "{} = \"{}\",".format(ck,child[ck])
print("\t\twidget_{} = ttk.{}(self,{})".format(child_idx,class_name,option),file=file)
print("\t\twidget_{}.place(x={},y={})".format(child_idx,x,y),file=file)
child_idx +=1
self.writeCommandFunction(file,button_command)
def writeCommandFunction(self,file,command_list):
"""
ボタンコマンドを作成する
とりあえずなにもしないpass文を書いておく
command_list:ボタンの関数リスト
"""
for command in command_list:
func_name = command.replace("self.","")
print("\tdef {}(self):".format(func_name),file=file)
print("\t\tpass",file=file)
def writeMain(self,file):
"""
main文の作成
file:ファイルオブジェクト
"""
width = self.canvas_parts["width"]
height = self.canvas_parts["height"]
main_list=[
"if __name__ == '__main__':",
"\tmaster = Tk()",
"\tmaster.title('UISample')",
"\tmaster.geometry(\"{}x{}\")".format(width,height),
"\tUISample(master)",
"\tmaster.mainloop()"
]
for item in main_list:
print(item,file=file)
class UIBuilderControl():
"""
UIBuilderのViewとLogicの管理を行うコントロール
"""
def __init__(self):
"""
UIBilderの起動を行う
"""
master = Tk()
master.title("UIBuilder")
master.geometry("800x600")
self.view =view= UIBuilderApp(master)
self.logic = UIBuilderLogic(view.getCanvasParts())
self.setupMenu()
master.mainloop()
def setupMenu(self):
"""
作成メニューのoutputにsetOutputCommandを紐づける
"""
self.view.setOutputCommand(self.setOutputCommand)
def setOutputCommand(self):
"""
ロジックの出力メソッドを実行する
実行結果によってメッセージボックスを切り替え
"""
ret = self.logic.outputCommand()
if ret:
messagebox.showinfo("output", "succeed")
else:
messagebox.showerror("output", "failed")
if __name__ == '__main__':
UIBuilderControl()