Tkinterを使ってファイル選択GUIを構築しよう

先日PythonによるExcel業務自動化のツールの1つとして、openpyxlを紹介しました。作成したPythonプログラムを自分で使ううちは、コマンドプロンプトやターミナルからコードを実行して求めていた出力を得ることが出来れば特に不自由はないと思います。

ですが、第三者、そしてPythonのことを全く知らない人に使ってもらう必要が生じたのであれば、使いやすいUIを提供しなくてはなりません。

簡単な業務効率化のために導入したのであれば、多くの場合UIにそれほど時間を割きたくはないはずです。tkinterを使えば、さくっと短時間にGUIを構築できます!

それでは早速、Python付属の高速なGUI構築パッケージtkinterを使って、シンプルなファイル選択GUIを作成してみましょう!

https://datascientist-toolbox.com/wp-content/uploads/2019/09/man.png
りーぐる

簡単に実行ファイル形式にして配布することもできる(ローカルで実行できる)ので、導入の障壁が少ないのも魅力の一つ!

tkinterのインストール

tkinterは、ほとんどのPythonに付属しているためインストールの必要はありません。

tkinterがインストールされているかどうか確かめるには、Pythonのパスが通っている各種ターミナル上で下記のコマンドを実行します。(Python3環境を想定しています)

$ python3 -m tkinter

以下のウィンドウが表示されれば、tkinterは使用可能です!

この記事で作るGUI

今回作成するのは下記のシンプルなGUIです。

● GUIトップ画面(Ubuntu)

● ファイル選択ダイアログ(Ubuntu)

● プログレスウィンドウ(Ubuntu)

私のOS環境はUbuntu18.04ですが、tkinterはWindows、MacOS、Linux全てのプラットフォームで動作することができます。ただし、一部見た目が異なる場合があるので、コンポーネントの微調整を必要とする可能性はあります。

業務自動化では、ユーザーが選択したファイルをインプットとして、何らかの処理を行い、その結果を出力するのはよくある利用パターンの1つです。

GUIと自動化ロジックをなるべく分離させておくことで、自動化スクリプト部分は入れ替え可能となり、一度構築したGUIは様々な自動化案件で再利用可能な道具となります。

このようなユースケースが多いのであれば、この機会に是非PythonのGUIプログラミングにチャレンジしてみてはいかがでしょうか。

tkinterのオブジェクトと配置方法

GUIの作成に移る前に、tkinterのオブジェクトと配置方法について、簡単な説明をします。tkinterのコードを読む上での予備知識として、軽く流し読みしてみてください。

オブジェクトの分類

tkinterのGUIは大きく分けて、GUIのルートであるウィンドウ、ウィンドウ内で配置の枠組みを決めるフレーム、そして実際に配置されるボタンやラベル、コンボボックス等を含むヴィジェットで構成されます。

フレームは上図のように複数配置してもよいですが、単一のフレームをウィンドウ全体に広げることも可能です。

ウィンドウに直接ヴィジェットを配置することも出来ますが、再描画の際などにウィンドウとヴィジェットが直接結びついていると不便なので、例え単一フレームでもフレームの上にヴィジェットを配置することをおすすめします。

3種類の配置方法(pack、grid、place)

tkinterではGUIウィンドウ内にフレームやヴィジェットといったオブジェクトを配置する方法は、3種類存在します。

オブジェクト配置方法

  • pack:1次元的にオブジェクトを配置する方向や、パディングを指定する方法
  • grid:2次元的にx行y列の形で、オブジェクトを配置するセルを指定する方法
  • place:オブジェクトを配置するウィンドウまたはフレームの左上からのピクセルを指定する方法

このあたりは実際にソースコードを触って、色々GUIをいじってみるのが一番良いと思いますので、早速ファイル選択GUIの作成へと進みましょう。

今回作成するGUIではフレームの配置にpackメソッド、その他ヴィジェットの配置にplaceメソッドを使っています。

詳しいリファレンスは下記に存在します。

参考 Tcl/Tk の Python インタフェースdocs.python.org

ファイル選択GUIの作成

ここからは順を追って、ファイル選択GUIのソースコードを解説します。

ソースコードの全体はgithubのリポジトリに公開しているので、ご自由にお使いください。

全3ファイルを作成しますが、今回の主な作成対象はtkinter_gui.pyファイルです。

