プログラムでおかえしできるかな

定年を過ぎて何かの役に立てないかなと始めた元SEのブログです

はてなブログ向け画像ツールの作り方【Python】

このエントリーをはてなブックマークに追加

はてなブログ向け画像ツール 公開した、はてなブログに掲載する写真や画像を変換して、アップロードして、画像のURLをコピーできるアプリの作り方を紹介します。
公開している独自ライブラリの使い方などを説明します。

アプリはお使いいただけます アプリ(バイナリ)を使ってみたい方は、こちらの記事から取得できます。
 📔 はてなブログ向け画像ツール【フリー】 🔗
目次

はじめに 本アプリは、今まで作成してきたコードを基に作成しています。
そのため、本記事はそれらを説明した記事の参照が多くなります。
既存の記事の中で詳しく説明している内容については、本記事であらためて説明していません。
本記事では、既存の記事の内容と異なる部分について説明します。
読者の方にはご不便をおかけしますが、ご理解いただければ幸いです。

◆アプリのサンプル画像

▽アプリ起動後の画面

起動画面

上部のボタンで操作します。
設定項目が画面の右に表示されます。

◆アプリの機能・特長

  • 画像のサイズ変更
  • 画像のミラー反転
  • 画像を左または右に回転
  • 画像に文字透かしを挿入
  • 画像を はてなフォトライフにアップロード
  • アップロードした画像のURLをコピー
  • 設定の保存

◆アプリが参照しているライブラリなど

本アプリでは、次の記事で説明したものをライブラリとして使用しています。

こちらはライブラリとしていませんが、画像一覧の表示など画面の基本的な構成はそのまま利用しています。

◆タブに画像一覧を格納

画像一覧を表示する部分は、すでに作成したものがあったのでそれをベースにしました。

▶画像一覧の作り方については こちらの記事を参照してください。
 📔 ◆画像一覧の作り方 - ScrolledFrameとwrapped_gridで作る画像一覧の作り方【Python】 🔗

参考記事との違いは、本アプリでは、画像一覧をタブに格納していることです。

したがって、参考記事でも使用している全フレームを保存する frame_children は、タブ名をキーにした辞書型にしてタブごとに管理します。

選択された画像の持ち方
タブに入れる ScrolledFrame オブジェクトの parent_frame 属性にセット型の checked_image_paths 属性を追加しています。
これは、選択された画像のパスを保持します。
画像変換などの処理を選択された画像に対して実行するためです。
こちらも参考記事とは違い、カレントタブの ScrolledFrame オブジェクトを対象にします。

カレントタブのウィジェットの取得
複数タブから現在選択中のタブを処理対象にするために nametowidget() メソッドを使用して対象のウィジェットを取得します。

▽現在選択しているタブのウィジェットを取得する処理

        w = self.note.nametowidget(self.note.select())  # get current tab widget

ScrolledFrame クラスの変更
タブの中に ScrolledFrame オブジェクトを入れています。
元々 ScrolledFrame オブジェクトのトップウィジェットCanvas でした。
しかし、タブの中に Canvas ウィジェットは入れられません。
そこで、Frame ウィジェットを用意してそこに Canvas ウィジェットを格納しました。
これでトップウィジェットを Frame にしています。
更に、作成した Frame ウィジェットparent_frame 属性としてタブ追加できるようにしました。

スクロール
ScrolledFrame クラスは、スクロールに対応しています。
画像を読み直した時に、先頭にスクロールするよう処理を追加しています。
理由は、新しく画像を読み直した時に、以前の表示状態に依り画像が表示されないことがあるためです。 1

▽先頭にスクロール

        # 画像の数が少なくなるとcanvasが大きいままなのでスクロール位置を戻す
        parent.parent_canvas.yview_moveto(0.0)

【コード】
▽タブの作成処理

        # タブの作成
        self.note = ttk.Notebook(self.b_frame)
        self.note.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        # 新しいタブの追加
        self.frame4images = self.create_frame4images(self.note, tab_name=self.tab1_name)

