How to get the details of an event in wxPython - events

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()

Related

wxPython in MSYS2: SetLabel in threaded handler causes freeze?

I am testing on windows 7, 64-bit, MSYS2 Mingw64 shell (the shell start command is C:\msys64\msys2_shell.cmd -use-full-path -mingw64); here I have installed via pacman: mingw-w64-x86_64-python2-2.7.13-1, mingw-w64-x86_64-wxWidgets-3.0.2-17 and mingw-w64-x86_64-wxPython-3.0.2.0-6.
Consider this code, which only has a title label, button and target label; when button is clicked, the label should change from "X" to "1":
import wx #, wx.html
import sys, os
from threading import Thread
# disable buffering (Windows)
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
sys.stderr = os.fdopen(sys.stderr.fileno(), 'w', 0)
class Frame(wx.Frame):
def __init__(self, *args, **kwds):
kwds["style"] = wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.label = wx.StaticText(self, wx.ID_ANY, "Click the button to change label below: ")
self.bt_main = wx.Button(self, label="Click ME")
self.bt_main.Bind(wx.EVT_BUTTON, self.BtnClickHandler)
self.label2 = wx.StaticText(self, wx.ID_ANY, "XX")
sizer_vmain_app = wx.BoxSizer(wx.VERTICAL)
sizer_vmain_app.Add(self.label, proportion=0, flag=wx.EXPAND, border=0)
sizer_vmain_app.Add(self.bt_main, proportion=0, flag=0, border=0)
sizer_vmain_app.Add(self.label2, proportion=0, flag=0, border=0)
self.SetSizer(sizer_vmain_app)
self.Layout()
def BtnClickHandler(self, event):
testThread = Thread(target=self.DoBtnClick)
testThread.start()
testThread.join()
def DoBtnClick(self):
print("BtnClickHandler ")
myval = int("1")
self.label2.SetLabel(str(myval))
if __name__ == "__main__":
app = wx.PySimpleApp(0)
wx.InitAllImageHandlers()
app_frame = Frame(None, wx.ID_ANY, "")
app.SetTopWindow(app_frame)
app_frame.Show()
app.MainLoop()
When I run this code as is, then the application freezes when it comes to the self.label2.SetLabel(str(myval)).
However, if I avoid the threading, and use this function instead:
def BtnClickHandler(self, event):
# testThread = Thread(target=self.DoBtnClick)
# testThread.start()
# testThread.join()
self.DoBtnClick()
... then everything works fine. Note that I call this script by running python test.py in the MSYS2 Mingw64 shell.
So, is it possible to run this code with threading on Windows, and if so, how? (Otherwise, running it with threading under Linux works without problems)
The problem is that wxWidgets library is not thread-safe.
What it means is that you can't access GUI elements from the secondary thread. GUI access is supported from the main thread only - the one on which you created the application object.
You can't call methods affecting the GUI from any thread other than the main one. Instead, post an event to the main thread asking it to perform the required operation on the worker thread behalf.
In C++ the simplest way to do it, by far, is to use CallAfter() with a lambda, e.g. you could just do CallAfter([=](){ label2->SetLabel(myval); }) in the thread code. Unfortunately I don't know if this is available from Python.

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.

Works with QGridLayout not with QVBoxLayout

from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys, os, time
class SetName(QWidget):
def __init__(self):
QWidget.__init__(self)
self.show()
toplayout = QVBoxLayout()
self.setWindowTitle('Personal Info')
self.form_layout = QFormLayout()
self.setLayout(self.form_layout)
self.line_edit_param = QLineEdit(self)
self.line_edit_param.setPlaceholderText("Write Here")
self.form_layout.addRow('Write Name', self.line_edit_param)
toplayout.addLayout(self.form_layout)
self.setFocus()
class LearnApp(QDialog):
def __init__(self):
super(QDialog, self).__init__()
self.setWindowTitle("LearnApp")
self.active = False
close_button = QPushButton("Close")
close_button.clicked.connect(self.close)
self.check_button = QPushButton("Check")
self.check_button.clicked.connect(self.set_data)
self.tr = QTextEdit()
self.tr.setReadOnly(True)
# layout
layout = QHBoxLayout()
#layout.addWidget(self.button3)
sub_layout = QVBoxLayout()
sub_layout.addWidget(self.check_button)
sub_layout.addWidget(close_button)
layout.addLayout(sub_layout)
layout.addWidget(self.tr)
self.setLayout(layout)
self.setFocus()
def set_data(self):
print "in set_data"
SetName()
app = QApplication(sys.argv)
dialog = LearnApp()
dialog.show()
app.exec_()
This is the code I'm trying. If edit it with toplayout = QGridLayout(), program works fine but with toplayout = QVBoxLayout(), it gives message QLayout::addChildLayout: layout "" already has a parentand just flashes the new window. What could be the problem? How should I tackle this? I wanna use QVBoxLayout instead of QGridLayout
Firstly, the new window disappears straight away because you don't store a reference to it. You need to store a reference to the instance in your LearnApp class, or parent it to another Qt object outside of set_data() if you want it to stick around.
The error message regarding the layouts is not occurring because of your choice of layouts, but because you are calling
self.setLayout(self.form_layout)
and then
toplayout.addLayout(self.form_layout)
The first call assigns the layout to the instance of SetName, but in doing so also makes the instance the parent of self.form_layout. The second call is trying to add the same layout to toplayout and set it as the parent, but Qt sees that self.form_layout already has a parent (i.e. is being used elsewhere). This is what the error message is trying to tell you.
I suspect that instead of self.setLayout(self.form_layout), you intended to write something like
self.setLayout(toplayout)

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_())

wxPython ListCtrl OnClick Event

So, I have a wxPython ListCtrl which contains rows of data. How can I make an event that calls a function, with the row contents, when one of the rows if clicked on?
You can use the Bind function to bind a method to an event. For example,
import wx
class MainWidget(wx.Frame):
def __init__(self, parent, title):
super(MainWidget, self).__init__(parent, title=title)
self.list = wx.ListCtrl(parent=self)
for i,j in enumerate('abcdef'):
self.list.InsertStringItem(i,j)
self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnClick, self.list)
self.Layout()
def OnClick(self, event):
print event.GetText()
if __name__ == '__main__':
app = wx.App(redirect=False)
frame = MainWidget(None, "ListCtrl Test")
frame.Show(True)
app.MainLoop()
This app will print the item in the ListCtrl that is activated (by pressing enter or double-clicking). If you just want to catch a single click event, you could use wx.EVT_LIST_ITEM_SELECTED.
The important point is that the Bind function specifies the method to be called when a particular event happens. See the section in the wxPython Getting Started guide on event handling. Also see the docs on ListCtrl for the events that widget uses.

Resources