2017-07-30 7 views
1

誰かが私を助けてくれますか?私はクラスについての演習をしていて、他のスレッドでタスクを実行しています。別のクラスのラベルを変更したい。スクリプトを動作させることができません。他のクラスのtkinterラベルを変更しますか?

私はさまざまなことを試みましたが、クラスとスレッドから継承していることを理解することにいくつかの問題があります。これはこれについてもっと学ぶための単なる例です。

from tkinter import * 
import tkinter as tk 
from tkinter import ttk 
import threading 

#Gloabl for stopping the run task 
running = True 

#class 1 with window 
class App(): 

    def __init__(self): 
      #making the window 
      self.root = tk.Tk() 
      self.root.geometry("400x400+300+300") 
      self.root.protocol("WM_DELETE_WINDOW", self.callback) 
      self.widgets() 
      self.root.mainloop() 

    # stop task and close window 
    def callback(self): 
      global running 
      running = False 
      self.root.destroy() 

    # all the widgets of the window 
    def widgets(self): 
      global labelvar 
      #startbutton 
      self.start_button = tk.Button(self.root, text="Start",  command=lambda:App2()) 
      self.start_button.pack() 

      #stopbutton 
      self.stop_button = tk.Button(self.root, text="Stop", command=lambda:self.stop()) 
      self.stop_button.pack() 

      #Defining variable for text for label 
      labelvar = "Press start to start running" 
      self.label = tk.Label(self.root, text=labelvar) 
      self.label.pack() 

    #stop the task 
    def stop(self): 
      global running 
      running = False 


#class 2 with task in other thread 
class App2(threading.Thread): 

    def __init__(self): 
      global running 
      #check if task can be run 
      running = True 
      threading.Thread.__init__(self) 
      self.start() 

    def run(self): 
       #starting random work 
       for i in range(10000): 
        print(i) 
        labelvar = "running" 
        App.label.pack() 
        #checking if task can still be running else stop task 
        if running == False: 
         break 
         labelvar = "stopped" 
         App.label.pack() 

#initiate main app 
app = App() 
+0

'tkinter'は本質的にマルチスレッドをサポートしていません。メインスレッドだけがGUIを更新するために呼び出しを行うことができます。スレッドを使用している間は、その制限とその周囲のコードに注意する必要があります。 – martineau

答えて

0

私がコメントで言ったように、tkinter自体がマルチスレッドをサポートしていませんが、あなたははそれがある限り一つのスレッドのみ、通常はメイン1、用途(または「会談」)としてそれを行うことができます。

GUIに表示される内容に影響を与えたい場合、他のスレッドは何らかの形でGUIスレッドと通信する必要があります。これは多くの場合、queue.Queueで行われますが、この比較的簡単なケースでは、global変数で実行できます。ただし、手段共有メモリ空間(つまりグローバル変数)によって同時アクセスが制御される場合はの利点があります。マルチスレッドとマルチタスクの両方をサポートしていますが、正しく実行して実行する必要があります。

このようなリソースを簡単に共有するには、その目的で専用のthreading.Lockを使用します。 この共有リソース(runningフラグ)へのすべての参照は、Lockを「取得」して後で「解放」する場合にのみ行う必要があります(詳細はWikipedia記事Lock (computer science)を参照してください)。幸いにも、Python withステートメント(下記参照)を使用してこれを行うことは自明です。

マルチスレッドの問題のもう一つの重要な側面は、2つのスレッド間で交換される情報がどのように処理されるかです。この場合、tkinterスレッドポーリングを実行フラグにして、変更を監視し、それに応じて影響を受けるウィジェットを更新することを選択します。これはユニバーサルウィジェットメソッドafter()を使用することによって行うことができます。tkinterは、( 'mainloop'内の)将来の呼び出しをユーザ提供の関数またはメソッドにスケジュールし、特定の引数を渡すように指示します。これを繰り返し実行するには、呼び出された関数は、自身の実行を再実行し、終了する前にafter()を呼び出して再度実行することができます。

以下は、これらのことを行うコードの修正版です。 App2tkinterを呼び出すことはなく、ウィジェットのいずれかに触れることはありません。

