wxpython using time.sleep() without blocking complete GUI - user-interface

I want to change the text displayed in my GUI at specific time intervals. After a lot of approaches, I find that, specifically to my requirements, I must use time.sleep() instead of wx.Timer, but time.sleep() freeze the complete GUI. Here's an example of my code:
import wx
import time
DWELL_TIMES = [1, 2, 1, 3]
SCREEN_STRINGS = ['nudge nudge', 'wink wink', 'I bet she does', 'say no more!']
class DM1(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
panel = wx.Panel(self)
text_display = wx.StaticText(panel, pos = (400, 150))
for dwell_time in DWELL_TIMES:
text_display.SetLabel(SCREEN_STRINGS[dwell_time])
time.sleep(float(DWELL_TIMES[dwell_time]))
app = wx.App()
DM1Frame = DM1(None, size = (800, 600))
DM1Frame.Center()
DM1Frame.Show()
app.MainLoop()
Does somebody know why this happen, and how to make the GUI doesn't block?
I guess that Threading could help me, doesn't it? If it does, which is the correct way to put threads inside this code? Is there an alternative to Threading?
Thanks a lot!

As mentioned by others, wx.CallAfter and wx.CallLater are your friends. Study them and learn them. Here is a complete, working example using wx.CallLater. I included other refactoring as I saw fit.
import wx
DATA = [
(1, 'nudge nudge'),
(2, 'wink wink'),
(1, 'I bet she does'),
(3, 'say no more!'),
]
class Frame(wx.Frame):
def __init__(self):
super(Frame, self).__init__(None)
panel = wx.Panel(self)
self.text = wx.StaticText(panel)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.AddStretchSpacer(1)
sizer.Add(self.text, 0, wx.ALIGN_CENTER)
sizer.AddStretchSpacer(1)
panel.SetSizer(sizer)
self.index = 0
self.update()
def update(self):
duration, label = DATA[self.index]
self.text.SetLabel(label)
self.index = (self.index + 1) % len(DATA)
wx.CallLater(int(duration * 1000), self.update)
if __name__ == '__main__':
app = wx.App(None)
frame = Frame()
frame.SetTitle('Example')
frame.SetSize((400, 300))
frame.Center()
frame.Show()
app.MainLoop()

If you look at the documentation for time.sleep(), you see that it basically blocks execution of that thread for the specified interval. The problem is that currently your GUI has only a single thread, so if you block the thread then you block ALL execution in that thread. This means, as you've experienced, that the GUI is unusable during the sleep.
Even using threading, the time.sleep() call can't be in the same thread as the GUI, thus trying to get your GUI to refresh after the sleep is over will be very complicated. Beyond that, it's basically reimplementing wx.Timer! No use redoing something that's already been done for you.
It seems to me that your question should be less "how do I make sleeps work?" and more "Why isn't wx.Timer working properly?" Please explain the problem you're having with wx.Timer in detail. Why won't it work? Maybe post some code. My guess is you probably aren't binding the wx.EVT_TIMER properly. Take a look at this tutorial.

Which is the correct way to put threads inside this code?
Although using wx.Timer is the correct solution to this simplified example, if your real goal is to know how to use a worker thread to do long tasks and give updates to your main GUI without freezing your whole application, here's how:
import wx
import threading
import time
class WorkerThread(threading.Thread):
DWELL_TIMES = [1, 2, 1, 3]
SCREEN_STRINGS = ['nudge nudge', 'wink wink', 'I bet she does', 'say no more!']
def __init__(self, window):
threading.Thread.__init__(self)
self.window = window
def run(self):
for i in range(len(WorkerThread.DWELL_TIMES)):
wx.CallAfter(self.window.set_text, WorkerThread.SCREEN_STRINGS[i])
time.sleep(float(WorkerThread.DWELL_TIMES[i]))
wx.CallAfter(self.window.close)
class DM1(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
panel = wx.Panel(self)
self.text_display = wx.StaticText(panel, pos = (400, 150))
self.kickoff_work()
def kickoff_work(self):
t = WorkerThread(self)
t.start()
def set_text(self, text):
self.text_display.SetLabel(text)
def close(self):
self.Close()
app = wx.App()
DM1Frame = DM1(None, size = (800, 600))
DM1Frame.Center()
DM1Frame.Show()
app.MainLoop()

You might try making a global variable that gets the time when it first starts, then having a second variable get the current time and see if the two times are far enough apart to work. Something like this:
When the text changes to something new,
global timestart
timestart = gettime()
Then, where you check if you are changing the code,
timestop = gettime()
if timestop - timestart >= timebetweenchanges:
change code

I don't understand why you can't use a timer for this. They seem to be made for the exact purpose you need them for. As acattle mentioned already, I wrote a tutorial on the subject.
He is completely right though. Using time.sleep() will freeze the GUI because it blocks wx's main event loop. If you absolutely HAVE to use time.sleep() (which I doubt), then you can use a thread. I wrote a tutorial on that subject too. In fact, I actually use time.sleep() in that example.

I might suggest you go use wx.CallLater. Refer to official doc: http://wxpython.org/docs/api/wx.CallLater-class.html
A convenience class for wx.Timer, that calls the given callable object
once after the given amount of milliseconds, passing any positional or
keyword args. The return value of the callable is availbale after it
has been run with the GetResult method.
If you don't need to get the return value or restart the timer then
there is no need to hold a reference to this object. It will hold a
reference to itself while the timer is running (the timer has a
reference to self.Notify) but the cycle will be broken when the timer
completes, automatically cleaning up the wx.CallLater object.
Possible further reference can be found in this question: Using wx.CallLater in wxPython

Related

Play image sequence using Qt QMainWindow

I have an image sequence rendered out. which I want to payback in a simple QMainWindow or QDialog. This is what I have sofar. It loads the images into the qlabel, but I cant see the label being updated, its just show the last loaded image, and nothing in between.
Maybe someone knows something?
from PySide import QtCore, QtGui
import shiboken
import maya.OpenMayaUI as apiUI
import time
def getMayaWindow():
"""
Get the main Maya window as a QtGui.QMainWindow instance
#return: QtGui.QMainWindow instance of the top level Maya windows
"""
ptr = apiUI.MQtUtil.mainWindow()
if ptr is not None:
return shiboken.wrapInstance(long(ptr), QtGui.QWidget)
class Viewer(QtGui.QMainWindow):
def __init__(self, parent = getMayaWindow()):
super(Viewer, self).__init__(parent)
self.setGeometry(400, 600, 400, 300)
self.setUi()
def setUi(self):
self.label = QtGui.QLabel()
self.setCentralWidget(self.label)
def showUi(self):
self.show()
def loadImage(self, path):
self.label.clear()
image = QtGui.QImage(path)
pp = QtGui.QPixmap.fromImage(image)
self.label.setPixmap(pp.scaled(
self.label.size(),
QtCore.Qt.KeepAspectRatio,
QtCore.Qt.SmoothTransformation))
x = Viewer()
x.showUi()
for i in range(1, 11):
x.loadImage("C://anim%03d.png" % i)
time.sleep(0.5)
You change pixmaps in loop and sleep (stop) all GUI thread, that's why your GUI freeze.
http://www.tutorialspoint.com/python/time_sleep.htm
It is not correct. qLabel.repaint() it is bad solution because it still blocks GUI. Of course you can use processEvents but it is bad approach too.
You should use QTimer for this purpose, use timeout() signal, create slot and change pixmaps in this slot. In this case your GUI will not be blocked because QTimer works asynchronously and images will be successfuly changed.
Same code with loop and sleep can help you only when this code will execute in another thread (multi threading) but it is not necessary because there is special class QTimer.

How to interrupt QThread from PyQt GUI?

I'm writting an application that encrypt an image. The main problem is that i want to add to my GUI option to interrupt (or even terminate) an encrypting thread (while it is working) just by clicking an gui button. Gui and algorithm work fine (ia also provide a gui's progressbar connection) but when the thread start to procced i can't click anything on gui (even the terminating button). Beside that button is properly connected becuse if there occured an error in thread and gui i still working i can click the button and it terminate the process.
I thought that gui froze because thread was defined in gui function so I've moved it out of gui to main program function.
I want to point out that i don't create therad subclass (as Maya Posch suggest http://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/)
Here is the code of main function:
def main():
app = QApplication(sys.argv)
cryptoThread = QtCore.QThread()
prog = ProgramWindow()
worker = ic.imageCryptographer()
worker.moveToThread(cryptoThread)
prog.progressButton.clicked.connect(lambda: prog.interruptEncrypting(cryptoThread))
prog.startEncrypting.connect(cryptoThread.start)
worker.encryptSignal.connect(prog.progressbar.setValue)
worker.done.connect(lambda: prog.endEncrypting(cryptoThread, worker))
cryptoThread.started.connect(lambda: worker.compute(prog.shareFlag, prog.binMatrix))
sys.exit(app.exec_())
functions from class ProgramWindow:
def interruptEncrypting(self, thread):
thread.terminate()
thread.wait()
self.interrupt()
return
def endEncrypting(self, thread, worker):
self.keys = worker.keys
thread.quit()
self.progressbarWidget.setVisible(False)
self.saveOption.setEnabled(True)
self.cryptoWorkdeskOption.setEnabled(True)
self.openCryptoWorkdesk()
def interrupt(self):
self.progressbarWidget.setVisible(False)
if self.state==1:
self.buttonSwapWidget.setVisible(True)
elif self.state==2:
self.keyChooseWidget.setVisible(True)
Variables: shareFlag and binMatrix has no connection to thread communication (their are variables necceseray to compute worker methods. StartEncrypting is a signal emited from one of ProgramWindow function.
Thanks in advance for any advice where I made a mistake or what should I do.
Yep, just tried it and can confirm what I already indicated in my comment. Although you moved worker to cryptoThread (and therefore all its methods) the moment you connect cryptoThread.started.connect(lambda: worker.compute(prog.shareFlag, prog.binMatrix)) you create a new lambda object which is not running within cryptoThread but within the main thread. That's why it does not run besides your main application. You would have to connect cryptoThread.started.connect(worker.compute) and pass the arguments in some additional initializer / configure method.
I tested it with the following code:
import sys
import time
from PyQt4 import QtGui, QtCore
class Worker(QtCore.QObject):
def __init__(self, parent=None):
QtCore.QObject.__init__(self, parent)
self.t1 = QtCore.QThread()
self.moveToThread(self.t1)
self.t1.start()
def do_stuff(self):
while True:
print 'loop'
time.sleep(1)
class MainWindow(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.worker = Worker()
self.button = QtGui.QPushButton('start', self)
self.button.clicked.connect(self.worker.do_stuff) # connect directly with worker's method do_stuff
#self.button.clicked.connect(lambda: self.worker.do_stuff()) # connect with lambda object containing do_stuff
app = QtGui.QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())

How to kill previously ran methods called by an event

Dear members of our community,
The following is a simple wx.app with only a text control. When this text control is edited a large process starts. If one edits the control slowly, the editing feels normal. However, if the human is a fast typer, the editor stalls. I think the solution is to kill the previous call of the function self.VeryLargeProcess().
How do you do it?
Is there a better way to do this?
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "My Frame")
main_panel = wx.Panel(self)
static_box = wx.StaticBox( main_panel, -1, "Text control" )
static_box.SetFont( wx.Font( 15, wx.ROMAN, wx.NORMAL, wx.NORMAL ) )
main_sizer = wx.StaticBoxSizer( static_box, wx.VERTICAL )
text_ctrl = wx.TextCtrl( main_panel, wx.ID_ANY, "Edit me, a large process will start", wx.DefaultPosition, [200,100], wx.TE_MULTILINE )
main_sizer.Add( text_ctrl, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
main_panel.SetSizer( main_sizer )
main_sizer.SetSizeHints( self )
self.Bind(wx.EVT_TEXT, self.VeryLargeProcess, text_ctrl)
def VeryLargeProcess(self, event):
# kill previous self.VeryLargeProcess()
for i in range(0, 3000000):
a = i
if __name__ == '__main__':
app = wx.App()
frame = MyFrame()
frame.Show()
app.MainLoop()
If the process takes a "long time" to run, then you need to put that process in a separate thread or your GUI's responsiveness will slow or stop. The wxPython wiki has a good article on the various ways to use threads and I wrote a tutorial on the topic as well.

PyGTK FileChooserDialog Keep Getting Errors

Now I made WidgetArea originally for Windows, but being primarily a Linux user. I wanted to make it for Linux as well, but mainly to learn more about the file dialog in PyGTK. So I took a look at this tutorial to have a better understanding of it, while working on this simple, yet small application, as that's easier for me to learn, and understand by experimentation.
So here's my source code.
#!/usr/bin/env python
import sys, os
import pygtk, gtk, gobject
import pygst
pygst.require("0.10")
import gst
class WidgetArea(gtk.Window):
def addwidget(self, w):
self.win = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.win.set_title("Widget")
self.win.set_decorated(False)
self.win.set_has_frame(False)
self.win.set_resizable(False)
self.win.set_keep_above(True)
self.win.set_property('skip-taskbar-hint', True)
self.previewimage = gtk.Image()
self.win.add(self.previewimage)
self.win.show_all()
def pinning(self, checkbox):
if checkbox.get_active():
self.set_keep_above(True)
else:
self.set_keep_above(False)
def change_size(self, w):
width = int(self.entryw.get_text())
height = int(self.entryh.get_text())
self.win.set_size_request(width,height)
def __init__(self):
super(WidgetArea, self).__init__()
self.set_position(gtk.WIN_POS_CENTER)
self.set_title("WidgetArea")
self.set_resizable(False)
self.set_keep_above(True)
self.set_property('skip-taskbar-hint', True)
self.connect("destroy", gtk.main_quit, "WM destroy")
vbox = gtk.VBox(spacing=0)
hbox = gtk.HBox(spacing=0)
hbox2 = gtk.HBox(spacing=0)
hbox3 = gtk.HBox(spacing=0)
hbox4 = gtk.HBox(spacing=0)
self.widgetsize = gtk.Label("Widget Size:")
self.widgetsize.set_size_request(100, 30)
self.entryw = gtk.Entry()
self.entryh = gtk.Entry()
self.entryw.set_text("270")
self.entryw.set_size_request(75, 30)
labelcoma = gtk.Label(",")
labelcoma.set_size_request(10, 30)
self.entryh.set_text("221")
self.entryh.set_size_request(75, 30)
labelspac1 = gtk.Label(" ")
labelspac1.set_size_request(10, 30)
hbox.pack_start(self.widgetsize)
hbox.pack_start(self.entryw)
hbox.pack_start(labelcoma)
hbox.pack_start(self.entryh)
hbox.pack_start(labelspac1, 0, 0, 10)
check = gtk.CheckButton("Pin This Window")
check.set_active(True)
check.connect("clicked", self.pinning)
hbox.pack_start(check, 0, 0, 10)
labelspac2 = gtk.Label(" ")
labelspac2.set_size_request(250, 15)
hbox2.pack_start(labelspac2)
filefilter = gtk.FileFilter()
filefilter.set_name("Images")
filefilter.add_mime_type("image/png")
filefilter.add_mime_type("image/jpeg")
filefilter.add_mime_type("image/gif")
filefilter.add_mime_type("image/tiff")
filefilter.add_mime_type("image/svg+xml")
filefilter.add_pattern("*.jpg")
self.ref_file_button = gtk.FileChooserButton('Add Widget')
self.ref_file_button.set_current_folder("/".join([self.rootdir,"pics"]))
self.ref_file_button.set_filter(filefilter)
self.ref_file_button.connect("file-set", self.on_open_clicked)
hbox3.pack_start(self.ref_file_button, 150, 150, 10)
labelspac5 = gtk.Label(" ")
labelspac5.set_size_request(0, 10)
hbox4.pack_start(labelspac5)
vbox.pack_start(hbox)
vbox.pack_start(hbox2)
vbox.pack_start(hbox3)
vbox.pack_start(hbox4)
self.add(vbox)
self.show_all()
def on_open_clicked(self, widget, data=None):
ref_image_path = widget.get_filename()
self.previewimage.set_from_file(ref_image_path)
self.addwidg.connect("clicked", self.addwidget)
self.addwidg.connect("clicked", self.change_size)
ref_image_path.destroy()
WidgetArea()
gtk.gdk.threads_init()
gtk.main()
I removed the following code (1st), due to the following error (2nd).
self.ref_file_button.set_current_folder("/".join([self.rootdir,"pics"]))
Traceback (most recent call last):
File "./widgetarea.py", line 109, in <module>
WidgetArea()
File "./widgetarea.py", line 86, in __init__
self.ref_file_button.set_current_folder("/".join([self.rootdir,"pics"]))
AttributeError: 'WidgetArea' object has no attribute 'rootdir'
Now this isn't a big deal at this point. My main goal is to get the image displayed in a new window. So after I removed the code above, due to that error I got another one.
Traceback (most recent call last):
File "./widgetarea.py", line 103, in on_open_clicked
self.previewimage.set_from_file(ref_image_path)
AttributeError: 'WidgetArea' object has no attribute 'previewimage'
All I'm having problems with is when you browse to select an image I want the chosen image, when pressed OK to launch as a new window displaying the chosen image in that window, as stated above.
To correct the first error, use gtk.FILE_CHOOSER_ACTION_OPEN instead of gtk.FileChooserAction.OPEN.
The second problem is because there is no variable named image at that point in your code (line 116). Perhaps you are coming from a C++ or Java background, where a name like image can be resolved by looking at the attributes of the enclosing class, i.e. this.image?
In Python you can't do that. You have to assign explicitly to self.image in your addwidget() method. Otherwise the name image remains local to the addwidget() method and is not available outside of it.
This raises a different problem, what happens every time the button gets clicked and addwidget() is called? self.win and self.image are overwritten. That may be what you want, but I'm just calling it to your attention --- it seems a little odd to me.
I have used something like this in one of my projects. And it's working well for me in Linux.
def __init__(self):
# Define all the widgets
image_filter = gtk.FileFilter()
image_filter.set_name("Images")
image_filter.add_mime_type("image/png")
image_filter.add_mime_type("image/jpeg")
image_filter.add_mime_type("image/gif")
image_filter.add_mime_type("image/tiff")
image_filter.add_mime_type("image/svg+xml")
image_filter.add_pattern("*.jpg")
self.ref_file_button = gtk.FileChooserButton('Select Image')
self.ref_file_button.set_size_request(100,30)
self.ref_file_button.set_current_folder("/".join([self.rootdir,"pics"])) # set directory path
self.ref_file_button.set_filter(image_filter)
self.ref_file_button.set_tooltip_text('Select Image')
self.ref_file_button.connect("file-set", self.ref_image_selected)
def ref_image_selected(self,widget,data=None):
ref_image_path = widget.get_filename()
print ref_image_path
After getting the path of the image, you can load it using gtk.Image
EDIT:
Your code is a bit erroneous. You are never calling the function addwidget(), and hence self.previewimage is not defined. and so it gives AttributeError.
def __init__(self):
# your code
self.add(vbox)
self.addwidget(200) # I don't know what 'w' is. so I took a random number.
self.show_all()
def on_open_clicked(self, widget, data=None):
ref_image_path = widget.get_filename()
self.previewimage.set_from_file(ref_image_path)
self.addwidg.connect("clicked", self.addwidget)
self.addwidg.connect("clicked", self.change_size)
ref_image_path.destroy()
What is self.addwidg ?
And I am able to view the image now.

How to get the details of an event in wxPython

I have a section of code which returns events generated by a slider.
I bind the event with self.Bind(wx.EVT_SCROLL,self.OnSlide).
The code which handles the event reads something like:
def OnSlide(self,event):
widget = event.GetEventObject()
This is great but an error gets thrown every time the code is executed. It reads:
AttributeError: 'PyEventBinder' object has no attribute 'GetEventObject'
I want to be able to see which of the sliders generated the event but the error appears every time I attempt to find out.
How can I get the code to execute correctly?
Many thanks in advance.
To debug something like this, put the following as the first statement in your event handler:
import pdb; pdb.set_trace()
This will stop the execution of the program at this point and give you an interactive prompt. You can then issue the following command to find out what methods are available:
print dir(event)
When I was first learning wxPython I found this technique invaluable.
The following works for me on Windows 7, wxPython 2.8.10.1, Python 2.5
import wx
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, title="Tutorial")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
slider = wx.Slider(panel, size=wx.DefaultSize)
slider.Bind(wx.EVT_SLIDER, self.onSlide)
#----------------------------------------------------------------------
def onSlide(self, event):
""""""
obj = event.GetEventObject()
print obj
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm().Show()
app.MainLoop()

Resources