▽ScrolledFrame オブジェクトの作成処理

    def create_frame4images(self, parent:ttk.Notebook, tab_name:str) -> ScrolledFrame:
        """
        画像用ScrolledFrameを作成し、parentにタブを追加
        Args:
            Any:    親ウィジェット(Notebook)
            str:    タブ名
        """
        scrolled_frame = ScrolledFrame(parent)
        
        # チェックされた画像用セットを初期化
        scrolled_frame.parent_frame.checked_image_paths = set()

        # bind
        scrolled_frame.bind_class("Checkbutton", "<Double 3>", self.preview_image)  # マウスを右ダブルクリックしたときの動作
        # wrapped_gridのbind
        self.frame_children[tab_name] = []
        scrolled_frame.parent_canvas.bind("<Configure>", lambda event: TkinterLib.wrapped_grid(
            scrolled_frame.parent_canvas, *self.frame_children.get(tab_name), event=event, flex=False), add=True)
        self.note.add(scrolled_frame.parent_frame, text=tab_name)   # タブを追加
        return scrolled_frame

▶ScrolledFrame クラスの使い方については、こちらの記事を参照してください。
 📔 ◆ScrolledFrame-クラスの使い方 - ScrolledFrameとwrapped_gridで作る画像一覧の作り方【Python】 🔗

▶タブの使い方については、こちらの記事を参照してください。
 📔 ■タブの実装(Notebookウィジェットの使い方) - CSV viewerアプリの作り方(ドラッグアンドドロップ)【Python】 🔗

◆設定項目(TOMLユーティリティの使い方)

事前に準備した(作成方法は次にあります) TOML ファイルを読んで画面(ウィジェット)を作成します。

設定項目は、次の記事で説明している create_frame_from_toml_dict() メソッドを使用して作成します。

▶設定項目の主要な処理は、こちらの記事を参照してください。
 📔 ◆設定画面作成メソッド(TomlFileUtilクラス) - TOMLで設定ファイルを扱うユーティリティ【Python】 🔗

設定項目数が思いのほか多くなったので、親ウィジェットを ScrolledFrame にしました。
何もコードを書かなくてスクロールバーが付くので便利です。

【処理】

  1. exe の場所を取得
  2. TOML ファイルのパスを作成
  3. TomlFileUtil クラスのインスタンスを作成
  4. TOML ファイルの読み込み
  5. TOML ファイルが読めない時のエラー処理
  6. TOML ファイルを基にした設定画面の作成
    設定項目の辞書が返るので self.var_dict に設定
  7. 保存ボタンにメソッドの割り付け 2
  8. 画面の更新
  9. 親は ScrolledFrame オブジェクトなので parent_canvas の幅を更新
  10. 読み込んだ TOML データの USER セクションを辞書に設定

【コード】
▽親フレームの作成

        self.r_frame = ScrolledFrame(master, width=50, bg="lightblue")  # 背景色を付けるとセクションの境に色が出る
        self.r_frame.parent_frame.pack(side="right", fill="y")
        # 設定用フレーム内の作成 設定値をself.var_dictに設定
        self.create_config_frame(self.r_frame)

▽設定項目画面作成

    def create_config_frame(self, parent):
        """
        設定項目用画面の作成
        self.var_dictの作成
        """
        # pyinstallerで作成したexeか判断してexeの場所を特定する
        if getattr(sys, 'frozen', False):
            exe_path = os.path.dirname(sys.executable)
        else:
            exe_path = sys.prefix
        my_path = os.path.join(exe_path, r"blog_image_tool.toml")
        toml = TomlFileUtil()
        result = toml.read_toml(my_path)
        if not result:
            self.master.withdraw()  # トップレベルウィンドウを出さない
            messagebox.showerror("使用上のエラー", f"{my_path}\nファイルが見つかりません")
            sys.exit()
        self.var_dict = toml.create_frame_from_toml_dict(parent, True)
        toml.btn_save.config(command=lambda path=my_path: toml.save_toml(path, "USER"))
        parent.update()
        if parent.parent_canvas.winfo_width() > parent.winfo_width():
            parent.parent_canvas.config(width=parent.winfo_width())
        toml.set_toml2var_dict("USER")

◇TOMLファイルの作成

設定項目をTOMLファイルで作成します。
下記は、DEFINITIONについてだけ載せています。
 TOML ファイル全体は、ソースの取得にあるリンク先から取得できます。

▶設定項目用TOMLファイルの仕様は、こちらの記事を参照してください。
 📔 ◇要件にあったTOMLファイル - TOMLで設定ファイルを扱うユーティリティ【Python】 🔗

