PyQT force update textEdit before calling other function - python

My question concerns PyQT5.
I try to have a dialog window with a button that when clicked
updates some text of a QTextEdit field
calls a function (which needs much time to terminate)
Something like this:
class StartDialog(QtWidgets.QWidget, start_dialog_ui.Ui_Dialog):
def __init__(self, parent):
super(self.__class__, self).__init__()
self.setupUi(self)
self.OKButton.clicked.connect(self.start)
def start(self):
self.startDialogTextEdit.append("simulation running ...")
run_lengthy_function(self)
However, when I run my GUI I notice that the text is updated only after the lengthy function has terminated, although the QTextEdit.append is called before the lengthy function. How can I enforce that the text is updated in advance?
What I tried so far (but didn't work) was to let Python wait some time before triggering the lengthy function call, i.e.
from time import sleep
class StartDialog(QtWidgets.QWidget, start_dialog_ui.Ui_Dialog):
def __init__(self, parent):
super(self.__class__, self).__init__()
self.setupUi(self)
self.OKButton.clicked.connect(self.start)
def start(self):
self.startDialogTextEdit.append("simulation running ...")
sleep(5)
run_lengthy_function(self)

The repaint is called in event loop so sleep the whole thread does not change anything.
You can call repaint manually by:
self.startDialogTextEdit.repaint()
or call static method:
QCoreApplication.processEvents()
which also call repaint internally

The solution for the case that the text is displayed in the QTextEdit is to call qApp.processEvents(), this force to the GUI update:
def start(self):
self.startDialogTextEdit.append("simulation running ...")
QtWidgets.qApp.processEvents()
[...]
On the other hand if the task is heavy it may be blocking the GUI, so maybe one solution is to run it on another thread, I can not give a proper recommendation since I do not know your function

Related

maya close window signal

I have created a ui from scratch using the commands within Maya documentation.
The following function that I have wrote applies in two scenerios:
When the user has clicked onto another button - Import, in which it will do as what it was written in the code then close it off with the following function (see readFile function)
When user has clicked onto the button where it close the UI without running anything.
In my script, to cater the above two scenarios, I wrote as the following where closeWindow is catered to Scenario1 and cancelWindow is catered to Scenario2
def ui(self):
...
cancelButton = cmds.button( label='Cancel', command=self.cancelWindow, width=150, height=35)
def closeWindow(self, *args):
cmds.deleteUI(self.window, window=True)
def cancelWindow(self, *args):
cmds.delete(camSel[0])
cmds.deleteUI(self.window, window=True)
def readFile(self, *args):
...
self.closeWindow()
As such, is it possible to create some sort of signal like those in PyQt (clicked(), returnPressed() etc) by combining the above 2 (automated + manual), seeing that the deleteUI command usage is the same?
Default Maya UI provides only callbacks, not signals. You can create a sort of 'pseudo signal' by calling an event handler object instead of a function. In that scenario the button only knows 'I fired the button event' and the handler can call as many functions as needed.
class Handler(object):
def __init__(self):
self.handlers = []
def add_handler (self, func):
self.handlers.append(func)
def __call__(self, *args, **kwargs):
for eachfunc in handler:
eachfunc(*args, **kwargs)
hndl = Handler()
hndl.add_handler(function1) # do some ui work...
hndl.add_handler(function2) # do some scene work...
hndl.add_handler(function3) # do something over network, etc....
b = cmds.button('twoFunctions', c = Hndl)
In a large complex UI this is a nice way to keep minor things like button higlights and focus changes separated out from important stuff like changing the scene. In your application it's almost certainly overkill. You've only sharing 1 line between close and cancel, that's not too bad :)
Heres' more background on on pseudo-events in maya gui.
You can also use Maya's QT directly to get at the close event... Again, seems like overkill. More here

Signals and Slots PyQt clarification

I have noticed that there are a lot of users, myself included, who don't quite grasp the concept of signals and slots in Qt. I was hoping to get some clarification on the following:
#I have a function that runs as soon as the GUI is built, this takes the information from
#a list and puts it into a string which is then uploaded to a texbox. At the bottom of this
#loop, I want it to call a function in the parent thread via signals and slots, as
#recommended by other users.
class MainWindow(QtGui.QMainWindow):
#all the code needed to build the GUI
thread_mythread = threading.Thread(target = self.updateText, args = ())
thread_mythread.start()
def clearText(self):
self.TextEdit.clear()
def updateText(self):
self.trigger.connect(self.clearText)
while True:
self.trigger.emit()
NewString = list.pop(0)
#I think I may have to use append, as setText() is not safe outside of the parent thread
self.TextEdit.append(NewString)
Although probably terribly incorrect, I attempt to use signals. Is this the proper way to do it? I also get an error that says that the Main Window object has no attribute "trigger",why is this?
thank you.
The reason you get that error is exactly the reason described by the error message - the signal trigger has not been defined anywhere in your class. You need to define it before you can emit it.
Signals and slots are used to communicate between different objects. In your example you are trying to do everything from within your MainWindow class and there is no interaction with other objects. You also only need to make the call to connect() once. You would typically call this either in the class constructor or from your main function after instantiating the objects you want to connect together.
Take a look at http://pyqt.sourceforge.net/Docs/PyQt4/new_style_signals_slots.html for some examples of how to use signals and slots properly in PyQt.
For threading, use QThread rather than threading.Thread as it is better integrated with the Qt framework. This post shows some simple examples of how to use QThread in PyQt. The second method (using moveToThread()) is considered to be the most correct way to create new threads.
The basic idea for your kind of problem is:
handle GUI operations from the main thread
handle blocking operations (in your case the while loop) in a separate thread
emit signals from the worker thread to call functions (slots) in the main thread and vice versa
Also note that:
You cannot call any methods of QWidget its descendents from a secondary thread
Signals can also send data if you need to pass it between threads
To add to #user3419537 good answer. A very quick threading example:
from PyQt4.QtCore import QObject, pyqtSlot, pyqtSignal, QThread, \
Q_ARG, Qt, QMetaObject
class MyWorker(QObject):
# define signal
clear = pyqtSignal()
update_text_signal = pyqtSignal(str) # passes a string back
finished = pyqtSignal()
def __init__(self, parent=None):
super(MyWorker, self).__init__(parent)
# Add functions etc.
#pyqtSlot(list)
def update_text(self, string_list):
#Intensive operation
self.clear.emit() # moved outside of while
while(True):
#This is infinite loop so thread runs forever
new_string = self.string_list.pop(0)
self.update_text_signal.emit(new_string) # Fixed this line
#Finished
self.finished.emit()
Then in your MainWindow class
self.my_thread = QThread()
self.handler = MyWorker()
self.handler.moveToThread(self.my_thread)
self.handler.clear.connect(self.clearText)
self.handler.update_text_signal.connect(self.update_line_edit)
self.handler.finished.connect(self.my_thread.quit)
# Start Thread
self.my_thread.start()
#pyqtSlot(str)
def update_line_edit(self, text):
self.TextEdit.append(text)
QMetaObject.invokeMethod(self.handler, 'update_text',
Qt.QueuedConnection,
Q_ARG(list, string_list))
You will need to call self.my_thread.quit() before your application closes to stop the thread and avoid the error: QThread: Destroyed while thread is still running
Please read docs for QMetaObject.invokeMethod.

Tkinter only calls after_idle once

I am new to Tkinter, so I apologize if this is easy, but I have search for a couple of hours and can't figure it out. What I want to do is after the mainloop is idle, I always want to call the function checkForGroupUpdates(). When I run the code below, it only runs once. I can't figure out to have it run every time the mainloop is idle. I appreciate the help.
from Tkinter import *
import random
class Network(Frame):
""" Implements a stop watch frame widget. """
def __init__(self, parent=None, **kw):
Frame.__init__(self, parent, kw)
self.makeWidgets()
def makeWidgets(self):
""" Make the time label. """
self._canvas = Canvas(self, width=600, height=400)
self._canvas.pack()
def checkForGroupUpdates(self):
print "checking"
h=0
this=10
while this>.0001:
this=random.random()
print h
h=h+1
print "checked"
def main():
root = Tk()
nw = Network(root)
nw.pack(side=TOP)
root.after_idle(nw.checkForGroupUpdates)
root.mainloop()
if __name__ == '__main__':
main()
#user1763510, notice that in Bryan Oakley's answer, he has checkForGroupUpdates call self.after again. This is because self.after only does a single call, so getting repeated calls requires having it call itself within the function that gets called by the first call. This way, it keeps repeatedly calling itself.
The same goes for the after_idle() function. You have to have checkForGroupUpdates call after_idle() again at the bottom.
Here is the documentation for after, after_idle, etc. There is even a little example in the after description, which makes it all clear.
Documentation: http://effbot.org/tkinterbook/widget.htm
Example from link above, under the afterdescription:
#Method 1
class App:
def __init__(self, master):
self.master = master
self.poll() # start polling
def poll(self):
... do something ...
self.master.after(100, self.poll)
To use after_idle instead, it would look like this:
#Method 2
class App:
def __init__(self, master):
self.master = master
self.poll() # start polling
def poll(self):
... do something ...
self.master.update_idletasks()
self.master.after_idle(self.poll)
Notice the addition of the self.master.update_idletasks() line. This draws the GUI and handles button presses and things. Otherwise, after_idle() will suck up all resources and not let the GUI self-update properly in the mainloop().
An alternative to using
self.master.update_idletasks()
self.master.after_idle(self.poll)
is to use:
#Method 3
self.master.update_idletasks()
self.master.after(0, self.poll)
Using self.master.after(0, self.poll) is my preferred technique, as it allows me to easily change the 0 to something else if I decide I don't need to run self.poll constantly. By increasing the delay time to at least 1 ms, you no longer need to call self.master.update_idletasks() at all. So, this works too:
#Method 4
self.master.after(1, self.poll)
Also notice that for all examples above, calling self.poll() in the __init__ function is what kicks it all off, and storing master into self.master is necessary simply so that inside poll you can call the after or after_idle function via self.master.after_idle, for example.
Q: Is this stable/does it work?
A: I ran a test code using Method 3 just above for ~21 hrs, and it ran stably the whole time, allowing the GUI to be usable and all.
Q: What is the speed comparison for each method above?
A:
Method 1: (I didn't speed test it)
Method 2: ~0.44 ms/iteration
Method 3: ~0.44 ms/iteration
Method 4: ~1.61 ms/iteration
Q: Which is my preferred method?
A: Method 3 or 4.
Instead of calling the function all the time when the app is idle, you should just call it once every fraction of a second. For example, if you want to check 10 times every second you would do something like this:
def checkForGroupUpdates(self):
<do whatever you want>
self.after(100, self.checkForGroupUpdates)
Once you call that function once, it will arrange for itself to be called again in 100ms. This will continue until the program exits. If the program goes "non-idle" (ie: while responding to a button click), this function will pause since tkinter is single-threaded. Once the program goes idle again, the check will continue.

wxpython frame initialization error with threads

Here's an example:
class DemoFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent)
self.panel = wx.Panel(self, -1)
...
initialize other elements
...
self.DoStuff()
def DoStuff(self):
self.panel.SetBackGroundColour(wx.Colour(240, 240, 240))
...
do something
...
Now as you know this is definitely not a good example of initializing your GUI since do something would most probably freeze the GUI while it's running, so I tweaked it to this:
import threading
class DemoFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent)
self.panel = wx.Panel(self, -1)
...
initialize other elements
...
DoStuffThead = threading.Thread(target = self.DoStuff, ())
DoStuffThead.start()
def DoStuff(self):
wx.CallAfter(self.ChangeBG, )
...
do something
...
def ChangeBG(self):
self.panel.SetBackGroundColour(wx.Colour(240, 240, 240))
Above code should work exactly the same as the first one does when do something is blank, but to my surprise I noticed there's little background drawing glitches when running the latter codes.
What part went wrong? Isn't this the right way to update GUI in threads?
It's a bad approach to update GUI from worker thread, not event saying it's not thread safe.
You have to communicate with main thread to update GUI.
The best way to achieve desired result is to user wx.PostEvent method. You can create custom events for your needs, inheriting from wx.PyEvent, and you better inherit threading.Thread to keep window you want to communicate within that thread class as an instance variable.
The best illustration of how to update GUI having long-running task can be found in wxPython wiki (first example).
After searching and playing with wxpython for a while, I finally found a solution for this, and it's actually quite simple, just refresh the panel and everything will be all right(add this line into ChangeBG method): self.panel.refresh(). I've no idea why the glitch exists though.
As to Rostyslav's answer, thanks a lot mate!
"It's a bad approach to update GUI from worker thread:", I think you mean it's rude to directly insert GUI codes into worker thread(which is exactly what I did in the first example) in terms of thread-safety concern, basically those GUI codes should be wrapped into thread-safe method(which is exactly what I was trying to do in my second example) and then queued into GUI main thread.
I found that there are basically three thread-safe methods as to GUI updating in worker thread: wx.PostEvent, wx.CallAfter and wx.CallLater, but I never liked wx.PostEvent, it's kind of cumbersome and you have to come up with your own event too, that's why wx.CallAfter is a better choice for me, it's more pythonic and easy to use, and actually wx.CallAfter is like a high level wrapper for wx.PostEvent if you check out the source code in _core.py:
def CallAfter(callable, *args, **kw):
"""
Call the specified function after the current and pending event
handlers have been completed. This is also good for making GUI
method calls from non-GUI threads. Any extra positional or
keyword args are passed on to the callable when it is called.
:see: `wx.CallLater`
"""
app = wx.GetApp()
assert app is not None, 'No wx.App created yet'
if not hasattr(app, "_CallAfterId"):
app._CallAfterId = wx.NewEventType()
app.Connect(-1, -1, app._CallAfterId,
lambda event: event.callable(*event.args, **event.kw) )
evt = wx.PyEvent()
evt.SetEventType(app._CallAfterId)
evt.callable = callable
evt.args = args
evt.kw = kw
wx.PostEvent(app, evt)
Well, I never tried wx.PostEvent implementation in my app, but I'm sure it would work as well.
Oh also I found this article very helpful: wxPython and Threads

