【Python GUI実用サンプル】Tkinter ttk.Treeviewを使ってフォルダ一括作成ツールを作ってみる
目次
今回作成するサンプル
概要
ttk.Treeviewを使用して、GUIから複数のフォルダを一括で作成するツールを作る
使い方
①RootPathにフォルダを作成したいディレクトリを指定します。
②Directoryツリーからディレクトリを追加したい階層を選択します。
③AddDirに追加したいディレクトリ名を入力し、addボタンを押下します。
③’間違えてディレクトリを追加した場合はそのディレクトリを選択し、TargetDirのdeleteボタンを押下してください。
④makeDirectoryボタンを押下するとDirectoryツリーに表示されているディレクトリがすべて作成されます。
今回の主に使用するモジュール
ttk.Treeview
下準備
・適当なフォルダにmakeDirectoryToolsSample.pyを作成してください
そして下記のコードを追加してください。
from tkinter import * import tkinter.ttk as ttk import tkinter.filedialog as filedialog import os import tkinter.messagebox as messagebox if __name__ == '__main__': master = Tk() master.title("MakeDirectory Tools") master.mainloop()
これで今回のtkinterで画面を作成していく下準備ができました。
※今回はTkinter系のモジュールの他にosモジュールをimportしています。
※osモジュールはフォルダを作成する際に使います
実装
1.ディレクトリ構造をクラス化する
今回のサンプルはディレクトリ作成ツールなのでファイルは考えません。
図のディレクトリ構成
makedirectoryTools/ ├test1 └test2
1つのディレクトリ(フォルダ)は
・自身のディレクトリ名
・子のディレクトリリスト(Directoryのリスト)
を持ちます。
その構造をクラス化すると下記になります。
class Directory(): # subDirs -> Directory List def __init__(self, dirPath,subDirs=None): self.dirPath = dirPath self.subDirs = subDirs def getSubDirs(self): return self.subDirs def getDirPath(self): return self.dirPath
2.画面を作成する
・アプリ全体をまとめるフレームを作成
まず、各Widgetを格納するフレームを作成します。
ttk.Frameを継承して作成します。
class MakeDirectoryTools(ttk.Frame): def __init__(self,master): super().__init__(master)
mainの中のmainloop前に呼び出してください。
・アプリの左側にTreeviewを使用してツリーを表示する
まずはツリービューを表示するフレームを作成します。
def createTreeView(self): treeFrame = ttk.Frame(self) self.tree = ttk.Treeview(treeFrame) # 列名をつける self.tree.heading("#0",text="Directory") self.tree.pack() return treeFrame
tree.heading(index,text=””)で列名を作成することができます。
アイコン列のindexは“#0”です
今回はアイコン列に”Directory”を指定します。
次に、アプリ内のGUIを作成するための関数を作成しcreateTreeViewを左側にpackします。
def create_widgets(self): leftframe = self.createTreeView() leftframe.pack(side="left") self.pack()
現段階で実行するとこんな感じです。
追加後のサンプルコード
from tkinter import * import tkinter.ttk as ttk import tkinter.filedialog as filedialog import os import tkinter.messagebox as messagebox # ディレクトリ構造をクラス化する class Directory(): # subDirs -> Directory List def __init__(self, dirPath,subDirs=None): self.dirPath = dirPath self.subDirs = subDirs def getSubDirs(self): return self.subDirs def getDirPath(self): return self.dirPath class MakeDirectoryTools(ttk.Frame): def __init__(self,master): super().__init__(master) self.create_widgets() def create_widgets(self): leftframe = self.createTreeView() leftframe.pack(side="left") self.pack() def createTreeView(self): treeFrame = ttk.Frame(self) self.tree = ttk.Treeview(treeFrame) # 列名をつける self.tree.heading("#0",text="Directory") self.tree.pack() return treeFrame if __name__ == '__main__': master = Tk() master.title("MakeDirectory Tools") MakeDirectoryTools(master) master.mainloop()
・アプリの右側に入力パラメータを表示する
次に右側に入力パラメータようFrameを作成します。
また入力パラメータのFrameには
・Directoryを作成するルートパスの登録、フォルダ選択用ボタン
・ディレクトリを加える対象のディレクトリ表示、誤って作成したとき用の削除ボタン
・新しく加えるディレクトリ名の入力、ディレクトリ追加ボタン
・ディレクトリ作成ボタン
大きく分けて上記4つのWidget群を作成します
入力パラメータは分かりやすくするために、
フレームに対してラベルを指定できるttk.LabelFrameを使用します。
packではうまくWidgetを配置できないので、入力パラメータ用のフレームではgridを使用します。
def createInputPanel(self): inputFrame = ttk.LabelFrame(self,text="InputParam") # 新しく作成するディレクトリのルート rootPathLabel = ttk.Label(inputFrame,text="RootPath") rootPathLabel.grid(column=0,row=0) rootpathEntry = ttk.Entry(inputFrame) rootpathEntry.grid(column=1,row=0) rootpathButton = ttk.Button(inputFrame,text="open") rootpathButton.grid(column=2,row=0) #新しく階層を作るディレクトリを表示するWidget addPathLabel = ttk.Label(inputFrame,text="TargetDir") addPathLabel.grid(column=0,row=1) addpathEntry = ttk.Entry(inputFrame,state="readonly") addpathEntry.grid(column=1,row=1) addPathButton = ttk.Button(inputFrame,text="delete") addPathButton.grid(column=2,row=1) #追加するディレクトリを表示するWidget addNodeLabel = ttk.Label(inputFrame,text="AddDir") addNodeLabel.grid(column=0,row=2) addNodeEntry = ttk.Entry(inputFrame) addNodeEntry.grid(column=1,row=2) addNodeButton = ttk.Button(inputFrame,text="add") addNodeButton.grid(column=2,row=2) # 作成するボタン makeDirButton = ttk.Button(inputFrame,text="makeDirectory") makeDirButton.grid(column=2,row=3) return inputFrame
次に、アプリ内のGUIを作成するための関数にcreateInputPanel右側にpackします。
create_widgetsを下記のコードに変更してください。
def create_widgets(self): leftframe = self.createTreeView() leftframe.pack(side="left") rightframe= self.createInputPanel() rightframe.pack(side="right") self.pack()
現段階で実行するとこんな感じです。
追加後のサンプルコード
from tkinter import * import tkinter.ttk as ttk import tkinter.filedialog as filedialog import os import tkinter.messagebox as messagebox # ディレクトリ構造をクラス化する class Directory(): # subDirs -> Directory List def __init__(self, dirPath,subDirs=None): self.dirPath = dirPath self.subDirs = subDirs def getSubDirs(self): return self.subDirs def getDirPath(self): return self.dirPath class MakeDirectoryTools(ttk.Frame): def __init__(self,master): super().__init__(master) self.create_widgets() def create_widgets(self): leftframe = self.createTreeView() leftframe.pack(side="left") rightframe= self.createInputPanel() rightframe.pack(side="right") self.pack() def createTreeView(self): treeFrame = ttk.Frame(self) self.tree = ttk.Treeview(treeFrame) # 列名をつける self.tree.heading("#0",text="Directory") self.tree.pack() return treeFrame def createInputPanel(self): inputFrame = ttk.LabelFrame(self,text="InputParam") # 新しく作成するディレクトリのルート rootPathLabel = ttk.Label(inputFrame,text="RootPath") rootPathLabel.grid(column=0,row=0) rootpathEntry = ttk.Entry(inputFrame) rootpathEntry.grid(column=1,row=0) rootpathButton = ttk.Button(inputFrame,text="open") rootpathButton.grid(column=2,row=0) #新しく階層を作るディレクトリを表示するWidget addPathLabel = ttk.Label(inputFrame,text="TargetDir") addPathLabel.grid(column=0,row=1) addpathEntry = ttk.Entry(inputFrame,state="readonly") addpathEntry.grid(column=1,row=1) addPathButton = ttk.Button(inputFrame,text="delete") addPathButton.grid(column=2,row=1) #追加するディレクトリを表示するWidget addNodeLabel = ttk.Label(inputFrame,text="AddDir") addNodeLabel.grid(column=0,row=2) addNodeEntry = ttk.Entry(inputFrame) addNodeEntry.grid(column=1,row=2) addNodeButton = ttk.Button(inputFrame,text="add") addNodeButton.grid(column=2,row=2) # 作成するボタン makeDirButton = ttk.Button(inputFrame,text="makeDirectory") makeDirButton.grid(column=2,row=3) return inputFrame if __name__ == '__main__': master = Tk() master.title("MakeDirectory Tools") MakeDirectoryTools(master) master.mainloop()
・制御変数を登録
各Widgetに制御変数を登録します。
・ルートパス
・ディレクトリを加える対象のディレクトリ
・追加するディレクトリ
今回は上記の3つを作成し対象のWidgetのtextvariableに登録します。
まず__init__を下記のコードに変更してください。
def __init__(self,master): super().__init__(master) self.rootPath = StringVar() self.targetDir = StringVar() self.addDir = StringVar() self.create_widgets()
次にWidgetに対して登録します
・rootpathEntry
rootpathEntry = ttk.Entry(inputFrame,textvariable=self.rootPath)
・addpathEntry
addpathEntry = ttk.Entry(inputFrame,state="readonly",textvariable=self.targetDir)
・addNodeEntry
addNodeEntry = ttk.Entry(inputFrame,textvariable=self.addDir)
・ツリーに親ディレクトリを登録
今回のツールでは”makedirectoryTools”ディレクトリを起点として指定されたフォルダを作成します。
ツリーにデータを登録するにはtree.insert( iid, “end”, text=””)を使用します。
(iidに””を指定することでルートに登録することができます)
※iidとはツリーデータの一意なindexです。
またinsertには戻り値があり、登録したデータのiidが取得できます。
では実際にツリーにデータを登録します。
createTreeViewを下記のコードに変更してください。
def createTreeView(self): treeFrame = ttk.Frame(self) self.tree = ttk.Treeview(treeFrame) # 列名をつける self.tree.heading("#0",text="Directory") self.tree.pack() self.rootiid = self.tree.insert("","end",text="makedirectoryTools") return treeFrame
親のiidを控えておくためにself.rootiidに登録しておきます。
self.rootiidは__init__で宣言し、初期化してください。
ここまでのサンプルコード
from tkinter import * import tkinter.ttk as ttk import tkinter.filedialog as filedialog import os import tkinter.messagebox as messagebox # ディレクトリ構造をクラス化する class Directory(): # subDirs -> Directory List def __init__(self, dirPath,subDirs=None): self.dirPath = dirPath self.subDirs = subDirs def getSubDirs(self): return self.subDirs def getDirPath(self): return self.dirPath class MakeDirectoryTools(ttk.Frame): def __init__(self,master): super().__init__(master) self.rootPath = StringVar() self.targetDir = StringVar() self.addDir = StringVar() self.rootiid="" self.create_widgets() def create_widgets(self): leftframe = self.createTreeView() leftframe.pack(side="left") rightframe= self.createInputPanel() rightframe.pack(side="right") self.pack() def createTreeView(self): treeFrame = ttk.Frame(self) self.tree = ttk.Treeview(treeFrame) # 列名をつける self.tree.heading("#0",text="Directory") self.tree.pack() self.rootiid = self.tree.insert("","end",text="makedirectoryTools") return treeFrame def createInputPanel(self): inputFrame = ttk.LabelFrame(self,text="InputParam") # 新しく作成するディレクトリのルート rootPathLabel = ttk.Label(inputFrame,text="RootPath") rootPathLabel.grid(column=0,row=0) rootpathEntry = ttk.Entry(inputFrame,textvariable=self.rootPath) rootpathEntry.grid(column=1,row=0) rootpathButton = ttk.Button(inputFrame,text="open") rootpathButton.grid(column=2,row=0) #新しく階層を作るディレクトリを表示するWidget addPathLabel = ttk.Label(inputFrame,text="TargetDir") addPathLabel.grid(column=0,row=1) addpathEntry = ttk.Entry(inputFrame,state="readonly",textvariable=self.targetDir) addpathEntry.grid(column=1,row=1) addPathButton = ttk.Button(inputFrame,text="delete") addPathButton.grid(column=2,row=1) #追加するディレクトリを表示するWidget addNodeLabel = ttk.Label(inputFrame,text="AddDir") addNodeLabel.grid(column=0,row=2) addNodeEntry = ttk.Entry(inputFrame,textvariable=self.addDir) addNodeEntry.grid(column=1,row=2) addNodeButton = ttk.Button(inputFrame,text="add") addNodeButton.grid(column=2,row=2) # 作成するボタン makeDirButton = ttk.Button(inputFrame,text="makeDirectory") makeDirButton.grid(column=2,row=3) return inputFrame if __name__ == '__main__': master = Tk() master.title("MakeDirectory Tools") MakeDirectoryTools(master) master.mainloop()
・ルートパス(ディレクトリ)指定用のコールバック関数を作成
ルートパス指定”open”ボタン用のコールバック関数を作成(MakeDirectoryTools内)します。
コールバック関数で行いたいことは、
①ファイルダイアログからディレクトリ名を取得
②取得したフォルダをルートパスに表示
です。
作成するコールバック関数
def openFileDialog(self): folder = filedialog.askdirectory(); self.rootPath.set(folder) self.logic.setRootPath(folder)
filedialog.askdirectory()とすることでディレクトリが選択できるファイルダイアログを表示することができます。
作成したコールバック関数を”open”ボタンに登録しましょう
rootpathButton = ttk.Button(inputFrame,text="open",command=self.openFileDialog)
・ツリーで選択したアイテム(ディレクトリ)を、TargetDirに反映する
まず選択したディレクトリをTargetDirに反映するコールバック関数を作成する
今回はボタンに対してではなく、
イベントに対してコールバック関数を登録するため第2引数eventを加える
def targetDirectory(self,event): self.iid = self.tree.focus() if self.iid : self.targetDir.set(self.tree.item(self.iid,"text"))
選択されている項目のiidをtree.focus()で取得することができ、
またtree.item(iid,”text”)とすることで指定されたiidを持つ項目のテキストを取得することができる。
self.iidは__init__で初期化してください。
次にコールバック関数をTreeviewに登録します。
createTreeViewを下記のコードに変更してください。
def createTreeView(self): treeFrame = ttk.Frame(self) self.tree = ttk.Treeview(treeFrame) # ツリーの項目が選択されたら指定されたを新しく階層を作るディレクトリ名を更新する self.tree.bind("<<TreeviewSelect>>",self.targetDirectory) # 列名をつける self.tree.heading("#0",text="Directory") self.tree.pack() # rootのiidを登録 self.rootiid = self.tree.insert("","end",text="makedirectoryTools") return treeFrame
“<<TreeviewSelect>>”はツリーの項目が選択されたときに呼び出されるイベントです。
ここまでのサンプルコード
from tkinter import * import tkinter.ttk as ttk import tkinter.filedialog as filedialog import os import tkinter.messagebox as messagebox # ディレクトリ構造をクラス化する class Directory(): # subDirs -> Directory List def __init__(self, dirPath,subDirs=None): self.dirPath = dirPath self.subDirs = subDirs def getSubDirs(self): return self.subDirs def getDirPath(self): return self.dirPath class MakeDirectoryTools(ttk.Frame): def __init__(self,master): super().__init__(master) self.rootPath = StringVar() self.targetDir = StringVar() self.addDir = StringVar() self.iid="" self.rootiid="" self.create_widgets() def create_widgets(self): leftframe = self.createTreeView() leftframe.pack(side="left") rightframe= self.createInputPanel() rightframe.pack(side="right") self.pack() def createTreeView(self): treeFrame = ttk.Frame(self) self.tree = ttk.Treeview(treeFrame) # ツリーの項目が選択されたら指定されたを新しく階層を作るディレクトリ名を更新する self.tree.bind("<<TreeviewSelect>>",self.targetDirectory) # 列名をつける self.tree.heading("#0",text="Directory") self.tree.pack() # rootのiidを登録 self.rootiid = self.tree.insert("","end",text="makedirectoryTools") return treeFrame def createInputPanel(self): inputFrame = ttk.LabelFrame(self,text="InputParam") # 新しく作成するディレクトリのルート rootPathLabel = ttk.Label(inputFrame,text="RootPath") rootPathLabel.grid(column=0,row=0) rootpathEntry = ttk.Entry(inputFrame,textvariable=self.rootPath) rootpathEntry.grid(column=1,row=0) rootpathButton = ttk.Button(inputFrame,text="open",command=self.openFileDialog) rootpathButton.grid(column=2,row=0) #新しく階層を作るディレクトリを表示するWidget addPathLabel = ttk.Label(inputFrame,text="TargetDir") addPathLabel.grid(column=0,row=1) addpathEntry = ttk.Entry(inputFrame,state="readonly",textvariable=self.targetDir) addpathEntry.grid(column=1,row=1) addPathButton = ttk.Button(inputFrame,text="delete") addPathButton.grid(column=2,row=1) #追加するディレクトリを表示するWidget addNodeLabel = ttk.Label(inputFrame,text="AddDir") addNodeLabel.grid(column=0,row=2) addNodeEntry = ttk.Entry(inputFrame,textvariable=self.addDir) addNodeEntry.grid(column=1,row=2) addNodeButton = ttk.Button(inputFrame,text="add") addNodeButton.grid(column=2,row=2) # 作成するボタン makeDirButton = ttk.Button(inputFrame,text="makeDirectory") makeDirButton.grid(column=2,row=3) return inputFrame # ルートパスのディレクトリを決める def openFileDialog(self): folder = filedialog.askdirectory(); self.rootPath.set(folder) # 指定されたディレクトリを反映 def targetDirectory(self,event): self.iid = self.tree.focus() if self.iid : self.targetDir.set(self.tree.item(self.iid,"text")) if __name__ == '__main__': master = Tk() master.title("MakeDirectory Tools") MakeDirectoryTools(master) master.mainloop()
・Directory追加用コールバック関数を作成する
コールバック関数を作成する際下記に気を付けて実装します。
・ディレクトリ名が空
・同じ階層のディレクトリ名の重複
では下記コールバック関数を作成しましょう
def insertDirectory(self): addDir = self.addDir.get() # ディレクトリ名がない場合は処理しない if addDir != "": children = self.tree.get_children(self.iid) # 同じ階層に同じ名前で作成は不可 for child in children: childname = self.tree.item(child,"text") if childname == addDir: messagebox.showerror("登録エラー","既に登録されています") return self.tree.insert(self.iid,"end",text=self.addDir.get())
tree.get_children(iid)でiidに紐づいた子のiidリストが取得できます。
また同じ階層を誤って作成しようとした場合に分かりやすくするため
エラーメッセージダイアログを表示するようにしています。
ではコールバック関数を”add”ボタンに登録しましょう
addNodeButton = ttk.Button(inputFrame,text="add",command=self.insertDirectory)
・ディレクトリ削除用コールバック関数を登録する
削除用コールバック関数を登録する際にも気を付ける点があります
・選択されているiidが親(self.rootiid)の場合は削除させない
ではコールバック関数を作成します。
def deleteDirectory(self): if self.iid == "" or self.iid == self.rootiid: messagebox.showerror("削除エラー","削除する階層を選択してください。\nmakedirectoryToolsディレクトリは削除できません。") return self.tree.delete(self.iid)
tree.delete(iid)とすることでアイテムの削除ができる。
またこの方法で削除した場合は子要素も削除される。
では”delete”ボタンにコールバック関数を登録しましょう。
addPathButton = ttk.Button(inputFrame,text="delete",command = self.deleteDirectory)
3.フォルダ作成ロジックを作成
・ロジックを画面と切り離す
フォルダ作成ロジックは画面作成クラスと切り離します。
理由としてはいくつかありますが、
ロジック単体でも動作、テストを可能にするためです。
ではロジック用のクラスを作成しましょう。
下記コードを追加してください。
class MakeDirectoryLogic(): def __init__(self,rootPath): self.setRootPath(rootPath) def setRootPath(self,rootPath): self.rootPath = rootPath
Directoryを作成する際には作成するルートパスが必要になるので__init__にルートパスを与えています。
また外部からメソッド(セッター)を使用して設定できるようにsetRootPathを作成しています。
・フォルダ作成用再帰関数作成
フォルダを作成する関数を作成します。
makedirectoryTools/ ├test1/ │ └sub1/ └test2/
このようなディレクトリを作成する手順として
①makedirectoryToolsを作成
②makedirectoryToolsの下にtest1を作成
③test1の下にsub1を作成
④makedirectoryToolsの下にtest2を作成
が考えられます。
②と③は階層は違いますが同じ処理がされていると思います。
それを実際にコードに落とし込んだのが下記の関数です。
def makeDirectory(self,rootPath,dirList): # パスが存在しない場合は即終了 if os.path.exists(rootPath) ==False: return for dir in dirList: path = os.path.join(rootPath,dir.getDirPath()) print("path",path) os.mkdir(path) # 子の階層を作成する children = dir.getSubDirs() if len(children) > 0: self.makeDirectory(path,children)
親となるDirectoryを作成した後、
子のディレクトリを同じ関数を呼び出すことによって作成します。(再帰関数)
・GUIから呼び出されたときの処理を追加する
①ツリーの階層をDirectoryに落とし込む
tree.itemとtree.get_childrenを使用してDirectoryを作成します。
def createDirectoryList(self,rootiid,tree,dirList): dirname = tree.item(rootiid,"text") children = tree.get_children(rootiid) childlist=[] dir = Directory(dirname,childlist) dirList.append(dir) if len(children)>0: for child in children: self.createDirectoryList(child,tree,childlist)
実はこれも再帰関数になっています。
②GUIから実際に呼び出す関数を作成
実際にGUIからに呼び出す関数を作成する。
順番としては
①ツリーの情報からDirectoryリストを作成
②Directoryリストをもとにフォルダを作成
def makeDirectoryAction(self,tree,rootiid): dirList=[] self.createDirectoryList(rootiid,tree,dirList) self.makeDirectory(self.rootPath,dirList)
ここまでのサンプルコード
from tkinter import * import tkinter.ttk as ttk import tkinter.filedialog as filedialog import os import tkinter.messagebox as messagebox # ディレクトリ構造をクラス化する class Directory(): # subDirs -> Directory List def __init__(self, dirPath,subDirs=None): self.dirPath = dirPath self.subDirs = subDirs def getSubDirs(self): return self.subDirs def getDirPath(self): return self.dirPath #Directory作成ロジック class MakeDirectoryLogic(): def __init__(self,rootPath): self.setRootPath(rootPath) #Directoryの情報をもとに実際にフォルダを作成する def makeDirectory(self,rootPath,dirList): # パスが存在しない場合は即終了 if os.path.exists(rootPath) ==False: return for dir in dirList: path = os.path.join(rootPath,dir.getDirPath()) print("path",path) os.mkdir(path) # 子の階層を作成する children = dir.getSubDirs() if len(children) > 0: self.makeDirectory(path,children) # tree =ttk.Treeview # ツリーの情報からDirectoryリストを作成する def createDirectoryList(self,rootiid,tree,dirList): dirname = tree.item(rootiid,"text") children = tree.get_children(rootiid) childlist=[] dir = Directory(dirname,childlist) dirList.append(dir) if len(children)>0: for child in children: self.createDirectoryList(child,tree,childlist) # ルートパスを設定する def setRootPath(self,rootPath): self.rootPath = rootPath # GUIから呼び出すディレクトリ作成アクション def makeDirectoryAction(self,tree,rootiid): dirList=[] self.createDirectoryList(rootiid,tree,dirList) self.makeDirectory(self.rootPath,dirList) class MakeDirectoryTools(ttk.Frame): def __init__(self,master): super().__init__(master) self.rootPath = StringVar() self.targetDir = StringVar() self.addDir = StringVar() self.iid="" self.rootiid="" self.create_widgets() def create_widgets(self): leftframe = self.createTreeView() leftframe.pack(side="left") rightframe= self.createInputPanel() rightframe.pack(side="right") self.pack() def createTreeView(self): treeFrame = ttk.Frame(self) self.tree = ttk.Treeview(treeFrame) # ツリーの項目が選択されたら指定されたを新しく階層を作るディレクトリ名を更新する self.tree.bind("<<TreeviewSelect>>",self.targetDirectory) # 列名をつける self.tree.heading("#0",text="Directory") self.tree.pack() # rootのiidを登録 self.rootiid = self.tree.insert("","end",text="makedirectoryTools") return treeFrame def createInputPanel(self): inputFrame = ttk.LabelFrame(self,text="InputParam") # 新しく作成するディレクトリのルート rootPathLabel = ttk.Label(inputFrame,text="RootPath") rootPathLabel.grid(column=0,row=0) rootpathEntry = ttk.Entry(inputFrame,textvariable=self.rootPath) rootpathEntry.grid(column=1,row=0) rootpathButton = ttk.Button(inputFrame,text="open",command=self.openFileDialog) rootpathButton.grid(column=2,row=0) #新しく階層を作るディレクトリを表示するWidget addPathLabel = ttk.Label(inputFrame,text="TargetDir") addPathLabel.grid(column=0,row=1) addpathEntry = ttk.Entry(inputFrame,state="readonly",textvariable=self.targetDir) addpathEntry.grid(column=1,row=1) addPathButton = ttk.Button(inputFrame,text="delete",command = self.deleteDirectory) addPathButton.grid(column=2,row=1) #追加するディレクトリを表示するWidget addNodeLabel = ttk.Label(inputFrame,text="AddDir") addNodeLabel.grid(column=0,row=2) addNodeEntry = ttk.Entry(inputFrame,textvariable=self.addDir) addNodeEntry.grid(column=1,row=2) addNodeButton = ttk.Button(inputFrame,text="add",command=self.insertDirectory) addNodeButton.grid(column=2,row=2) # 作成するボタン makeDirButton = ttk.Button(inputFrame,text="makeDirectory") makeDirButton.grid(column=2,row=3) return inputFrame # ルートパスのディレクトリを決める def openFileDialog(self): folder = filedialog.askdirectory(); self.rootPath.set(folder) # 指定されたディレクトリを反映 def targetDirectory(self,event): self.iid = self.tree.focus() if self.iid : self.targetDir.set(self.tree.item(self.iid,"text")) #指定されたディレクトリに子階層を加える def insertDirectory(self): addDir = self.addDir.get() # ディレクトリ名がない場合は処理しない if addDir != "": children = self.tree.get_children(self.iid) # 同じ階層に同じ名前で作成は不可 for child in children: childname = self.tree.item(child,"text") if childname == addDir: messagebox.showerror("登録エラー","既に登録されています") return self.tree.insert(self.iid,"end",text=self.addDir.get()) #ディレクトリを削除する def deleteDirectory(self): if self.iid == "" or self.iid == self.rootiid: messagebox.showerror("削除エラー","削除する階層を選択してください。\nmakedirectoryToolsディレクトリは削除できません。") return self.tree.delete(self.iid) if __name__ == '__main__': master = Tk() master.title("MakeDirectory Tools") MakeDirectoryTools(master) master.mainloop()
4.”makeDirectory”のコールバック関数を作成
まずロジックをアプリに紐づける。
MakeDirectoryTools.__init__を下記のコードに変更してください。
def __init__(self,master): super().__init__(master) self.rootPath = StringVar() self.targetDir = StringVar() self.addDir = StringVar() self.iid="" self.rootiid="" self.logic = MakeDirectoryLogic(self.rootPath.get()) self.create_widgets()
次に実際にコールバック関数を作成する
“makeDirectory”ボタン用コールバック関数を作成する際に気を付けるべきは
①rootPathが空
②rootPathが存在しないパス
③ツールで作成されたパスが既に存在する
の3点。
③は実装によって回避方法がいくつかあるが、
今回はすでにツールで作成された痕跡があれば作成しない方法をとる。
def makeDirectory(self): rootPath = self.rootPath.get() # ルートのパスが存在しない場合は作成できないのでエラーダイアログを出して終了 if rootPath =="": messagebox.showerror("作成エラー","ディレクトリを作成するルートディレクトリを指定してください。") return elif os.path.exists(rootPath) == False: messagebox.showerror("作成エラー","ルートディレクトリが不正です。") return mkpath = os.path.join(rootPath,self.tree.item(self.rootiid,"text")) # 上書きを防ぐため、すでにこのツールで作成されたフォルダがある場合はエラーダイアログを出して終了 if os.path.exists(mkpath): messagebox.showerror("作成エラー","既にmakedirectoryToolsディレクトリが作成されています") return # ロジックのディレクトリ作成アクションを呼び出す self.logic.makeDirectoryAction(self.tree,self.rootiid)
①~③の条件がクリアされたときにロジックを実行する
ではコールバック関数をmakeDirectory”ボタンに登録する
makeDirButton = ttk.Button(inputFrame,text="makeDirectory",command=self.makeDirectory)
またopenFileDialogで選択されたルートをロジックにも反映させるため、下記のコードに変更してください。
def openFileDialog(self): folder = filedialog.askdirectory(); self.rootPath.set(folder) self.logic.setRootPath(folder)
細かい調整
①ツリー作成時self.iidにself.rootiidを入れる
def createTreeView(self): treeFrame = ttk.Frame(self) self.tree = ttk.Treeview(treeFrame) # ツリーの項目が選択されたら指定されたを新しく階層を作るディレクトリ名を更新する self.tree.bind("<<TreeviewSelect>>",self.targetDirectory) # 列名をつける self.tree.heading("#0",text="Directory") self.tree.pack() # rootのiidを登録 self.rootiid = self.tree.insert("","end",text="makedirectoryTools") self.iid = self.rootiid return treeFrame
このようにすることで、初期で項目が選択されていなくてもフォルダをaddできる
サンプルコード
from tkinter import * import tkinter.ttk as ttk import tkinter.filedialog as filedialog import os import tkinter.messagebox as messagebox # ディレクトリ構造をクラス化する class Directory(): # subDirs -> Directory List def __init__(self, dirPath,subDirs=None): self.dirPath = dirPath self.subDirs = subDirs def getSubDirs(self): return self.subDirs def getDirPath(self): return self.dirPath #Directory作成ロジック class MakeDirectoryLogic(): def __init__(self,rootPath): self.setRootPath(rootPath) #Directoryの情報をもとに実際にフォルダを作成する def makeDirectory(self,rootPath,dirList): # パスが存在しない場合は即終了 if os.path.exists(rootPath) ==False: return for dir in dirList: path = os.path.join(rootPath,dir.getDirPath()) print("path",path) os.mkdir(path) # 子の階層を作成する children = dir.getSubDirs() if len(children) > 0: self.makeDirectory(path,children) # tree =ttk.Treeview # ツリーの情報からDirectoryリストを作成する def createDirectoryList(self,rootiid,tree,dirList): dirname = tree.item(rootiid,"text") children = tree.get_children(rootiid) childlist=[] dir = Directory(dirname,childlist) dirList.append(dir) if len(children)>0: for child in children: self.createDirectoryList(child,tree,childlist) # ルートパスを設定する def setRootPath(self,rootPath): self.rootPath = rootPath # GUIから呼び出すディレクトリ作成アクション def makeDirectoryAction(self,tree,rootiid): dirList=[] self.createDirectoryList(rootiid,tree,dirList) self.makeDirectory(self.rootPath,dirList) class MakeDirectoryTools(ttk.Frame): def __init__(self,master): super().__init__(master) self.rootPath = StringVar() self.targetDir = StringVar() self.addDir = StringVar() self.iid="" self.rootiid="" self.logic = MakeDirectoryLogic(self.rootPath.get()) self.create_widgets() def create_widgets(self): leftframe = self.createTreeView() leftframe.pack(side="left") rightframe= self.createInputPanel() rightframe.pack(side="right") self.pack() def createTreeView(self): treeFrame = ttk.Frame(self) self.tree = ttk.Treeview(treeFrame) # ツリーの項目が選択されたら指定されたを新しく階層を作るディレクトリ名を更新する self.tree.bind("<<TreeviewSelect>>",self.targetDirectory) # 列名をつける self.tree.heading("#0",text="Directory") self.tree.pack() # rootのiidを登録 self.rootiid = self.tree.insert("","end",text="makedirectoryTools") self.iid = self.rootiid return treeFrame def createInputPanel(self): inputFrame = ttk.LabelFrame(self,text="InputParam") # 新しく作成するディレクトリのルート rootPathLabel = ttk.Label(inputFrame,text="RootPath") rootPathLabel.grid(column=0,row=0) rootpathEntry = ttk.Entry(inputFrame,textvariable=self.rootPath) rootpathEntry.grid(column=1,row=0) rootpathButton = ttk.Button(inputFrame,text="open",command=self.openFileDialog) rootpathButton.grid(column=2,row=0) #新しく階層を作るディレクトリを表示するWidget addPathLabel = ttk.Label(inputFrame,text="TargetDir") addPathLabel.grid(column=0,row=1) addpathEntry = ttk.Entry(inputFrame,state="readonly",textvariable=self.targetDir) addpathEntry.grid(column=1,row=1) addPathButton = ttk.Button(inputFrame,text="delete",command = self.deleteDirectory) addPathButton.grid(column=2,row=1) #追加するディレクトリを表示するWidget addNodeLabel = ttk.Label(inputFrame,text="AddDir") addNodeLabel.grid(column=0,row=2) addNodeEntry = ttk.Entry(inputFrame,textvariable=self.addDir) addNodeEntry.grid(column=1,row=2) addNodeButton = ttk.Button(inputFrame,text="add",command=self.insertDirectory) addNodeButton.grid(column=2,row=2) # 作成するボタン makeDirButton = ttk.Button(inputFrame,text="makeDirectory",command=self.makeDirectory) makeDirButton.grid(column=2,row=3) return inputFrame # ルートパスのディレクトリを決める def openFileDialog(self): folder = filedialog.askdirectory(); self.rootPath.set(folder) self.logic.setRootPath(folder) # 指定されたディレクトリを反映 def targetDirectory(self,event): self.iid = self.tree.focus() if self.iid : self.targetDir.set(self.tree.item(self.iid,"text")) #指定されたディレクトリに子階層を加える def insertDirectory(self): addDir = self.addDir.get() # ディレクトリ名がない場合は処理しない if addDir != "": children = self.tree.get_children(self.iid) # 同じ階層に同じ名前で作成は不可 for child in children: childname = self.tree.item(child,"text") if childname == addDir: messagebox.showerror("登録エラー","既に登録されています") return self.tree.insert(self.iid,"end",text=self.addDir.get()) #ディレクトリを削除する def deleteDirectory(self): if self.iid == "" or self.iid == self.rootiid: messagebox.showerror("削除エラー","削除する階層を選択してください。\nmakedirectoryToolsディレクトリは削除できません。") return self.tree.delete(self.iid) # ディレクトリを作成する def makeDirectory(self): rootPath = self.rootPath.get() # ルートのパスが存在しない場合は作成できないのでエラーダイアログを出して終了 if rootPath =="": messagebox.showerror("作成エラー","ディレクトリを作成するルートディレクトリを指定してください。") return elif os.path.exists(rootPath) == False: messagebox.showerror("作成エラー","ルートディレクトリが不正です。") return mkpath = os.path.join(rootPath,self.tree.item(self.rootiid,"text")) # 上書きを防ぐため、すでにこのツールで作成されたフォルダがある場合はエラーダイアログを出して終了 if os.path.exists(mkpath): messagebox.showerror("作成エラー","既にmakedirectoryToolsディレクトリが作成されています") return # ロジックのディレクトリ作成アクションを呼び出す self.logic.makeDirectoryAction(self.tree,self.rootiid) if __name__ == '__main__': master = Tk() master.title("MakeDirectory Tools") MakeDirectoryTools(master) master.mainloop()