[DEFINITION."画像変換"]
do_resize = ["サイズ変更する","bool",]
dest_path = ["保存先フォルダ","str",]
width = ["","int",]
height = ["高さ","int",]
overwrite = ["新しいファイルをいくつも作らない","bool",]
exif = ["Exif情報出力","bool",]
mirror = ["ミラー反転","bool",]
rotate270 = ["右回転","bool",]
rotate90 = ["左回転","bool",]

[DEFINITION."透かし"]
is_water_mark = ["透かし文字付加","bool",]
water_mark = ["透かし文字","str",]
wm_font_name = ["フォントファイル名","str",]
wm_size = ["フォントサイズ","int",]
wm_f_color = ["文字色","str",]
wm_b_color = ["縁取りの色","str",]
wm_stroke = ["縁取りの幅(px)","int",]
wm_padx = ["右下からの隙間(横)","int",]
wm_pady = ["右下からの隙間(縦)","int",]

[DEFINITION."アップロード"]
use_hatena_folder = ["デフォルトフォルダを使用","bool",]
folder = ["フォトライフのフォルダ","str",]

[DEFINITION."画像URLのコピー"]
foto = ["fotolife記法で出力","bool",]
only_url = ["画像番号またはURLだけを出力","bool",]
add_title = ["ファイル名をタイトルとして付加","bool",]
add_options = ["オプション","str",]

[DEFINITION."表示設定"]
width4thumbnail = ["サムネイル画像の幅","int",]
geometry = ["アプリのサイズと位置","str",]

◆画像変換(ImageUIクラスの使い方)

画像変換は、以前に作成したコードを呼び出して使用しています。

▶画像変換の主要な処理は、こちらの記事を参照してください。
 📔 ❖ライブラリとして使用する場合の仕様 - 画像サイズを変更し文字透かしを入れるアプリの作り方【Python】 🔗

基本的には、ImageUI クラスの convert_image_from_dialog_or_args() メソッドを実行するだけです。

画像変換の結果は新しいタブを作成して画面に表示します。
変換後の画像をアップロードするためです。

【処理】
▽画像変換処理

  1. 選択された画像があるか確認
    無ければメッセージを出して戻る
  2. 画面の設定項目(ウィジェット変数の辞書)を辞書に変換
  3. 画像変換
    convert_image_from_dialog_or_args() メソッドを実行
  4. 新しいタブを作成して変換後の画像を表示する
    create_new_tab() を実行
  5. エラーメッセージがあれば設定

▽新しいタブの作成処理

  1. タブ名の初期値を設定
  2. 既に同名のタブ名があったら末尾に数字(2~)を足してタブ名を作成
  3. 画像用 ScrolledFrame オブジェクトを作成し NoteBook ウィジェットに追加
    create_frame4images() を実行
  4. 追加したタブを表示
  5. image_paths のパスからファイル情報、画像サムネイルを作成し
    ScrolledFrame オブジェクトに画像を含んだ Frame を追加

【コード】
▽画像変換処理

    def convert_images(self, event=None):
        """
        選択された画像の変更(リサイズ、回転、反転、Exif除去、透かし)
        画像変更し、保存し、新しいタブに表示する
        """
        self.msg.set("")
        w = self.note.nametowidget(self.note.select())  # get current tab widget
        paths = list(w.checked_image_paths)
        if not paths:
            self.msg.set("選択された画像がありません")
            return
        settings_dict = {key: value.get() for key, value in self.var_dict.items()}  # variableの辞書を値の辞書に変換
        self.resized_paths, err_msg = self.image_ui.convert_image_from_dialog_or_args(settings_dict, paths=paths)
        self.create_new_tab(self.resized_paths)
        if err_msg:
            self.msg.set(err_msg)

▽新しいタブの作成処理

   def create_new_tab(self, image_paths:set):
        tab_name_resize = "リサイズ画像"
        # 複数の結果に対応するためタブ名を変える
        x = 2
        tab_name = tab_name_resize
        while tab_name in self.frame_children:
            tab_name = f"{tab_name_resize}{x}"
            x += 1

        self.frame4images2 = self.create_frame4images(self.note, tab_name)
        self.note.select(len(self.note.tabs()) - 1)     # 追加したタブを表示
        self.open_files_get_images_set2frame(parent=self.frame4images2
                                            , file_paths_=tuple(image_paths)
                                            , tab_name=tab_name)

◆画像のアップロード(HatenaFotolifeUIクラスの使い方)

アップロードは、以前に作成したコードを呼び出して使用しています。

