Публикация темы Progress обратно в tkinter Frame - PullRequest
0 голосов
/ 20 марта 2020

Я публикую это как информацию для людей, которые ищут способ сообщить о прогрессе потока обратно в кадр или окно tkinter. Я видел несколько подходов, подробно описанных в SO и других сайтах, но ни один из них не казался мне подходящим. Итак, вот подход, который отображает прогресс как в виде обновления окна сообщения, так и в виде виджета Scale. Он использует классы переменных tkinter StringVar и DoubleVar вместо того, чтобы пытаться использовать обратные вызовы или постоянно опрашивать очередь в главном потоке.

Комментарии, конечно, приветствуются, но, похоже, этот подход работает хорошо.

`

import tkinter as tk
from tkinter import ttk, END, NW, GROOVE
import threading
import queue
import time


class App(tk.Tk):

    def __init__(self):
      tk.Tk.__init__(self)
      self.queue = queue.Queue()
      self.msgCt=0
      self.listbox = tk.Listbox(self, width=20, height=5)
      self.scaleVal=tk.DoubleVar()
      self.progressbar = ttk.Scale(self, orient='horizontal',
                                         length=300,
                                         from_=0.0, to=100.0,
                                         variable=self.scaleVal)
      self.scaleVal.set(0.0)
      self.button = tk.Button(self, text="Start", command=self.spawnthread)
      self.msgBtn = tk.Button(self,text="Set Msg", command=self.sendMessage)
      self.msgTxt=tk.StringVar(self,"Messages Here...")
      self.msgBox=tk.Message(self,textvariable=self.msgTxt,width=200,
                             anchor=NW,relief=GROOVE)

      self.listbox.grid(row=0,column=0,columnspan=2)
      self.msgBox.grid(row=1,column=0,columnspan=2)
      self.progressbar.grid(row=2,column=0,columnspan=2)
      self.button.grid(row=3,column=0)
      self.msgBtn.grid(row=3,column=1)

    def spawnthread(self):
      self.button.config(state="disabled")
      self.listbox.delete(0, END)
      self.thread = ThreadedClient(self.queue,self.msgTxt,self.scaleVal)
      self.thread.start()
      self.periodiccall()

    def sendMessage(self,msg=None):
      if not msg==None:
        self.msgTxt.set(msg)
      else:
        self.msgTxt.set("Message {}".format(self.msgCt))
      self.msgCt+=1

    def periodiccall(self):
        self.checkqueue()
        if self.thread.is_alive():
            self.after(100, self.periodiccall)
        else:
            self.button.config(state="active")

    def checkqueue(self):
        while self.queue.qsize():
            try:
                msg = self.queue.get(0)
                self.listbox.insert('end', msg)
                # self.progressbar.step(25)
            except queue.Empty:
                pass


class ThreadedClient(threading.Thread):

    def __init__(self, qu, mtxt,dvar):
        threading.Thread.__init__(self)
        self.queue = qu
        self.msgTxt=mtxt
        self.scaleVal=dvar

    def run(self):
      self.scaleVal.set(0.0)
      for x in range(1, 10):
          time.sleep(2)
          msg = "Function %s finished..." % x
          self.msgTxt.set(msg)
          self.scaleVal.set(x*10)
          self.queue.put(msg)


if __name__ == "__main__":
    app = App()
    app.mainloop()

`

1 Ответ

0 голосов
/ 22 марта 2020

В ответ на несколько комментариев, вот новая версия кода:

import tkinter as tk
from tkinter import ttk, END, NW, GROOVE
import threading
import queue
import time
from pickle import FALSE


