QtWebkit segfaults whenever I return a QObject from a method of another QObject.
Here's the simplest test case that I can come up with (it works for both PySide and PyQt4):
#!/usr/bin/env python2
import sys
try:
from PyQt4.QtCore import QObject, QVariant, pyqtProperty
from PyQt4.QtGui import QApplication
from PyQt4.QtWebKit import QWebView
Property = pyqtProperty
except ImportError:
from PySide.QtCore import QObject, Property
from PySide.QtGui import QApplication
from PySide.QtWebKit import QWebView
class TestObject(QObject):
#Property(str)
def property(self):
return 'foo'
class TestObjectContainer(QObject):
#Property(QObject) # Swapping QObject with TestObject doesn't fix it
def object(self):
return TestObject()
if __name__ == '__main__':
application = QApplication(sys.argv)
browser = QWebView()
frame = browser.page().mainFrame()
frame.addToJavaScriptWindowObject('container', TestObjectContainer())
frame.addToJavaScriptWindowObject('test_object', TestObject())
browser.show()
#frame.evaluateJavaScript('test_object.property')
frame.evaluateJavaScript('container.object.property')
sys.exit(application.exec_())
Here is the odd part:
test_object.property; // This doesn't segfault
container.object.property; // This does
Is there any way to return QObjects like this? Am I doing something wrong, or is this a bug?
I ended up creating a simple wrapper class with the help of #HYRY's answer:
class WebkitQObject(QObject):
def __init__(self):
super(WebkitQObject, self).__init__()
self.__cache__ = []
def store(self, item):
self.__cache__.append(item)
return self.__cache__[-1]
Now, this code works with a slight modification:
class TestObjectContainer(WebkitQObject):
#Property(QObject)
def object(self):
return self.store(TestObject())
You need a Python reference to the QObject to keep it alive. You can test this by changing the code as following,
class TestObjectContainer(QObject):
#Property(QObject)
def object(self):
self.tmp = TestObject()
return self.tmp
Related
blocking signals to reach QObjects is fairly simple with using the QSignalBlocker Class
like
# functionality
self.clickbuton.clicked.connect(self.printsomething)
self.clickbuton.clicked.connect(self.blockprint)
def printsomething(self):
print("dude")
def blockprint(self):
self.clickbuton.blockSignals(True)
what about custom slots like def printsomething(self): ?
trying the same operation but with blocking def printsomething(self): from printing
def blockprint(self):
self.printsomething.blockSignals(True)
will give a AttributeError: 'function' object has no attribute 'blockSignals'
it looks like this method works only for QObjects
how can I block def printsomething(self): from printing without using disconnect while its connected to the clicked signal?
code example
"""
Testing Template for throw away experiment
"""
import sys
import os
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg
class MainWindow(qtw.QWidget):
def __init__(self):
super().__init__()
# widget
self.clickbuton = qtw.QPushButton("click me")
# set the layout
layout = qtw.QVBoxLayout()
layout.addWidget(self.clickbuton)
self.setLayout(layout)
# functionality
self.clickbuton.clicked.connect(self.printsomething)
self.clickbuton.clicked.connect(self.blockprint)
def printsomething(self):
print("dude")
def blockprint(self):
self.printsomething.blockSignals(True)
# self.m_blocker = qtc.QSignalBlocker(self.clickbuton)
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())
What you're trying to do is to block a slot which is impossible.
You can only block signals of objects inherited from QObject.
As a solution you can instead of
def blockprint(self):
self.printsomething.blockSignals(True)
do this
def blockprint(self):
self.blockSignals(True)
that would block mainwindow signals until you turn blockSignals flag to false again.
from PySide2 import QtGui,QtCore,QtWidgets
from PySide2.QtGui import*
from PySide2.QtCore import *
from PySide2.QtWidgets import *
from shiboken2 import wrapInstance
import maya.OpenMayaUI as mui
import sys
class ui(QWidget):
def __init__(self,parent):
super(ui,self).__init__(parent)
self.resize(300,500)
self.mainWindow = QtWidgets.QMainWindow(parent)
self.setupUI(self.mainWindow)
self.setFocus()
def setupUI(self,mainWindow):
mymainWindow = QWidget(mainWindow)
mymainWindow.resize(300,500)
def mousePressEvent(self,e):
print 'sdfasdf'
if e.button()==Qt.RightButton:
print "Clickkkk"
def Show(self):
self.mainWindow.show()
class app():
def __init__(self):
self.ptr = mui.MQtUtil.mainWindow()
self.ptr = wrapInstance(long(self.ptr),QtWidgets.QWidget)
self.ui = ui(self.ptr)
def runApp(self):
self.ui.Show()
self.ui.setFocus()
tt = app()
tt.runApp()
Here is the code I'm testing on. After using wrapInstance the mouseEvent are no longer working.
But if I didn't wrap it it's work
not working
class app():
def __init__(self):
self.ptr = mui.MQtUtil.mainWindow()
self.ptr = wrapInstance(long(self.ptr),QtWidgets.QWidget)
self.ui = ui(self.ptr)
def runApp(self):
self.ui.Show()
self.ui.setFocus()
working
I Also change some parent structure in UI class
class app():
def __init__(self):
self.ui = ui()
def runApp(self):
self.ui.Show()
Can anyone explain why the MouseEvent won't work after I wrap it? And how to make it work?
The crux of the problem is this: self.ui.Show(). This runs your custom method, which in turn runs this self.mainWindow.show(). This causes self.mainWindow to show, but you subclassed mousePressEvent for ui, not self.mainWindow! So it's not running the event because you're clicking on the wrong widget.
Instead, since ui is a QWidget, call self.ui.show(). You may also have to put self.setWindowFlags(QtCore.Qt.Window) in ui's constructor. With this the mouse event runs as expected when the user clicks on it.
Some side notes:
I doubt that you actually want to create a QMainWindow in a QWidget. It just strikes as odd. Consider sub-classing a QMainWindow instead as it should be the 'top' widget.
Also try to avoid importing modules like from PySide2.QtCore import *, and import them like this instead from PySide2 import QtCore. It's bad practice, pollutes your module's scope, and makes the code much more unreadable/un-maintainable since it's hard to traceback where these variables come from.
Oh, and for the love of god, use some vertical white-spacing :)
I am trying to get my stdout displayed on a QTextEdit made via Qt Designer (PyQt5). Actually I made it work yet it doesn't show the info at the same time it was made. Instead it waits for the process to completely end and only then it shows all the information at once. I understand that this should be solved via threading. Also since the QTextEdit (itself) is a GUI element i need a different approach. I found the answer I was looking for here:
This question is referenced to:
Redirecting stdout and stderr to a PyQt4 QTextEdit from a secondary thread
#three_pineapples provided the answer.
My question is pretty much exactly the same, thus the answer is also correct. But my scenario is a little different and I'm having trouble making it work.
In all the threading answers I only see them using Classes. But the thing is in my main class I have a function that does all the stuff that would be printed on the QTextEdit. Sometimes it takes minutes to complete. I am looking for a way for the example code below to work using the answer provided by #three_pineapples.
Here is the example code:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtGui import QTextCursor
from ui_form import Ui_Form
class EmittingStream(QObject): # test
textWritten = pyqtSignal(str)
def write(self, text):
self.textWritten.emit(str(text))
class Form(QMainWindow):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
# Install the custom output stream
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten) # test
self.ui = Ui_Form()
self.ui.setupUi(self)
self.ui.pushButton_text.clicked.connect(self.test_write)
def __del__(self): # test
# Restore sys.stdout
sys.stdout = sys.__stdout__
def normalOutputWritten(self, text): # test
"""Append text to the QTextEdit."""
cursor = self.ui.textEdit.textCursor()
cursor.movePosition(QTextCursor.End)
cursor.insertText(text)
self.ui.textEdit.setTextCursor(cursor)
self.ui.textEdit.ensureCursorVisible()
def test_write(self): # this is a long, complicated function. its nested in this class. I don't have a way to get it out as a different class.
print("something written")
def main():
app = QApplication(sys.argv)
form = Form()
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Is there a way to get the provided solution to work -directly- on the (test_write) function in my main class? How would I implement it?
To make it more clear, from the reference link "LongRunningThing" class is not available for me. The function that needs to run on a separate thread is within the main class (named Form() in the example code). Perhaps a nested class could be used that encapsulates the test_write function inside my main class? Is that even possible?
For this case you can use the native threading of python:
import sys
import threading
import time
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtGui import QTextCursor
from ui_form import Ui_Form
class EmittingStream(QObject): # test
textWritten = pyqtSignal(str)
def write(self, text):
self.textWritten.emit(str(text))
class Form(QMainWindow):
finished = pyqtSignal()
def __init__(self, parent=None):
super(Form, self).__init__(parent)
# Install the custom output stream
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten) # test
self.ui = Ui_Form()
self.ui.setupUi(self)
self.ui.pushButton_text.clicked.connect(self.start_task)
self.finished.connect(lambda: self.ui.pushButton_text.setEnabled(True))
def start_task(self):
var = self.ui.lineEdit.text()
self.thread = threading.Thread(target=self.test_write, args=(args, ))
self.thread.start()
self.ui.pushButton_text.setEnabled(False)
def __del__(self): # test
# Restore sys.stdout
sys.stdout = sys.__stdout__
def normalOutputWritten(self, text): # test
"""Append text to the QTextEdit."""
cursor = self.ui.textEdit.textCursor()
cursor.movePosition(QTextCursor.End)
cursor.insertText(text)
self.ui.textEdit.setTextCursor(cursor)
self.ui.textEdit.ensureCursorVisible()
def test_write(self, *args):
var1 = args[0]
print("something written")
time.sleep(5) # simulate expensive task
print("something written ----")
self.finished.emit()
def main():
app = QApplication(sys.argv)
form = Form()
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
I've been trying to make this code work, but I still can't see where the flaw is.
I'm trying to emit the signal from a new thread, so the main receives the signal and executes a function.
If I try to do it within the same thread, everything works fine - but with this code, the thread is created, but the signal is never connected.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import QtCore
class WorkThread(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
def run(self):
print("From thread")
self.emit(QtCore.SIGNAL("trying"))
return
class Foo(QObject):
def handle_trigger(self):
print ("trigger signal received")
def new_thread(self):
self.get_thread = WorkThread()
self.connect(self.get_thread, QtCore.SIGNAL("trying"), self.handle_trigger)
self.get_thread.start()
a = Foo()
a.new_thread()
Edited based on comments.
There is one main problem with your code. You're not actually starting the Qt application, so there is no main event loop running. You must add the following lines:
app = QApplication(sys.argv)
app.exec_()
Also, you should use the new-style Qt signals/slots if possible, or if you stick with the old-style, know that the Qt Signal should be in the form of a C function if you want it to also work with PySide. To change this to work with PySide, it would be QtCore.SIGNAL("trying()"), not QtCore.SIGNAL("trying"). See the comments (specifically mine and #ekhumoro's comments) for details.
Here's a working version of your code (using the old-style signals/slots), I tried to change the least amount possible so that you could see the small changes. I had to use PySide instead, but it should work with PyQt as well:
from PySide.QtCore import *
from PySide.QtGui import *
from PySide import QtCore
import sys
class WorkThread(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
def run(self):
print("From thread")
self.emit(QtCore.SIGNAL("trying()"))
class Foo(QObject):
def handle_trigger(self):
print ("trigger signal received")
self.get_thread.quit()
self.get_thread.wait()
QApplication.quit()
def new_thread(self):
self.get_thread = WorkThread()
self.connect(self.get_thread, QtCore.SIGNAL("trying()"), self.handle_trigger)
self.get_thread.start()
a = Foo()
a.new_thread()
app = QApplication(sys.argv)
app.exec_()
And here's a version using the new signal/slot style (see #three_pineapples comment). This is the recommended way to implement signals/slots in PyQt/PySide.
from PySide.QtCore import *
from PySide.QtGui import *
from PySide import QtCore
import sys
class WorkThread(QtCore.QThread):
ranThread = QtCore.Signal()
# for PyQt, use QtCore.pyqtSignal() instead
def __init__(self):
QtCore.QThread.__init__(self)
def run(self):
print("From thread")
self.ranThread.emit()
class Foo(QObject):
def handle_trigger(self):
print ("trigger signal received")
self.get_thread.quit()
self.get_thread.wait()
QApplication.quit()
def new_thread(self):
self.get_thread = WorkThread()
self.get_thread.ranThread.connect(self.handle_trigger)
self.get_thread.start()
a = Foo()
a.new_thread()
app = QApplication(sys.argv)
app.exec_()
I muddled through this myself and I think this will work for you.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import QtCore
class WorkThread(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
tryThis = QtCore.Signal(str) #added this variable
def run(self):
print("From thread")
x = "trying"
self.tryThis.emit(QtCore.SIGNAL(x)) #pass your new variable like this
return
class Foo(QObject):
def handle_trigger(self):
print ("trigger signal received")
def new_thread(self):
self.get_thread = WorkThread()
self.get_thread.start()
self.get_thread.tryThis.connect(self.handle_trigger,QtCore.Qt.QueuedConnection) #passed it here
a = Foo()
a.new_thread()
How come the textChanged event is not happening whenever I input some data in the QLineEdit?
from PyQt4.Qt import Qt, QObject,QLineEdit
from PyQt4.QtCore import pyqtSlot,SIGNAL,SLOT
from PyQt4 import QtGui, QtCore
import sys
class DirLineEdit(QLineEdit, QtCore.QObject):
"""docstring for DirLineEdit"""
#pyqtSlot(QtCore.QString)
def textChanged(self, string):
QtGui.QMessageBox.information(self,"Hello!","Current String is:\n"+string)
def __init__(self):
super(DirLineEdit, self).__init__()
self.connect(self,SIGNAL("textChanged(QString&)"),
self,SLOT("textChanged(QString *)"))
app = QtGui.QApplication(sys.argv)
smObj = DirLineEdit()
smObj.show()
app.exec_()
everything seems to be correct to me, what am I missing ?
Replace following line:
self.connect(self,SIGNAL("textChanged(QString&)"),
self,SLOT("textChanged(QString *)"))
with:
self.connect(self,SIGNAL("textChanged(QString)"),
self,SLOT("textChanged(QString)"))
Or you can use self.textChanged.connect (handler should be renamed because the name conflicts):
class DirLineEdit(QLineEdit, QtCore.QObject):
def on_text_changed(self, string):
QtGui.QMessageBox.information(self,"Hello!","Current String is:\n"+string)
def __init__(self):
super(DirLineEdit, self).__init__()
self.textChanged.connect(self.on_text_changed)