▶アップロードの主要な処理は、こちらの記事を参照してください。
 📔 ❖ライブラリとして使用する場合の仕様 - はてなフォトライフへ画像をアップロードするアプリの作り方(AtomAPIの使い方)【Python】 🔗

基本的には、HatenaFotolifeUI クラスの upload_image_to_hatena() メソッドを実行するだけです。

アップロードに時間が掛かるので別スレッドで動かしています。
Tkinter は mainloop に戻してあげないとアプリが止まったように見えます。
また、中断ボタンを用意してアップロードを中断できるようにしましたが、アップロード処理自体を別スレッドにしないと中断ボタンが押された時の処理が動きません。

スレッドでは、一つのスレッドの中でファイルの数だけ処理を回しています。
(ファイルごとにスレッドにすると相手に負荷をかけるかもしれないのでやりませんでした)

アップロードは時間が掛かるので進行状況が分かるようにメッセージ表示するのと画像のファイ名を表示しているラベルの背景色を変えるようにしました。

【処理】
▽画像アップロード処理

  1. 「アップロード」ボタンを無効にし、「中断」ボタンを有効にします
  2. カレントタブの選択中の画像のパスを取得
    self.note.nametowidget( self.note.select() ).checked_image_paths
  3. 未選択の場合はメッセージを出し、ボタンを戻して抜ける
  4. はてなフォトライフのフォルダを指定
    デフォルトフォルダを使用する場合は Hatena Blog を設定
  5. 選択された画像分以下を処理
    1. 画像をアップロード
    2. 成功すればパス情報の辞書が返るので uploaded_url 辞書に追加
      画像のラベルの背景を lightgreen に設定
      ※URL コピーの時にデータの有無の判断に使用
    3. アップロードの途中経過を表示
    4. 中断フラグを確認し、オンならメッセージを出力して中断
  6. ボタンの状態を戻します

▽ラベルの背景色を変える処理
処理している画像のラベルウィジェットのオブジェクトを直接参照する手段を用意していないので、該当のラベルウィジェットを探して背景色を設定します。

  1. カレントタブのタブ名を取得
  2. タブ名で指定したタブ内の子ウィジェット分以下を処理
    1. ウィジェットは Frame で Checkbutton と Label を持つ
      ウィジェット分以下を処理
      1. ウィジェットが Checkbutton ならパス( text オプション)を取得し引数と比較、結果を保持
      2. ウィジェットが Label ならオブジェクトを保持
    2. 引数と同じパスだったら保持した Label オブジェクトの背景色を設定

【コード】
▽アップロード処理(ボタンにバインドしたメソッドとそこから起動するスレッド)

    def upload_images(self, event=None):
        """
        選択された画像をアップロード
        """
        th = threading.Thread(target=self.th_upload_images)
        th.start()

    def th_upload_images(self, event=None):
        """
        選択された画像をアップロード
        """
        self.do_break = False   # 中断フラグの初期化
        self.btn_upload.config(state="disable") # アップロードボタンを押下不可にします
        self.btn_break.config(state="active")  # 中断ボタンを押下可にします
        self.msg.set("アップロードを開始します")
        self.update_idletasks()
        self.uploaded_url = {}
        w = self.note.nametowidget(self.note.select())  # get current tab widget
        paths = list(w.checked_image_paths)
        if not paths:
            self.msg.set("選択された画像がありません")
            self.btn_upload.config(state="active")  # アップロードボタンを押下可にします
            self.btn_break.config(state="disable")  # 中断ボタンを押下不可にします
            return
        # はてなフォトライフのフォルダを指定
        if self.var_dict["use_hatena_folder"].get():
            folder_ = "Hatena Blog"     # デフォルトフォルダ
        else:
            folder_ = self.var_dict["folder"].get()
            if not folder_:                 # 空の場合は
                folder_ = "Hatena Blog"     # デフォルトフォルダ

        # 1画像ずつ呼び出しても複数画像で呼び出しても内部的に1画像ずつ処理するので1画像ずつ呼び出す
        for i, path_ in enumerate(paths):
            uploaded_url_1path = self.upload_ui.upload_image_to_hatena(paths=[path_], folder=folder_)
            if uploaded_url_1path:
                self.uploaded_url.update(uploaded_url_1path)    # 辞書に辞書を追加
                self.set_label_gb_in_frame_children(path_, "lightgreen")
            else:
                self.set_label_gb_in_frame_children(path_, "red")
            self.msg.set(f"{i+1} / {len(paths)} 件、アップロードが完了しました")
            self.update_idletasks()
            # 中断の確認
            if self.do_break:
                self.msg.set(f"{i+1} / {len(paths)} 件、アップロードしたところで中断しました")
                break;
        self.btn_upload.config(state="active")  # アップロードボタンを押下可にします
        self.btn_break.config(state="disable")  # 中断ボタンを押下不可にします