import threading 
from time import sleep 
from tkinter import * 
import tkinter as tk 
from tkinter import ttk 

DELAY = 100 # millisecs between status label updates 

# global flag and a Lock to control concurrent access to it 
run_flag_lock = threading.Lock() 
running = False 


# class 1 with window 
class App(): 
    def __init__(self): 
     global running 
     self.root = tk.Tk() 
     self.root.geometry("400x400+300+300") 
     self.root.protocol("WM_DELETE_WINDOW", self.quit) 
     self.create_widgets() 
     with run_flag_lock: 
      running = False 
     self.root.after(DELAY, self.update_status, None) # start status widget updating 
     self.root.mainloop() 

    # create all window widgets 
    def create_widgets(self): 
     self.start_button = tk.Button(self.root, text="Start", command=self.start) 
     self.start_button.pack() 

     self.stop_button = tk.Button(self.root, text="Stop", command=self.stop) 
     self.stop_button.pack() 

     self.status_label = tk.Label(self.root, text='') 
     self.status_label.pack() 

    def update_status(self, run_state): 
     """ Update status label text and state of buttons to match running flag. """ 
     # no need to declare run_flag_lock global since it's not being assigned a value 
     with run_flag_lock: 
      if running != run_state: # status change? 
       if running: 
        status_text = 'Press Stop button to stop task' 
        run_state = True 
       else: 
        status_text = 'Press Start button to start task' 
        run_state = False 
       self.status_label.config(text=status_text) 
       # also update status of buttons 
       if run_state: 
        self.start_button.config(state=DISABLED) 
        self.stop_button.config(state=ACTIVE) 
       else: 
        self.start_button.config(state=ACTIVE) 
        self.stop_button.config(state=DISABLED) 

     # run again after a delay to repeat status check 
     self.root.after(DELAY, self.update_status, run_state) 

    # start the task 
    def start(self): 
     global running 
     with run_flag_lock: 
      if not running: 
       app2 = App2() # create task thread 
       app2.start() 
       running = True 

    # stop the task 
    def stop(self): 
     global running 
     with run_flag_lock: 
      if running: 
       running = False 

    # teminate GUI and stop task if it's running 
    def quit(self): 
     global running 
     with run_flag_lock: 
      if running: 
       running = False 
     self.root.destroy() 


# class 2 with task in another thread 
class App2(threading.Thread): 
    def __init__(self): 
     super(App2, self).__init__() # base class initialization 
     self.daemon = True # allow main thread to terminate even if this one is running 

    def run(self): 
     global running 
     # random work 
     for i in range(10000): 
      print(i) 
      # Normally you shouldn't use sleep() in a tkinter app, but since this is in 
      # a separate thread, it's OK to do so. 
      sleep(.25) # slow printing down a little 
      # stop running if running flag is set to false 
      with run_flag_lock: 
       if not running: 
        break # stop early 

     with run_flag_lock: 
      running = False # task finished 

# create (and start) main GUI app 
app = App() 
+0

明確な情報と可能な解決策をありがとう。私はスレッディングを深くする必要があります。私のループが遅くなる原因となる遅延ですか?それは私が避けることができるものなのか、それとも私のコードで欲しいものなのでしょうか? – Bart1986

+0

status_textを変更した後に.pack()を使用しなかったのは、フレーム全体を100ミリ秒ごとに更新するからですか?この種のフレームを更新して、「インタラクティブな」tkinterフレームを使用するすべてのプログラムでよりうまく使用していますか?私はちょうどあなたがそれを遅くするループで使用する。スリープステートメントを見た、それを見ていない。 – Bart1986

+0

私は故意に、 'App2.run()'メソッドの 'for'ループの中に' sleep() '呼び出しを追加しました。ウィジェットを 'pack()'する必要があります。その後、 'config()'メソッドを使うだけでいつでも設定を調整することができます。更新の頻度はあなた次第です。私は100ミリ秒を選択しました。25秒遅れてスレッドの 'for'ループに追加しました(常にそれに追いつきます)。 – martineau

関連する問題