作成ファイルとその内容

  • main_application.py:GUIアプリケーションの実行
  • tkinter_gui.py:ヴィジェットの配置やコールバック関数をまとめたクラスの定義
  • logic.py:アプリケーションのロジック本体(今回は一定時間ごとにGUIのプログレスバーを進める処理のみを実装)

main_application.py

main_applicationでは、ルートとなるGUIウィンドウを生成して、ユーザーのイベント待ちを行う短いソースコードとなっています。

5行目はGUIに表示されるアプリケーションのタイトルを指定していますので、自身のアプリ名に変更してください。

11行目のMainFrameクラスは、tkinter_gui.pyからインポートしたもので、ルートウィンドウ内全体に広がるフレームを、MainFrameクラスのコンストラクタ内で配置する様々なヴィジェットと共に配置します。

https://datascientist-toolbox.com/wp-content/uploads/2019/09/man.png

tkinterのGUI描画終了時には、mainloopメソッド(イベントループ)を呼び出して、GUIをイベント待ち状態にすることをお忘れなく!

import tkinter as tk
from tkinter_gui import MainFrame

if __name__ == "__main__":
    APP_TITLE = "DS Hack GUI Application"

    root = tk.Tk()
    root.geometry('680x220+300+200')
    root.title(APP_TITLE)
    # メインウィンドウ内全体に白背景のフレームを生成
    MainFrame(root, app_title=APP_TITLE, bg="white").pack(side="top", 
                                                          fill="both",
                                                          expand=True)
    root.mainloop()

tkinter_gui.py

tkinterのGUIプログラミングでは、フレーム内のヴィジェットとヴィジェットに対するコールバック関数をまとめたクラスを定義することで、複雑さを回避しながらGUIを構築することができます。

またツールチップの生成など、ある程度複雑なロジックを持つヴィジェットはその処理か、ヴィジェット自体をクラス化することを推奨します。

今回フォルダアイコンと歯車アイコンに割り当てるツールチップの生成を行うクラスをCreateToolTipクラスとして定義しました。

では、順番にソースコードを確認していきましょう。

各種モジュールのインポート

アプリケーションロジック本体の実行と描画の更新を並行して行うため、threadingモジュールを使用します。

ttkモジュールは、tkから派生したモジュールであり、今回はプログレスバーに使用しています。

アプリケーションロジック本体は、logic.pyのexecute_logic関数に実装します。

import os
import threading
import tkinter as tk
import tkinter.filedialog
from tkinter import ttk
from logic import execute_logic

MainFrameクラス

MainFrameクラスは、tk.Frameクラスを継承しています。コンストラクタでフレーム内に配置するヴィジェットをplaceメソッドにより配置し、メソッドとして主にヴィジェットのコードバックを定義しています。

少し長いですが、まずは全体像をご確認ください。