▽アップロードの完了をラベルの背景色を変えて知らせる処理

    def set_label_gb_in_frame_children(self, image_path:str, color:str):
        """
        image_pathをtextオプション持つチェックボックスを含むフレームのラベルの背景を変える
      Args:
          bool: 設定値
        """
        tab_name = self.note.tab("current", "text")
        for child_ in self.frame_children.get(tab_name):    # self.frame_childrenはタブごとのFrameの集まり
            for item_ in child_.winfo_children():           # FrameにはCheckbuttonとLabel
                if type(item_) == tk.Checkbutton:
                    is_same_path = item_.cget("text") == image_path  # 引数と同じパスか判断
                elif type(item_) == tk.Label:
                    label_ = item_
            if is_same_path:
                label_.config(background = color)
                label_.update()   # update_idletasksでは更新されない
                return

▽アップロード中断処理

    def break_upload(self, event=None):
        self.btn_break.state(["pressed"])
        self.do_break = True    # 中断フラグをオンにする

◆アップロードされた画像のURLをコピー

はてなフォトファイルに画像をアップロードした時に返される XML から画像の URL を取得し、加工してクリップボードにコピーします。

画像の URL は次のような形式です。

  • 画像のURL:http://f.hatena.ne.jp/images/fotolife/n/naoya/XXXXXXXX/XXXXXXXXXXXXXX.jpg
  • foto 記法:f:id:naoya:XXXXXXXXXXXXXX:image

これらをこのままコピーするのと画像としての書式を付けてコピーする方法を提供します。

