Sibainu Relax Room

愛犬の柴犬とともに過ごす部屋

Python で Tkinter を使ってみる2

まだまだ暑い、くたびれた顔をしている柴犬です。

概要

今回、久しぶりに Python を使ってみました。

個々の Python の記憶も薄れていましたが、意外に早く出来たなという感じです。

ツボにはまると Python のコードはホント簡単になります。

私が、使っている Python の教科書です。

柴犬は、前にある狛犬が気になって少し警戒して固まっています。

今日の成果

フォームは前回とあまり変わりませんが、ボタン「キャンセル」「実行」を押下したときの結果のインフォメーションを赤枠のところに表示するようにスペースを作りました。

ボタン「参照」を押下すると「フォルダーの選択」ダイヤログが開きます。

この場合、テスト用データのフォルダーを選択しています。

「バックアップ元」「バックアップ先」のホルダーを選択して、「コピー条件」「貼り付け条件」を適宜選択します。

そしてボタン「実行」を押下した結果です。

次の画像がバックアップの結果です。

意図したとおりになっています。

直下のフォルダー「F100」「F101」「F102」「F103」「F104」がフォルダー「tmp」に統合されています。

最終のコード

まだまだ工夫の必要なところはあります。

copy

import os
import shutil as st
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog as fd

##############
# ウィンドウ #
##############
root = tk.Tk()

root.geometry('800x250+300+300')
root.resizable(False, False)

root.title('BackUp Application')
root.columnconfigure(0, weight=1)

variables = dict()

####################
# フォームタイトル #
####################
ttk.Label(
    root,
    text="BackUp Application",
    font=("TkDefaultFont", 16)
).grid()

##################
# ベースフレーム #
##################
drf = ttk.Frame(root)
drf.grid(padx=10, sticky=(tk.E + tk.W))
drf.columnconfigure(0, weight=1)

########################
# バックアップ元レーム #
########################
c_info = ttk.LabelFrame(drf, text='バックアップ元Folder')
c_info.grid(sticky=(tk.W + tk.E))
for i in range(3):
    c_info.columnconfigure(i, weight=1 )

variables['コピー条件'] = tk.StringVar()
ttk.Label(c_info, text='コピー条件').grid(row=2, column=0)

labframe = ttk.Frame(c_info)
for lab in ('ALL', '直下のFolderのみ', '直下のFileのみ'):
    ttk.Radiobutton(
        labframe, value=lab, text=lab, variable=variables['コピー条件']
).pack(side=tk.LEFT, expand=True)
labframe.grid(row=3, column=0, sticky=(tk.W + tk.E))

variables['バックアップ元'] = tk.StringVar()
ttk.Label(c_info, text='バックアップ元').grid(row=2, column=1)
tk.Entry(
    c_info, textvariable=variables['バックアップ元'], width=65
).grid(row=3, column=1, sticky=(tk.W + tk.E))

sansho_copy = ttk.Button(
    c_info,
    text='参照',
    default='active'
)
sansho_copy.grid(row=3, column=2, sticky=(tk.W + tk.E))

ttk.Label(c_info, text='').grid(row=4, column=0)

######################
# バックアップ先レーム #
######################
p_info = ttk.LabelFrame(drf, text='バックアップ先Folder')
p_info.grid(sticky=(tk.W + tk.E))
for i in range(3):
    p_info.columnconfigure(i, weight=1 )

variables['貼り付け条件'] = tk.StringVar()
ttk.Label(p_info, text='貼り付け条件').grid(row=5, column=0)

labframe = ttk.Frame(p_info)
for lab in ('直下のFolderを維持', '直下のFolderを統合'):
    ttk.Radiobutton(
        labframe, value=lab, text=lab, variable=variables['貼り付け条件']
    ).pack(side=tk.LEFT, expand=True)
labframe.grid(row=6, column=0, sticky=(tk.W + tk.E))

variables['バックアップ先'] = tk.StringVar()
ttk.Label(p_info, text='バックアップ先').grid(row=5, column=1)
tk.Entry(
    p_info, textvariable=variables['バックアップ先'], width=65
).grid(row=6, column=1, sticky=(tk.W + tk.E))

