I am having difficulty connecting a signal with a method in PyQt4.
I can connect a bound signal of object A with a method of object B,
but I can't connect a bound signal of object A with a method of self
(object where the connections are made.)
What am I doing wrong? See below:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class FooA(QObject):
trigger=pyqtSignal(str)
def receive_trigger(self,a):
print'triggered in FooA, string',a
class MainObj(QObject):
def __init__(self):
self.a1=FooA()
self.a2=FooA()
#I can connect signals and methods of separate objects:
self.a1.trigger.connect(self.a2.receive_trigger)
self.a1.trigger.emit('hi')
#... but I can't connect a signal with a method of self
self.a1.trigger.connect(self.receive_trigger)
self.a1.trigger.emit('hi')
def receive_trigger(self,a):
print 'triggered in MainObj'
executes as:
MainObj()
triggered in FooA, string hi
triggered in FooA, string hi
I expected to see an additional line, > triggered in MainObj
Thanks in advance.
Bill
As you already seem to know, signals must belong to QObjects, but this problem is occurring because you are not calling the constructor of QObject. FooA does not override the constructor, therefore the default constructor is called and the signals work as expected. In MainObj however, you do not call the superclass' (QObject) constructor, so signals will not work. To fix, either put:
QObject.__init__(self)
or
super(QObject, self).__init__()
(based on your conventions) at the top of MainObjs contructor, and the signals will then work as expected.
Related
I don't very understand the classes logic in python but cannot answer on web.
I have create a class to generate person info:
class person:
def fristnameGen(gender):
...
def emailGen(firstname,surname):
...
i create a bot to call it like this:
from person import *
class bots:
def __init__(self):
self.person = person()
def createDB(self):
print(self.person.name)
#do something...
finally i call it by a button with thread
from bots import *
import threading
class Panel:
def __init__(self):
self.top = tk.Tk()
self.bot = bots()
self.buildUI()
def foo(self):
self.bot.createDB(self.stringPhone.get())
def threadTheAction(func, *args):
t = threading.Thread(target=func, args=args)
t.setDaemon(True)
t.start()
def buildUI(self):
Button = tk.Button(self.top, text ="Start", command = lambda :self.threadTheAction(self.foo))
I get this error:
TypeError: 'Panel' object is not callable
However, i call it directly, its work
Button = tk.Button(self.top, text ="Start", command = lambda :self.foo())
How to fix the error?
...
2. Moreover, i tried create p1 = person() and p2= person() and print it. Found p1 and p2 is a same person, i prefer each new a class have a new one. How to generate "new person" using classes?
Thank you
You seem to have a lot of confusion about Object Oriented Programming in Python. Some of your methods have self parameters and some do not, seemingly at random. That's the source of your current error.
The threadTheAction method in your Panel class is getting the Panel instance passed in as its first argument, but that's named func in the method (since you omitted self). The real function you're passing as an argument gets caught in the variable argument *args. When the thread tries unsuccessfully to call it, you get an exception. Adding self before func would fix the immediate problem:
def threadTheAction(self, func, *args):
I suspect if your code got further along, you'd run into other errors with other methods without self in their parameter lists. For instance, none of the methods you've shown in person are likely to work correctly.
As for your second question, you haven't shown enough of person to know what's happening, but you're probably doing instance variables wrong somehow. With no self parameter in the methods, that's almost inevitable (since you assign to self.whatever to set a whatever attribute on the current instance). If you need help squaring that away, I suggest you ask a separate question (Stack Overflow is best when each question is self-contained) and provide the full code for your person class.
I have assigned one method to combobox like this
def run(self):
GetAllLayers(self) #custom Methods
#attach index changed event / passing parametric method
self.dlg.cbLayerNamesAll.currentIndexChanged.connect(lambda arg=self: LayersValueChange(arg))
getting error here
def LayersValueChange(self):
layers = self.iface.legendInterface().layers()//here error
And Error is:
layers = self.iface.legendInterface().layers()
AttributeError:
'int' object has no attribute 'iface'
self is object but it getting it like int.
Assuming LayersValueChange is an external function and not a method of the same class, you can connect the signal like this:
self.dlg.cbLayerNamesAll.currentIndexChanged.connect(
lambda: LayersValueChange(self))
This simply ignores the parameters sent by the signal, and creates a function enclosure that allows self to be referenced later (so there's no need to use arg=self).
If you also want the index sent by the signal, you will have to change the signature of the function, and then connect the signal like this:
self.dlg.cbLayerNamesAll.currentIndexChanged.connect(
lambda index: LayersValueChange(self, index))
def LayersValueChange(self, index):
layers = self.iface.legendInterface().layers()
print(index)
However, a much better design would be to make all the functions methods of the same class. Then your code would look like this:
class MyClass(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MyClass, self).__init__(parent)
...
self.dlg.cbLayerNamesAll.currentIndexChanged.connect(
self.layersValueChange)
def run(self):
self.getAllLayers()
def layersValueChange(self, index):
layers = self.iface.legendInterface().layers()
def getAllLayers(self):
...
The whole signature of your signal is: currentIndexChanged(int index) (see the doc). So the argument, your lambda is receiving is of type int and from that information Python assumes the self parameter of your LayersValueChanged is of type int.
You need to do two things:
Don’t pass self as default parameter. In fact you don’t need any default parameter, because currentIndexChanged always provides one.
Change your slot signature to correctly accept the int parameter:
def LayersValueChange(self, index):
…
In my PyQt4-based program, QSliders (with signals sliderMoved and sliderReleased connected to callables) sometimes "freeze", i.e. they don't move anymore when trying to drag them with the mouse, even though sliderMoved and sliderReleased are still emitted.
This behaviour happens seemingly randomly, sometimes after running the program for hours -- making it more or less impossible to reproduce and test.
Any help to solve this issue would be welcome.
EDIT: This is with PyQt 4.10.4 on Python 3.4 and Windows 7.
After some debugging I am pretty sure that this was due to calling a GUI slot from a separate thread, which (I knew) is forbidden. Fixing this to use a proper signal-slot approach seems to have fixed the issue.
After calling the patch function defined below, all slot calls are wrapped by a wrapper that checks that they are called only from the GUI thread -- a warning is printed otherwise. This is how I found the culprit.
import functools
import sys
import threading
import traceback
from PyQt4.QtCore import QMetaMethod
from PyQt4.QtGui import QWidget
SLOT_CACHE = {}
def patch():
"""Check for calls to widget slots outside of the main thread.
"""
qwidget_getattribute = QWidget.__getattribute__
def getattribute(obj, name):
attr = qwidget_getattribute(obj, name)
if type(obj) not in SLOT_CACHE:
meta = qwidget_getattribute(obj, "metaObject")()
SLOT_CACHE[type(obj)] = [
method.signature().split("(", 1)[0]
for method in map(meta.method, range(meta.methodCount()))
if method.methodType() == QMetaMethod.Slot]
if (isinstance(attr, type(print)) and # Wrap builtin functions only.
attr.__name__ in SLOT_CACHE[type(obj)]):
#functools.wraps(
attr, assigned=functools.WRAPPER_ASSIGNMENTS + ("__self__",))
def wrapper(*args, **kwargs):
if threading.current_thread() is not threading.main_thread():
print("{}.{} was called out of main thread:".format(
type(obj), name), file=sys.stderr)
traceback.print_stack()
return attr(*args, **kwargs)
return wrapper
else:
return attr
QWidget.__getattribute__ = getattribute
I'm trying to expose a method to QML in pyqt 5, and I need it to return a custom QObject. I can't seem to work out anyway to do this. The snippet below shows roughly what I'm trying to do. I've tried changed the result parameter to QObject, "QObject", and various other types. Each either gives me a different error message, or if no error, the QML receives null instead of the object. I've also tried registering Bar as a type, but still no help.
What is the right way to return a custom object like this? (Note: we've had this working in C++ previously.)
class Bar(QObject):
# etc.
bar = Bar()
class Foo(QObject):
#pyqtSlot(int, result=QObject)
def foo(self, arg1):
return bar
class Foo(QObject):
#pyqtSlot(int, result=QObject)
def foo(self, arg1):
sip.transferto(bar, bar)
return bar
I wish to query a SQL database via QSqlQueryModel (PyqQt 5/Qt 5.2) asynchronously, so that the GUI doesn't block. How can this be accomplished? Maybe through multithreading? Please provide code of how to do this. If using QSqlQueryModel asynchronously isn't practical, feel free to provide alternatives (should be usable with QTableView though).
My (synchronous) code currently looks as shown beneath. The main script bin/app.py loads gui/__init__.py and executes its main method. That in turn uses gui.models.Table to load data from the database. The problem is that gui.models.Table queries the database synchronously and locks up the GUI in the meantime.
bin/app.py:
import os.path
import sys
sys.path.insert(0, os.path.abspath(os.path.join(
os.path.dirname(__file__), "..")))
import gui
if __name__ == "__main__":
gui.main()
gui/__init__.py:
import sys
import os.path
from PyQt5 import uic
from PyQt5 import QtCore, QtWidgets
from gui import models
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
uic.loadUi(os.path.join(os.path.dirname(__file__), 'app.ui'), self)
self.tableView.setModel(models.Table(self))
def main():
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
gui/models.py:
import os.path
from PyQt5.QtCore import *
from PyQt5.QtSql import *
class Table(QSqlQueryModel):
def __init__(self, parent=None):
super(Table, self).__init__(parent)
pth = os.path.abspath(os.path.join(os.path.dirname(__file__), "..",
"test.sqlite"))
db = QSqlDatabase.addDatabase("QSQLITE")
db.setDatabaseName(pth)
if not db.open():
raise Exception("Couldn't open database '{}'".format(pth))
try:
self.setQuery("select * from Test")
finally:
db.close()
Unfortunately, a typical database driver that Qt (or anyone else, really) uses is synchronous. Qt views unfortunately don't know how to deal with models in foreign threads.
The solution thus requires a shim proxy model, subclassing QIdentityProxyModel. The first step in the implementation is to shim all of the source model's method calls with blocking QMetaObject::invokeMethod calls. This is needed just to be correct, if not asynchronous just yet. It' just to expose a safe interface to a model that lives in another thread.
The next step is to provide an asynchronous veneer over some of the functionality. Suppose that you want to make the data method asynchronous. What you do is:
For each role, have a cache of variant values keyed by the model index.
On the dataChanged signal from the source model, cache all the values that were changed, across all roles. The data call needs to be queued in the model's thread - more on that later.
In data, if there's a cache hit, return it. Otherwise return a null variant and queue the data call in the model's thread.
Your proxy should have a private method called cacheData that will be called from the queued calls. In another answer, I've detailed how to queue functor calls in another thread. Leveraging that, your data call queuing method can look like:
void ThreadsafeProxyModel::queueDataCall(const QModelIndex & index, int role) {
int row = index.row();
int column = index.column();
void * data = index.internalPointer();
postMetacall(sourceModel()->thread(), [this, row, column, data, role]{
QVariant data = sourceModel()->data(createIndex(row, column, data), role);
QMetaObject::invoke(this, "cacheData",
Q_ARG(QVariant, data), Q_ARG(int, role),
Q_ARG(int, row), Q_ARG(int, column), Q_ARG(void*, data));
});
}
This is just a sketch. It'd be fairly involved, but certainly doable, and still maintaining the semantics of a real model.