質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.12%

Python3 tkinter whileループを開始するとボタンが押せなくなる

解決済

回答 3

投稿

  • 評価
  • クリップ 1
  • VIEW 6,694

opyon

score 987

参考にした質問と回答:Python3のtkinterで並行処理?を行いたい
参考にしたサイト:簡易ストップウォッチを作ってみた @Python3

似たような質問で恐縮です。
上記を参考に見よう見真似で簡易ストップウォッチを作ったのですがスタートした後に他ボタンを押すと
反応が無く強制終了するしかない状態です。
他のボタンを押さなければ1秒毎に標準出力で秒数のカウントは出来ています。

スレッド化無しで動くならスレッド化は無くてもよいのですがwhileループ中にボタンを反応させるにはどこが間違っているのでしょうか?
ヒントだけでもご教示いただけると助かります。

実現したいこと

スタート,ストップ,終了ボタン制御
ループを使ったラベルの常時更新

試したこと

Python3のtkinterで並行処理?を行いたいの@KSwordOfHasteさんの回答コードで動作確認済み。
クラス化したこと以外は同じような処理にしたつもりなのですがうまく動きません。
フラグをself.インスタンス変数にしてみたりフラグ変数の配置場所を変えてみましたが結果は変わりませんでした。
ループ無しでの動作は一通り問題無く動いています。

Python3.7.0

# coding=UTF-8
import sys
import time
import tkinter as tk
import threading as th


class SSW(tk.Frame):


    def __init__(self, master = None):

        tk.Frame.__init__(self, master)
        self.master.title("Sample Stop Watch")
        self.master.bind("<Control-q>", self.quit)
        self.master.geometry("280x160")

        self.lbl_msg = tk.Label(master, text = "<quit:Control-q>")
        self.lbl_msg.pack(side = "top")
        self.lbl_lap = tk.Label(master, text = "0.00")
        self.lbl_lap.pack(side = "top")

        self.btn_run = tk.Button(master, text = "run", command = self.run)
        self.btn_run.pack()
        self.btn_end = tk.Button(master, text = "end", command = self.end)
        self.btn_end.pack()
        self.btn_quit = tk.Button(master, text = "quit", command = self.quit)
        self.btn_quit.pack()

    def loop(self):

        global timer_start
        timer_start = time.time()

        global stop_flg
        stop_flg = False

        while not stop_flg:
            time.sleep(1)
            timer_lap = time.time() - timer_start
            out_lap = round(timer_lap, 2)
            self.lbl_lap.configure(text = out_lap)
            print(out_lap)

    def run(self):
        print('Start!')
        # スレッド開始
        th.Thread(target = self.loop()).start()

    def end(self):
        print('Stop!')
        global stop_flg
        stop_flg = True

    def quit(self):
        sys.exit()


# main
if __name__ == '__main__':
    ss = SSW()
    ss.pack()
    ss.mainloop()
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+3

スレッド化対応を行う時のよくあるミスとして、関数を渡さずに関数呼び出を行ってしまうというのがあります。

th.Thread(target = self.loop()).start()

th.Thread(target = self.loop).start()

別解)スレッドを使わなくても、afterを使う形でも可能です。あとはイベントスケジューラdbader/scheduleなどでも。

以下はafterを使ったサンプルコードです。

# -*- coding: utf8 -*-
# coding=UTF-8
import sys
import time
import tkinter as tk

stop_flg = False
ROOT = tk.Tk()


def stop_watch():
    timer_start = time.time()

    def elapsed():
        timer_lap = time.time() - timer_start
        out_lap = round(timer_lap, 2)
        return out_lap
    return elapsed


class SSW(tk.Frame):
    def __init__(self, master = None):
        super().__init__(master)
        self.master.title("Sample Stop Watch")
        self.master.bind("<Control-q>", self.quit)
        self.master.geometry("280x160")
        # masterではなく、親コンポーネントはselfで。
        self.lbl_msg = tk.Label(self, text="<quit:Control-q>")
        self.lbl_msg.pack(side = "top")
        self.lbl_lap = tk.Label(self, text="0.00")
        self.lbl_lap.pack(side = "top")

        self.btn_run = tk.Button(self, text="run", command = self.run)
        self.btn_run.pack()
        self.btn_end = tk.Button(self, text="end", command = self.end)
        self.btn_end.pack()
        self.btn_quit = tk.Button(self, text="quit", command = self.quit)
        self.btn_quit.pack()

    def loop(self):
        self.lbl_lap.configure(text=self.clock())
        if not stop_flg:
            # 次のイベント呼び出しを予約
            self.after(1000, self.loop)

    def run(self):
        global stop_flg
        print('Start!')
        stop_flg = False
        self.clock = stop_watch()
        #
        self.after(0, self.loop)

    def end(self):
        global stop_flg
        print('Stop!')
        stop_flg = True

    def quit(self):
        sys.exit()


# main
if __name__ == '__main__':
    ss = SSW(ROOT)
    ss.pack()
    ss.mainloop()

Python言語のスレッドは他の言語と違って、IO律速の処理の高速化に使われることが多いです。
■参考情報

並列方法 律速ポイント GILの制限 データの受け渡し
プロセス 演算律速 影響を受けない pickle可能なオブジェクト
スレッド IO律速 影響を受ける 制約なし

