at backyard

Color my life with the chaos of trouble.

tkinterでノートアプリを書いた

tkinterでシンプルなノートアプリ(メモ帳)を書いてみた

f:id:shinshin86:20220204081849p:plain
tkinterで書いたノートアプリ

年末辺りから集中的にtkinterを使ってデスクトップアプリを作成していた。

tkinterについて触るのは初めてだったので色々と学びつつアプリ作成をしていたが、せっかく学んだのだから何かしら公開できるシンプルなアプリを書いてみようと思い、余暇に趣味感覚でノートアプリを書いた。

ソースコードGitHubにあげている。

github.com

目次

長くなるので目次をおいておく

環境や利用しているライブラリなど

なるべく外部ライブラリは利用しないで作成しようと決めていたので、利用した外部ライブラリ(pip install するやつ) は実行ファイル化するために使用したcx_Freezeだけのような気がする。

一応開発時の環境などの下記に箇条書きで残しておくと、

  • Python 3.10.1 (pyenvで構築)
  • tkinter.TkVersionは 8.6
  • 私の開発マシンはM1 MacBook Airだが、そのままではcx_Freezeが動かないので、cx_Freezeを動かすときのみ、 miniforge3-4.10.3-10pyenvを使って切り替えている。

tkinterについてだが、pyenv経由でのPythonインストールやmacOS 10.6以降のユーザでMacPythonインストーラを使っている場合、日本語が入力できないケースがある。tkinterが依存しているTcl/Tkパッケージのバージョン 8.5 で起きるもので、こちらの内容については下記のzennの記事に対応策を書いている。

zenn.dev

またM1環境でcx_Freezeを利用するためのアレコレについては下記にまとめた。

shinshin86.hateblo.jp

その他、tkinterについて触りはじめの頃に基本的なアレコレを下記のポストにまとめているので、こちらも併せてはらせていただく。

shinshin86.hateblo.jp

実装について

実装については直接ソースコードを見ていただくのが早いとは思うが、一応自身の備忘録的な意味でも実装時に記憶に残ったところを書き残しておこうと思う。
(といってもすでに結構忘れてきてしまっているので、自身でもソースコードを見返しながら備忘録として残しておきたいところを抜粋している形だ。)

設計について

このようなGUIアプリを普段書く機会はほとんどなく、設計をどうするかちょっと考えた。
(といっても趣味アプリなので正直そこまで真剣には悩んでいない)

ノートアプリのデータはSQLiteに保存するような作りとなっているので、DBとのやり取りを担当するmodelと表示に関するviewという2つのディレクトリを作成して、それぞれに処理を書くようにした。

本当はMVCのような形でアプリの処理を制御するようなcontrollerも作成しようと思ったのだが、それらの処理は結局 main.py の中にまとまっている。分離させようと思ったときもあったが、自身のPython力が足りず、そのままとなった形だ。

アプリの実行は main.py からスタートし、DBとUIの初期化を行う。

UIの処理について

viewディレクトリでUIを担当すると書いたが、基本的にview側は

  • 与えられたデータの表示(と書いたが、描画制御などについては main.py 側で書いているな...)
  • UIに対して実行されたアクションに対する処理の実行

の2つだけを実装している。

UIの配置について

UIの配置についてだが、今回のアプリでは ttk.Labelframe を作成し、その中に各種コンポーネント(という言い方が正しいのかはわからない)を格納していくようにした。

配置する際、細かな調整は不要だったのでpackを利用してtkinter側でよしなに配置してもらうようにしている。

tkinter上で実装したイベントハンドラ(command)

おそらくこれはよく利用される形かと思うが、下記はUpdateボタンの記述となる。

self.update_button = tk.Button(self.menu_frame, text="Update", command=lambda: self.on_click_update_button(save_note_with_title))

このボタンが押された場合、commad内に記載された関数や引数を利用して処理を行っている。

仮に引数を渡さない場合、 command=関数名 だけでも良いのだが、引数を渡したい場合 command=lambda: self.on_click_update_button(save_note_with_title) というようにlambda:をつけてその後の処理を実装していく必要がある。

Listboxのクリックを取得する処理について(curselection)

f:id:shinshin86:20220204100137p:plain
画面左側はListboxを利用している。

今回のアプリでは画面左側にListboxを利用して、ノートの一覧を表示させるようにしている。
この一覧をクリックした際の取得処理には curselectionを利用している。

この curselection を利用すると、クリックした一覧の位置を取得できる。
これを利用して下記のように取得した一覧項目の値を取得する処理を書いた。

    def on_click_note_list(self, event):
        if self.note_list.curselection():
            self.note_list.select_note(self.note_list.get(self.note_list.curselection())

※ 最後に渡しているselect_note は指定されたノートタイトルをもとに、DBから対象となるノート情報を取得する処理となる。

現在選択中のListboxの値を取得する方法(tk.ACTIVE)

次はListboxで現在選択されている項目を取得する方法についてだが、これは今回のアプリでは self.note_list.get(tk.ACTIVE) とやることで取得できる。

Listboxで持っている get メソッドに tk.ACTIVE(中身は active という文字列) を渡すことで現在選択されている値を取得することができる。

    def on_click_update_button(self, update_note_with_title):
        update_note_with_title(self.note_list.get(tk.ACTIVE))

update_note_with_title というのはノートタイトルをもとに対象となるノート情報を更新する処理となる。

一旦ここで休憩

ここまで書いてスタミナが尽きたので、続きはまた後日追記していきます。