class MainFrame(tk.Frame):

    def __init__(self, parent, app_title, *args, **kwargs):
        """
        メインウィンドウ内のフレームを生成するコンストラクタ
        下記の処理を実行する
        ・フレームの設定
        ・フレーム内に含まれるヴィジェット(ラベル・ボタンなど)の配置

        parameters
        ----------
        parent : tk.Tk
            フレームの親となるメインウィンドウ
        app_title : app_title
            アプリケーションのタイトル(GUIに表示される)
        *args : variable arguments
            Frameオブジェクトの初期化引数
        **kwargs : variable arguments
            Frameオブジェクトの初期化引数
        """
        tk.Frame.__init__(self, parent, *args, **kwargs)
        self.parent = parent

        # titleラベル
        self.gui_title_label = tk.Label(self,
                                        text=app_title,
                                        font=('メイリオ', 32),
                                        bg='white',
                                        fg='limegreen')
        # titleラベルの設置(今回はplaceメソッドを使う)
        self.gui_title_label.place(x=80, y=30)

        # file entry (ファイルパスの表示部分)
        self.file_path = tk.StringVar()
        self.file_entry = tk.Entry(self,
                                   textvariable=self.file_path,
                                   font=('メイリオ', 10),
                                   width=55,
                                   bd=3)
        self.file_entry.place(x=80, y=110)

        # file選択ボタン
        self.file_button_img = tk.PhotoImage(file='./icons/folder.png')
        self.file_button = tk.Button(self,
                                     image=self.file_button_img,
                                     cursor='hand2')
        # ボタンがクリックされた際のコールバック関数をバインド(割り当て)
        self.file_button.bind('<Button-1>', self.file_button_clicked)
        self.file_button.place(x=540, y=107)
        # ツールチップの生成
        self.file_menu_ttp = CreateToolTip(self.file_button, "入力ファイルを選択")

        # 設定ボタン
        # 今回設定ウィンドウは実装しないので、コールバック関数は設定しない。
        self.setting_button_img = tk.PhotoImage(file='./icons/settings.png')
        self.setting_button = tk.Button(self,
                                        image=self.setting_button_img,
                                        cursor='hand2')
        self.setting_button.place(x=585, y=107)
        self.file_menu_ttp = CreateToolTip(self.setting_button, "設定画面を開く")

        # アプリケーション実行ボタン
        self.execute_button = tk.Button(self,
                                        text='アプリケーション実行',
                                        font=('メイリオ', 10),
                                        relief='raised',
                                        cursor='hand2',
                                        fg='limegreen',
                                        bg='snow',
                                        highlightbackground='limegreen',
                                        width=20)
        # マウスホバーで色を変化させる
        self.execute_button.bind('<Enter>', self.hover_enter)
        self.execute_button.bind('<Leave>', self.hover_leave)
        self.execute_button.bind('<Button-1>', self.execute_button_clicked)
        self.execute_button.place(x=250, y=160)


    def file_button_clicked(self, event):
        # xlsまたはxlsx形式のファイルを選択させる
        filetypes = [("Excelブック(.xlsx)","*.xlsx"),
                     ("Excelブック(.xls)",".xls")]
        # ファイル選択ダイアログの初期フォルダ
        initialdir = os.path.abspath(os.path.dirname(__file__))
        path = tk.filedialog.askopenfilename(filetypes=filetypes,
                                             initialdir=initialdir)
        self.file_path.set(path)

    def hover_enter(self, event):
        # (注)mac, linuxではbuttonのbackgroundオプションが働かない
        self.execute_button.configure(fg='snow', bg='limegreen')

    def hover_leave(self, event):
        self.execute_button.configure(fg='limegreen', bg='snow')

    def execute_button_clicked(self, event):
        # スレッドの開始(GUIの描画更新のため)
        file_path = self.file_path.get()
        # アプリケーションの進捗確認(プログラスバー)ウィンドウの生成
        progress_window = tk.Toplevel(master=self.parent)
        progress_window.geometry('640x200+400+300')
        progress_window.title("アプリ実行進捗状況")
        # MainFrame同様、フレーム内部のヴィジェットとコールバック関数をクラス化
        self.progress_frame = ProgressFrame(progress_window,
                                            file_path=file_path,
                                            bg="white")
        self.progress_frame.pack(side="top", fill="both", expand=True)
        # メインロジックの実行
        self.progress_frame.execute_logic()
        progress_window.mainloop()

GUIのタイトルラベル、ファイルパスのエントリー、ファイル選択ボタン、設定ボタン、実行ボタンの合計5つのヴィジェットをコンストラクタ内で配置しています。

また、ボタンにはクリック時やマウスホバー時のコールバック関数をバインドしています。

タイトルラベル

今回ヴィジェットの配置にはplaceメソッドを使っています。placeメソッドは微調整をしやすいので、細かな配置を整えたいときには便利ですが、規則正しく行・列上にヴィジェットを整列する場合はpackやgridの方がやりやすいでしょう。

tk.Labelクラスに、タイトル・フォント・色のオプションを渡してオブジェクトを生成しています。オブジェクトの生成だけでは、配置は行われません。placeメソッドで配置を行うのを忘れないようにしましょう!

# titleラベル
self.gui_title_label = tk.Label(self,
                                text=app_title,
                                font=('メイリオ', 32),
                                bg='white',
                                fg='limegreen')
# titleラベルの設置(今回はplaceメソッドを使う)
self.gui_title_label.place(x=80, y=30)

ファイルパス入力エントリー

エントリーヴィジェットは、tk.Entryで生成できます。

ここで注意が必要なのは、エントリー内の文字列はユーザーによってダイアログからファイルが選択されたときに変更したいので、可変であることです。

tk.StringVarは文字列の変数で、tk.Entryクラスのtextvariable引数に渡すことで変数とエントリーを関連付けることができます。

