pythonによるGUIアプリ テキストエディタの作成

# テーマ：fletを利用したGUIアプリケーションの作成

このようなテキストエディタを作成します。

![画像1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3809517/8ff2c16c-a2ab-e635-03ae-611158efd8a8.png)

前回の記事を参考にして頂けるとスムーズです。

# １．ファイルの書き込み
## with句
```python:python
        with open(path, "w", encoding="utf-8") as f:
            # fにtext_areaに記述されているテキストの値を受け取り、書き込む
            f.write(text_box.value)
```
with句を使うことによって、ファイルへの書き込みが行えます。

ファイルの書き込み時には、終了処理を行わなければならないのですが、with句を使うことで自動的に終了処理が行われます。

## 全体のコード

```diff_python:example01.py
# as を使うことでftとして呼び出す
import flet as ft

def main(page):
    
    page.title = "テキストエディタ"
    
    # ファイルの保存を行う関数
    def save_file(e):
        # ファイルの場所や保存名（ファイルパス）を指定
        path = "example.txt"
        # file_nameで設定したファイルパスを受け取り、書き込みモードで実行
        # With文を使うことで開始と終了を自動で処理、as fで以降はfとして扱う
        with open(path, "w", encoding="utf-8") as f:
            # fにtext_areaに記述されているテキストの値を受け取り、書き込む
            f.write(text_box.value)
    
    # 保存ボタンクリック時にfile_save関数を呼び出す
    save_button = ft.ElevatedButton("保存",on_click=save_file)
    
    # テキストフィールドを作成
    text_box = ft.TextField(label="テキストをここに入力",multiline=True,min_lines=20)
    
    page.add(save_button,
             text_box)

ft.app(target=main)
```
## ファイル名の指定

さらにファイル名を指定できるようにします。

テキストフィールドの値を取得し、ファイル名としています。

```diff_python:example02.py
import flet as ft

def main(page):
    
    page.title = "テキストエディタ"
    
    def save_file(e):
        # テキストフィールドに入力されている値を取得し、suddix_textの値を加え、ファイル名とする。
        # なお、カレントディレクトリを基準にパスごとファイル名を指定できるが、ここでは特に触れない
        with open(text_file_name.value + text_file_name.suffix_text, "w", encoding="utf-8") as f:
            f.write(text_box.value)
    
    save_button = ft.ElevatedButton("保存",on_click=save_file)
    
    # ファイル名を指定するテキストフィールドを作成
    text_file_name = ft.TextField(label="ファイル名",suffix_text=".txt")
    
    text_box = ft.TextField(label="テキストをここに入力",multiline=True,min_lines=20)
    
    page.add(save_button,
             # テキストフィールドを配置
             text_file_name,
             text_box)

ft.app(target=main)
```
## エラー処理

try節の内容が実行できなければ、except節の内容を実行するというものです。

```diff_python:example03.py
import flet as ft

def main(page):
    
    page.title = "テキストエディタ"
    
    def save_file(e):
        # tryの内容が実行できなければException(エラー)がraiseされますが、
        # 別途if notの部分が実行された場合も明示的にエラーを発生させてます。
        try:
            # ここではファイル名に何かが入力されているかどうかの判定しており、strip()で空白(スペース)を削除します。
            # notと書くことでデータが空である場合にExceptionをraiseします。
            if not text_file_name.value.strip():
                raise Exception
                
            with open(text_file_name.value + text_file_name.suffix_text, "w", encoding="utf-8") as f:
                f.write(text_box.value)
                # 保存後の処理として、メッセージを表示します。
                text_file_name.helper_text = text_file_name.value + text_file_name.suffix_text + "を保存しました"
                page.update()
                
        # エラー発生時の処理、Exceptionはpythonが出力するエラーの内容となります。
        except Exception as e:
            text_file_name.helper_text = "エラーが発生しました。" + str(e)
            page.update()
            
    
    save_button = ft.ElevatedButton("保存",on_click=save_file)
    
    # 後ほどhelper_textの値にエラー文などを表示させる。
    text_file_name = ft.TextField(label="ファイル名",suffix_text=".txt",helper_text="")
    
    text_box = ft.TextField(label="テキストをここに入力",multiline=True,min_lines=20)
    
    page.add(save_button,
             text_file_name,
             text_box)

ft.app(target=main)
```
## OS標準のファイルダイアログを利用する

WindowsやMacではファイルを開く時にOSの機能であるファイルダイアログが開きます。これをアプリケーション側から呼び出し、ファイルパス等の情報を受け取ります。

ボタンをクリックするとファイルダイアログを呼び出し、その戻り値にであるファイルパスやファイルデータに対して、処理を行います。つまり、呼び出し側と受け取り側の２つの関数が必要になるということです。
ファイルピッカーは変数に代入しておき、呼び出し時には呼び出し用のメソッドpick_files()を適用しています。

通常はラムダ文などを使って簡潔に書く（公式ドキュメントではそうなってます）と思いますが、ラムダ文の書き方についての前提知識が必要になりますのではここでは関数型で記述していきます。

実際には公式ドキュメントが動くものを提供しているので、基本的はそのまま使うのがおすすめです。

https://flet.dev/docs/controls/filepicker/

```diff_python:example04.py
# WindowsやMac等のOS搭載のファイルダイアログを呼び出し、その返り値をパスやファイルデータとして受け取ります。
import flet as ft

def main(page):
    # 選択したファイルを読み取っていきます。
    def pick_files_result(e):
        # if e ではダイアログから受け取ったデータ自体があるかないかをif文で判断しています。
        if e:
            # e.files[0]が最初のデータになります。単体のファイルを選択しているので、e.files[1]は存在しません。
            # .path でファイルパスをtext_file_name.valueに代入し、画面に表示させます。
            text_file_name.value = e.files[0].path
            page.update()
        
        # if e.files[0].pathではファイルパスのデータがあるかないかを判断しています。
        if e.files[0].path:
            # データがあれば、with openの読み込みモード"r"でファイルを開き、fとします。
            with open(e.files[0].path,"r", encoding="utf-8") as f:
                # text_box.valueの中に読み込んだデータを代入します。
                text_box.value = f.read()
            page.update()
    
    # ボタンから呼び出されたpick_files_dialogに対して、pick_files()というメソッド（処理を）行うことで、
    # ダイアログを呼び出します。ダイアログを呼び出すだけですが、ファイルを選択する部分はOS側でやりますので、
    # 呼び出すだけの動作が必要になるわけです。
    def open_pick_files_dialog(e):
        pick_files_dialog.pick_files()

    # fletからファイルピッカー（ダイアログを呼び出すための部品）を呼び出してpick_files_dialogに格納します。
    # 「on_result=」のオプションで、ファイルピッカーが呼び出されたときの処理をpick_files_resultに指定します。
    pick_files_dialog = ft.FilePicker(on_result=pick_files_result)
    
    # 「開く」ボタンの作成します。ボタンをクリックした際の処理を、open_pick_files_dialogに指定します。
    pick_button = ft.ElevatedButton("開く",on_click=open_pick_files_dialog)
    
    text_file_name = ft.Text()
    
    text_box = ft.TextField(label="テキストをここに入力",multiline=True,min_lines=20)

    page.overlay.append(pick_files_dialog)

    page.add(
        pick_button,
        text_file_name,
        text_box
    )

ft.app(target=main)
```
## まとめ
中途半端ではありますが、ここまでとします。

何かの足しになれば幸いです。