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

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

ただいまの
回答率

87.58%

forループの処理でtkinter GUI上のラベルを動的に変更したい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 5,053

score 49

前提・実現したいこと

現在,python, tkinterを用いたGUIアプリケーションの作成をしています。そのアプリの中で,ボタン押し後にループ処理を開始し,そのループ内で動的にGUI上のラベルのテキストを変更をする機能を持たせたいと考えています。GUIの表示変更処理と,ループ処理をそれぞれ異なるスレッドで動かさなければいけないと考え,コードを書いている状況です。

発生している問題

そこで以下のコードのよう,GUI表示をスレッド化し,ボタン押しに伴うループ処理内でラベルのテキストを変更するよう処理を記述したつもりでした。しかしながら,ボタンを押したとしてもGUI上のテキストは動的に更新されず,ループ処理全体が終了したのちにやっとテキストが更新されてしまいます。ループ処理の実行はprint処理にて確認をしています。

イメージ説明
ボタン押し前の状態

イメージ説明
ループ処理中の状態:ボタンが押された状態でテキストが更新されない

イメージ説明
ループ処理後の状態

該当のソースコード

import time
import threading
import tkinter as tk

class GUI(threading.Thread):

    def __init__(self):
        threading.Thread.__init__(self)
        self.value = 0

    def start(self):
        self.root = tk.Tk()
        # ラベルの表示
        self.label = tk.Label(self.root, text=self.value)
        self.label.pack()
        # ボタンの表示
        self.button = tk.Button(self.root, text='push', command=self.change_value)
        self.button.pack()
        self.root.mainloop()


    def change_value(self):

        for value in range(100):
            time.sleep(0.05)
            self.value = value
            # ラベルの値を変更
            self.label['text'] = str(self.value)
            # ラベルに表示されるだろう値を表示
            print(self.value)

if __name__ == '__main__':
    gui = GUI()
    gui.start()

試したこと

そこで,ラベルの値を変更する処理もさらに別のスレッドで処理する必要があるのかと考え,以下のようにコードを変更しました。しかしながら,コードの変更前と変わらず,ボタン押しをしても,GUI上のテキストは動的に更新されない状況です。
スレッド別に処理を実行することで,テキスト表示の変更をループ処理内で動的に変更可能と考えていたのですが,発想から根本的に間違っているのでしょうか?あるいはコードそのものが根本的に間違っているのでしょうか?恐れ入りますが,ご示唆いただけると幸いです。

import time
import threading
import tkinter as tk

class GUI(threading.Thread):

    def __init__(self):
        threading.Thread.__init__(self)
        self.value = 0

    def start(self):
        self.root = tk.Tk()
        # ラベルの表示
        self.label = tk.Label(self.root, text=self.value)
        self.label.pack()
        # ボタンの表示
        self.button = tk.Button(self.root, text='push', command=self.change_value)
        self.button.pack()
        self.root.mainloop()


    def change_value(self):

        for value in range(100):
            time.sleep(0.05)
            self.value = value

            # 新たに変更した個所
            new_thread = threading.Thread(target=self.change_label)
            new_thread.start()

            # ラベルに表示されるだろう値を表示
            print(self.value)

    def change_label(self):
        self.label['text'] = str(self.value)

if __name__ == '__main__':
    gui = GUI()
    gui.start()

補足

python = 3.7.6

  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 1

checkベストアンサー

+1

StringVarを利用します。
labelの中身をStringVarにバインドし、データそのものの更新はStringVarに対し実施するとGUIスレッドがlabelの中身をStringVarに変更してくれます。
StringVarを更新する処理を別スレッドで実行すればGUIスレッドがブロックされることなく画面表示を更新することができます。

以下サンプルソースです。ご自身のソースと相違点を見比べてみてください。

import time
import threading
import tkinter as tk

class GUI(threading.Thread):

    def __init__(self):
        threading.Thread.__init__(self)
        self.value = 0

    def start(self):
        self.root = tk.Tk()
        # StringVarをフィールドに定義する
        self.sv = tk.StringVar()
        self.sv.set("0")
        # ラベルの表示 データはStringVarをバインドする
        self.label = tk.Label(self.root, textvariable=self.sv)
        self.label.pack()
        # ボタンの表示
        self.button = tk.Button(self.root, text='push', command=self.change_value_callback)
        self.button.pack()
        self.root.mainloop()

    # change_valueを別スレッドで実行するコールバック
    def change_value_callback(self):
        th = threading.Thread(target=self.change_value, args=())
        th.start()

    # StringVarを更新するように変更する
    def change_value(self):

        for value in range(100):
            time.sleep(0.05)
            # StringVarを変更するとGUIスレッドでラベル文字列が更新される
            self.sv.set(str(value))
            # ラベルに表示されるだろう値を表示
            print(value)

if __name__ == '__main__':
    gui = GUI()
    gui.start()

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2020/08/21 07:40

    頂いたコードを実行したところ,目的とする動作を達成することができました。
    テキスト変更を動的に行いたい場合には,一度テキストをStringVarとして宣言する必要があったのですね。大変勉強になりました。ご回答いただきありがとうございました!

    キャンセル

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

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

関連した質問

同じタグがついた質問を見る