Wrap a Python class in an asynchronous PyQt class

I am developing my pet project. It is a small app which downloads my inbox folder from Facebook. I want it to be available in both CLI and GUI (in PyQt) mode. My idea was that I write a class for the communication, and then the front-ends. I know that the downloading process is blocking which is not problem in CLI mode but it is in GUI.
I know that there is QNetworkAccessManager but I would have to re-implement the class that I have already written and then maintain two classes the same time.
I was searching for a while and I came up with one solutions where I subclass QObject and my FB class, implement the signals, than create a QThread object and use moveToThread() method which would be OK, but I must take care of creating and stopping the tread.
Is it possible to wrap my Python class to something to make it behave like QNetworkAccessManager? So the methods would return immediately and the object will emit signals when the data is ready.
Update
Thank you the comments. I have 2 main classes. The first is called SimpleGraph which just hides urllib2.urlopen(). It prepares the query and returns the decoded json got from Facebook. The actual work is happening in FBMDown class:
class FBMDown(object):
def __init__(self, token):
self.graph = SimpleGraph(token)
self.last_msg_count = 0
def _message_count(self, thread_id):
#TODO: Sanitize thread_id
p = {'q': 'SELECT message_count FROM thread WHERE thread_id = {0} LIMIT 1'.format(thread_id)}
self.last_msg_count = int(self.graph.call(params=p, path='fql')['data'][0]['message_count'])
return self.last_msg_count
So here when _message_count is called it returns the number of messages of the given thread id. This method works great in CLI mode, blocking is not a problem there.
I want to wrap this class into a class (if it is possible) which works asynchronous like QNetworkAccessManager, so it would not block the GUI but it would emit a signal when the data is ready.
The only technique I know now is to subclass QObject. It looks like this:
class QFBMDown(QtCore.QObject):
msg_count_signal = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
QtCore.QObject.__init__(self, parent)
def get_msg_count(self):
#Here happens the IO
time.sleep(2)
self.msg_count_signal.emit(1)
And this is my Windows class:
class Window(QtGui.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.centralwidget = QtGui.QWidget(self)
self.button_getmsgcount = QtGui.QPushButton(self.centralwidget)
self.setCentralWidget(self.centralwidget)
self.i = 0
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.block_indicator)
self.timer.start(20)
#self.worker = QtCore.QThread()
self.fbobj = QFBMDown()
#self.fbobj.moveToThread(self.worker)
#self.worker.start()
self.button_getmsgcount.clicked.connect(self.fbobj.get_msg_count)
self.fbobj.msg_count_signal.connect(self.show_msg_count)
def block_indicator(self):
self.button_getmsgcount.setText(str(self.i))
self.i += 1
def show_msg_count(self, data):
print 'Got {0} msgs in the thread'.format(data)
Now when I press the button the GUI blocks. If I de-comment line 13, 15, 16 (where I create a thread, move fbobj into this thread then start it) it does not block, it works nearly as I want but I have to do everything by hand every time and I have to take care of shutting down the thread (now I implement QMainWindow's closeEvent method where I shut down the thread before quit but I'm sure that this is not the right way to do).
Is it even possible what I want to do? I would not want to implement a new class and then maintain two classes which are doing the same thing.
The reason I am so insisted to do it like this is to make my app work without Qt but give a nice gui for those who don't want to type command line arguments.

Categories

Resources