Python Tkinter の Treeview ウィジェットを用いて、画像ビューアを作成しました。
画像は Tkinter でサポートしている GIF、PNG に加え JPEG、WebP も表示します。
JPEG、WebP の表示と画像の情報取得には pillow を使用しています。
チェックボックスにチェックを付けてプレビューすることもできます。
ドラッグアンドドロップにも対応しています。
また、アプリ(exe)にファイルをドラッグアンドドロップすると、アプリが起動し画像を表示します。
画像表示の仕方や疑似チェックボックスの作り方、アプリの作り方をサンプルコードも交えて説明します。
アプリ(exe)も提供しています。
目次
- ◆アプリのサンプル画像
- ◆機能・特長
- ◆記事の構成
- ◆Treeviewで画像を表示
- ◆Treeviewに疑似チェックボックスの実装
- ◆ダブルクリック時の注意
- ◆Tkinterの使い方
- ◆ドラッグアンドドロップの実装(TkinterDnD2の使い方)
- ◆表の実装(Treeviewウィジェットの使い方)
- ◆スクロールバーの設置
- ◆ファイル選択ダイアログ
- ◆必要なパッケージ
- ◆全体のソース
- ◆バイナリ(アプリ)
- ■更新情報
- ◆さいごに
- ◆参考
◆アプリのサンプル画像
▽画像のサムネイルと画像情報を表示します
ブロガーの方はブログの画像を保存して余計な情報が出ていないか確認することができますよ。
※撮影条件を表示できるようにしました。
更新:2022-12-12
◆機能・特長
- GIF、PNG、JPEG、WebP ファイルを読み込みファイル情報を画面に表示
- 画像のサムネイルを表示
- 画像の Exif 情報、GPS 情報、撮影条件を表示
- チェックボックスを提供
- 行の高さを自動調整
- ドラッグアンドドロップでファイルを指定可能(TkinterDnD2使用)
- exeにドラッグアンドドロップでファイルを指定可能(TkinterDnD2使用でも)
- 元サイズの画像をダイアログ表示 更新:2022-12-12
◆記事の構成
こちらから目的の章へ移動できます。
- 画像の表示方法を知りたい
- 画像の Exif情報、GPS 情報、撮影条件の取得方法を知りたい
- 疑似チェックボックスの実装方法を知りたい
- ダブルクリック時の注意
- Tkinterの使い方から知りたい
- 画面をどう作っているか知りたい
- ドラッグアンドドロップの実装方法を知りたい
- Treeviewで表の実装方法を知りたい
- スクロールバーの設置方法を知りたい
- ファイル選択ダイアログの実装方法を知りたい
- 全体のソースをまとめて見たい
- アプリとして使いたい
更新:2022-12-12
◆Treeviewで画像を表示
Treeview に画像のサムネイルを表示する方法を説明します。
Treeview で画像を表示するには、ツリーカラムに表示します。
表形式の要素の中に画像を埋め込むことはできません。
ツリーカラムに画像を表示する機能は、そもそもファイルやフォルダをツー表示する時にアイコンを表示するために用意された機能なのではないかと思われます。
Tkinter で扱える画像ファイルの種類はPGM、PPM、GIF、PNG です。
メジャーな JPEG を扱うには、pillow パッケージが良く使われます。
ここでも pillow を使用して JPEG、WebP の画像も表示します。
◇画像を表示するには
画像を表示するには pillow のインストールと画像の読み込みを実装します。
- インストール:
pip install pillow
- インポート:
from tkinter import PhotoImage
from PIL import Image, ImageTk
- 画像オブジェクトの作成
- 画像を含めてデータを追加
◇画像オブジェクトの作成
Tkinter で扱う画像は、PhotoImage クラスのオブジェクトとして作成します。
Tkinter だけを使用する場合(PGM、PPM、GIF、PNG)
img = PhotoImage(file="sample.gif")
Tkinter と pillow を使用する場合(PGM、PPM、GIF、PNG、JPEG、WebPなど)
img = Image.open("sample.jpg")
(pillowのImageオブジェクト作成)
img = ImageTk.PhotoImage(img)
(TkinterのPhotoImageに変換)
どちらも pillow のクラスですまたは、
ImageTk.Photoimage(file="sample.jpg")
※縮小などを考慮するとpillowで加工してからTk用に変換した方が良い
さらに、画像ビューアとしてはサムネイルを表示させるため、元の画像を 150 ✖ 150 に変換します。
変換には thumbnail
メソッドを使用します。
thumbnail
メソッドは、アスペクト比を保ったまま変換します。
ただし、拡大はしません。
Treeview のツリーカラムは必ず左寄せで表示されます。
そのため、画像サイズに違いがあるとチェックボックス用のテキストの位置がずれてしまいます。
ずれないようにするため、画像のサイズを統一します。
ここでは、160 ✖ 160 の透明な画像を作成し、そこに縮小した表示したい画像を貼り付けます。
【処理】
- 画像ファイルを開く
Image.open()
- 縮小
thumbnail()
- 透明イメージの作成
Image.new()
- 余白の計算
- 貼り付け
paste()
【コード】
# 画像の取得 image1 = Image.open(file_name) # 縮小 image1.thumbnail((150, 150), Image.BICUBIC) # サムネイルの大きさを統一(そうしないとチェックボックスの位置がまちまちになるため) # ベース画像の作成と縮小画像の貼り付け(中央寄せ) base_image = Image.new('RGBA', (160, 160), (255, 0, 0, 0)) # 透明なものにしないとgifの色が変わる horizontal = int((base_image.size[0] - image1.size[0]) / 2) vertical = int((base_image.size[1] - image1.size[1]) / 2) base_image.paste(image1, (horizontal, vertical)) image1 = base_image
◇画像を含めてデータを追加
画像を含めてデータを追加する場合、insert()
メソッドを使用します。
既に存在する行に対して画像を指定するには、item()
メソッドを使用します。
ここでは、insert
メソッドについて説明します。
- 【構文】
treeview.insert(parent, index, iid=None, オプション)
サンプル
【構文】treeview.insert("", tk.END, image=画像オブジェクト, values=列データ (リスト))
引数
parent
:"" で最上位index
:tk.END で最後尾に追加iid
:=None(省略している)で iid の作成をお任せ
"I001" から順に割り当てる
- オプション
image
:画像(PhotoImage)オブジェクトを指定values
:列データ(リスト)を指定text
:ツリーカラムのテキストを指定tgs
:タグを指定open
:子アイテムの展開を指定
【コード】
コードでは、列データに row
、画像に images[i]
、タグ(背景色の切替)に tags1
、ツリー列の文字(チェックボックスの文字) self.check_str["umcheck"]
を指定してデータ追加しています。
# 要素の追加(image=はツリー列の画像、text=はツリー列の文字(疑似チェックボックス)) iid = tree1.insert("", tk.END, values=row, tags=tags1, image=images[i], text=self.check_str["uncheck"]) # Treeviewに1行分のデータを設定
◇ダイアログでプレビュー
画像をダイアログでプレビューできるように対応しました。
マウスの右ボタンでダブルクリックするとダイアログを表示します。
pillow のイメージオブジェクトであれば、show()
メソッドで表示することができます。
この時、画像ファイルに割りついているアプリが起動します。
本アプリでは、show()
メソッドを用いず、Tkinter のダイアログを使用してプレビューします。
画像は Toplevel オブジェクトにオプションで画像を指定したラベルウィジェットを配置するだけで表示できます。
マウスでダブルクリックされた行は、イベント情報からY座標(event.y)を取得し、identify_row() メソッドで iid を取得します。
【処理】
- ダブルクリックした行を特定
- Treeview からパスを取得
パスは第1列にあり改行されているので改行を削除 - モードレスでダイアログ作成
- PhotoImage オブジェクト作成
- ラベルのオプション image を使用してラベルを追加して pack
【コード】
▽バインド
self.treeview1.bind("<Double 3>", self.preview_image) # マウスを右ダブルクリックしたときの動作
▽ダイアログでプレビュー
def preview_image(self, event=None, path=""): if event: rowid = self.treeview1.identify_row(event.y) # マウスの座標から対象の行を取得する path1 = self.treeview1.item(rowid)["values"][0].replace("\n", "") # ファイル名取得 else: path1 = path # ダイアログ表示 dialog = tk.Toplevel(self) # モードレスダイアログの作成 dialog.title("Preview") # タイトル self.d_images.append(ImageTk.PhotoImage(file=path1)) # 複数表示する時のために画像を残す label1 = tk.Label(dialog, image=self.d_images[-1]) # 最後のものを表示 label1.pack()
◇画像の Exif 情報、GPS 情報、撮影条件の取得方法
pillow には Exif 情報を取得するメソッド getexif()
があります。
これは PIL.Image.Exif オブジェクトを返します。
PIL.Image.Exif オブジェクトは collections.abc.MutableMapping ( 辞書のようなもの ) を継承しています。
※_getexif()メソッドは古いメソッド(戻り値はdict)
Exif 情報はキー (TAGID) が数字なのでそのままでは読みずらく、理解しやすい文字に変換します。
変換には、pillow パッケージの ExifTags モジュールにある TAGS を使用します。
GPS 情報は、Exif 情報のタグ ID 34853
が該当します。
その内容を取得するには、PIL.Image.Exif オブジェクトの get_ifd()
メソッドを使用します。
GPS 情報もキーが数字なので Exif 情報と同様に変換して読みやすくします。
こちらは、pillow パッケージの ExifTags モジュールにある GPSTAGS を使用します。
▽タグの例
TAGID | 略称 | 日本語の説明 | 英語の説明 |
---|---|---|---|
256 | ImageWidth | 画像の幅 | Image width |
306 | ModifyDate | ファイル変更日時 | File change date and time |
34853 | GPSInfoIFDPointer | GPS タグ | GPS IFD pointer |
【処理】
- Exif のタグを取得
getexif()
- TAGS で読みやすい文に変換
- 改行でつないで表示用文字列を作成
- GPS 情報を取得
get_ifd(34853)
- GPSTAGS で読みやすい文に変換
- 改行でつないで表示用文字列を作成
【コード】
from PIL.ExifTags import TAGS, GPSTAGS # Exifタグ情報 # 画像の取得 image1 = Image.open(file_name) # Exif情報の取得 exif_dict = image1.getexif() exif = [TAGS.get(k, "Unknown")+ f": {str(v)}" for k, v in exif_dict.items()] exif_str = "\n".join(exif) # GPS情報の取得 gps_dict = exif_dict.get_ifd(34853) gps = [GPSTAGS.get(k, "Unknown") + f": {str(v)}" for k, v in gps_dict.items()] gps_str = "\n".join(gps)
◎撮影条件の取得方法
◆Treeviewに疑似チェックボックスの実装
ファイル選択などのためにチェックボックスを用意する方法を説明します。
Treeview にはチェックボックスを表示する機能やCheckbutton(チェックボックス)ウィジェットをデータに埋め込む機能はありません。
したがって、チェックボックスの機能を実現するには、疑似的にチェックボックスを作成します。
そのうち2つの方法を示します。(他にもあるかもしれませんがそこまで調査していません)
どちらも、チェック状態の画像(文字)とアンチェック状態の画像(文字)を用意してそれを切り替えることでチェックボックスを実現します。
どちらも表部分ではなくツリーカラムで実装します。
本アプリはツリー部分のイメージを画像のサムネイルに使用しているのでイメージで対応ができません。
それで、文字で実装します。
考え方は簡単でチェックボックス表示をトグルする動作を用意して操作にバインドします。
トグル動作では、item()
メソッドでチェックボックスの表示を切り替えます。
- 【構文】
treeview.item(item, option=None, オプション)
サンプル
【構文】treeview.item("I001", image=画像オブジェクト)
引数
item
:アイテムIDoption
:オプション名を指定するとオプション内容が返ります
Noneの場合はすべてのオプション
- オプション
image
:ツリーカラムに表示する画像(PhotoImage)オブジェクトを指定text
:ツリーカラムのテキストを指定
マウスでクリックされた行は、イベント情報からY座標(event.y
)を取得し、identify_row()
メソッドで iid を取得します。
【コード】
本アプリでは、文字(textオプション)を使用してチェックボックス状態の変化を表現しています。
▽バインド
# bind self.treeview1.bind("<Button 1>", self.togle_checkbox) # マウスを左クリックしたときの動作
▽トグル動作
check_str = {"uncheck":"☐", "checked":"☑"} # ☐☑☒チェックボックス用文字 def togle_checkbox(self, event=None): rowid = self.treeview1.identify_row(event.y) # マウスの座標から対象の行を取得する if self.treeview1.item(rowid, text=None) == self.check_str["uncheck"]: self.treeview1.item(rowid, text=self.check_str["checked"]) else: self.treeview1.item(rowid, text=self.check_str["uncheck"])
◆ダブルクリック時の注意
Tkinter でマウスのダブルクリックとシングルクリックを別の関数にバインドしている場合、次のような注意が必要です。
Tkinter では、ダブルクリックのイベントが発生した場合、シングルクリックのイベントも発生します。
したがって、それぞれのイベントにバインドした関数があり、動作がダブルクリックの場合には、シングルクリックにバインドした関数も動作することに注意が必要です。
その場合の解決方法は3つ
何もしない
クリック用関数の動作がダブルクリック時に見えないことが多いため
例:クリックでカーソル位置を指定、ダブルクリックで文字選択クリック動作を元に戻す
ダブルクリック用関数でシングルクリック動作を元に戻す
ダブルクリックの前半かどうか判断する
シングルクリックイベントでダブルクリックイベントの発生を待ち、
ダブルクリックが発生してから共通の処理(中身は分ける)を実行する
※ほとんどのユーザはこの遅延は煩わしい
※ソースコードにはこの対応方法がコメントで残してあります。
本アプリでは開発中に2、3の方法を試しましたが、ユーザーが許容できる見た目ではないと判断して、ダブルクリックをマウスの右ボタンに変えました。
◆Tkinterの使い方
Tkinter(ティーキンター)は Python が標準で提供している GUI パッケージです。
他のプログラムの開発ツールで提供されているようなルック&フィールな GUI ツールではありません。
従って、画面を構成するには、すべてコードを書く必要があります。
Tkinter を使用するには import が必要です。
import tkinter as tk
慣例的に tk と別名を付けるようです。
◇初歩的な使い方
ウィンドウを出すだけのサンプルです。
Python を起動して次の命令を入れるとウィンドウが出てきてアプリらしさをちょっとだけ体験できます。
import tkinter as tk #tkと省略することが慣例のようです win = tk.Tk() #ウィンドウの作成と表示 win.title("Hello, World") #タイトル設定 win.geometry("400x300") #ウィンドウサイズ設定 lbl = tk.Label(win, text="ラベル") #ラベルウィジェットの作成 lbl.pack() #ラベルウィジェットの配置 win.mainloop() #イベント待ち
◇Tkinterの使用ウィジェット
Tkinter がサポートする GUI の部品をウィジェット (widget) と呼びます。
ここでは次のウィジェットを使用しています。
これらの使い方も説明します。
- セルの表示には Treeview ウィジェットを Listview 形式で使用
- スクロールバーの表示には Scrollbar ウィジェットを使用
- 1行おきに背景色を設定するには tag 機能(Treeview ウィジェットの一部)を使用
◇使用ウィジェット
ウィジェットは基本的にインスタンスを作成し、表示テキストや表示方法などを指定して使用します。
今回使用しているウィジェットです。
パッケージ | ウィジェット名 | 用途 | 見た目 |
---|---|---|---|
tkinter | Frame フレーム |
ウィジェットの受け皿 | |
tkinter | Label ラベル |
文字の表示 | |
tkinter | Button ボタン |
押すと処理が動くボタン | |
tkinter | Scrollbar スクロールバー |
画面をスクロール | |
tkinter.ttk | Treeview ツリービュー |
ツリー表示やリスト表示 ここではリスト表示で使用 |
◎ウィジェットに変数を関連付ける
ウィジェットの値と連動して変更される変数をウィジェット変数と呼びます。
今回は、ファイルパス用リストボックス、文字コード選択用コンボボックス、メッセージ表示用ラベルにウィジェット変数を関連付けます。
ウィジェット変数とウィジェットを関連付けるには、ウィジェットの属性 variable, textvariable にウィジェット変数を指定します。
ウィジェット変数のコンストラクタに value= 引数で初期値を与えられます。
ウィジェット変数の値の読み取りは get() メソッドで、値の書き込みは set() メソッドで行います。
- ウィジェット変数 (抜粋)
- StringVar
- IntVar
他に、DoubleVar, BooleanVar があります。
【コード】
self.msg = tk.StringVar(value="msg") self.lbl_msg = tk.Label(parent , textvariable=self.msg , justify=tk.LEFT , font=("Fixedsys", 11) , relief=tk.RIDGE , anchor=tk.W)
◇画面表示の特徴
画面表示には次の特徴があります。
◇画面構成 - フレーム構造
プログラムを作るにあたって、どのような画面にするか決めなくてはなりません。画面構成を考えるということですね。
画面を構成するというのは、GUI 部品(ウィジェット)を画面に配置することです。
ウィジェットは設定したテキストに合わせてサイズが決まるので、そのまま使用しても画面構成上問題ないものと、広げないと見栄えが良くないものが出てきます。
ウィジェットを広げる場合、他のウィジェットとの位置関係で思ったように配置できないことがあります。
そのようなときに Frame ウィジェットを使用して区分けをして希望する位置に配置します。
引き出しの整理に使うトレイのようなイメージでしょうか。
今回の画面構成です。
- u_frame(frame)
- btn_f_sel(Button)
- btn_select_all(Button)
- btn_deselection(Button)
- btn_preview(Button)
- lbl_msg(Label)
- b_frame(frame)
- frame1(Frame)
- treeview1(Treeview)
- h_scrlbar(Scrollbar)
- v_scrlbar(Scrollbar)
- frame1(Frame)
※「u_frame」などはプログラムで使用した変数名です。
※図形の中の矢印はこの後説明するオプション fill の方向です。
入力をする部分と出力をする部分で大きくフレームを分けています。
結果を表示する部分は余った領域をすべて使用してできるだけ大きく表示させるためです。
◇ウィジェットの配置 - pack
ウィジェットはインスタンスを作成した後に配置 (pack) をしないと表示されません。
配置には他に grid と place がありますが、基本的な pack を使用します。
pack とは、スーツケースに荷物をパッキングするようなイメージなんじゃないかなと勝手に思っています。
ウィジェットを pack すると side オプションで指定された方向から配置します。
その時に配置した方向と直行する方向には可能な限りの領域を割り当てます。
例えば、デフォルトの TOP で配置すると直行する横方向に可能なだけ領域を確保します。横一杯になるということですね。
ただし、これは領域の話で、実際のウィジェットの表示は他のオプションに依ります。
【構文】
ウィジェット.pack(オプション1 = 設定値, オプション2 = 設定値,・・・)
【主なオプション】
オプション 説明 設定値 side 次の方向から詰め込む TOP:上(デフォルト)、BOTTOM:下、LEFT:左、RIGHT:右 fill 割り当て領域内での引き伸ばし NONE:なし(デフォルト)、X:水平方向、Y:垂直方向、BOTH:両方向 expand 割り当て領域の詰め込み方向への拡大 True:する、False:しない(デフォルト)
その他のオプション
オプション | 説明 | 設定値 |
---|---|---|
after | 指定ウィジェットの後に配置 | 他のウィジェット |
before | 指定ウィジェットの前に配置 | 他のウィジェット |
anchor | 割り当て領域内の配置位置 | NW,N,NE,W,CENTER,E,SW,S,SE(方角) |
in | 配置する親 | 親 |
ipadx,ipady | 内部に空ける間隔 | 間隔(単位無しはピクセル) |
padx,pady | 外側に空ける間隔 | 間隔(単位無しはピクセル) |
pack で「よく表示されない」という問題が発生しますが、私もそうでした、pack の順番を変えてあげることで解決する場合があるようです。
必ずではないようですが、配置の時に次のことを考慮しておくと良いみたいです。
- expand オプションを True に指定して pack するウィジェットは、後から pack する。
ウィジェットの pack についてはこちらのサイトが詳しいです。
◇動作をウィジェットに割り当てる - バインド
ウィジェットで何か操作した場合にそれに連動して動作するメソッドを割り当てることができます。
この動作(イベント)とメソッドを割り当てることをバインドと呼びます。
イベントには、キー入力可能なウィジェットでキー入力した場合やマウス操作した場合、コンボボックスで選択肢を選択した場合などがあります。
◎マウスバインドキーバインド
コンボボックスはエントリーを継承しているのでキー操作後に動作する処理を割り当てられます。
コンボボックスで Enter を押した時にCSV ファイルを読み込むようにバインドします。
マウスの左ボタンのクリックと右ボタンのダブルクリックに対して、それぞれチェックボックスのトグルと画像のプレビューを割り当てます。
【構文】
ウィジェット.bind(シーケンス, コールバック関数, 追加フラグ)
引数
▽バインド
# bind self.treeview1.bind("<Button 1>", self.togle_checkbox) # マウスを左クリックしたときの動作 self.treeview1.bind("<Double 3>", self.preview_image) # マウスを右ダブルクリックしたときの動作
※引数が必要な場合はラムダ式を使用します。(詳細は省略)
▽コールバック関数
def togle_checkbox(self, event=None): """ チェックボックスの状態を反転 """ def preview_image(self, event=None, path=""): """ 画像のプレビュー ダイアログ表示
◆ドラッグアンドドロップの実装(TkinterDnD2の使い方)
◆表の実装(Treeviewウィジェットの使い方)
◆スクロールバーの設置
◆ファイル選択ダイアログ
◆必要なパッケージ
◇必要な Python パッケージ
- pillow
- TkinterDnD2
◆全体のソース
全体のソースはこちらから取得できます。
取得先:GitHub juu7g/Python-Image-Viewer
◆バイナリ(アプリ)
pyinstaller
でバイナリ( exe
) ファイルを作成しました。
- 作成コマンド:
pyinstaller -F -w --additional-hooks-dir . ファイル名
※実行にはhook-tkinterdnd2.py
ファイルが必要です(作り方は別記事の「pyinstaller使用時の注意」を参照)
◇バイナリ取得先
バイナリも公開します。
こちらから取得してください。Github からダウンロード
◇使い方
インストール
ダウンロードした zip ファイルを任意のフォルダで解凍します
実行
- image_viewer.exe を実行します
- または image_viewer.exe のアイコンに表示したいファイルをドラッグアンドドロップします
操作
- ドラッグアンドドロップでの操作
- アプリ画面上の任意の位置に表示したいファイルをドラッグアンドドロップ
- ファイル選択での操作
- ファイル選択ボタンをクリックしファイルを選択
- 画像の表示
- 表示したい画像の行で右ボタンでダブルクリックします
- 表のチェックボックスをオン(行をクリックで切り替わる)にして「プレビュー」ボタンをクリック
- 画像の選択(チェックボックスのオン/オフ)
- 選択したい画像の行でマウスクリック
再度クリックすると選択解除 - 「すべて選択」ボタンをクリックするとすべての画像を選択
- 「選択解除」ボタンをクリックするとすべての画像の選択を解除
- 選択したい画像の行でマウスクリック
- ドラッグアンドドロップでの操作
画面の説明
アンインストール
解凍したファイルをすべて削除します
◇使用上の注意
制限事項があります。
- 制限事項
- 表示できるのはファイルの拡張子が次のものだけです
png, jpg, gif, webp - プレビューしたウィンドウを開いたままメインウィンドウで別の画像を開いた場合、プレビューウィンドウの画像が消えます
- 表示できるのはファイルの拡張子が次のものだけです
■更新情報
- 2022-12-12
- Exifの撮影条件を表示
◆さいごに
画像ツールのベースにするために画像ビューアを作成しました。
はじめは画像無しでファイラーにしようかと思ったのですが、画像用ファイラーで画像が見えないのは使えないなと思い、画像も表示することにしました。
Tkinter の Treeview で表の中に画像を埋め込むことができないと分かり、調べるとツリーカラムに表示できると分かり対応しました。
難しくないと思ったのですが、なかなか画像が出ず、列幅の設定や画像オブジェクトの持ち方などを試行錯誤して表示できるようになりました。
この記事を読んだ方がそんな苦労をしないことを祈ります。
基本的な機能を紹介されているサイトは多いです。いつも参考にさせていただいて助かっています。
でも、組み合わせるとまた別の苦労が始まります。
そんな苦労の一助になれば幸いです。
◇ご注意
本記事は次のバージョンの下で動作した内容を元に記述しています。
- Python 3.8.5
- Pillow 8.3.0
- TkinterDnD2 0.3.0
◇免責事項
ご利用に際しては、『免責事項』をご確認ください。
お気づきの点がございましたら『お問い合わせ』からお問い合わせください。
追加:2022-12-12
◆参考
- Tkinter解説:Python:tkinter.(ttk.)Widget【ウィジェット関連】 - リファレンス メモ
- Tk解説:tk_getOpenFile manual page - Tk Built-In Commands
- ダブルクリック:python - Tkinter binding mouse double click - Stack Overflow
- ダブルクリック:Binding to a single mouse click
- Tkinter画像:Tkinter の使い方:PhotoImage・BitmapImageクラスで画像を扱う
- Treeview画像表示:Python Tkinter Treeview add an image as a column value
- JPG表示:Pythonスクリプト【tkinterでJPEG画像を表示しよう】
- Treeview checkbox(動画):Checkboxes in Treeview Table Tkinter - Python [Best Video] - YouTube
- Exif情報:Exif 2.3 タグ - Vieas Web
- pillow 仕様:Pillow — Pillow (PIL Fork) 8.4.0 documentation