これによりユーザーがファイルを選択した際に、パスをtk.Stringvarクラスのset()メソッドでセットすれば、エントリーにパスが表示できます。

# file entry (ファイルパスの表示部分)
self.file_path = tk.StringVar()
self.file_entry = tk.Entry(self,
                           textvariable=self.file_path,
                           font=('メイリオ', 10),
                           width=55,
                           bd=3)
self.file_entry.place(x=80, y=110)

ファイル選択ボタン、設定ボタン

少し味気ないGUIなので、ファイル選択ボタンと設定ボタンにはアイコンを使いましょう。同一階層にあるiconsフォルダ内の、「folder.png」「settings.png」の2枚の画像をtk.PhotoImageクラスに渡しています。

tk.PhotoImageクラスのオブジェクトをtk.Buttonのimage引数に渡すことで、アイコンボタンが完成します。placeメソッドで適切な位置に配置することも忘れないように気をつけましょう。

また、マウスホバー時にアイコンボタンの意味を表すツールチップが表示されると親切です。ツールチップの表示は少々複雑なので、任意のヴィジェットにツールチップを加える別クラスを使用しています。(58行目、67行目)

# file選択ボタン
self.file_button_img = tk.PhotoImage(file='./icons/folder.png')
self.file_button = tk.Button(self,
                             image=self.file_button_img,
                             cursor='hand2')
# ボタンがクリックされた際のコールバック関数をバインド(割り当て)
self.file_button.bind('<Button-1>', self.file_button_clicked)
self.file_button.place(x=540, y=107)
# ツールチップの生成
self.file_menu_ttp = CreateToolTip(self.file_button, "入力ファイルを選択")

# 設定ボタン
# 今回設定ウィンドウは実装しないので、コールバック関数は設定しない。
self.setting_button_img = tk.PhotoImage(file='./icons/settings.png')
self.setting_button = tk.Button(self,
                                image=self.setting_button_img,
                                cursor='hand2')
self.setting_button.place(x=585, y=107)
self.file_menu_ttp = CreateToolTip(self.setting_button, "設定画面を開く")

55行目ではボタンにコールバック関数をバインドしています。”<Button-1>”はボタンがクリックされるというイベントを表しており、イベントが生じた際にfile_button_clicked()メソッドを呼び出します。

def file_button_clicked(self, event):
    # xlsまたはxlsx形式のファイルを選択させる
    filetypes = [("Excelブック(.xlsx)","*.xlsx"),
                 ("Excelブック(.xls)",".xls")]
    # ファイル選択ダイアログの初期フォルダ
    initialdir = os.path.abspath(os.path.dirname(__file__))
    path = tk.filedialog.askopenfilename(filetypes=filetypes,
                                         initialdir=initialdir)
    self.file_path.set(path)

tk.filedialog.askopenfilenameを使用することで、ファイル選択のダイアログボックスを開くことができます。ちなみにaskdirectoryでフォルダ選択のダイアログボックスが作成できます

選択されたファイルパスをエントリーに関連付けられた文字列変数オブジェクトに代入することで、エントリーにファイルパスが表示されます。

実行ボタン

実行ボタンで、ロジック本体の実行を行います。今回は1秒間に10%プログレスバーを進める処理のみを実装しています。

ファイルに対して行いたい処理をlogic.pyに実装することで、GUIを変更することなく様々なアプリケーションに使用できるので、アプリケーション固有の処理はなるべくlogic.pyに書いてしまいましょう。

# アプリケーション実行ボタン
self.execute_button = tk.Button(self,
                                text='アプリケーション実行',
                                font=('メイリオ', 10),
                                relief='raised',
                                cursor='hand2',
                                fg='limegreen',
                                bg='snow',
                                highlightbackground='limegreen',
                                width=20)
# マウスホバーで色を変化させる
self.execute_button.bind('<Enter>', self.hover_enter)
self.execute_button.bind('<Leave>', self.hover_leave)
self.execute_button.bind('<Button-1>', self.execute_button_clicked)
self.execute_button.place(x=250, y=160)

実行ボタンは、マウスホバー時にハイライトされると視覚的にわかりやすいので、クリック時の他、マウスがボタン上に入った(<Enter>)・マウスがボタン上から離れた(<Leave>)のイベントに対するコールバック関数も用意しています。

def hover_enter(self, event):
    # (注)mac, linuxではbuttonのbackgroundオプションが働かない
    self.execute_button.configure(fg='snow', bg='limegreen')

