wxPython manipulate GUI in same program - multithreading

I am trying to create a GUI with wxPython. After I create the GUI I want the code to continue to execute, so I have tried to throw the app.MainLoop() onto a new thread. This gives me issues. Also when I try to change the background color for example, it freezes up the GUI.
import wx
import threading
from threading import Thread
from multiprocessing import Process
class Display(wx.Frame):
def __init__(self, *args, **kwargs):
print 'here'
self.app = wx.App(False)
super(Display, self).__init__(*args, **kwargs)
self.SetTitle("Yo mama")
self.Show(True)
thread = Thread(target = self.app.MainLoop)
thread.start()
self.Close()
def DisplayGUI(self):
self.SetTitle("MyTitle")
self.Show(True)
def Changetitle(self):
self.SetTitle("Title2")
display = Display(None)
# do stuff
display.ChangeTitle()
# do stuff
So basically I want to be able to run a GUI, and control it later on within my program (not through the GUI itself with events). I plan on having pictures display at random times.

The GUI's MainLoop needs to run in the main thread. So do this, and if you want events at random times, you can use a timer, for example, wx.Timer.
Using this approach, all the changes you mention (displaying pictures, changing the titles, colors, etc) should be straightforward. Plus, you'll get a lot more control this way, where you can start and stop timers, change the interval, have multiple timers, setup timers that trigger each other, and many other options.
To learn more about wx.Timer, a good place to start is always the demo. There's also a tutorial here, and a video here.
(I've heard that there are ways to run the MainLoop in a separate thread, but nothing you mention requires that, and it will be much, much easier to just use a timer.)

Related

Start Tk.mainloop() thread with another thread

I want to exec some code while Tk.mainloop() is running, so I think I need threading module.
I tried to put Tk.mainloop() inside the run method of a thread class and then I put the code I want to run while mainloop is running inside another thread.
from threading import Thread
import tkinter as tk
class MyThread(Thread):
def __init__(self):
Thread.__init__(self)
def run():
# window is tk.Tk()
window.mainloop()
class MyCode(Thread):
# my code
TkinterThread = MyThread()
TkinterThread.start()
OtherThread = MyCode()
OtherThread.start()
and tkinter report me an error
RuntimeError: Calling Tcl from different apartment
so I searched on internet and I understand that the mainloop can be runned only out from thread becouse work in only one of them.
So, is there a way to run others thread while mainloop is running?
You can run code in other threads. The problem isn't so much multiple threads per se, but that you've got tkinter code in more than one thread. All tkinter code needs to be in a single thread.
Usually it's best to make that your main thread (both creating the widgets and starting mainloop), and run your other code in a secondary thread or process. You can use a thread-safe queue to send information between the threads, such as when passing results back to the GUI.

Scheduling actions

I'm making a BlackJack application using Kivy, I basically need to make a sort of delay or even a time.sleep, but of course, it doesn't have to freeze the program. I saw kivy has Clock.whatever to schedule certain actions. What I'd like to do is scheduling multiple actions so that when the first action has finished, the second will be run and so on. What's the best way to achive this? or is there in the Clock module something to perform multiple delays one after another?
This could be an example of what i need to do:
from kivy.clock import Clock
from kivy.uix import BoxLayout
from functools import partial
class Foo(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
for index_time, card in enumerate(cards, 1):
# Schedule this action to be run after 1 sec from the previous one and so on
Clock.schedule_once(partial(self.function, card), index_time)
def function(self, card, *args):
self.add_widget(card)
First, I'm surprised that your question didn't get down-voted since this is not supposed to be a place for opinion questions. So you shouldn't ask for best.
The Clock module doesn't have a specific method to do what you want. Obvioulsy, you could do a list of Clock.schedule_once() calls, as your example code does. Another way is to have each function schedule its successor, but that assumes that the functions will always be called in that order.
Anyway, there are many ways to do what you want. I have used a construct like the following:
class MyScheduler(Thread):
def __init__(self, funcsList=None, argsList=None, delaysList=None):
super(MyScheduler, self).__init__()
self.funcs = funcsList
self.delays = delaysList
self.args = argsList
def run(self):
theLock = threading.Lock()
for i in range(len(self.funcs)):
sleep(self.delays[i])
Clock.schedule_once(partial(self.funcs[i], *self.args[i], theLock))
theLock.acquire()
It is a separate thread, so you don't have to worry about freezing your gui. You pass it a list of functions to be executed, a list of arguments for those functions, and a list of delays (for a sleep before each function is executed). Note that using Clock.schedule_once() schedules the execution on the main thread, and not all functions need to be executed on the main thread. The functions must allow for an argument that is a Lock object and the functions must release the Lock when it completes. Something like:
def function2(self, card1, card2, theLock=None, *args):
print('in function2, card1 = ' + str(card1) + ', card2 = ' + str(card2))
if theLock is not None:
theLock.release()
The MyScheduler class __init__() method could use more checking to make sure it won't throw an exception when it is run.

PyQt5 - QMovie in MainWindow doesn't play even when using QThread

I am trying to display a loading gif on my PyQt5 QMainWindow while an intensive process is running. Rather than playing normally, the QMovie pauses. As far as I can tell, the event loop shouldn't be blocked as the intensive process is in its own QObject passed to its own QThread. Relevant code below:
QMainWindow:
class EclipseQa(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.initUI()
def initUI(self):
...
self.loadingMovie = QMovie("./loading.gif")
self.loadingMovie.setScaledSize(QSize(149, 43))
self.statusLbl = QLabel(self)
self.statusLbl.setMovie(self.loadingMovie)
self.grid.addWidget(self.statusLbl, 6, 2, 2, 2, alignment=Qt.AlignCenter)
self.statusLbl.hide()
...
def startLoadingGif(self):
self.statusLbl.show()
self.loadingMovie.start()
def stopLoadingGif(self):
self.loadingMovie.stop()
self.statusLbl.hide()
def maskDose(self):
self.startLoadingGif()
# Set up thread and associated worker object
self.thread = QThread()
self.worker = DcmReadWorker()
self.worker.moveToThread(self.thread)
self.worker.finished.connect(self.thread.quit)
self.worker.updateRd.connect(self.updateRd)
self.worker.updateRs.connect(self.updateRs)
self.worker.updateStructures.connect(self.updateStructures)
self.worker.clearRd.connect(self.clearRd)
self.worker.clearRs.connect(self.clearRs)
self.thread.started.connect(lambda: self.worker.dcmRead(caption, fname[0]))
self.thread.finished.connect(self.stopLoadingGif)
self.maskThread.start()
def showDoneDialog(self):
...
self.stopLoadingGif()
...
Worker class:
class DoseMaskWorker(QObject):
clearRd = pyqtSignal()
clearRs = pyqtSignal()
finished = pyqtSignal()
startLoadingGif = pyqtSignal()
stopLoadingGif = pyqtSignal()
updateMaskedRd = pyqtSignal(str)
def __init__(self, parent=None):
QObject.__init__(self, parent)
#pyqtSlot(name="maskDose")
def maskDose(self, rd, rdName, rdId, rs, maskingStructure_dict):
...
self.updateMaskedRd.emit(maskedRdName)
self.finished.emit()
For brevity, '...' indicates code that I figured is probably not relevant.
Your use of lambda to call the slot when the threads started signal is emitted is likely causing it to execute in the main thread. There are a couple of things you need to do to fix this.
Firstly, your use of pyqtSlot does not contain the types of the arguments to the maskDose method. You need to update it so that it does. Presumably you also need to do this for the dcmRead method which you call from the lambda but haven't included in your code. See the documentation for more details.
In order to remove the use of the lambda, you need to define a new signal and a new slot within the EclipseQa class. The new signal should be defined such that the required number of parameters for the dcmRead method are emitted, and the types correctly specified (documentation for this is also in the link above). This signal should be connected to the workers dcmRead slot (make sure to do it after the worker object has been moved to the thread or else you might run into this bug!). The slot should take no arguments, and be connected to the threads started signal. The code in the slot should simply emit your new signal with the appropriate arguments to be passed to dcmRead (e.g. like self.my_new_signal.emit(param1, param2)).
Note: You can check what thread any code is running in using the Python threading module (even when using QThreads) by printing threading.current_thread().name from the location you wish to check.
Note 2: If your thread is CPU bound rather than IO bound, you may still experience performance issues because of the Python GIL which only allows a single thread to execute at any one time (it will swap between the threads regularly though so code in both threads should run, just maybe not at the performance you expect). QThreads (which are implemented in C++ and are theoretically able to release the GIL) do not help with this because they are running your Python code and so the GIL is still held.

Terminating a QThread that wraps a function (i.e. can't poll a "wants_to_end" flag)

I'm trying to create a thread for a GUI that wraps a long-running function. My problem is thus phrased in terms of PyQt and QThreads, but I imagine the same concept could apply to standard python threads too, and would appreciate any suggestions generally.
Typically, to allow a thread to be exited while running, I understand that including a "wants_to_end" flag that is periodically checked within the thread is a good practice - e.g.:
Pseudocode (in my thread):
def run(self):
i = 0
while (not self.wants_to_end) and (i < 100):
function_step(i) # where this is some long-running function that includes many streps
i += 1
However, as my GUI is to wrap a pre-written long-running function, I cannot simply insert such a "wants_to_end" flag poll into the long running code.
Is there another way to forcibly terminate my worker thread from my main GUI (i.e. enabling me to include a button in the GUI to stop the processing)?
My simple example case is:
class Worker(QObject):
finished = pyqtSignal()
def __init__(self, parent=None, **kwargs):
super().__init__(parent)
self.kwargs = kwargs
#pyqtSlot()
def run(self):
result = SomeLongComplicatedProcess(**self.kwargs)
self.finished.emit(result)
with usage within my MainWindow GUI:
self.thread = QThread()
self.worker = Worker(arg_a=1, arg_b=2)
self.worker.finished.connect(self.doSomethingInGUI)
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
self.thread.start()
If the long-running function blocks, the only way to forcibly stop the thread is via its terminate() method (it may also be necessary to call wait() as well). However, there is no guarantee that this will always work, and the docs also state the following:
Warning: This function is dangerous and its use is discouraged. The
thread can be terminated at any point in its code path. Threads can be
terminated while modifying data. There is no chance for the thread to
clean up after itself, unlock any held mutexes, etc. In short, use
this function only if absolutely necessary.
A much cleaner solution is to use a separate process, rather than a separate thread. In python, this could mean using the multiprocessing module. But if you aren't familiar with that, it might be simpler to run the function as a script via QProcess (which provides signals that should allow easier integration with your GUI). You can then simply kill() the worker process whenever necessary. However, if that solution is somehow unsatisfactory, there are many other IPC approaches that might better suit your requirements.

Why does some widgets don't update on Qt5?

I am trying to create a PyQt5 application, where I have used certain labels for displaying status variables. To update them, I have implemented custom pyqtSignal manually. However, on debugging I find that the value of GUI QLabel have changed but the values don't get reflected on the main window.
Some answers suggested calling QApplication().processEvents() occasionally. However, this instantaneously crashes the application and also freezes the application.
Here's a sample code (all required libraries are imported, it's just the part creating problem, the actual code is huge):
from multiprocessing import Process
def sub(signal):
i = 0
while (True):
if (i % 5 == 0):
signal.update(i)
class CustomSignal(QObject):
signal = pyqtSignal(int)
def update(value):
self.signal.emit(value)
class MainApp(QWidget):
def __init__(self):
super().__init__()
self.label = QLabel("0");
self.customSignal = CustomSignal()
self.subp = Process(target=sub, args=(customSignal,))
self.subp.start()
self.customSignal.signal.connect(self.updateValue)
def updateValue(self, value):
print("old value", self.label.text())
self.label.setText(str(value))
print("new value", self.label.text())
The output of the print statements is as expected. However, the text in label does not change.
The update function in CustomSignal is called by some thread.
I've applied the same method to update progress bar which works fine.
Is there any other fix for this, other than processEvents()?
The OS is Ubuntu 16.04.
The key problem lies in the very concept behind the code.
Processes have their own address space, and don't share data with another processes, unless some inter-process communication algorithm is used. Perhaps, multithreading module was used instead of threading module to bring concurrency to avoid Python's GIL and speedup the program. However, subprocess has cannot access the data of parent process.
I have tested two solutions to this case, and they seem to work.
threading module: No matter threading in Python is inefficient due to GIL, but it's still sufficient to some extent for basic concurrency demands. Note the difference between concurrency and speedup.
QThread: Since you are using PyQt, there's isn't any issue in using QThread, which is a better option because it takes concurrency to multiple cores taking advantage of operating system's system call, rather than Python in the middle.
Try adding
self.label.repaint()
immediately after updating the text, like this:
self.label.setText(str(value))
self.label.repaint()

Resources