書式の場合は次のようになります。

  • Markdownの画像:![](http://f.hatena.ne.jp/images/fotolife/n/naoya/XXXXXXXX/XXXXXXXXXXXXXX.jpg) "タイトル"
  • foto 記法:[f:id:naoya:XXXXXXXXXXXXXX:plain:title="タイトル"]

クリップボードにコピーするために pyperclip モジュールを使用しました。
使い方は簡単で pyperclip.copy(文字列) とするだけです。(インポートが必要です)

【処理】
▽画像のURLコピー処理

  1. 画面の設定項目(ウィジェット変数の辞書)を辞書に変換
  2. カレントタブのタブ名を取得
  3. カレントタブのウィジェットを取得
  4. タブ名で指定したタブ内の子ウィジェット分以下を処理
    1. ウィジェットは Frame で Checkbutton と Label を持つ
      ウィジェット分以下を処理
      1. ウィジェットが Checkbutton なら
        パス( text オプション)を取得し選択されている画像か確認し、結果とパスを保持
      2. ウィジェットが Label なら
        背景色が lightgreen と同じか確認し、結果を保持
    2. 対象画像でアップロード済みならフォトライフの URL をコピーして clip_strings に追加
  5. clip_strings が空でなければ、クリップボードにコピー

はてなフォトライフの画像URL取得処理

  1. 設定項目辞書から設定項目を取得
  2. アップロードした画像の URL 情報から該当パスの情報(画像のURLとfoto記法のタプル)を取得
  3. 設定項目に合わせて画像のURLかfoto記法のどちらかを取得
  4. パスからファイル名(拡張子なし)を取得
  5. 設定項目でfoto記法で出力の場合以下を処理
    1. 「:image」を「:plain」に置換
    2. 設定項目でタイトルを付加ならタイトルを付加
    3. 設定項目でその他のオプションがあれば付加
    4. 設定項目で中身だけなら、中身だけにする
  6. 設定項目でfoto記法で出力以外の場合以下を処理
    1. 設定項目でタイトルを付加ならタイトルを付加
    2. 設定項目で中身だけなら、中身だけにする
  7. 結果を返す

【コード】
▽画像のURLコピー処理

    def copy_image_url(self):
        """
      Args:
          bool: 設定値
        """
        clip_strings = []
        self.msg.set("")
        settings_dict = {key: value.get() for key, value in self.var_dict.items()}  # 設定をウィジェット変数から値に変換
        tab_name = self.note.tab("current", "text")
        w = self.note.nametowidget(self.note.select())      # get current tab widget
        for child_ in self.frame_children.get(tab_name):    # self.frame_childrenはタブごとのFrameの集まり
            for item_ in child_.winfo_children():           # FrameにはCheckbuttonとLabel
                if type(item_) == tk.Checkbutton:
                    selected = item_.cget("text") in w.checked_image_paths  # 選択されている画像か判断
                    path_ = item_.cget("text")
                elif type(item_) == tk.Label:
                    uploaded = item_.winfo_rgb(item_.config("background")[4]) == item_.winfo_rgb("lightgreen")
            if uploaded and selected:
                clip_strings.append(self.get_image_url(path_, settings_dict))
        if clip_strings:
            pyperclip.copy(os.linesep.join(clip_strings))
            self.msg.set("コピーしました")
        else:
            self.msg.set("コピーするものがありません")

はてなフォトライフの画像URL取得処理

    def get_image_url(self, file_name:str, settings_dict:dict) -> str:
        result = ""
        isfoto = settings_dict.get("foto", False)
        istitle = settings_dict.get("add_title")
        isonly_url = settings_dict.get("only_url", False)
        isoption = settings_dict.get("add_options", "")
        url_or_foto = self.uploaded_url.get(file_name)  # タプルが返る(url, foto)
        s1 = url_or_foto[isfoto]
        title_ = os.path.splitext(os.path.basename(file_name))[0]
        if isfoto:  # fotolife記法の編集
            # [f:id:はてなID:画像番号:title="タイトル"]
            s1 = s1.replace(":image", ":plain") # :imageはfotolifeが起動するので:plainに置換
            if istitle:
                s1 = s1 + f':title="{title_}"'
            if isoption:
                s1 = s1 + isoption
            if not isonly_url:
                s1 = f"[{s1}]"
        else:
            # ![](url "タイトル")
            if istitle:
                s1 = f'{s1} "{title_}"'
            if not isonly_url:
                s1 = f"![]({s1})"
        result = s1
        return result

◆別記事で説明している内容

◇画像表示にCheckbuttonクラスを使用

▶Checkbutton クラスの使い方については こちらの記事を参照してください。
 📔 ◆画像表示にCheckbuttonクラスを使用 - ScrolledFrameとwrapped_gridで作る画像一覧の作り方【Python】 🔗

説明している内容は次の通りです。

◇ScrolledFrame クラスの使い方

▶ScrolledFrame クラスの使い方については こちらの記事を参照してください。
 📔 ◆ScrolledFrame-クラスの使い方 - ScrolledFrameとwrapped_gridで作る画像一覧の作り方【Python】 🔗

説明している内容は次の通りです。

◇プレビュー処理

▶プレビュー処理については こちらの記事を参照してください。
 📔 ◆プレビュー処理 - ScrolledFrameとwrapped_gridで作る画像一覧の作り方【Python】 🔗

説明している内容は次の通りです。

Tkinterの使い方

Tkinter(ティーキンター)は Python が標準で提供している GUI パッケージです。
他のプログラムの開発ツールで提供されているようなルック&フィールな GUI ツールではありません。
従って、画面を構成するには、すべてコードを書く必要があります。

Tkinter を使用するには import が必要です。

   import tkinter as tk

慣例的に tk と別名を付けるようです。

Tkinterの使用ウィジェット

Tkinter がサポートする GUI の部品をウィジェット (widget) と呼びます。

ここでは次のウィジェットを使用しています。

◇使用ウィジェット

ウィジェットは基本的にインスタンスを作成し、表示テキストや表示方法などを指定して使用します。

今回使用しているウィジェットです。

パッケージ ウィジェット 用途 見た目
tkinter Frame
フレーム
ウィジェットの受け皿
tkinter Label
ラベル
文字の表示
tkinter Button
ボタン
押すと処理が動くボタン
tkinter Checkbutton
チェックボックス
チェックでオン/オフを設定
tkinter Entry
エントリー
テキストを入力するボックス
tkinter.ttk Notebook
ノートブック
タブ切り替え表示
独自 ScrolledFrame
スクロールバー付フレーム
画面をスクロール

◇画面表示の特徴

画面表示には次の特徴があります。

  • 画像変換の結果を別タブで表示
  • 画像を格子状に配置(wrapped_grid() メソッドを使用)
  • ウィンドウのサイズを変更すると表示する画像の列数をウィンドウの幅に合わせる(wrapped_grid() メソッドを使用)
  • 縦スクロールバーを表示(ScrolledFrame クラスを使用)
  • マウスホイールでスクロール(ScrolledFrame クラスを使用)
  • 右側に設定項目を配置

◇画面構成 - フレーム構造

プログラムを作るにあたって、どのような画面にするか決めなくてはなりません。画面構成を考えるということですね。
画面を構成するというのは、GUI 部品(ウィジェット)を画面に配置することです。
ウィジェットは設定したテキストに合わせてサイズが決まるので、そのまま使用しても画面構成上問題ないものと、広げないと見栄えが良くないものが出てきます。
ウィジェットを広げる場合、他のウィジェットとの位置関係で思ったように配置できないことがあります。
そのようなときに Frame ウィジェットを使用して区分けをして希望する位置に配置します。
引き出しの整理に使うトレイのようなイメージでしょうか。

今回の画面構成です。

  • u_frame (frame)
    • btn_f_sel (Button)
    • btn_resize (Button)
    • btn_upload (Button)
    • btn_break (ttk.Button)
    • btn_copy (Button)
    • btn_select_all (Button)
    • btn_deselection (Button)
    • btn_preview (Button)
    • lbl_msg (Label)
  • b_frame (frame)
    • note (Notebook)
      • frame4images (ScrolledFrame)
        • frame1 (Frame)
          • check_bon (Checkbutton)
          • label_f_name (Label)
  • r_frame (ScrolledFrame)

※「u_frame」などはプログラムで使用した変数名です。()内はクラス名です。

入力をする部分と出力をする部分で大きくフレームを分けています。
結果を表示する部分は余った領域をすべて使用して、できるだけ大きく表示させています。

ウィジェットの配置 - pack

▶説明は別記事を参照してください
 📔 ◇ウィジェットの配置 - pack - SQLクライアントアプリの作り方(Tkinterで表)【Python】 🔗

◇動作をウィジェットに割り当てる - バインド

▶説明は別記事を参照してください
 📔 □動作をウィジェットに割り当てる - Excel viewerアプリの作り方(Tkinterでタブと表)【Python】 🔗

◆必要なパッケージ

◇必要な Python パッケージ

  • pillow
    • pip install Pillow
    • from PIL import Image
  • TkinterDnD2
    • pip install tkinterdnd2
    • from tkinterdnd2 import *
  • pyperclip
    • pip install pyperclip
    • import pyperclip
  • requests
    • pip install requests
    • import requests
  • tomli
    • pip install tomli
    • import tomli
  • tomli-w
    • pip install tomli-w
    • import tomli-w

TkinterPythonの標準パッケージです。

◇必要な Python 独自モジュール

  • image_resize_sig.py ver 1.0.1
  • fotolifeUpload.py ver 1.0.1
  • toml_file_util.py ver 1.0.0
  • tkinter_libs.py ver 1.0.1

◆ソースの取得

全体のソースはこちらから取得できます。

ライブラリのソースはこちらから取得できます。
 (点在してて申し訳ありません)

◆さいごに

アプリを公開してからずいぶん経ってしまいましたが、コードの説明記事を公開します。
ライブラリとして使っているコードをパッケージとしてリリースできていれば、もう少し簡単にご利用いただけるのではないかと思います。
パッケージの作り方を勉強して、パッケージ化していけたらと思っています。
今のところ、パッケージにするほどバリバリのライブラリでもないので、優先順位を下げています。
もうしばらくは、こじんまりしたアプリを作っていこうと思っています。

こんなアプリが欲しいということがあれば教えていただけると幸いです。


あわせて読みたい 📔 ブログの記事を管理するNotionデータベースとデータ更新アプリ【フリー】 🔗

◇ご注意

本記事は次のバージョンの下で動作した内容を基に記述しています。

  • Python 3.8.5
  • Pillow 8.3.0
  • TkinterDnD2 0.3.0
  • Requests 2.25.1
  • pyperclip 1.8.2

◇免責事項

ご利用に際しては、『免責事項』をご確認ください。
お気づきの点がございましたら『お問い合わせ』からお問い合わせください。

投稿:

  1. Canvas のサイズを小さくする方が、適切だと思います。安易な方法にしてしまいました。

  2. よく考えたら保存ボタンと保存メソッドの割り付けは、TomlFileUtil クラスの中で実施して、ボタンを表示するかどうかを選択するようにすれば、この処理は不要ですね。