def hover_leave(self, event):
    self.execute_button.configure(fg='limegreen', bg='snow')

アプリケーションを実行した際には、新しくプログレスバーでアプリケーションの実行状態を表示するウィンドウを出力します。

新しいウィンドウには、また複数のヴィジェットとそれに紐づくコールバック関数が存在するので、ProgressFrameという新しいクラスを定義してフレーム内のヴィジェットはクラス内で配置しています。

また、main_application.pyでルートウィンドウを生成した際はtk.Tk()によって生成を行いましたが、ウィンドウを親としてウィンドウを生成する際はtk.Toplevel()によって生成することに注意しましょう。

def execute_button_clicked(self, event):
    # スレッドの開始(GUIの描画更新のため)
    file_path = self.file_path.get()
    # アプリケーションの進捗確認(プログラスバー)ウィンドウの生成
    progress_window = tk.Toplevel(master=self.parent)
    progress_window.geometry('640x200+400+300')
    progress_window.title("アプリ実行進捗状況")
    # MainFrame同様、フレーム内部のヴィジェットとコールバック関数をクラス化
    self.progress_frame = ProgressFrame(progress_window,
                                        file_path=file_path,
                                        bg="white")
    self.progress_frame.pack(side="top", fill="both", expand=True)
    # メインロジックの実行
    self.progress_frame.execute_logic()
    progress_window.mainloop()

ProgressFrameを生成し、配置を行ったあとにProgressFrameクラスの持つexecute_logic()メソッドでアプリケーション本体の実行を行います。

ProgressFrameクラス

ProgressFrameクラスもクラスの構造としては、MainFrameと似ています。

新しいのはプログレスバーとウィンドウの削除方法、そしてプログレスバーの描画更新とアプリケーションの実行を並行して行うためにthreadingモジュールを使用していることです。

class ProgressFrame(tk.Frame):

    def __init__(self, parent, file_path, *args, **kwargs):
        """
        プログレスウィンドウ内のフレームを生成するコンストラクタ
        下記の処理を実行する
        ・フレームの設定
        ・フレーム内に含まれるヴィジェット(ラベル・ボタンなど)の配置

        parameters
        ----------
        parent : tk.Tk
            フレームの親となるメインウィンドウ
        file_path : str
            メインウィンドウで選択されたファイルのパス
        *args : variable arguments
            Frameオブジェクトの初期化引数
        **kwargs : variable arguments
            Frameオブジェクトの初期化引数
        """
        tk.Frame.__init__(self, parent, *args, **kwargs)
        self.parent = parent
        self.file_path = file_path

        # 進捗状況ラベル
        self.progress_message = tk.StringVar()
        self.progress_message.set('アプリケーション実行中...')
        self.progress_label = tk.Label(self,
                                       font=('メイリオ', 12),
                                       textvariable=self.progress_message,
                                       bg='white',
                                       justify='left')
        self.progress_label.place(x=80, y=30)

        # プログレスバー
        # プログレスバーのスタイル設定
        s = ttk.Style()
        s.theme_use('clam')
        s.configure("green.Horizontal.TProgressbar",
                    foreground='limegreen',
                    background='limegreen')
        # プログレスバーの配置
        self.progress_bar = ttk.Progressbar(self,
                                            style="green.Horizontal.TProgressbar",
                                            orient='horizontal',
                                            length=400,
                                            mode='determinate')
        self.progress_bar.configure(maximum=100)
        self.progress_bar.place(x=80, y=120)


    def execute_logic(self):
        """
        メインロジックの実行(描画更新のため、別スレッドで実行)
        選択したファイルのパスと、プログレスバーのオブジェクトを渡す
        """
        th = threading.Thread(target=execute_logic,
                              args=(self.file_path,
                                    self.progress_bar,
                                    self.after_complete_process))
        th.start()

    def after_complete_process(self):
        """
        メインロジックの終了時に実行する処理
        ここでは、完了メッセージと完了(OK)ボタンの表示を行う。
        """
        # メッセージ
        self.progress_message.set('アプリケーションの実行が完了しました')
        # OKボタン
        self.ok_button = tk.Button(self,
                                   text='OK',
                                   font=('メイリオ', 10),
                                   relief='raised',
                                   cursor='hand2',
                                   fg='limegreen',
                                   bg='snow',
                                   highlightbackground='limegreen',
                                   width=10)
        self.ok_button.bind('<Enter>', self.hover_enter)
        self.ok_button.bind('<Leave>', self.hover_leave)
        self.ok_button.bind('<Button-1>', self.ok_button_clicked)
        self.ok_button.place(x=500, y=30)

    def hover_enter(self, event):
        # (注)mac, linuxではbuttonのbackgroundオプションが働かない
        self.ok_button.configure(fg='snow', bg='limegreen')

    def hover_leave(self, event):
        self.ok_button.configure(fg='limegreen', bg='snow')

    def ok_button_clicked(self, event):
        # progress_windowの削除
        self.parent.destroy()

