PySimpleGUIでVBAの代わりになるUIをつくってみる（ファイルダイアログ、リスト、ログの出力）

# この記事を読んでできるもの

![結果.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11002/4dc656bc-d0f7-21e5-f9a5-eb8976f1a79f.jpeg)

PySimpleGUIを使用して、ファイル、フォルダをダイアログを使ってファイルを取得、
取得したファイルを使用して処理を実行、ログを画面にすることができます

# 概要
脱VBAとしてPythonを使用している記事や本がちらほらあり、Excelファイルの操作に[OpenPyXl](https://openpyxl.readthedocs.io/en/stable/index.html) を使用している記事はありますが、ファイルの起動に関してはCLIのものがほとんどでGUIで操作する記事はあまり見かけません。
今回はVBAのGUI相当のものをPySimpleGUIで行う説明を行います。
PySimpleGUIの基本的な操作については[Tkinterを使うのであればPySimpleGUIを使ってみたらという話](https://qiita.com/dario_okazaki/items/656de21cab5c81cabe59)を参考にしてください。



例えばエクセルで複数ファイルを読み込んで１ファイルにまとめる操作を行う場合には以下の動作および表示が必要かとと思います。

1. ファイルを複数選択して読み込む
2. 選択したファイル一覧を表示
3. オプションの選択
4. 実行
5. 実行結果のログ出力

コードにすると以下の内容になります。なお今回はエクセル操作の機能は載せていません。
あくまでもGUI部分の説目のみになります。

```python
# 元のファイル　https://pysimplegui.trinket.io/demo-programs#/examples-for-reddit-posts/visual-basic-mockup

import PySimpleGUI as sg
from os.path import basename

frame1 = [[sg.Radio('1ファイルにシートごとにまとめる', 1, key='-MULTI-SHEET-', default=True)],
          [sg.Radio('1ファイル1シートにまとめる', 1, key='-ONE-SHEET-')]]

col1 = [[sg.Button('実行')],
        [sg.Button('終了')]]


layout = [[sg.Text('ファイル選択', size=(15, 1), justification='right'),
          sg.InputText('ファイル一覧', enable_events=True,),
          sg.FilesBrowse('ファイルを追加', key='-FILES-', file_types=(('Excell ファイル', '*.xlsx'),))],
          [sg.Button('ログをコピー'), sg.Button('ログをクリア')],
          [sg.Output(size=(100, 5), key='-MULTILINE-')],
          [sg.Button('入力一覧をクリア')],
          [sg.Listbox([], size=(100, 10), enable_events=True, key='-LIST-')],
          [sg.Frame('処理内容', frame1), sg.Column(col1)]]

window = sg.Window('エクセルの結合', layout)

new_files = []
new_file_names = []

while True:             # Event Loop
    event, values = window.read()
    if event in (None, '終了'):
        break

    if event == '実行':
        print('処理を実行')
        print('処理対象ファイル：', new_files)

        # ラジオボタンの値によって処理が変わる
        if values['-MULTI-SHEET-']:
            print('複数シートを１ファイルにまとめる')
        elif values['-ONE-SHEET-']:
            print('複数シートを１シートにまとめる')

        # ポップアップ
        sg.popup('処理が正常終了しました')
    elif event == 'ログをクリア':
        print('ログをクリア')
        window.FindElement('-MULTILINE-').Update('')
    elif event == 'ログをコピー':
        window.FindElement('-MULTILINE-').Widget.clipboard_append(window.find_element('-MULTILINE-').Get())
        sg.popup('ログをコピーしました')
    elif event == '入力一覧をクリア':
        print('入力一覧をクリア')

        new_files.clear()
        new_file_names.clear()
        window['-LIST-'].update('')
    elif values['-FILES-'] != '':
        print('FilesBrowse')

        # TODO:実運用には同一ファイルかどうかの処理が必要
        new_files.extend(values['-FILES-'].split(';'))
        new_file_names.extend([basename(file_path) for file_path in new_files])

        print('ファイルを追加')
        window['-LIST-'].update(new_file_names)  # リストボックスに表示します

window.close()
```

以下それぞれの機能のついての紹介です

## ファイルを複数選択して読み込む
PySimpleGUIでファイルを指定するには以下の3つのボタン（メソッド）があります。

- FolderBrowse()
    - フォルダを読み込み
- FileBrowse()
    - ファイルを一つ読み込む
- FilesBrowse()
    - 複数ファイルを一つ読み込む


動作的には、レイアウトに追加するとボタンが表示される、クリックするとダイアログが表示される、ダイアログ内で選択したファイルが表示されます。
選択したファイルは絶対パスで取得できます。

以下コードの例です。なお今回のコードは公式の[Visual Basic Mockup](https://pysimplegui.trinket.io/demo-programs#/examples-for-reddit-posts/visual-basic-mockup)を参考に機能追加を加えています。

```python
import PySimpleGUI as sg

sg.InputText('ファイル一覧',enable_events=True,), sg.FilesBrowse('ファイルを追加', key='-FILES-', file_types=(("Excell ファイル", "*.xlsx"),))],
```

`FilesBrowse`で複数ファイルを取得した場合は、値は「ファイル1の絶対パス;ファイルの絶対パス;」となっています。`values['-FILES-'].split(';'))`というようにするとファイルを分割して取得できます

### ドラッグ＆dドロップについて
Pythonに付属しているtkinterにはドラッグ＆ドロップの機能は標準ではついていません。
自分で拡張機能をインストールするとできるようになるようです。

- [How can I create a Drag and Drop interface?](https://stackoverflow.com/questions/44887576/how-can-i-create-a-drag-and-drop-interface)

PySimpleGUIはtkinterのラッパーですのでドラッグ＆ドロップの機能はないです。
ただPythonの3.9のドキュメントを見ると以下のページがあるので次期バージョンの3.9では
Python付属のtkinterでもドラッグ＆ドロップができるようになってPySimpleGUIでドラッグ＆ドロップが機能追加されるようになるかもしれません

- [tkinter.dnd --- Drag and drop support](https://docs.python.org/dev/library/tkinter.dnd.html)

2020/2/6更新　公式に教えてもらいました。tkinterのドラッグ＆ドロップはアプリ内のドラッグ＆ドロップのみ対応だそうです。
エクスプローラーのドラッグ＆ドロップに関してはPySimpleGUIQtが対応しているとのことでした。 

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">I think the tkinter drag and drop support is only within the application. I don&#39;t see how you can drag and drop a file from Windows explorer for example, which is the most likely source of a drag and drop. <br><br>It&#39;s supported in PySimpleGUIQt for the Input and Multiline elements.</p>&mdash; PySimpleGUI (@PySimpleGUI) <a href="https://twitter.com/PySimpleGUI/status/1228818322747281408?ref_src=twsrc%5Etfw">February 15, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
# 選択したファイル一覧を表示
選択したファイルを一覧表示するのに、今回はリストボックスを使って表示します。 PySimpleGUIでは`Listbox()`を使って表示します。

```
[sg.Listbox([], size=(100, 10), enable_events=True, key='-LIST-')],
```

入力された内容をリストボックスにするには`window`クラスの`update()`メソッドを使いっ更新を行います

```
window['-LIST-'].update(new_file_names)  # リストボックスに表示します
```

## オプションの選択

オプションの選択にはラジオボタンを使って実装しています。

```
frame1 = [[sg.Radio('1ファイルにシートごとにまとめる',1, key='-MULTI-SHEET-', default=True)],
          [sg.Radio('1ファイル1シートにまとめる', 1, key='-ONE-SHEET-')]]
```

実行時にラジオボタンのどの値が選択されているかは`key`に設定してる値が`True`かどうかで判定しています。

```py
if values['-MULTI-SHEET-']:
    print('複数シートを１ファイルにまとめる')
elif values['-ONE-SHEET-']:
    print('複数シートを１シートにまとめる')
```

## 実行結果のログ出力
画面に実行内容を表示するには`Output()`エレメントを使います。これを配置すると`print()`で記載した内容が出力されます

```py
[sg.Output(size=(100,5), key='-MULTILINE-'),],
```

### クリップボードにコピー
クリップボード関連はtkinterの以下のメソッドを使用します

- `widget.clipboard_get()`
    - クリップボードに格納している値を取得します。
- `widget.clipboard_clear()`
    - クリップボードの内容を削除します。
- `widget.clipboard_append()`
    - クリップボードに値を追加します。
   

今回は`clipboard_append`を使って`Output()`に出力されたログの値をコピーします。

```py
window.FindElement('-MULTILINE-').Widget.clipboard_append( window.FindElement('-MULTILINE-').Get())
```

# まとめ
PySimpleGUIでVBA相当のGUIを作る方法を紹介してみました。
ファイルをダイアログを開いて選択する。選択したファイルに処理を行うことができるGUIは応用が利くかと思います。