【Python】自動フォルダ作成ツールを開発してみた!

![プロジェクト紹介スライド.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695597/489fa532-19a7-f4ac-1cae-6db772eef132.gif)



## はじめに
前回の記事で自動でフォルダを作成してくれるコードを書きましたが、GUI化して、操作しやすいようにした方が複数のフォルダを作成するときに、楽だと思ったので作成してみることにしました。
## 環境
実行は以下の環境で行いました。

Windows 10 version 1903
Python 3.7.6
PyInstaller 3.6
## 完成品
完成したものはこちらになります:point_down:
![フォルダ作成動画.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695597/cb65d6fe-aef2-9c55-42d9-ebe0991dd416.gif)

パスを指定して、フォルダ名とフォルダを何個まで作成するかを入力します。
次に、数字の種類を選び、「作成」ボタンを押します。
すると、指定した分の数だけフォルダが作成されます。
フォルダ名の表記はex.『No.1～No.100』を選択、フォルダ名[Test]_No.[5]までを指定すると、

```
Test_No.1
Test_No.2
Test_No.3
Test_No.4
Test_No.5
```

のように作成されます。

数字の表記は

- 『01～100』までの数字
- 『No.1～No.100』までの数字
- 『One～One_Hundred』までの英語表記の数字
- 『一～百』までの漢字表記の数字
- 『I～C』のローマ数字

となっております。英語表記と漢字表記の数字は自動ソートがされないので使い勝手が悪いです。
今回、フォルダの個数は誤入力が起こらないように100個までに制限しています。
また、フォルダ名を入力しない場合でも、パスとフォルダの数の指定があれば、作成され、同じフォルダ名があったとき、作成個数が違った場合、作成されないです。

※注意点として、何回もフォルダを作成・削除して、デフラグを起こさないように!!
## ツールが欲しい方
GitHubにプッシュしたので、下のURLからダウンロードしてください。

[自動フォルダ作成ツール](https://github.com/taiiii123/GUI_auto_mkdirs)

###ダウンロード方法
![Github画面.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695597/58188a6c-ebf5-d5b6-72f3-f532f0aed041.png)

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


## 実装内容
### 設計
今回、Pythonの標準ライブラリであるTkinterを使いました。サードパーティ製ライブラリであるkivyというライブラリが人気らしいですが、Tkinterの方が参考文献がたくさんあったのでTkinterを選びました。

自動フォルダ作成ツールを考えたときに、まず、設計から考えました。

1. デザインやレイアウト等
2. 条件処理について
3. クリック処理について

デザインはまず、どうするかを考え、「参考記事[【Python】tkinterでファイル&フォルダパス指定画面を作成する](https://qiita.com/dgkmtu/items/2367a73f7e2d498e6075)」をもとにデザインを考えました。

・考えたデザイン
![デザイン1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695597/a45058d6-044f-c66c-10e9-f8f66802a4fc.jpeg)

こんな感じで作っていこうと考えました。

次に、何個まで作成するか等の条件を考えました。入力の桁を打ち間違ったときに、何千個と作成されないように100個までという条件を付けました。また、「SELECT」ボタンですが、本当は実装する予定がなく、ただディレクトリ名と数字だけだと機能が少ないなと思い、「No.1～No.100」以外で選択できるように実装しようと考えました。

最後に、ユーザーが選択して、「作成」ボタンを押したときにディレクトリ作成されるように実装していこうと考えました。
### コードについて
全コード🔜[Github](https://github.com/taiiii123/GUI_auto_mkdirs)
まず、Tkinterのレイアウト等を決めるPythonファイルとディレクトリ作成用の関数がもとまったPythonファイルに分けることにしました。
ディレクトリ作成用のPythonファイルから、
『01～100』と『No.1～No.100』は数字の部分でfor文のint型でそれぞれ処理できるのですが、英語表記の数字、漢数字、ローマ数字は文字列として扱うので、それぞれの1から100までのタプル形式で用意しました。リストにしなかったのは、後から変更しないためです。

```py:例
ENG_NUMS = ( "One", "Two", "Three",‥‥, "One_Hundred")
```
用意した番号の一覧をもとに、ディレクトリを作成する関数を作っていきます。

```py
def mkdir_eng_nums(dirPath, dir_name, dir_count):
    for count in range(0, int(dir_count)):
        os.mkdir(dirPath + '/' + '{}_{}'.format(dir_name, ENG_NUMS[count]))
```
引数に、それぞれ、パスを指定した絶対パス、入力したディレクトリ名、入力した作成個数の指定として、`dir_count`をint型にキャストし、for文で回しています。for文内の処理は、osモジュールのmkdir関数をつかって、指定した絶対パスとディレクトリ名とタプルの中身を連結することで、1個ずつディレクトリを作成していきます。他の数字や漢数字等は同様な原理で作成しています。

続いて、メインとなるレイアウト等のPythonファイルですが、まずは、osやtkinterモジュール等をインポート。

```py
import os,sys

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

from nums_kind_mkdirs import *
```
次に、フォルダ参照ですが、以下のように処理しています。

```py
    entry1 = StringVar()
    IDirEntry = ttk.Entry(frame1, textvariable=entry1, width=30)
    IDirEntry.pack(side=LEFT)

    IDirButton = ttk.Button(frame1, text="参照", command=dirdialog_clicked)
    IDirButton.pack(side=LEFT)
```

ボタンのオブジェクトを設置し、ボタンを押したときに、`dirdialog_clicked`関数を呼び出しています。`dirdialog_clicked`関数内は、ユーザーがディレクトリ指定したときに、絶対パスを取得します。また、取得した絶対パスは変数`entry1`に代入します。
同様に、ファイル名およびディレクトリ個数入力欄も入力したときに、フォルダ名は`entry2`に、作成個数は`entry3`に代入しています。

また、ドロップダウンリストでは、以下のように処理しています。

```py
    nums_kind = ["SERECT", "01-xx", "No.1-No.xx", "英語表記(One~)", "漢数字(一~)", "ローマ数字(I~)"]
    co = ttk.Combobox(frame2, state="readonly", values=nums_kind, width=12)
    co.set(nums_kind[0])
    co.pack(side=LEFT, padx=(2, 10))
```
`ttk.Combobox`でコンボボックスを作成し、選択肢はリスト型の`nums_kind`から選びます。`co.set(nums_kind[0])`で何もしない処理としてデフォルトで**SERECT**をセットしています。

続いて、ディレクトリを作成する処理やエラー処理についてですが、以下のように処理をしています。

```py
    try:
        if dirPath:
            if int(input_dir_count) <= 100:
                if co.get() != 'SERECT':
                    if co.get() == '01-xx':
                        mkdir_nums(dirPath, input_dir_name, input_dir_count)
                    elif co.get() == 'No.1-No.xx':
                        mkdir_numbers(dirPath, input_dir_name, input_dir_count)
                    elif co.get() == '英語表記(One~)':
                        mkdir_eng_nums(dirPath, input_dir_name, input_dir_count)
                    elif co.get() == '漢数字(一~)':
                        mkdir_kansuuji(dirPath, input_dir_name, input_dir_count)
                    elif co.get() == 'ローマ数字(I~)':
                        mkdir_roman_numbers(dirPath, input_dir_name, input_dir_count)
                    else:
                        pass
                    text += dirPath
                else:
                    messagebox.showwarning("警告", "数字の表記が選択されていません")
            else:   
                messagebox.showwarning("警告", "最大、100個までのフォルダを作成することが可能です")
        elif not dirPath and not input_dir_name and not input_dir_count:
            messagebox.showerror("エラー", "何も入力されていません")
        else:  
            messagebox.showerror("エラー", "パスの指定がありません。")
```
まず、実行ボタンを押したときに、絶対パスが入力され、ディレクトリ作成個数が100個以下という条件だったときに、それぞれのディレクトリを作成する関数を呼び出して、複数のディレクトリを作成していきます。そのとき、入力したパスは文字列になるので、`text = ""`に代入することで、フォルダ作成メッセージを表示させています。

```py
    if text:
        messagebox.showinfo("フォルダ作成情報", "{}個のフォルダが作成されました!".format(input_dir_count))
```
![スライド3.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695597/a6f4f4d9-3a15-e88f-839b-c4ab7a2c0ffb.jpeg)


エラー処理については、演算子記号の**not**を用いて、3つの入力欄に何も入力していないときの処理として、「入力エラー」を表示させています。また、パスが入力され、フォルダ作成個数が100より大きければ、「ディレクトリ大量作成エラー」を表示させています。さらに、パスが入力され、100個以下の作成条件下でドロップダウンリストが選択されていないとき、「選択エラー」を表示させています。最後に、パス以外の入力がされているとき、「パスエラー」を表示させています。以下にエラー表示パターンを示します。
![メッセージ一覧1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695597/95ca3fdb-56bc-afc6-7464-300b5b4a743e.jpeg)
続いて、例外処理ですが、作成個数入力欄に半角英数字の数字以外を入力すると、ValueErrorが返されるので、ValueErrorが発生したときに、「半角英数字入力エラー」を表示させるようにしています。また、ディレクトリが重複した場合、FileExistsErrorが返されるので、FileExistsErrorが発生したときに、「ディレクトリ作成エラー」を表示させるようにしています。
以下にエラー表示パターンを示します。
![メッセージ一覧2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695597/863070b3-0f44-20ee-e0b9-2f8776aafb07.jpeg)

もっと、エラー処理について、簡単な書き方がある気がする、、、

最後に、作成ボタンやキャンセルボタンについてですが、以下のように処理をしています。

```python:作成ボタンコード
    button1 = ttk.Button(frame3, text="作成", command=conductMain)
    button1.pack(fill = "x", padx=85, side = "left")
```

```python:キャンセルボタンコード
    button2 = ttk.Button(frame3, text=("閉じる"), command=root.quit)
    button2.pack(fill = "x", padx=30, side = "left")
```
作成ボタンを押したときに、`command=`でイベントを発生させ、`conductMain`関数を呼び出しています。その後、条件によって、ディレクトリが作成されます。
また、キャンセルについては、ボタンを押したときに、`root.quit`でウィンドウを閉じています。

長くなりましたが、実装内容は以上となります。
説明が必要ないところも説明したかもしれませんがご容赦ください。

## つまずいたところ
### エラーハンドリング
どこを入力して、入力しないかで思いもよらなかった動作をするので、ちょっと大変でした。
エラーするたびに、条件を変えたり、メッセージを表示させたり、トライ＆エラーの繰り返しでした。

### ディレクトリを作成する関数
作成する関数の部分はちょっと間違えばCドライブ直下に大量にディレクトリが作成されるので、気を付けながら条件等を変えていました。

### アイコンの設定
tkinterでは

```py
root = tk.Tk()
iconfile = 'アイコンのパス'
root.iconbitmap(default=iconfile)
```

のように、記述すると、tkinterで表示される画面のアイコンは設定されるのですが、Pyinstallerでexe化したときにスクリプトが実行されず、アイコンも設定されないので、画像ファイルの拡張子をgifにしてBase64のデータを取得する必要がありました。なので、

```console:terminal
certutil -encode icon.gif text.txt
```
でCUI操作をして、iconデータをテキストファイルに書き出しました。Base64データをコピペしてコードに埋め込むことによって解決しました。また、実行ファイルのアイコンは通常のPyinstallerの操作では

```console:terminal
 pyinstaller ファイル名.py --onefile --noconsole
```
のように実行しますが、このままだとPythonの実行ファイルのアイコンが変えられないので、

```console:terminal
 pyinstaller gui_auto_mkdir.py --onefile --noconsole --icon=favicon.ico
```
のように` --icon=アイコンファイル`を付与しました。そうすることで、Tkinterで表示される画面と実行ファイルのアイコンを設定することが出来ました。

## 参考
- [【Python】tkinterでファイル&フォルダパス指定画面を作成する](https://qiita.com/dgkmtu/items/2367a73f7e2d498e6075)
- [tkinter --- Tcl/Tk の Python インタフェース](https://docs.python.org/ja/3/library/tkinter.html)
- [Tkinter 入門: 2. Widget を配置しよう](https://www.shido.info/py/tkinter2.html)
- [Tkinter による GUI プログラミング](https://python.keicode.com/advanced/tkinter.php)
- [仕事を自動化する!Python入門講座](http://www.sotechsha.jp/pc/html/1264.htm)

## 終わりに
最後まで、見ていただき、ありがとうございます。
皆さんの、作業効率化にお役に立てれば、嬉しいです。
今回、指定した数だけフォルダを作成する機能を実装しましたが、拡張機能としてチェックボックスを利用したフォルダ作成機能もこれから実装する予定です。