LoginSignup
4
8

More than 3 years have passed since last update.

【Python】自動フォルダ作成ツールを改良して追加実装した

Last updated at Posted at 2020-12-22

プロジェクト紹介スライド.gif

はじめに

前回の記事では自動フォルダ作成ツールを開発しました。
まだ見てない方はこちらをご覧下さい。
今回は、チェックボックスにチェックを入れ、作成ボタンを押すと自動でフォルダを作成してくれる機能を実装したので、ご紹介したいと思います。
紹介する前に、前回の機能を修正・改善したので、変更箇所を以下に示します。尚、修正後のコードおよび実行ファイルは前回記事のGithubにはないので今回新しく作成したこちらをご参照ください。

  • 背景を白色に変更
  • フォルダ参照のパスが正しくなかった場合にエラーメッセージを表示
  • フォルダ名および作成個数の入力欄を右詰めに修正
  • 作成時にパスの確認メッセージを追加
  • 作成時に重複があるフォルダのエラーメッセージを無効

開発環境

OS 言語 ライブラリ
Windows 10 version 1909 Python 3.9.1 Pyinstaller 4.1

動作

以下のように動作します。

フォルダ作成動作1.gif

パスを指定して、作成したい都道府県名のフォルダを選択します。
次に「作成」ボタンを押すと番号付きで指定した都道府県名のフォルダが作成されます。
尚、「全選択」ボタンで全部のチェックボックスが選択され、「全解除」ボタンで全部のチェックボックスが解除されます。
ex.)以下のように選択したボタンを選択したとします。

  • 北海道
  • 青森県
  • 岩手県
  • 宮城県
  • 秋田県 … 省略

作成ボタンを押すと参照先フォルダに以下のように作成されます。

参照先フォルダ
01北海道
02青森県
03秋田県

ツールのダウンロード

GitHubにプッシュしたので、下記のURLからダウンロードしてください。

自動フォルダ作成ツールver2.0

メモリ :10.1 MB

ダウンロード方法

ダウンロード説明.png

  1. 上記のURLからGithubにアクセスして頂き、右上のCodeをクリックします。
  2. Download ZIP」という項目が表示されるので、その項目をクリック。クリックすると、ZIPフォルダがダウンロードされます。
  3. ダウンロードしたZIPフォルダを解凍して頂き、binフォルダにある「gui_auto_mkdirs2.exe 」の実行ファイルが確認できたら完了です。
  4. 最後は、実行ファイルを起動することを確認したら、任意の場所にフォルダを作成してみてください。

実装内容

設計

前回と引き続き、Pythonの標準ライブラリであるTkinterでデスクトップアプリケーションを作成しました。

まず、設計ですが、以下のように考えました。

  1. デザインやレイアウト等
  2. チェックボックス押下処理について

まず、アイコンは自作したかったのでPhotoshopを使って、以下のように作成しました。
(左:デスクトップアイコン 右:GUIアイコン)
iconの制作.png

次にGUIウィンドウのレイアウト図ですがPowerpointを使って以下のように考えました。

・考えたデザイン

こんな感じに作っていこうと考えました。
次に、チェックボックス押下処理ですが、チェックボックスを押した順に番号振りにするか悩みましたが、使い勝手が悪いと考えたので、選択された県名を北海道から沖縄県までの順で番号振りするように処理していこうと考えました。

実装

Pythonのダウンロード
環境 : 参考サイト(最新バージョンPython 3.9.1 2020/12/22)

ライブラリのインストール

作成したコードを実行ファイル化(.exe)するために、必要なライブラリをターミナルからインストールします。

terminal
> pip install pyinstaller

コードについて

  • ディレクトリ構成

同階層のディレクトリにて、以下のファイルを作成します。
尚、アイコンデータは1階層下のフォルダに置いておきます。

./
┃
┣━ main.py 
┣━ mkdirs.py
┣━ icon/
┃   ┣━ desktop_icon.ico
┃   ┣━ icon.icon
┃   ┣━ icon.txt

全ソースコード🔜Github

以下のコードを開く際は300行を超えるものもあるので注意が必要です!