※GILはグローバルインタプリタロックといいます。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/09/19 16:02

    演算だけならマルチスレッド化しないほうが良さそうですね。

    キャンセル

  • 2018/09/19 16:08 編集

    日本語記事はほぼありませんが、
    1, async https://docs.python.jp/3/library/asyncio-task.html
    2,concurrent.futures https://docs.python.jp/3/library/concurrent.futures.html
    も使えるかと、asyncはJavaScriptからconcurrent.futuresはJava言語からだそーです。

    キャンセル

  • 2018/09/19 16:44

    @umyuさんのように英語堪能で海外情報詳しい方が羨ましいです。
    日本語の数倍数十倍の情報量ですよね。
    Google翻訳先生に頑張ってもらいます!

    キャンセル

+2

検索で辿り着いた方へ解決済みコード載せておきます。
質問解決後別のエラーが残っていたので修正。
スレッド終了処理をしていなかったので追記。
@magichanさん回答コード内参考

レイアウトは冗長なので割愛。

# coding=UTF-8
import sys
import time
import tkinter as tk
import threading as th


class SSW(tk.Frame):

    def __init__(self, master = None):

        self.th_main = None
        self.update_time = 0.02

        super().__init__(master)
        self.master.title("Sample Stop Watch")
        self.master.geometry("300x200")

        # レイアウト?
        # must be top, bottom, left, or right
        self.f1 = tk.Frame(self)
        self.f1.lbl_total = tk.Label(self, text = "0.00", font = ("", 30))
        self.f1.lbl_total.pack()
        self.f1.lbl_lap = tk.Label(self, text = "0.00", font = ("", 20))
        self.f1.lbl_lap.pack()
        self.f1.pack()

        self.f2 = tk.Frame(self)
        self.f2.btn_run = tk.Button(self.f2, text = "run"
                                    , width = 20, command = self.run)
        self.f2.btn_run.pack()
        self.f2.btn_lap = tk.Button(self.f2, text = "lap"
                                    , width = 20, command = self.lap)
        self.f2.btn_lap.pack()
        self.f2.btn_end = tk.Button(self.f2, text = "end"
                                    , width = 20, command = self.end)
        self.f2.btn_end.pack()
        self.f2.btn_quit = tk.Button(self.f2, text = "quit"
                                     , width = 20, command = self.quit)
        self.f2.btn_quit.pack()
        self.f2.pack()

    # メインループ
    def loop(self):

        timer_start = time.time()
        self.timer_lap_total = 0

        self.stop_flg = False
        self.stop_lap = False

        while not self.stop_flg:
            # 更新時間
            time.sleep(self.update_time)

            # トータル更新
            timer_total = time.time() - timer_start
            out_total = round(timer_total, 2)

            self.f1.lbl_total.configure(text = out_total)

            # ラップ更新
            if not self.stop_lap:
                timer_lap = timer_total - self.timer_lap_total
                out_lap = round(timer_lap, 2)
                self.f1.lbl_lap.configure(text = out_lap)

    # スレッド開始処理
    def run(self):
        print('Start!')
        if self.th_main == None:
            self.th_main = th.Thread(target = self.loop)
            self.th_main.start()

    # ラップ計算
    def lap(self):
        if self.stop_lap:
            print('Start Lap!')
            self.timer_lap_tmp = time.time() - self.timer_lap_tmp
            self.timer_lap_total += self.timer_lap_tmp
            self.stop_lap = False
        else:
            print('Stop Lap!')
            self.stop_lap = True
            self.timer_lap_tmp = time.time()

    # スレッド終了処理
    def end(self):
        print('Stop!')
        self.stop_flg = True

        if self.th_main:

            print("end 2-1")
            print(self.th_main)
            self.th_main = None
            print(self.th_main)

            # うまく止まらないので=Noneに変更
            # self.th_main.th_join()
            # self.th_main.stop()

            print("end 3-1")

    # フォームを閉じる
    def quit(self):
        self.stop_flg = True
        self.th_main = None
        sys.exit()


# main
if __name__ == '__main__':
    ss = SSW()
    ss.pack()
    ss.mainloop()

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+1

並行処理技術を応用して、動的に時間を更新できる(ストップウォッチのように時間が常に更新される)ストップウォッチを作ってみました
役に立てれれば幸いです

import time
import tkinter as tk
import threading

class Test(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.stop_fg=False
        self.frame=0.00
        self.time_start=None
        self.time_now=None
        self.GUI()

    def GUI(self):
        self.Text001=tk.Label(text=self.frame)
        self.Text001.grid(column=0,row=0)
        Text001=tk.Label(text="sec")
        Text001.grid(column=1,row=0)        
        Button001=tk.Button(text="Start",command=self.on_start)
        Button001.grid(column=0,row=1)
        Button002=tk.Button(text="Stop",command=self.on_stop)
        Button002.grid(column=1,row=1)
        Button002=tk.Button(text="Reset",command=self.on_reset)
        Button002.grid(column=2,row=1)   

    def run(self):
        self.stop_fg=False
        if self.time_start==None:
            self.time_start=time.time()

        while self.stop_fg!=True:
            self.time_now=time.time()
            self.frame=round((self.time_now-self.time_start),2)
            print(self.frame)
            self.Text001["text"]=self.frame

    def on_start(self):
        threading.Thread(target=self.run).start()

    def on_stop(self):
        self.stop_fg=True

    def on_reset(self):
        self.on_stop()
        self.time_start=None
        self.time_now=None     
        self.frame=0.00
        self.Text001["text"]=self.frame

root=tk.Tk()
app=Test(master=root)
app.mainloop()

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/09/21 18:22

    umyuさんの掲示したプログラムでは、時間がたつにつれズレが少しづつ目立つのでちょっと自分なりに組んでみました(汗)

    キャンセル

  • 2020/05/26 16:28

    大変参考になりました。ありがとうございます!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.12%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる