Try to add widgets dinamycally in another thread PyQT5 - python

I have some QDialog with some initial widgets. I try to run a QThread to add others widgets after my api request but I cant.
main.py
class Lobby(QDialog):
signal_api_response = pyqtSignal(dict)
def __init__(self, _token, parent=None):
super().__init__()
self.initUI() # build initial ui
self.mythread= MyThread('arg test')
self.mythread.new_signal.connect(self.response_my_api)
self.mythread.start()
self.showFullScreen()
#pyqtSlot(dict)
def response_worker_mychar(self, response):
print(response)
# build dynamic widget (not creating new widgets here)
self.some_slot = QLabel('zzzzz', self)
self.some_slot.move(10, 10)
self.some_slot.setCursor(Qt.PointingHandCursor)
self.some_slot.setStyleSheet(f"border: 2px solid red;color:white;")
Thread Class
class MyThread(QThread):
new_signal = pyqtSignal(dict)
def __init__(self, token=None, parent=None):
super(QThread, self).__init__(parent)
self.token = token
def run(self):
response = get_blabla_api(self.token)
self.new_signal.emit(response)
QThread is working but it doesn't create new widgets (QLabel, ...). I tried to change an existing widget and it worked, but creating others didn't work.
It doesn't give an error but it doesn't create the widgets.
Note: The function where I create these dynamic widgets after the signal emit works, it just doesn't work using QThread. I'm using QThread because as I make a request in the api, PyQt freezes.

Related

Send signals from Nested children widgets to ancestors in Qt (PyQT5)

I have a window that contains two main widgets, both of which are containers of other widgets, which in turn may contain further widgets.
Each Widget is a separate class ( to maintain code readability ), and child widgets are instantiated directly inside this class, which is similar to something like React components structure.
I would like to call some methods from any widget to a top level one, or send signals from a deeply nested widget to one near top level without having to use something akin to "self.parent().parent().parent().doStuff(args)", it works but if hierarchy changes this will raise bugs, it gets harder to maintain the more complex my GUI gets.
Edit : Here is a simplified example of what I'm trying to achieve :
from PySide2.QtWidgets import *
import sys
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.central = QWidget()
self.central.setObjectName("Central_Widget")
self.setCentralWidget(self.central)
mainLayout = QHBoxLayout(self.central)
self.central.setStyleSheet("""
#Central_Widget{
background-color: #2489FF;
}""")
#init mainView widget
self.leftSide = leftWindow(self.central)
self.rightSide = rightWindow(self.central)
mainLayout.addWidget(self.leftSide)
mainLayout.addWidget(self.rightSide)
class leftWindow(QWidget):
def __init__(self, parent):
super().__init__(parent)
self.setObjectName("leftWindow")
layout = QVBoxLayout()
self.setLayout(layout)
self.label = QLabel(" Left ")
self.leftButton = customBtn(" Left Button ", "someSVGHere", self)
layout.addWidget(self.label)
layout.addWidget(self.leftButton)
self.setStyleSheet("border : 2px solid white;")
class rightWindow(QWidget):
def __init__(self, parent):
super().__init__(parent)
self.setObjectName("rightWindow")
layout = QVBoxLayout()
self.setLayout(layout)
self.label = QLabel(" Right ")
self.rightButton = customBtn(" Right Button ", "someSVGHere", self)
layout.addWidget(self.label)
layout.addWidget(self.rightButton)
self.setStyleSheet("border : 2px solid black;")
class customBtn(QWidget):
def __init__(self, text, svgIcon, parent):
super().__init__(parent)
layout = QVBoxLayout()
self.setLayout(layout)
self.btnText = QLabel(text)
self.Icon = svgIcon
layout.addWidget(self.btnText)
# setup the icon and the stylesheet etc ....
def mouseReleaseEvent(self, event):
# if it's the right button getting clicked, change the stylesheet of the central widget in main window
# How do I do that here ???
return super().mouseReleaseEvent(event)
# Launcher
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MainWindow()
win.setWindowTitle("nesting test")
win.show()
sys.exit(app.exec_())
In the above example, I want to change the background of the central widget in the MainWindow class using the right button inside the rightWindow widget, in this example it's only 2 layers of nesting so it's not really deep, but in my App I would have up to 5 or 6 layers.
ideally, each class would be in a separate file but that doesn't change this example.
I unfortunately cannot use QT designer because I'm building a modern GUI app, so every single widget will be a custom one, and I would like to keep my code fragmented instead of instantiating everything in a single class like what QT designer generates.
Generally speaking, when dealing with object hierarchy, child objects should not attempt to directly interact their parents. In fact, they should always be able to "work" on their own, no matter of their parent, and even when they have no parent at all.
Considering this, instead of connecting a signal to a slot of a [great-][grand]parent, it should be that parent that connects the [great-][grand]child signal to its own slot. This perspective is important, because it's also what allows us to connect sibling objects that are (nor could) be aware of each other.
class customBtn(QWidget):
customSignal = pyqtSignal(Qt.MouseButton)
def mouseReleaseEvent(self, event):
self.customSignal.emit(event.button())
class MainWindow(QMainWindow):
def __init__(self):
# ...
self.leftSide = leftWindow(self.central)
self.leftSide.leftButton.customSignal.connect(self.something)
def something(self):
# whatever
If the structure has many levels, it's also good practice to create custom signals for the intermediate classes, since signals can also be chained as long as they have a compatible signature (the target signal must have the same argument types or fewer arguments with same types).
class leftWindow(QWidget):
customSignal = pyqtSignal()
def __init__(self, parent):
# ...
self.leftButton = customBtn(" Left Button ", "someSVGHere", self)
self.leftButton.customSignal.connect(self.customSignal)
class MainWindow(QMainWindow):
def __init__(self):
# ...
self.leftSide = leftWindow(self.central)
self.leftSide.customSignal.connect(self.something)
In the case above, the signal of leftWindow doesn't have any arguments, so it's compatible with the button signal, as those arguments will be automatically discarded.

Calling method after init doesn't show application

I have a simple PySide Gui which I want to show, however there currently is a blocking task which is a long process, populating the dropdown list with items. Which means the UI does not show until the dropdown list is finished being populated. I want to know is there a way to force show the UI before it attempts to populate the list. I would prefer the dialog show so users know they opened the tool before assuming it's crashed or something.
I'm using Qt since my application needs to run in both PySide and PySide2. I was initially trying to use the qApp.processEvents() but it doesn't seem to be available in the Qt wrapper, or I may have been missing something. If you know what the equivalent would be, I'm fine with the process events being the solution. Optionally if there is an elegant way to populate the list from a background thread somehow...
from Qt import QtGui, QtCore, QtWidgets
class Form(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
self.resize(200,50)
self.items = QtWidgets.QComboBox()
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.items)
self.setLayout(layout)
# init
self.long_process()
def long_process(self):
for i in range(30000):
self.items.addItem('Item {}'.format(i))
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
form = Form()
form.show()
sys.exit(app.exec_())
A good option for these cases is always to use QTimer:
class Form(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
[...]
# init
timer = QtCore.QTimer(self)
timer.timeout.connect(self.on_timeout)
timer.start(0)
def on_timeout(self):
self.items.addItem('Item {}'.format(self.counter))
self.counter += 1
if self.counter == 30000:
self.sender().stop()

PyQt4 custom widget (uic loaded) added to layout is invisible

I've created a custom widget in pyqt4 that I've worked on and tested and now am ready to load it into my main window. Since it doesn't show up in designer, I need to manually add it to my main window manually.
My widget uses uic to load the ui file instead of converting it to a py file (it's been quicker less hassle so far) so it looks something like this:
class widgetWindow(QtGui.QWidget):
def __init__(self, parent = None):
super(widgetWindow, self).__init__(parent)
self.ui = uic.loadUi("widget.ui")
#everything else
now in my main class (example for brevity) I create the layout, add the widget to the layout and then add it to the main widget
class main(QtGui.QMainWindow):
def __init__(self, parent = None):
super(main, self).__init__(parent)
self.ui = uic.loadUi("testWindow.ui")
mainlayout = QtGui.QVBoxLayout()
window = widgetWindow(self)
mainlayout.addWidget(window)
centerWidget = QtGui.QWidget()
centerWidget.setLayout(mainlayout)
self.ui.setCentralWidget(centerWidget)
There are no errors thrown, and it will make space for the widget, but it simply won't show anything.
adding in the line window.ui.show() will just pop open a new window overtop the space that it should be occupying on the main window. What am I missing?
Doing some more research into the uic loader, there are two ways to load a ui file. The way I'm using it in the question is one way, the other way is with the uic.loadUiType(). This creates both the base class and the form class to be inherited by the class object instead of just the QtGui.QWidget class object.
widgetForm, baseClass= uic.loadUiType("addTilesWidget.ui")
class windowTest(baseClass, widgetForm):
def __init__(self, parent = None):
super(windowTest, self).__init__(parent)
self.setupUi(self)
This way, the widget can be loaded into another form as expected. As for exactly why, I haven't found that answer yet.
Some more info on the different setup types: http://bitesofcode.blogspot.com/2011/10/comparison-of-loading-techniques.html
Try to add the parent argument into the loadUi statements:
self.ui = uic.loadUi("widget.ui",parent)
self.ui = uic.loadUi("testWindow.ui",self)
And try the following line at the end of your main class.
self.setCentralWidget(centerWidget)
You need to specify that 'centerWidget' is the central widget of the main window.
i.e your code for class main should be have a line like:
self.setCentralWidget(centerWidget)
class main(QMainWindow):
def __init__(self, parent = None):
super(main, self).__init__(parent)
....
self.setCentralWidget(centerWidget)

Open a second window in PyQt

I'm trying to use pyqt to show a custom QDialog window when a button on a QMainWindow is clicked. I keep getting the following error:
$ python main.py
DEBUG: Launch edit window
Traceback (most recent call last):
File "/home/james/Dropbox/Database/qt/ui_med.py", line 23, in launchEditWindow
dialog = Ui_Dialog(c)
File "/home/james/Dropbox/Database/qt/ui_edit.py", line 15, in __init__
QtGui.QDialog.__init__(self)
TypeError: descriptor '__init__' requires a 'sip.simplewrapper' object but received a 'Ui_Dialog'
I've gone over several online tutorials, but most of them stop just short of showing how to use a non built-in dialog window. I generated the code for both the main window and the dialog using pyuic4. What I think should be the relevant code is below. What am I missing here?
class Ui_Dialog(object):
def __init__(self, dbConnection):
QtGui.QDialog.__init__(self)
global c
c = dbConnection
class Ui_MainWindow(object):
def __init__(self, dbConnection):
global c
c = dbConnection
def launchEditWindow(self):
print "DEBUG: Launch edit window"
dialog = QtGui.QDialog()
dialogui = Ui_Dialog(c)
dialogui = setupUi(dialog)
dialogui.show()
class StartQT4(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
conn = sqlite3.connect('meds.sqlite')
c = conn.cursor()
self.ui = Ui_MainWindow(c)
self.ui.setupUi(self)
def main():
app = QtGui.QApplication(sys.argv)
program = StartQT4()
program.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Bonus question: since it looks like you can't pass arguments in pyqt function callbacks, is setting something which would otherwise be passed as an argument (the poorly named "c") to be global the best way to get information into those functions?
I've done like this in the past, and i can tell it works.
assuming your button is called "Button"
class Main(QtGui.QMainWindow):
''' some stuff '''
def on_Button_clicked(self, checked=None):
if checked==None: return
dialog = QDialog()
dialog.ui = Ui_MyDialog()
dialog.ui.setupUi(dialog)
dialog.setAttribute(QtCore.Qt.WA_DeleteOnClose)
dialog.exec_()
This works for my application, and I believe it should work with yours as well. hope it'll help, it should be pretty straight forward to do the few changes needed to apply it to your case.
have a good day everybody.
Ui_Dialog should inherent from QtGui.QDialog, not object.
class Ui_Dialog(QtGui.QDialog):
def __init__(self, dbConnection):
QtGui.QDialog.__init__(self)
global c
c = dbConnection
class StartQT4(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
Why QtGui.QWidget.__init___ ???
Use insted:
class StartQT4(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
You must call __init__ methon from base class (name in parenthesis '()')
QDialog have two useful routins:
exec_()
show()
First wait for closing dialog and then you can access any field form dialog. Second show dialog but don't wait, so to work properly you must set some slot/signals connections to respond for dialog actions.
eg. for exec_():
class Dialog(QDialog):
def __init__(self, parent):
QDialog.__init__(parent)
line_edit = QLineEdit()
...
dialog = Dialog()
if dialog.exec_(): # here dialog will be shown and main script will wait for its closing (with no errors)
data = dialog.line_edit.text()
Small tip: can you change your ui classes into widgets (with layouts). And perhaps problem is that your __init__ should be __init__(self, parent=None, dbConnection)
Because when you create new widget in existing one PyQt may try to set it as children of existing one. (So change all init to have additional parent param (must be on second position)).

Stealing wheelEvents from a QScrollArea

I want to put my custom widget in a QScrollArea, but in my custom widget, I reimplemented wheelEvent(e) and it never gets called.
I'm fine with the scroll area not having its mouse wheel scrolling functionality. I just need those wheelEvents to call my handler. I tried handling the events out at the level of the main window but I only got them when the scroll widget was at one of its extremes and couldn't have moved any further anyways, I need all of them.
Heres a simplified version of my code:
class custom(QWidget):
def __init__(self, parent=None):
super(custom, self).__init__(parent)
self.parent = parent
def wheelEvent(self,event):
print "Custom Widget's wheelEvent Handler"
class mainw(QMainWindow):
def __init__(self, parent=None):
super(mainw, self).__init__(parent)
scroll = QScrollArea()
self.tw = thread_widget(scroll)
scroll.setWidget(self.tw)
self.setCentralWidget(scroll)
def wheelEvent(self,event):
print "Main Window's wheelEvent Handler"
Can someone explain to me how it is determined which event handler gets the events in this situation?
I figured out that its got something to do with the installEventFilter method of QObject, but I couldn't get the example to work so I said to hell with this and changed my plan completely.
problem solved
You can install a eventFilter in your custom class
class custom(QWidget):
def __init__(self, parent=None):
super(custom, self).__init__(parent)
self.parent = parent
self.installEventFilter(self)
def eventFilter(self, qobject, qevent):
qtype = qevent.type()
if qtype == QEvent.Wheel:
... wheel event logic
return True
# parents event handler for all other events
return super(custom,self).eventFilter(qobject, qevent)

Categories