icon.txt(iconデータを読み込み用)
icon.txt
R0lGODlhwADAAPcAAACw8AGw8AKx8AOx8ASx8Aay8Aey8Aiy8Amz8Qqz8Quz8Qy0
8Q208Q608Q+18RC18RG18RK28RW38Ra38Re38Ri38Ry58h258h658h+68iC68iG6
8iO78iW78ie88iu98yy+8y2+8y6+8y+/8zC/8zK/8zPA8zbB8zfB8zjB8znC8zrC
8zzD9EDE9EPF9EXF9EfG9EjG9EnH9ErH9EvH9EzI9E7I9U/I9VDJ9VHJ9VLJ9VPK
9VTK9VXK9VbL9VfL9VjL9VnM9VrM9VvM9VzN9V3N9V7N9l/N9mDO9mHO9mLO9mPP
9mTP9mbQ9mnR9mrR9mvR9mzR9m3S9m7S9nDT93bV93zW94na+I3c+I7c+I/c+JHd
+ZPe+ZTe+ZXe+Zbe+Zff+Znf+Zvg+Zzg+Z3h+Z7h+aDi+aHi+aLi+qPi+qTj+qXj
+qbj+qfk+qjk+qnk+qrl+qzl+q3m+q7m+q/m+rDn+rHn+rLn+rPn+7Xo+7bo+7vq
+7zq+7/r+8Ls+8Ps+8Tt/MXt/Mbt/Mju/Mvv/Mzv/M3w/M/w/NDw/NHx/NTy/Nby
/dr0/dz0/d30/d71/d/1/eD1/eH2/eL2/eP2/eT3/eX3/eb3/uf4/uj4/un4/ur4
/uv5/uz5/u35/u76/u/6/vD6/vH7/vL7/vP7/vT8/vX8/vb8/vf9//j9//n9//r9
//v+//z+//3+/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAACH5BAEAAK8ALAAAAADAAMAAAAj+AF8JHEiwoMGDCBMqXMiw
ocOHECMe1ATniAYIGDNqhLAhCR1QEkOKHEmypMmTryha3Miy40eUMGPKnElT4CQx
MAIA2MmzJwABNdR4qkm0qNGjAx99ceGz6U4ZaYYinUq1qsRHYWYgcNpTQY42n6yK
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
CHsZYVorjdSwjfGwGBKxrhoQADs=

main.py(メインで実行)
main.py
import copy
import functools
import os
import re
import sys

from tkinter import *
from tkinter import ttk
from tkinter import messagebox
from tkinter import filedialog

from mkdirs import *


class GuiApplication(ttk.Notebook):

    def __init__(self, master=None):
        super().__init__(master)

        # Tabに適用する文字と背景の色の設定
        tab_style = ttk.Style()
        tab_style.configure(
            'new.TFrame', foreground='black', background='white')

        tab1 = ttk.Frame(self.master, style='new.TFrame')
        tab2 = ttk.Frame(self.master, style='new.TFrame')

        self.add(tab1, text='フォルダ名(番号付き)')
        self.add(tab2, text='都道府県名(番号付き)')

        Tab1(master=tab1)
        Tab2(master=tab2)

        self.pack()

    def dirdialog_clicked(self, entry):
        iDir = os.path.abspath(os.path.dirname(sys.argv[0]))
        iDir_path = filedialog.askdirectory(initialdir=iDir)
        entry.set(iDir_path)