class App(tk.Tk):

    def __init__(self):
      tk.Tk.__init__(self)
      self.msgCt=0
      self.thrdCt=0;
      self.scaleVal=tk.DoubleVar()
      self.doneSignal=tk.BooleanVar()
      self.doneSignal.set(False)
      self.doneSignal.trace("w",self.on_doneSignal_set)
      self.progressbar = ttk.Progressbar(self, orient='horizontal',
                                         length=300,
                                         maximum=100.0,
                                         variable=self.scaleVal)
      self.scaleVal.set(0.0)
      self.startBtn = tk.Button(self, text="Start", command=self.spawnthread)
      self.stopBtn=tk.Button(self,text="Stop", command=self.stopthread)
      self.stopBtn.config(state="disabled")
      self.msgBtn = tk.Button(self,text="Set Msg", command=self.sendMessage)
      self.msgTxt=tk.StringVar(self,"Messages Here...")
      self.msgBox=tk.Message(self,textvariable=self.msgTxt,width=200,
                             anchor=NW,relief=GROOVE)

      self.msgBox.grid(row=0,column=0,columnspan=3)
      self.progressbar.grid(row=1,column=0,columnspan=3)
      self.startBtn.grid(row=2,column=0)
      self.stopBtn.grid(row=2,column=1)
      self.msgBtn.grid(row=2,column=2)

    def on_doneSignal_set(self,*kwargs):
      self.sendMessage("Thread is DONE")
      self.startBtn.config(state="active")
      self.stopBtn.config(state="disabled")

    def stopthread(self):
      if self.thread.is_alive():
        self.thread.stopNow()

    def spawnthread(self):
      self.thrdCt=0
      self.startBtn.config(state="disabled")
      self.stopBtn.config(state="active")
      self.thread = ThreadedClient(self.msgTxt,self.scaleVal,self.doneSignal)
      self.thread.start()
      # self.periodiccall()

    def sendMessage(self,msg=None):
      if not msg==None:
        self.msgTxt.set(msg)
      else:
        self.msgTxt.set("Message {}".format(self.msgCt))
      self.msgCt+=1

class ThreadedClient(threading.Thread):

    def __init__(self, mtxt,dvar,dsig):
      threading.Thread.__init__(self)
      self.msgTxt=mtxt
      self.scaleVal=dvar
      self._stopNow=False
      self._doneSignal=dsig
      self._lock=threading.Lock()

    def run(self):
      self._stopNow=False
      self.scaleVal.set(0.0)
      for x in range(1, 10):
        if not self.checkStopNow():
          time.sleep(2)
          msg = "Function %s finished..." % x
          self.msgTxt.set(msg)
          self.scaleVal.set(x*10)
        else:
          break
      self._doneSignal.set(True)

    def stopNow(self):
      with self._lock:
        self._stopNow=True

    def checkStopNow(self):
      rtrn=False
      with self._lock:
        rtrn=self._stopNow
      return rtrn

if __name__ == "__main__":
    app = App()
    app.mainloop()

Во-первых, мотивация для этого упражнения в первую очередь: у меня есть большое приложение python, которое использует Scipy.optimize, чтобы найти решение проблемы моделирования. Это часто может занять много времени, поэтому я хотел, чтобы он работал, но периодически публиковать сообщения пользователю, чтобы они знали, что происходит, позволяли пользователю прервать работу посередине и, наконец, отправлять сообщение на главную Поток, что моделирование сейчас сделано. Мой исходный код был частично основан на примере потоков, который предполагал модель потоков производителя / потребителя, при которой производитель создает данные (помещенные в очередь), а потребитель их потребляет. Это неправильная модель для моей проблемы, поэтому у этого нового кода нет очереди. Он просто моделирует длительный процесс моделирования с использованием метода run (), в котором есть вызовы sleep (), которые, очевидно, могут быть заменены шагами в вызовах функции SciPy.minimize.

В этом новом примере DoubleVar используется для позволяют потоку обновлять progressBar (спасибо stovfl за это предложение), StringVar обновляет окно сообщения в главном потоке из потока моделирования и, наконец, BooleanVar, чтобы сигнализировать основному потоку, что все сделано. Эта версия не имеет опроса в основной теме. Мне это не кажется очень изящным решением!

Как я узнаю, что изменения в DoubleVar, StringVar и BooleanVar проходят в основной поток? Только то, что эта программа работает !! Обратите внимание, что окно сообщения может быть обновлено либо из потока моделирования, либо с помощью кнопки в главном потоке GUI.

Опять же, комментарии приветствуются. Укажите причины, по которым это НЕ должно работать, а затем скажите, почему оно делает с учетом этих причин! Это нарушает какой-то базовый c дизайн Python, или возникнет ситуация, когда по какой-то причине это не сработает ??

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...