Python, tkinterで作成したGUIに強制停止ボタンを取り付ける

目的

tkinterで作成したGUIに強制停止ボタンを取り付けます。

GUIで時間のかかる何かしらの動作を開始し、その動作途中で停止したい場合実行しているプログラム自体を停止させる方法もあります。しかしこの方法では動作途中で変更してしまったもの、例えばファイルの操作や接続している機器の操作などが元の状態に戻らず、そのまま停止してしまうことになります。

こういった現象をさけるため、GUIに強制停止ボタンを取り付け、停止ボタンを押した際に同時に変更したものを元の状態に戻すことができるようにしていきます。

解決方法

逐次処理と並列処理

Pythonは通常命令を一つ一つ処理していく逐次処理形式になっています。そのためtkinterではGUIにつけられたボタンを押し何かしらの動作をさせる関数を呼び出した場合、その動作が終了するまではGUIで他の動作を行おうとしても反応しません。

従って動作を強制停止させるボタンを実装するためには、動作部分の処理とGUIの処理を並列に処理できるようにしてあげることが必要になります。

threadingによる並列処理

今回はその並列処理の手法としてthreadingを用います。(threadingはPython3に最初から含まれていますので、ライブラリのダウンロードなどは必要ありません。)

まずはじめはGUI抜きの並列処理の説明として、以下の天丼を作る工程を表示するプログラムを並列処理に変えていきます。

import time

def fry_shrimp():
    print("エビを揚げます。")
    time.sleep(5)
    print("エビが揚がりました。")

def cook_rice():
    print("お米を炊き始めます。")
    time.sleep(5)
    print("お米が炊きあがりました。")

print("天丼を作ります。")
cook_rice()
fry_shrimp()
print("盛り付けます。")
print("タレをかけます。")
print("天丼が完成しました。")

このコードの場合、お米を炊く動作が終わってからエビを揚げる動作に移るため出力は以下のようになります。

>python threading1.py
天丼を作ります。
お米を炊き始めます。
お米が炊きあがりました。
エビを揚げます。
エビが揚がりました。
盛り付けます。
タレをかけます。
天丼が完成しました。

ここでお米を炊く動作とエビを揚げる動作を並列して行うように変更すると、

import time
import threading

def fry_shrimp():
    print("エビを揚げます。")
    time.sleep(5)
    print("エビが揚がりました。")

def cook_rice():
    print("お米を炊き始めます。")
    time.sleep(5)
    print("お米が炊きあがりました。")

print("天丼を作ります。")

thread1 = threading.Thread(target=cook_rice)
thread2 = threading.Thread(target=fry_shrimp)
thread1.start()
thread2.start()
thread1.join()
thread2.join()

print("盛り付けます。")
print("タレをかけます。")
print("天丼が完成しました。")

このようになります。このコードを実行すると、結果は以下のように炊飯とエビの処理が並列して行われるものになります。

>python threading2.py
天丼を作ります。
お米を炊き始めます。
エビを揚げます。
お米が炊きあがりました。
エビが揚がりました。
盛り付けます。
タレをかけます。
天丼が完成しました。

コードについて解説を行うと、threading.Threadで実行するメソッドを指定してインスタンスを生成、生成したインスタンスをthread.startで呼び出してスレッド上で処理という流れになっています。

また.join()は他のスレッドの処理が終わるまで待機するというもので、ここでは炊飯が終わった後で、エビが揚がるのを待っている状態になります。

threadingでGUIに強制停止ボタンを実装

これらを利用してGUIに強制停止ボタンを実装します。

import tkinter as tk
import threading
import time

def start():
    thread = threading.Thread(target=count)
    thread.start()

def count():
    global flg
    i = 0
    while 1:
        if flg == False:
            print("動作を途中停止します。")
            flg = True
            break
        else:
            print("カウント",i)
            time.sleep(2)
            i += 1

def stop():
    global flg
    flg = False

root = tk.Tk()

flg = True

btn1 = tk.Button(text="開始",command=start)
btn1.grid(row=0,column=0)

btn2 = tk.Button(text="停止",command=stop)
btn2.grid(row=1,column=0)

root.mainloop()

上のプログラムを実行すると、下の画像のようなGUIが表示されます。

開始ボタンを押すと約2秒おきに1ずつ増える数字が出力され、停止ボタンを押すと「動作を途中停止します。」と表示され、数字をカウントしていく動作が停止するようになっています。

中身をさらに詳しく解説すると、開始ボタンを押すとthreadingによってcount関数の並列処理が始まります。その動作中に停止ボタンを押すと、フラグがFalseになり、そのフラグの変化を受けてカウントを行っているループから抜け出すようになっています。

このようして、countの部分に行いたい任意の動作を入れてあげることでGUIに強制停止ボタンを実装することができます。

コメント

タイトルとURLをコピーしました