class Tab1(ttk.Frame, GuiApplication):

    def __init__(self, master=None):
        super().__init__(master)

        style = ttk.Style()
        style.configure('Tab1.TFrame', foreground='black', background='white')
    # ========================================================================================================
        frame1_tab1 = ttk.Frame(
            master, padding=10, relief='groove', style='Tab1.TFrame')
        frame1_tab1.grid(row=0, column=1, padx=20, pady=10, ipadx=30, sticky=E)

        dir_label = ttk.Label(frame1_tab1, text='フォルダ参照>>',
                              padding=(25, 5, 5, 0), background='white')
        dir_label.pack(side=LEFT)

        self.entry1_tag1 = StringVar()
        dir_entry = ttk.Entry(
            frame1_tab1, textvariable=self.entry1_tag1, width=30)
        dir_entry.pack(side=LEFT)

        dir_button = ttk.Button(frame1_tab1, text='参照', command=lambda: super(
            Tab1, self).dirdialog_clicked(self.entry1_tag1))
        dir_button.pack(side=LEFT)
    # ========================================================================================================
        frame2_tab1 = ttk.Frame(master, padding=10, style='Tab1.TFrame')
        frame2_tab1.grid(row=2, column=1, padx=20, sticky=E)

        dir_name_label = ttk.Label(
            frame2_tab1, text='フォルダ名:', padding=(5, 0), background='white')
        dir_name_label.pack(side=LEFT)

        self.entry2_tag1 = StringVar()
        dir_name_entry = ttk.Entry(
            frame2_tab1, textvariable=self.entry2_tag1, justify='right', width=20)
        dir_name_entry.pack(side=LEFT)

        dir_name_label = ttk.Label(frame2_tab1, text='__', background='white')
        dir_name_label.pack(side=LEFT)

        dir_name_label = ttk.Label(
            frame2_tab1, text='No.', padding=(5, 0), background='white')
        dir_name_label.pack(side=LEFT)

        self.entry3_tag1 = StringVar()
        dir_nums_entry = ttk.Entry(
            frame2_tab1, textvariable=self.entry3_tag1, justify='right', width=10)
        dir_nums_entry.pack(side=LEFT)

        until_dir_nums_label = ttk.Label(
            frame2_tab1, text='まで', padding=(0, 5, 8, 5), background='white')
        until_dir_nums_label.pack(side=LEFT)

        nums_kind = ["SERECT", "01-xx", "No.1-No.xx",
                     "英語表記(One~)", "漢数字(一~)", "ローマ数字(I~)"]
        self.co = ttk.Combobox(
            frame2_tab1, state="readonly", values=nums_kind, width=12)
        self.co.set(nums_kind[0])
        self.co.pack(side=LEFT, padx=(2, 10))
    # ========================================================================================================
        frame3_tab1 = ttk.Frame(master, padding=10, style='Tab1.TFrame')
        frame3_tab1.grid(row=4, column=1, padx=20, sticky=W)

        create_dir_button = ttk.Button(
            frame3_tab1, text='作成', command=self.clicked_mkdir_button)
        create_dir_button.pack(fill='x', padx=70, side='left')

        cancel_button = ttk.Button(
            frame3_tab1, text=('閉じる'), command=master.quit)
        cancel_button.pack(fill='x', padx=75, side='right')
    # ========================================================================================================

    def clicked_mkdir_button(self):
        dir_path = self.entry1_tag1.get()
        input_dirName = self.entry2_tag1.get()
        input_dirCount = self.entry3_tag1.get()

        match_head_drive_path = re.search(r'^[A-Z]:/', dir_path)

        if (dir_path, input_dirName, input_dirCount) == (False, False, False):
            messagebox.showerror('エラー', '何も入力されていません。')
            return
        if not dir_path:
            messagebox.showerror('エラー', 'パスの指定がありません。')
            return
        elif not match_head_drive_path:
            messagebox.showerror('エラー', '正しいパスを入力してください!')
            return

        try:
            ismessage = messagebox.askyesno(
                '確認',
                '作成先のパスはあっていますか?\n'
                + '-------------------------------------------------------------------------------\n'
                + '{}\n'.format(dir_path)
                + '-------------------------------------------------------------------------------\n'
                + '作成しますがよろしいですか?')
            if ismessage == True:
                if int(input_dirCount) > 100:
                    messagebox.showwarning('警告', '最大、100個までのフォルダを作成することが可能です。')
                    return
                if self.co.get() == "SERECT":
                    messagebox.showwarning('警告', '数字の表記が選択されていません。')
                    return
                if self.co.get() == "01-xx":
                    mkdir_nums(dir_path, input_dirName, input_dirCount)
                elif self.co.get() == "No.1-No.xx":
                    mkdir_numbers(dir_path, input_dirName, input_dirCount)
                elif self.co.get() == "英語表記(One~)":
                    mkdir_eng_nums(dir_path, input_dirName, input_dirCount)
                elif self.co.get() == "漢数字(一~)":
                    mkdir_kansuuji(dir_path, input_dirName, input_dirCount)
                elif self.co.get() == "ローマ数字(I~)":
                    mkdir_roman_numbers(dir_path, input_dirName, input_dirCount)
                else:
                    return
                messagebox.showinfo(
                    'フォルダ作成情報', '{}個のフォルダが作成されました!'.format(input_dirCount))
                return
        except ValueError:
            messagebox.showerror('エラー', '半角英数字で番号を入力してください。')
            return
        except Exception as e:
            print('エラー :', e)
            messagebox.showerror('エラー', '予期しないエラーが発生しました。')
            return