sansho_paste = ttk.Button(
    p_info,
    text='参照',
    default='active'
)
sansho_paste.grid(row=6, column=2, sticky=(tk.W + tk.E))

variables['Lab'] = tk.StringVar()
ttk.Label(p_info, text='').grid(row=7, column=0)

##########################
# 実行・キャンセルボタン #
##########################
buttons = ttk.Frame(drf)
buttons.grid(sticky=tk.E + tk.W)
exec_button = ttk.Button(buttons, text='実行')
exec_button.pack(side=tk.RIGHT)

cancel_button = ttk.Button(buttons, text='キャンセル')
cancel_button.pack(side=tk.RIGHT)

############################
# フォームのステータスバー #
############################
status_variable = tk.StringVar()
ttk.Label(
    root, textvariable=status_variable
).grid(sticky=tk.W + tk.E, row=99, padx=10)

##########################
# ボタンクリック時の関数 #
##########################
def on_sansho(buttonname):
    def shori():
        dir = variables[buttonname].get()
        if not os.path.isdir(dir):
            dir = ''
        fld = fd.askdirectory(initialdir = dir) 
        variables[buttonname].set(fld)
    return shori

sansho_copy.configure(command=on_sansho("バックアップ元"))
sansho_paste.configure(command=on_sansho("バックアップ先"))

def on_exec(buttonname):
    def shori():
        copyfolder = variables['バックアップ元'].get()
        pastefolder =variables['バックアップ先'].get()
        # フォルダーの確認
        if not (os.path.isdir(copyfolder) and os.path.isdir(pastefolder)) :
            status_variable.set(
              f'{buttonname}   「バックアップ元」若しくは「バックアップ先」の指定に誤りがあります')
            return
        # コピー条件を基本にして場合分けをして処理します
        cc = variables['コピー条件'].get()
        if cc == 'ALL':
            pp = variables['貼り付け条件'].get()
            if pp == '直下のFolderを維持':
                st.copytree(copyfolder , pastefolder, dirs_exist_ok=True) 
            elif pp == '直下のFolderを統合':
                # 直下のフォルダー名を格納した配列を作成します
                listfolders = [f for f in os.listdir(copyfolder) if os.path.isdir(os.path.join(copyfolder, f))]
                for listfolder in listfolders:
                    st.copytree(os.path.join(copyfolder, listfolder), pastefolder, dirs_exist_ok=True)
                # 直下のファイル名を格納した配列を作成します
                listfiles = [f for f in os.listdir(copyfolder) if os.path.isfile(os.path.join(copyfolder, f))]
                for listfile in listfiles:
                    st.copy(os.path.join(copyfolder, listfile) , pastefolder)
            else:
                pp = '指定がありません'
        elif cc == '直下のFolderのみ':
            pp = variables['貼り付け条件'].get()
            if pp == '直下のFolderを維持':
                # 直下のフォルダー名を格納した配列を作成します
                listfolders = [f for f in os.listdir(copyfolder) if os.path.isdir(os.path.join(copyfolder, f))]
                for listfolder in listfolders:
                    st.copytree(os.path.join(copyfolder, listfolder), os.path.join(pastefolder, listfolder), dirs_exist_ok=True) 
            elif pp == '直下のFolderを統合':
                # 直下のフォルダー名を格納した配列を作成します
                listfolders = [f for f in os.listdir(copyfolder) if os.path.isdir(os.path.join(copyfolder, f))]
                for listfolder in listfolders:
                    st.copytree(os.path.join(copyfolder, listfolder), pastefolder, dirs_exist_ok=True)
            else:
                pp = '指定がありません'
        elif cc == '直下のFileのみ':
            # 直下のファイル名を格納した配列を作成します
            listfiles = [f for f in os.listdir(copyfolder) if os.path.isfile(os.path.join(copyfolder, f))]
            for listfile in listfiles:
                st.copy(os.path.join(copyfolder, listfile) , pastefolder)
            pp = '指定は無効'
        else:
            cc = '指定がありません'
            pp = '指定は無効'
        status_variable.set(
          f'{buttonname}     コピー条件:{cc}     貼り付け条件:{pp}')
    return shori

exec_button.configure(command=on_exec(exec_button.cget("text")))