進捗状況ラベル

プログレスバーと共にアプリケーションの実行進捗状況をユーザーに伝えるためのラベルを表示します。

実行完了時にラベルの内容を変更するため、tk.Labelクラスのtextvariable引数にtk.Stringvar()から生成したオブジェクトを渡しています。

# 進捗状況ラベル
self.progress_message = tk.StringVar()
self.progress_message.set('アプリケーション実行中...')
self.progress_label = tk.Label(self,
                               font=('メイリオ', 12),
                               textvariable=self.progress_message,
                               bg='white',
                               justify='left')
self.progress_label.place(x=80, y=30)

プログレスバー

プログレスバーの実装はtkではなくttkで行います。

ttkモジュールはtkinterと関連のあるモジュールですが、混同してはいけません。今回はプログレスバーでのみttkモジュールを使用しています。ttkモジュールではスタイルとヴィジェットを分離して書くことができるという特徴を持ちます。

GUIのイメージカラーに合わせスタイル設定を行い、プログレスバーのstyle引数にスタイル名を指定しています。また、167行目でプログレスバーの最大値を100に設定しています。

# プログレスバー
# プログレスバーのスタイル設定
s = ttk.Style()
s.theme_use('clam')
s.configure("green.Horizontal.TProgressbar",
            foreground='limegreen',
            background='limegreen')
# プログレスバーの配置
self.progress_bar = ttk.Progressbar(self,
                                    style="green.Horizontal.TProgressbar",
                                    orient='horizontal',
                                    length=400,
                                    mode='determinate')
self.progress_bar.configure(maximum=100)
self.progress_bar.place(x=80, y=120)

execute_logicメソッド

MainFrameクラスから呼び出されるロジック本体の実行メソッドです。

プログレスバーの描画更新とロジック本体の並行実行のため、threading.Threadクラスを使って新規スレッドでlogic.pyのexecute_logic関数を実行しています。

本メソッドと同名のlogic.py内の関数を実行していることに注意してください。引数としては処理を行うファイルパス、描画更新用のプログレスバーオブジェクト、実行完了後の処理を記述した後処理メソッドの3つとなります。

def execute_logic(self):
    """
    メインロジックの実行(描画更新のため、別スレッドで実行)
    選択したファイルのパスと、プログレスバーのオブジェクトを渡す
    """
    th = threading.Thread(target=execute_logic,
                          args=(self.file_path,
                                self.progress_bar,
                                self.after_complete_process))
    th.start()

after_complete_processメソッド

after_complete_processメソッド内には全てのロジックが完了した際の後処理を実装しています。

ここではアプリの進捗状況ラベルを更新し、ウィンドウを閉じる「OK」ボタンを表示しています。

def after_complete_process(self):
    """
    メインロジックの終了時に実行する処理
    ここでは、完了メッセージと完了(OK)ボタンの表示を行う。
    """
    # メッセージ
    self.progress_message.set('アプリケーションの実行が完了しました')
    # OKボタン
    self.ok_button = tk.Button(self,
                               text='OK',
                               font=('メイリオ', 10),
                               relief='raised',
                               cursor='hand2',
                               fg='limegreen',
                               bg='snow',
                               highlightbackground='limegreen',
                               width=10)
    self.ok_button.bind('<Enter>', self.hover_enter)
    self.ok_button.bind('<Leave>', self.hover_leave)
    self.ok_button.bind('<Button-1>', self.ok_button_clicked)
    self.ok_button.place(x=500, y=30)

ウィンドウはdestroy()メソッドによって、閉じることができます。

親ウィンドウであるself.parentのdestroy()メソッドを呼び出すことに注意しましょう。self.destroy()とすると、ウィンドウ内の全てのヴィジェットとフレームは削除されますが、空のウィンドウだけが残ってしまいます。