class Tab2(ttk.Frame, GuiApplication):

    def __init__(self, master=None):
        super().__init__(master)

        self.TODOUFUKEN = ("北海道", "青森県", "岩手県", "宮城県", "秋田県", "山形県", "福島県", "茨城県", "栃木県", "群馬県",
                           "埼玉県", "千葉県", "東京都", "神奈川県", "新潟県", "富山県", "石川県", "福井県", "山梨県", "長野県",
                           "岐阜県", "静岡県", "愛知県", "三重県", "滋賀県", "京都府", "大阪府", "兵庫県", "奈良県", "和歌山県",
                           "鳥取県", "島根県", "岡山県", "広島県", "山口県", "徳島県", "香川県", "愛媛県", "高知県", "福岡県",
                           "佐賀県", "長崎県", "熊本県", "大分県", "宮崎県", "鹿児島県", "沖縄県")

        #  チェックされたときに変更するリスト(リスト内:全てFalse)
        self.check_todoufuken = [False for fal in range(len(self.TODOUFUKEN))]

        # チェックボックスで選択された都道府県名を格納するリスト
        self.mkdirs_list = []

        style = ttk.Style()
        style.configure('Tab2.TFrame', foreground='black', background='white')
        style.configure('OFF_Checkbox.TCheckbutton',
                        foreground='#000000', background='white')
        style.configure('ON_Checkbox.TCheckbutton',
                        foreground='#0F1FFF', background='white')
    # ========================================================================================================
        frame1_tab2 = ttk.Frame(
            master, padding=10, relief='groove', style='Tab2.TFrame')
        frame1_tab2.grid(row=0, column=1, padx=30, pady=10, ipadx=20, sticky=E)

        dir_label = ttk.Label(frame1_tab2, text='フォルダ参照>>',
                              padding=(25, 5, 5, 0), background='white')
        dir_label.pack(side=LEFT)

        entry1_tag2 = StringVar()
        dir_entry = ttk.Entry(frame1_tab2, textvariable=entry1_tag2, width=30)
        dir_entry.pack(side=LEFT)

        dir_button = ttk.Button(frame1_tab2, text='参照', command=lambda: super(
            Tab2, self).dirdialog_clicked(entry1_tag2))
        dir_button.pack(side=LEFT)
    # ========================================================================================================
        frame2_tab2 = LabelFrame(master, text='都道府県', background='white')
        frame2_tab2.grid(row=2, column=1, ipadx=0, ipady=5, sticky=N+S)

        cols = 5
        rows = (len(self.TODOUFUKEN) // cols) + 1  # 10
        self.place_checkboxes_for_prefectures(
            rows, cols, frame2_tab2, checkset=False, checkcolor='OFF_Checkbox.TCheckbutton')

        select_all_button = functools.partial(
            self.check_all_checkeboxes, frame2_tab2, rows, cols)
        all_check_button = Button(
            frame2_tab2, text='全選択', command=select_all_button, overrelief='groove')
        all_check_button.grid(row=rows, column=3)

        unselect_all_button = functools.partial(
            self.clear_all_checkeboxes, frame2_tab2, rows, cols)
        all_check_button = Button(
            frame2_tab2, text='全解除', command=unselect_all_button, overrelief='groove')
        all_check_button.grid(row=rows, column=4)
    # ========================================================================================================
        frame3_tab2 = ttk.Frame(master, padding=(
            0, 15, 0, 25), style='Tab2.TFrame')
        frame3_tab2.grid(row=4, column=1, padx=30, sticky=W)

        create_dir_button = ttk.Button(
            frame3_tab2, text='作成', command=lambda: self.clicked_mkdirs_todoufuken(entry1_tag2))
        create_dir_button.pack(fill='x', padx=70, side='left')

        cancel_button = ttk.Button(
            frame3_tab2, text=('閉じる'), command=master.quit)
        cancel_button.pack(fill='x', padx=75, side='right')
    # ========================================================================================================

    def place_checkboxes_for_prefectures(self, rows, cols, frame, checkset=None, checkcolor=None):
        for row in range(rows):  
            for col in range(cols):  
                v = BooleanVar()
                v.set(checkset)
                if (row + col*10) < len(self.TODOUFUKEN):
                    f = functools.partial(self.after_cb, frame, row, col, v)
                    cb = ttk.Checkbutton(
                        frame, padding=10, text=self.TODOUFUKEN[row + col*10], style=checkcolor, variable=v, command=f)
                    cb.grid(row=row, column=col)

    def after_cb(self, frame, row, col, var):
        if (row + col*10) > len(self.TODOUFUKEN):
            return
        elif var.get() == True:
            self.check_todoufuken[row + col*10] = True
            f = functools.partial(self.after_cb, frame, row, col, var)
            cb = ttk.Checkbutton(frame, padding=10, text='{}'.format(
                self.TODOUFUKEN[row + col*10]), style='ON_Checkbox.TCheckbutton', variable=var, command=f)
            cb.grid(row=row, column=col)
        else:
            self.check_todoufuken[row + col*10] = False
            f = functools.partial(self.after_cb, frame, row, col, var)
            cb = ttk.Checkbutton(frame, padding=10, text='{}'.format(
                self.TODOUFUKEN[row + col*10]), style='OFF_Checkbox.TCheckbutton', variable=var, command=f)
            cb.grid(row=row, column=col)

        self.mkdirs_list = [self.TODOUFUKEN[i] for i in range(
            len(self.TODOUFUKEN)) if self.check_todoufuken[i] == True]

    def check_all_checkeboxes(self, frame, rows, cols):
        self.mkdirs_list = list(copy.copy(self.TODOUFUKEN))
        self.check_todoufuken.clear()

        for _ in range(len(self.TODOUFUKEN)):
            self.check_todoufuken.append(True)

        self.place_checkboxes_for_prefectures(
            rows, cols, frame, checkset=True, checkcolor='ON_Checkbox.TCheckbutton')

    def clear_all_checkeboxes(self, frame, rows, cols):
        self.mkdirs_list.clear()
        self.check_todoufuken = [False for fal in range(len(self.TODOUFUKEN))]

        self.place_checkboxes_for_prefectures(
            rows, cols, frame, checkset=False, checkcolor='OFF_Checkbox.TCheckbutton')

    def clicked_mkdirs_todoufuken(self, entry1_tag2):
        dir_path = entry1_tag2.get()

        match_head_drive_path = re.search(r'^[A-Z]:/', dir_path)

        if not dir_path:
            messagebox.showerror('エラー', 'パスの指定がありません。')
            return
        elif not match_head_drive_path:
            messagebox.showerror('エラー', '正しいパスを入力してください!')
            return
        try:
            if not self.mkdirs_list:
                messagebox.showerror('エラー', '都道府県名が選択されていません。')
                return
            else:
                ismessage = messagebox.askyesno(
                    '確認',
                    '作成先のパスはあっていますか?\n'
                    + '-------------------------------------------------------------------------------\n'
                    + '{}\n'.format(dir_path)
                    + '-------------------------------------------------------------------------------\n'
                    + '作成しますがよろしいですか?')
                if ismessage == True:
                    mkdir_todoufuken(dir_path, self.mkdirs_list)
                    messagebox.showinfo('フォルダ作成情報', 'フォルダが作成されました。')
                    return
        except FileExistsError:
            messagebox.showwarning('警告', '既に同じ名前のフォルダが存在します。')
            return
        except Exception as e:
            print('エラー', e)
            messagebox.showerror('エラー', '予期しないエラーが発生しました。')
            return


if __name__ == "__main__":

    win = Tk()
    win.title('自動フォルダ作成ツール')
    win.configure(background='white')

    # icon.txtまでのパスを取得
    def resource_path(relative_path):
        if hasattr(sys, '_MEIPASS'):
            return os.path.join(sys._MEIPASS, relative_path)
        return os.path.join(os.path.abspath("."), relative_path)

    # ウィンドウの大きさ変更イベント
    def changed_tab(event):
        nb = event.widget
        text = nb.tab(nb.select(), 'text')
        if text in winsize:
            win.geometry(winsize[text])

    # icon_text = resource_path('icon/icon.txt')
    # with open(icon_text, 'r') as f:
    #     icon_data = f.read()

    # アイコンの設置(base64)
    # win.tk.call('wm', 'iconphoto', win._w, PhotoImage(data=icon_data))

    app = GuiApplication(master=win)

    winsize = {
        'フォルダ名(番号付き)': "500x190",
        '都道府県名(番号付き)': "500x580",
    }

    app.bind('<<NotebookTabChanged>>', changed_tab)

    app.mainloop()

mkdirs.py
mkdirs.py
import os

from tkinter import messagebox


# 英語表記の数字の一覧
ENG_NUMS = ( 
    "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", 
    "Ten", "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen", 
    "Twenty", "Twenty_One", "Twenty_Two", "Twenty_Three", "Twenty_Four", "Twenty_Five", "Twenty_Six", "Twenty_Seven", "Twenty_Eight", "Twenty_Nine", 
    "Thirty", "Thirty_One", "Thirty_Two", "Thirty_Three", "Thirty_Four", "Thirty_Five", "Thirty_Six", "Thirty_Seven", "Thirty_Eight", "Thirty_Nine", 
    "Forty", "Forty_One", "Forty_Two", "Forty_Three", "Forty_Four", "Forty_Five", "Forty_Six", "Forty_Seven", "Forty_Eight", "Forty_Nine", 
    "Fifty", "Fifty_One", "Fifty_Two", "Fifty_Three", "Fifty_Four", "Fifty_Five", "Fifty_Six", "Fifty_Seven", "Fifty_Eight", "Fifty_Nine", 
    "Sixty", "Sixty_One", "Sixty_Two", "Sixty_Three", "Sixty_Four", "Sixty_Five", "Sixty_Six", "Sixty_Seven", "Sixty_Eight", "Sixty_Nine", 
    "Seventy", "Seventy_One", "Seventy_Two", "Seventy_Three", "Seventy_Four", "Seventy_Five", "Seventy_Six", "Seventy_Seven", "Seventy_Eight", "Seventy_Nine", 
    "Eighty", "Eighty_One", "Eighty_Two", "Eighty_Three", "Eighty_Four", "Eighty_Five", "Eighty_Six", "Eighty_Seven", "Eighty_Eight", "Eighty_Nine", 
    "Ninety", "Ninety_One", "Ninety_Two", "Ninety_Three", "Ninety_Four", "Ninety_Five", "Ninety_Six", "Ninety_Seven", "Ninety_Eight", "Ninety_Nine", 
    "One_Hundred"
)

# 漢数字の一覧
KANSUUJI = (
    "一", "二", "三", "四", "五", "六", "七", "八", "九",
    "十", "十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九",
    "二十", "二十一", "二十二", "二十三", "二十四", "二十五", "二十六", "二十七", "二十八", "二十九",
    "三十", "三十一", "三十二", "三十三", "三十四", "三十五", "三十六", "三十七", "三十八", "三十九",
    "四十", "四十一", "四十二", "四十三", "四十四", "四十五", "四十六", "四十七", "四十八", "四十九",
    "五十", "五十一", "五十二", "五十三", "五十四", "五十五", "五十六", "五十七", "五十八", "五十九",
    "六十", "六十一", "六十二", "六十三", "六十四", "六十五", "六十六", "六十七", "六十八", "六十九",
    "七十", "七十一", "七十二", "七十三", "七十四", "七十五", "七十六", "七十七", "七十八", "七十九",
    "八十", "八十一", "八十二", "八十三", "八十四", "八十五", "八十六", "八十七", "八十八", "八十九",
    "九十", "九十一", "九十二", "九十三", "九十四", "九十五", "九十六", "九十七", "九十八", "九十九",
    "百"
)

# ローマ数字の一覧
ROMAN_NUMBERS = ( 
    "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", 
    "X", "XI", "XII", "XIII", "XIV", "XV", "XVI", "XVII", "XVIII", "XIX", 
    "XX", "XXI", "XXII", "XXIII", "XXIV", "XXV", "XXVI", "XXVII", "XXVIII", "XXIX", 
    "XXX", "XXXI", "XXXII", "XXXIII", "XXXIV", "XXXV", "XXXVI", "XXXVII", "XXXVIII", "XXXIX", 
    "XL", "XLI", "XLII", "XLIII", "XLIV", "XLV", "XLVI", "XLVII", "XLVIII", "XLIX", 
    "L", "LI", "LII", "LIII", "LIV", "LV", "LVI", "LVII", "LVIII", "LIX", 
    "LX", "LXI", "LXII", "LXIII", "LXIV", "LXV", "LXVI", "LXVII", "LXVIII", "LXIX", 
    "LXX", "LXXI", "LXXII", "LXXIII", "LXXIV", "LXXV", "LXXVI", "LXXVII", "LXXVIII", "LXXIX", 
    "LXXX", "LXXXI", "LXXXII", "LXXXIII", "LXXXIV", "LXXXV", "LXXXVI", "LXXXVII", "LXXXVIII", "LXXXIX", 
    "XC", "XCI", "XCII", "XCIII", "XCIV", "XCV", "XCVI", "XCVII", "XCVIII", "XCIX", 
    "C"
)

""" タブ1で利用するフォルダ作成関数 """
def mkdir_nums(dir_path , dir_name , dir_count ):
    """ 
    数字表記のディレクトリ名を作成します

    Parameters
    ----------
    dir_path : str
        作成先のディレクトリパス。
    dir_name : str
        フォルダの作成名。
    dir_count : str
        フォルダ作成個数。
    """
    dir_count = int(dir_count)
    if dir_count < 10:
        for count in range(1, dir_count + 1):
            os.makedirs(dir_path + '/' + '{}_0{}'.format(dir_name, count), exist_ok=True)
    else:
        for count in range(1, 10):
            os.makedirs(dir_path + '/' + '{}_0{}'.format(dir_name, count), exist_ok=True)
        for count in range(10, dir_count + 1):
            os.makedirs(dir_path + '/' + '{}_{}'.format(dir_name, count), exist_ok=True)

def mkdir_numbers(dir_path, dir_name, dir_count):
    """ 
    数字(No.付き)表記のディレクトリ名を作成します

    Parameters
    ----------
    dir_path : str
        作成先のディレクトリパス。
    dir_name : str
        フォルダの作成名。
    dir_count : str
        フォルダ作成個数。
    """
    for count in range(1, int(dir_count) + 1):
        os.makedirs(dir_path + '/' + '{}_No.{}'.format(dir_name, count), exist_ok=True)


def mkdir_eng_nums(dir_path, dir_name, dir_count):
    """ 
    英語表記(One~)のディレクトリ名を作成します

    Parameters
    ----------
    dir_path : str
        作成先のディレクトリパス。
    dir_name : str
        フォルダの作成名。
    dir_count : str
        フォルダ作成個数。
    """
    for count in range(0, int(dir_count)):
        os.makedirs(dir_path + '/' + '{}_{}'.format(dir_name, ENG_NUMS[count]), exist_ok=True)

# 漢数字表記(一~)のディレクトリ名を作成する関数
def mkdir_kansuuji(dir_path, dir_name, dir_count):
    """ 
    漢数字表記(一~)のディレクトリ名を作成します

    Parameters
    ----------
    dir_path : str
        作成先のディレクトリパス。
    dir_name : str
        フォルダの作成名。
    dir_count : str
        フォルダ作成個数。
    """
    for count in range(0, int(dir_count)):
        os.makedirs(dir_path + '/' + '{}_{}'.format(dir_name, KANSUUJI[count]), exist_ok=True)

def mkdir_roman_numbers(dir_path, dir_name, dir_count):
    """ 
    ローマ数字表記(Ⅰ~)のディレクトリ名を作成します

    Parameters
    ----------
    dir_path : str
        作成先のディレクトリパス。
    dir_name : str
        フォルダの作成名。
    dir_count : str
        フォルダ作成個数。
    """
    for count in range(0, int(dir_count)):
        os.makedirs(dir_path + '/' + '{}_{}'.format(dir_name, ROMAN_NUMBERS[count]), exist_ok=True)

""" タブ2で利用するフォルダ作成関数 """
# チェックボックスで選択した都道府県名のディレクトリを作成する関数
def mkdir_todoufuken(dir_path :str, mkdirs_list :list):
    """ 
    チェックボックスで選択したディレクトリ名を作成します

    Parameters
    ----------
    dir_path : str
        作成先のディレクトリパス。
    mkdirs_list : list of str
        作成するフォルダ名のリスト。
    """
    for todoufuken_idx, todoufuken in enumerate(mkdirs_list):
        if todoufuken_idx < 9:
            os.mkdir(dir_path + '/' + '0{}{}'.format(todoufuken_idx+1, todoufuken))
        else:
            os.mkdir(dir_path + '/' + '{}{}'.format(todoufuken_idx+1, todoufuken))


コードの説明(一部抜粋)

コードが長文になってしまったので、だいぶ省略して、大変だった箇所を説明していきます。
まず、前回と同様にTkinterのレイアウト等を決めるメインファイルとディレクトリ作成用の関数がもとまったファイルで作業しました。
前回と同じファイルで作業していたのですが、全部関数で処理をしていたため、タブを作成時に関数間とのやり取りが複雑化してしまい、苦労しました。このとき、関数さえあれば、なんでも処理できると考えていたのでオブジェクト指向の知識があまりなく、利用することを考えていませんでした。タブを作成する方法をいろいろと調べてみるとクラスを利用することを多く見かけたので、オブジェクト指向を本格的に勉強しました。selfを利用することでオブジェクト自身の変数を取得したり、メソッド(クラス内で定義された関数)を呼び出すことが出来るので、使わない手はないと思い、クラスを利用することにしました。

次に、ウィンドウを変更する処理について躓きました。継承先のTab1Tab2のコンストラクタ内にroot.geometry(Horizontal size(px)×Vertical size(px))を指定すればいけるかなと思ったのですが、エラーが出てしまい上手くいきませんでした。調べてみるとToplevelがもつメソッド、つまり、アプリケーション内のウィンドウ自身がもつメソッドなので上手くいかないようです。なので、以下のように処理することによりウィンドウの大きさを変更することができました。

if __name__ == '__main__':
    # …省略
    winsize = {
        'フォルダ名(番号付き)': "500x190",
        '都道府県名(番号付き)': "500x580",
    }
    # ウィンドウの大きさ変更イベント
    def changed_tab(event):
        nb = event.widget
        text = nb.tab(nb.select(), 'text')
        if text in winsize:
            win.geometry(winsize[text])
   # …省略

上記のようにif __name__ == '__main__':内のwin.geometry(winsize[text])とすることで、タブ変更のイベントを起こし、選択されたタブに応じてサイズを変更することができます。

次に、都道府県名のチェックボックス配置処理ですが、以下のように関数化することで配置できるようになりました。


def place_checkboxes_for_prefectures(self, rows, cols, frame, checkset=None, checkcolor=None):
    for row in range(rows):  
        for col in range(cols):  
            v = BooleanVar()
            v.set(checkset)
            if (row + col*10) < len(self.TODOUFUKEN):
                f = functools.partial(self.after_cb, frame, row, col, v)
                cb = ttk.Checkbutton(
                    frame, padding=10, text=self.TODOUFUKEN[row + col*10], style=checkcolor, variable=v, command=f)
                cb.grid(row=row, column=col)

引数に行、列、フレーム、チェック状態の値、チェックボックスの文字の色を渡すことで関数内は、for文で10×5の行列の中で47個分の都道府県名のチェックボックスを配置するようにしています。また、チェックボタンを押したときに、command=after_cb関数を呼び出しています。

最後に、最も大変だったチェックボタンを押したときに、選んだ項目をリストに格納する処理について説明します。

def after_cb(self, frame, row, col, var):
    if (row + col*10) > len(self.TODOUFUKEN):
        return
    elif var.get() == True:
        self.check_todoufuken[row + col*10] = True
        f = functools.partial(self.after_cb, frame, row, col, var)
        cb = ttk.Checkbutton(frame, padding=10, text='{}'.format(
            self.TODOUFUKEN[row + col*10]), style='ON_Checkbox.TCheckbutton', variable=var, command=f)
        cb.grid(row=row, column=col)
    else:
        self.check_todoufuken[row + col*10] = False
        f = functools.partial(self.after_cb, frame, row, col, var)
        cb = ttk.Checkbutton(frame, padding=10, text='{}'.format(
            self.TODOUFUKEN[row + col*10]), style='OFF_Checkbox.TCheckbutton', variable=var, command=f)
        cb.grid(row=row, column=col)

    self.mkdirs_list = [self.TODOUFUKEN[i] for i in range(
        len(self.TODOUFUKEN)) if self.check_todoufuken[i] == True]

上記のコードでは予め47個のFalseを格納したリストを用意しておき、チェックボタンを押したときに、そのチェックボタンに対応するリストの要素をTrueになるように処理しています。このとき、True(リストのインデックス番号)にした真理値だけを取り出して、取り出したインデックス番号をself.TODOUFUKENから取り出して、self.mkdirs_listに都道府県名を格納するように処理しています。しかし、最初は、チェックボタンを押したときにappendメソッドで何とかなるかなと軽く考えていたのですが、当然チェックボタンを押したときに、ただ追加されるだけで順番通りに追加されるわけではなかったです。例えば、

  1. 青森県
  2. 青森県
  3. 北海道
  4. 北海道

のように、選択すれば['青森県', '北海道', '北海道']に格納され、重複ありで順番通りにならず、苦悩しました。そこで、上記のコードのように予めFalseを用意しておき、押したときにTrueにすれば順番通りかつ重複がない状態でリストに格納できると考えて、上手くいきました。もしかしたら、if文の処理でリストに重複なしで順番通りに格納できる方法があるかもしれませんが、、、
他の方法を知っていましたらリファクタリングして頂けると嬉しいです。

ターミナル(CUI)操作

以下にCUI操作とファイル内容の変更操作を示します。
まず、ターミナルで以下のコマンドを実行します。

terminal
> pyinstaller main.py --onefile --noconsole --icon=icon/desktop_icon.ico 

実行した結果、エラーが出ると思いますが、icon.txtが読み込めてないため、エラーが出ます。そこで、アイコンのデータが入ったテキストファイルを実行ファイル(.exe)に入れるために、main.specを変更していきます。
以下のように変更します。

mian.spec
# -*- mode: python ; coding: utf-8 -*-

block_cipher = None


a = Analysis(['main.py'],
             pathex=['C:\\Users\\...\\自動フォルダ作成ツールver2.0(任意の作成フォルダ)'],
             binaries=[],
             datas=[],
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
+ a.datas += [("icon/icon.txt", "icon\\icon.txt", "DATA")]
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          [],
          name='main',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          upx_exclude=[],
          runtime_tmpdir=None,
          console=False , icon='icon\\desktop_icon.ico')

再び、ターミナルに戻り、以下のコマンドを実行します。

terminal
> pyinstaller main.spec

実行が終わると、最後にcompleted successfullyと表示され、distフォルダに実行ファイル(.exe)が生成されます。

参考文献

終わりに

今回、作成したアプリの機能は使う機会が少ないと思いますが、コードの改変などして皆様の作業効率化にお役に立てれば、嬉しいです。

免責事項 : 当ツールに関する利用について一切責任を負いません。


付録

本記事では、チェックボックスを利用したフォルダ作成ツールを作成しましたが、それとは別に以下の機能を持ったデスクトップアプリを開発しました。

  • タイトルフレームに現在時刻の表示:clock3:
  • スクロール型チェックボックスによる自動フォルダ作成機能📁
    • フォルダ名の前に番号を付ける:1234:
    • フォルダ名の後に日付を付ける:date:
  • フォルダ内にある複数のPDFファイルを1つのPDFファイルにまとめる:page_facing_up:

☟動作(見づらいと思うので拡大してください)

フォルダ作成 複数のPDFファイルを一括結合

そこで、今回のようなツールはどれくらい需要があるのか実際に知るために、連絡頂ければチェックボタンを改変してオリジナルのツールを作成しますと告知しようかと思ったのですが、利用違反になるかもしれないと思ったので止めておきました。
その代わり、LGTMの数をみて、作成したツールの記事を書こうかなと思います。

4
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
8