def on_cancel(buttonname):
    def shori():
        for variable in variables.values():
            if isinstance(variable, tk.BooleanVar):
                variable.set(False)
            else:
                variable.set('')
        status_variable.set(
            f'{buttonname}し、初期化しました。')
    return shori

cancel_button.configure(command=on_cancel(cancel_button.cget("text")))

root.mainloop()

確認用のデータの作成

確認用のデータは次のようになっています。

確認用のデータを作成するコードは、次のようなパワーシェルで作成しています。

copy

$number = "10"
$folder = "C:\F1\F" + $number
$thead = "\T"
$fhead = "\F"
$extent = ".txt"

for ($i=0; $i -lt 5; $i++){

    #ファイル作成
    $fn0 = "$thead$number"+"00"+"$i$extent"
    "たちつてと" | Out-File "$folder$fn0"

    #ファイル名
    $fn1 = "$thead$number$i$extent"

    #フォルダー名
    $dn1 = "$folder$fhead$number$i"

  #フォルダー作成
    New-Item $dn1 -ItemType Directory


    for ($j=0; $j -lt 5; $j++){

        #フォルダー名
        $dn2 = "$dn1$fhead$number$i$j"

        #フォルダー作成
        New-Item $dn2 -ItemType Directory

        for ($k=0; $k -lt 5; $k++){

            #フォルダー名
            $dn3 = "$dn2$fhead$number$i$j$k"

      #フォルダー作成
            New-Item $dn3 -ItemType Directory

      #ファイル作成
            for ($l=0; $l -lt 5; $l++){

            	#ファイル作成
            	$fn3 = "$thead$number$i$j$k$l$extent"
                "あいうえお" | Out-File "$dn3$fn3"
            }
        }

    #ファイル作成
        for ($l=0; $l -lt 5; $l++){
            #ファイル名
            $fn2 = "$thead$number$i$j$l$extent"
            "かきくけこ" | Out-File "$dn2$fn2"
        }

    }
}

Python らしいと感じたこと

variables[‘コピー条件’] と variable=variables[‘コピー条件’] として同期させることができ、値を取得するのにボタンのオブジェクトに名前をつける必要がありません。

variables = dict()
-----------------------------------------------------------
variables['コピー条件'] = tk.StringVar()
ttk.Label(c_info, text='コピー条件').grid(row=2, column=0)

labframe = ttk.Frame(c_info)
for lab in ('ALL', '直下のFolderのみ', '直下のFileのみ'):
    ttk.Radiobutton(
        labframe, value=lab, text=lab, variable=variables['コピー条件']
).pack(side=tk.LEFT, expand=True)
labframe.grid(row=3, column=0, sticky=(tk.W + tk.E))

command に紐づける引数のある関数はクロージャを使って設定すればいいし、ボタンの設定時に決める必要はなく流動的に設定できます。

sansho_paste = ttk.Button(
    p_info,
    text='参照',
    # command='' を削除しています
    default='active'
)
------------------------------------------------------------
def on_sansho(buttonname):
    def shori():
        dir = variables[buttonname].get()
        if not os.path.isdir(dir):
            dir = ''
        fld = fd.askdirectory(initialdir = dir) 
        variables[buttonname].set(fld)
    return shori

sansho_copy.configure(command=on_sansho("バックアップ元"))
sansho_paste.configure(command=on_sansho("バックアップ先"))

フォルダーの中の直下のフォルダー、ファイルのそれぞれの一覧(リスト)が簡単に取得できます。

一行で済んでしまいます。

listfolders = [f for f in os.listdir(copyfolder) if os.path.isdir(os.path.join(copyfolder, f))]
# 直下のフォルダー名を格納した配列を作成します
listfolders = [f for f in os.listdir(copyfolder) if os.path.isdir(os.path.join(copyfolder, f))]
for listfolder in listfolders:
    st.copytree(os.path.join(copyfolder, listfolder), pastefolder, dirs_exist_ok=True)
# 直下のファイル名を格納した配列を作成します
listfiles = [f for f in os.listdir(copyfolder) if os.path.isfile(os.path.join(copyfolder, f))]
for listfile in listfiles:
    st.copy(os.path.join(copyfolder, listfile) , pastefolder)

修正するところはいっぱいあり、とりあえずの感がありますがここまでとします。