【Python GUI実用サンプル】Tkinter ttk.Treeviewを使ってフォルダ一括作成ツールを作ってみる

<tkinterトップページに戻る>

今回作成するサンプル

概要

ttk.Treeviewを使用して、GUIから複数のフォルダを一括で作成するツールを作る

 

makedirectoryTools

makedirectoryTools_folder

 

 

 

使い方

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_folder

図のディレクトリ構成

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()

現段階で実行するとこんな感じです。

Treeview

 

追加後のサンプルコード

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()

現段階で実行するとこんな感じです。

inputFrame

追加後のサンプルコード

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__で宣言し、初期化してください。

makedirectoryTools_0

ここまでのサンプルコード

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()

 

あわせて読みたい