ホバー時のコールバック関数の実装については、実行ボタンの時と同様です。

def hover_enter(self, event):
    # (注)mac, linuxではbuttonのbackgroundオプションが働かない
    self.ok_button.configure(fg='snow', bg='limegreen')

def hover_leave(self, event):
    self.ok_button.configure(fg='limegreen', bg='snow')

def ok_button_clicked(self, event):
    # progress windowの削除
    self.parent.destroy()

CreateToolTipクラス

CreateToolTipクラスでは、アイコンボタンへのマウスホバー時に出現するツールチップの作成を行っています。

コンストラクタでヴィジェットと表示テキストを受け取り、ヴィジェットの位置から相対的に適切な位置を指定して、ツールチップの生成・削除を行っています。

コードは長いですが、本質的ではないので細部の説明は省略させていただきます。

class CreateToolTip(object):
    """
    与えられたヴィジェットに対して、ツールチップを生成する
    """
    def __init__(self, widget, text='widget info'):
        self.waittime = 500   # 単位は[ms]
        self.wraplength = 180 # pixels
        self.widget = widget
        self.text = text
        self.widget.bind('<Enter>', self.enter)
        self.widget.bind('<Leave>', self.leave)
        self.widget.bind('<ButtonPress>', self.leave)
        self.id = None
        self.tw = None

    def enter(self, event=None):
        self.schedule()

    def leave(self, event=None):
        self.unschedule()
        self.hidetip()

    def schedule(self):
        self.unschedule()
        self.id = self.widget.after(self.waittime, self.showtip)

    def unschedule(self):
        id = self.id
        self.id = None
        if id:
            self.widget.after_cancel(id)

    def showtip(self, event=None):
        x = y = 0
        x, y, cx, cy = self.widget.bbox("insert")
        x += self.widget.winfo_rootx() + 25
        y += self.widget.winfo_rooty() + 20
        # トップレベルウィンドウの生成
        self.tw = tk.Toplevel(self.widget)
        # ラベルのみを残し、アプリケーションウィンドウを削除する
        self.tw.wm_overrideredirect(True)
        self.tw.wm_geometry("+%d+%d" % (x, y))
        label = tk.Label(self.tw,
                         text=self.text,
                         justify='left',
                         background='#ffffff',
                         relief='solid',
                         borderwidth=1,
                         wraplength=self.wraplength)
        label.pack(ipadx=1)

    def hidetip(self):
        tw = self.tw
        self.tw = None
        if tw:
            tw.destroy()

logic.py

execute_logic内部では、ループで1秒ごとにプログレスバーを10%進める処理を10秒間実行後にProgressFrameから渡された後処理メソッドを呼び出しています。

フォルダパスを渡してフォルダ内のファイルを全て処理するようなロジックを作成する場合のために、get_file_path_in_folder関数も用意しています。

### アプリケーションの実装 ###“部分に、自動化したい処理などのロジックを記載することでGUI付きの自動化アプリケーションを簡単に作成することができます。

import os
import glob
import time

def execute_logic(file_path, progress_bar, after_complete_process):
    """
    GUIから実行されるアプリケーションのロジック本体。
    インプットファイルに対しての処理を記述する。
    """
    ### アプリケーションの実装 ###
    for i in range(10):
        time.sleep(1)
        progress_bar.configure(val=(i+1)*10)

    # 実行完了時の処理
    after_complete_process()

def get_file_path_in_folder(folder_path, search_extension):
    """
    フォルダ内を再帰的に調べて、指定した拡張子を持つファイルの
    パスをすべて取得する。

    Parameters
    ----------
    folder_path : str
        検索を行うフォルダのパス
    search_extension : str
        パスを取得するファイルの拡張子
    """

    search_extension = '*.' + search_extension
    return glob.glob(os.path.join(folder_path, '**', search_extension),
                     recursive=True)
https://datascientist-toolbox.com/wp-content/uploads/2019/09/man.png
りーぐる

pandasやopenpyxlを使った自動化と、tkinterで構築したGUIを使ってPythonのみ完結する業務効率化のGUIアプリケーションが”お手軽に”作成できるので、よければ下記事も参考にしてください!

おすすめ記事

Openpyxlチートシート!PythonでExcel業務を自動化する