I have a checkbox and a run button. When the checkbox is checked, I want to run some functions by clicking the button. The problem is that the function is in another class outside the button's class. My example codes are as below.
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Tab1Widget1(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.Tab1Widget1initUI()
def Tab1Widget1initUI(self):
self.setLayout(QGridLayout())
self.T1W1checkBox1 = QCheckBox('a', self)
self.layout().addWidget(self.T1W1checkBox1, 1, 0)
def run(self):
if self.T1W1checkBox1.isChecked() == True:
pass
class Tab1Layout(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.setLayout(QGridLayout())
self.group1 = Tab1Widget1(self)
self.layout().addWidget(self.group1, 0, 0)
btn = QPushButton('Run', self)
self.layout().addWidget(btn, 1, 0)
btn.clicked.connect(Tab1Widget1().run()) ##the problem is in this line.
class Page1(QTabWidget):
def __init__(self, parent=None):
super(Page1, self).__init__(parent)
self.tab1 = Tab1Layout()
self.addTab(self.tab1, "Tab1")
self.tab2 = QWidget()
self.tab3 = QWidget()
self.addTab(self.tab2, "Tab2")
self.addTab(self.tab3, "Tab3")
self.tab2_initUI()
self.tab3_initUI()
def tab2_initUI(self):
grid = QGridLayout()
self.tab2.setLayout(grid)
def tab3_initUI(self):
grid = QGridLayout()
self.tab3.setLayout(grid)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setGeometry(300, 200, 600, 370)
self.startPage1()
def startPage1(self):
x = Page1(self)
self.setWindowTitle("Auto Benchmark")
self.setCentralWidget(x)
self.show()
def main():
app = QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
As you can see, I want to run the "run" function in "Tab1Widget1" class. However, the button is in "Tab1Layout" class.
When I run the codes, it returns to me "TypeError: connect() slot argument should be a callable or a signal, not 'NoneType'"
If anyone knows how to solve this, pls let me know. Appreciated!
There is no problem in connecting any callable to a button click regardless of what object it is in. But your code has two specific problems. You write
btn.clicked.connect(Tab1Widget1().run())
The first problem here is that Tab1Widget1() is creating a new Tab1Widget1 but presumably you don't want that. You want to call run on the Tab1Widget1 you have already created and stored in self.group.
The second problem is that when you connect a signal you need to connect it to a callable: the method you want to call. Instead here you are calling the run method at connect time and trying to connect to the result of that call (which is None). So you are trying to connect the signal to None which will of course fail. You need to refer to the method without calling it: just remove the calling brackets.
Putting it together:
btn.clicked.connect(self.group1.run)
That seems to work.
Related
I am new to pyqt, and I tried to make an application window that contains a list of buttons that are able to toggle a different window. Since I want the number of these buttons to be of a varying quantity, I created a list of QPushButton elements for iterating over them, creating as many as defined by the length of the list, nevertheless I noticed a very weird behavior :
The following code ...
import sys
from random import randint
from PyQt5 import QtWidgets
class AnotherWindow(QtWidgets.QWidget):
"""
This "window" is a QWidget. If it has no parent,
it will appear as a free-floating window.
"""
def __init__(self):
super().__init__()
layout = QtWidgets.QVBoxLayout()
self.label = QtWidgets.QLabel("Another Window % d" % randint(0, 100))
layout.addWidget(self.label)
self.setLayout(layout)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self,windows):
super().__init__()
self.windows=[]
self.buttons=[]
l=QtWidgets.QVBoxLayout()
for i in range(len(windows)):
window=AnotherWindow()
self.windows.append(window)
button=QtWidgets.QPushButton(f'window {windows[i]}')
print(i," ",button)
self.buttons.append(button)
self.buttons[i].clicked.connect(self.toggle_window,i)
l.addWidget(self.buttons[i])
w = QtWidgets.QWidget()
w.setLayout(l)
self.setCentralWidget(w)
print(len(self.windows))
def toggle_window(self,i):
if self.windows[i].isVisible():
self.windows[i].hide()
else:
self.windows[i].show()
if __name__=="__main__":
app = QtWidgets.QApplication(sys.argv)
windows=[0,1,2,3]
windows=[str(i) for i in windows]
print(windows)
w = MainWindow(windows)
w.show()
app.exec()
produced the following error but only when the 4rth button (window 3) is pressed.
Qt: Dead lock detected while activating a BlockingQueuedConnection: Sender is QPushButton( ... ), receiver is PyQtSlotProxy( ... )
In effort to validate the code, I tried to narrow the list into a linear declaration of a static number of QPushButton instances, indicating that the issue occurs, only when I try to put the buttons on a list. For instance, the following script does not present any similar unpredictable behavior:
import sys
from random import randint
from PyQt5 import QtWidgets
class AnotherWindow(QtWidgets.QWidget):
"""
This "window" is a QWidget. If it has no parent,
it will appear as a free-floating window.
"""
def __init__(self):
super().__init__()
layout = QtWidgets.QVBoxLayout()
self.label = QtWidgets.QLabel("Another Window % d" % randint(0, 100))
layout.addWidget(self.label)
self.setLayout(layout)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.window0 = AnotherWindow()
self.window1 = AnotherWindow()
self.window2 = AnotherWindow()
self.window3 = AnotherWindow()
l = QtWidgets.QVBoxLayout()
button0 = QtWidgets.QPushButton("window 0")
button0.clicked.connect(self.toggle_window0)
l.addWidget(button0)
button1 = QtWidgets.QPushButton("window 1")
button1.clicked.connect(self.toggle_window1)
l.addWidget(button1)
button2 = QtWidgets.QPushButton("window 2")
button2.clicked.connect(self.toggle_window2)
l.addWidget(button2)
button3 = QtWidgets.QPushButton("window 3")
button3.clicked.connect(self.toggle_window3)
l.addWidget(button3)
w = QtWidgets.QWidget()
w.setLayout(l)
self.setCentralWidget(w)
def toggle_window0(self, checked):
if self.window0.isVisible():
self.window0.hide()
else:
self.window0.show()
def toggle_window1(self):
if self.window1.isVisible():
self.window1.hide()
else:
self.window1.show()
def toggle_window2(self):
if self.window2.isVisible():
self.window2.hide()
else:
self.window2.show()
def toggle_window3(self, checked):
if self.window3.isVisible():
self.window3.hide()
else:
self.window3.show()
if __name__=="__main__":
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()
To test it further, I extended the list to a list of random lengths (more than 10), where I reassured that the issue persist for specific indexes each time. For example if I create 20 buttons using the first approach, the same bug appears for - the 4rth, the 12fth and the last index exclusively - but not for the rest of them. I even tested it on a different machine. Having also searched in forums, I could not find a solution.
Do I do anything completely wrong here? Does anyone understands better to indicate why is this happening?
I kindly thank you in advance!
Environment: Ubuntu 22.04
Pyqt version : 1.9 (under conda)
Your problem is the following:
self.buttons[i].clicked.connect(self.toggle_window,i)
You are passing i as second argument to connect and expect the toggle_window function to be called with this argument. This is not happening. In toggle_window, i will always be False. See musicamente's comment regarding what this second argument to connect does.
What you should do instead is connect the button click to a function of your window object. From there, you can of course do a callback to a function of your main window as illustrated below:
import sys
from random import randint
from PyQt5 import QtWidgets
class AnotherWindow(QtWidgets.QWidget):
def __init__(self, parent, i):
super().__init__()
self.parent = parent
self.i = i
layout = QtWidgets.QVBoxLayout()
self.label = QtWidgets.QLabel("Another Window {}".format(i))
layout.addWidget(self.label)
self.setLayout(layout)
def toggle(self):
print("Toggling windows {}".format(self.i))
if self.isVisible():
self.hide()
self.parent.window_toggled(self.i, False)
else:
self.show()
self.parent.window_toggled(self.i, True)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, windows):
super().__init__()
self.windows=[]
self.buttons=[]
l=QtWidgets.QVBoxLayout()
for i,title in enumerate(windows):
window=AnotherWindow(self, i)
self.windows.append(window)
button=QtWidgets.QPushButton(title)
button.clicked.connect(window.toggle)
l.addWidget(button)
self.buttons.append(button)
w = QtWidgets.QWidget()
w.setLayout(l)
self.setCentralWidget(w)
def window_toggled(self, i, visible):
print("Window {} is now {}".format(i, "visible" if visible else "hidden"))
if __name__=="__main__":
app = QtWidgets.QApplication(sys.argv)
windows = ["window {}".format(i) for i in range(12)]
w = MainWindow(windows)
w.show()
app.exec()
I am struggling to add a side menu to my application.
I have a QMainWindow instance to which I was hoping to add a QDrawer object and achieve an effect similar to this sample.
Unfortunately, it seems that PySide2 only provides QMenu, QTooltip and QDialog widgets which inherit from the Popup class, and QDrawer is nowhere to be found. However, using a Drawer tag in a QML file works just fine. Shouldn't it be possible to also create an instance of QDrawer programmatically?
As another try, I tried to load a Drawer instance from a QML file and attach it to my QMainWindow. Unfortunately I can't quite understand what should I specify as parent, what should I wrap it in, what parameters should I use etc. - any advice would be appreciated (although I would much rather create and configure it programatically).
My goal is to create a QMainWindow with a toolbar, central widget and a QDrawer instance as a side navigation menu (such as in this sample). Can you please share some examples or explain what to do?
One possible solution is to implement a Drawer using Qt Widgets, the main feature is to animate the change of width for example using a QXAnimation, the other task is to set the anchors so that it occupies the necessary height. A simple example is the one shown in the following code:
import os
from PySide2 import QtCore, QtGui, QtWidgets
class Drawer(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setFixedWidth(0)
self.setContentsMargins(0, 0, 0, 0)
# self.setFixedWidth(0)
self._maximum_width = 0
self._animation = QtCore.QPropertyAnimation(self, b"width")
self._animation.setStartValue(0)
self._animation.setDuration(1000)
self._animation.valueChanged.connect(self.setFixedWidth)
self.hide()
#property
def maximum_width(self):
return self._maximum_width
#maximum_width.setter
def maximum_width(self, w):
self._maximum_width = w
self._animation.setEndValue(self.maximum_width)
def open(self):
self._animation.setDirection(QtCore.QAbstractAnimation.Forward)
self._animation.start()
self.show()
def close(self):
self._animation.setDirection(QtCore.QAbstractAnimation.Backward)
self._animation.start()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowFlag(QtCore.Qt.FramelessWindowHint)
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
self.tool_button = QtWidgets.QToolButton(
checkable=True, iconSize=QtCore.QSize(36, 36)
)
content_widget = QtWidgets.QLabel(alignment=QtCore.Qt.AlignCenter)
content_widget.setText("Content")
content_widget.setStyleSheet("background-color: green")
lay = QtWidgets.QVBoxLayout(central_widget)
lay.setSpacing(0)
lay.setContentsMargins(0, 0, 0, 0)
lay.addWidget(self.tool_button)
lay.addWidget(content_widget)
self.resize(640, 480)
self.drawer = Drawer(self)
self.drawer.move(0, self.tool_button.sizeHint().height())
self.drawer.maximum_width = 200
self.drawer.raise_()
content_lay = QtWidgets.QVBoxLayout()
content_lay.setContentsMargins(0, 0, 0, 0)
label = QtWidgets.QLabel(alignment=QtCore.Qt.AlignCenter)
label.setText("Content\nDrawer")
label.setStyleSheet("background-color: red")
content_lay.addWidget(label)
self.drawer.setLayout(content_lay)
self.tool_button.toggled.connect(self.onToggled)
self.onToggled(self.tool_button.isChecked())
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.onCustomContextMenuRequested)
#QtCore.Slot()
def onCustomContextMenuRequested(self):
menu = QtWidgets.QMenu()
quit_action = menu.addAction(self.tr("Close"))
action = menu.exec_(QtGui.QCursor.pos())
if action == quit_action:
self.close()
#QtCore.Slot(bool)
def onToggled(self, checked):
if checked:
self.tool_button.setIcon(
self.style().standardIcon(QtWidgets.QStyle.SP_MediaStop)
)
self.drawer.open()
else:
self.tool_button.setIcon(
self.style().standardIcon(QtWidgets.QStyle.SP_MediaPlay)
)
self.drawer.close()
def resizeEvent(self, event):
self.drawer.setFixedHeight(self.height() - self.drawer.pos().y())
super().resizeEvent(event)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Using the answer to this question:
Python: PyQt Popup Window
I was able to produce a gui with a button that has a popup window.
What I would like to do now is press a button in the popup and pass a command to a function in the MyPopup class. This is easily accomplished using the lambda function, however, when you press the button in the mainwindow the popup window no longer closes and a new instance of the popup is created, resulting in two popup screens. From my understanding this is due to a signal being produced by the lambda function. Is there a way to clear this lambda function such that when the main button is pressed the old instance is closed and a new instance of the popup is loaded?
If this is not possible using lambda, is there another way to pass variables to the function to obtain the results I am looking for?
Here are some example screenshots to better illustrate my issue:
Running Script without lambda in popup
Running Script with lambda in popup
Here is the modified popup code from the previous question:
import sys
from PyQt4.Qt import *
class MyPopup(QWidget):
def __init__(self):
QWidget.__init__(self)
self.btn_popup = QPushButton("broken", self)
self.btn_popup.clicked.connect(lambda state, x='lambda prevents refresh': self.function(x))
def function(self, word):
print('Now I dont close',word)
def paintEvent(self, e):
dc = QPainter(self)
dc.drawLine(0, 0, 100, 100)
dc.drawLine(100, 0, 0, 100)
class MainWindow(QMainWindow):
def __init__(self, *args):
QMainWindow.__init__(self, *args)
self.cw = QWidget(self)
self.setCentralWidget(self.cw)
self.btn1 = QPushButton("Click me", self.cw)
self.btn1.setGeometry(QRect(0, 0, 100, 30))
self.connect(self.btn1, SIGNAL("clicked()"), self.doit)
self.w = None
def doit(self):
print ("Opening a new popup window...")
self.w = MyPopup()
self.w.setGeometry(QRect(100, 100, 400, 200))
self.w.show()
class App(QApplication):
def __init__(self, *args):
QApplication.__init__(self, *args)
self.main = MainWindow()
self.main.show()
def main(args):
global app
app = App(args)
app.exec_()
if __name__ == "__main__":
main(sys.argv)
It seems that if the lambda function does not exist the popup is destroyed, verify this by adding the following:
class MyPopup(QWidget):
def __init__(self, i):
[..]
self.destroyed.connect(lambda: print("destroyed"))
in the case where there was no lambda, the message was printed, while in the other case it was not. So the solution is to destroy it manually using the deleteLater() method:
def doit(self):
print ("Opening a new popup window...")
if self.w:
self.w.deleteLater()
self.w = MyPopup()
self.w.setGeometry(QRect(100, 100, 400, 200))
self.w.show()
Why does my application crash when i run the function setup_controls() twice.
Am I missing a 'parent/self' somewhere that is critical in the design?
import sys
from PySide import QtGui, QtCore
class QCategoryButton(QtGui.QPushButton):
def __init__(self, Text, treeitem, parent=None):
super(QCategoryButton, self).__init__(Text, parent)
self.treeitem = treeitem
def mousePressEvent(self, event):
mouse_button = event.button()
if mouse_button == QtCore.Qt.LeftButton:
self.treeitem.setExpanded(not self.treeitem.isExpanded())
class Example(QtGui.QWidget):
def __init__(self,):
super(Example, self).__init__()
self.initUI()
def initUI(self):
# formatting
self.resize(300, 300)
self.setWindowTitle("Example")
# widgets
self.ui_treeWidget = QtGui.QTreeWidget()
self.ui_treeWidget.setRootIsDecorated(False)
self.ui_treeWidget.setHeaderHidden(True)
self.ui_treeWidget.setIndentation(0)
self.setup_controls()
# self.setup_controls()
# layout
self.mainLayout = QtGui.QGridLayout(self)
self.mainLayout.addWidget(self.ui_treeWidget)
self.show()
def setup_controls(self):
# Add Category
pCategory = QtGui.QTreeWidgetItem()
self.ui_treeWidget.addTopLevelItem(pCategory)
self.ui_toggler = QCategoryButton('Settings', pCategory)
self.ui_treeWidget.setItemWidget(pCategory, 0, self.ui_toggler)
pFrame = QtGui.QFrame(self.ui_treeWidget)
pLayout = QtGui.QVBoxLayout(pFrame)
self.ui_ctrl = QtGui.QPushButton('Great')
self.ui_ctrlb = QtGui.QPushButton('Cool')
pLayout.addWidget(self.ui_ctrl)
pLayout.addWidget(self.ui_ctrlb)
pContainer = QtGui.QTreeWidgetItem()
pContainer.setDisabled(False)
pCategory.addChild(pContainer)
self.ui_treeWidget.setItemWidget(pContainer, 0, pFrame)
# Main
# ------------------------------------------------------------------------------
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
The setItemWidget method takes ownership of the widget that is passed to it. If you don't keep a reference it, it could get garbage-collected by Python. But of course Qt knows nothing about Python, so when it subsequently tries to access the widget that is no longer there ... boom!
This is the problematic line:
self.ui_toggler = QCategoryButton('Settings', pCategory)
On the second call, the previous widget stored in self.ui_toggler will get deleted, because there is no other reference held for it (on the Python side). So instead you should do this:
ui_toggler = QCategoryButton('Settings', pCategory, self)
self.ui_treeWidget.setItemWidget(pCategory, 0, ui_toggler)
I need to create multi-window GUI, first I tried it with QWidgets, but finally I discover QStackWidget tool I need to use. So Im trying to, but Ive got some problems. Thanks for Your time.
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow,self).__init__()
self.mainWidget = MainWidget()
self.searchWidget = SearchWidget()
self.sWidget = QStackedWidget()
self.sWidget.addWidget(self.mainWidget)
self.sWidget.addWidget(self.searchWidget)
self.initUI()
and calling setCurrentWidget from the sub_widget class:
class MainWidget(QWidget):
def __init__(self, parent=MainWindow):
super(MainWidget,self).__init__()
self.initUI()
def initUI(self):
searchButton = QPushButton('searchButton',self)
optionButton = QPushButton('optionButton',self)
quitButton = QPushButton('quitButton',self)
listButton = QPushButton('listButton',self)
searchButton.clicked.connect(self.goSearch)
hbox = QHBoxLayout()
hbox.addWidget(listButton)
hbox.addWidget(quitButton)
vbox = QVBoxLayout()
vbox.addStretch(1)
vbox.addWidget(searchButton)
vbox.addWidget(optionButton)
vbox.addLayout(hbox)
self.setLayout(vbox)
def goSearch(self):
self.parent().sWidget.setCurrentWidget(self.parent().searchWidget)
Ive got this message from IDE:
self.parent().sWidget.setCurrentWidget(self.parent().searchWidget)
AttributeError: 'PySide.QtGui.QStackedWidget' object has no attribute 'sWidget'
What is the thing Im doing wrong?
I'm going to comment on the code you posted here: http://pastebin.com/fBfS1X5m
An important thing to know is that you can put widgets within widgets and so on. For example:
class Widget(QWidget):
def __init__(self, parent=None):
layout = QVBoxLayout(self)
childWidget = QWidget(parent=self)
layout.addWidget(childWidget)
Just a quick note: You don't need setLayout if you pass self to the main layout constructor - via the docs.
Anyways, what I'm trying to illustrate here is that the QStackedWidget and the SearchWidget really shouldn't be a part of the MainWindow, but should live inside their own relevant widget that will handle switching between the QStackedWidget pages.
For example the MainWindow.__init__ would only look like this:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.mainWidget = MainWidget(self)
self.setCentralWidget(self.mainWidget)
self.initUI()
Your MainWidget would then look something like:
class MainWidget(QtGui.QWidget):
...
def initUI(self):
...
self.stack = QtGui.QStackedWidget(parent=self)
self.searchWidget = SearchWidget(parent=self)
self.searchWidget.searchButton.clicked.connect(self.goSearch)
self.backWidget = BackWidget(parent=self)
self.backWidget.backButton.clicked.connect(self.goBack)
...
def goSearch(self):
self.stack.setCurrentWidget(self.backWidget)
def goBack(self):
self.stack.setCurrentWidget(self.searchWidget)
I've renamed some of the class names to make a little more sense (To me at least). SearchWidget was your old MainWidget. BackWidget was your old SearchWidget. Following those changes SearchWidget would look the same as your old MainWidget with one exception - we save a reference to the search button so we can access it in the MainWidget class as seen above (when we connect their signals to our slots). We do the same for the button in BackWidget.
The two renamed "child" widgets:
class SearchWidget(QtGui.QWidget):
...
def initUI(self):
self.searchButton = QtGui.QPushButton('searchButton', parent=self)
optionButton = QtGui.QPushButton('optionButton', parent=self)
quitButton = QtGui.QPushButton('quitButton', parent=self)
listButton = QtGui.QPushButton('listButton', parent=self)
vbox = QtGui.QVBoxLayout(self)
vbox.addStretch(1)
vbox.addWidget(self.searchButton)
vbox.addWidget(optionButton)
hbox = QtGui.QHBoxLayout()
hbox.addWidget(listButton)
hbox.addWidget(quitButton)
vbox.addLayout(hbox)
class BackWidget(QtGui.QWidget):
...
def initUI(self):
self.backButton = QtGui.QPushButton('GoBack', parent=self)
So now we have something like:
MainWindow
|---MainWidget
|---QStackedWidget
|---SearchWidget
|---BackWidget
You can find the full working code here.
This line:
def __init__(self, parent=MainWindow):
sets the MainWindow class as a default argument, when you actually need an instance. But even if it was an instance, in the next line, you also fail to pass it on to the base-class:
super(MainWidget,self).__init__()
What you need to do instead is something like this:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow,self).__init__()
# pass an instance of MainWindow here
self.mainWidget = MainWidget(self)
...
class MainWidget(QWidget):
def __init__(self, parent):
# pass the parent to the base-class
super(MainWidget, self).__init__(parent)
...
UPDATE:
The stack-widget will re-parent any widgets added to it, so that it becomes the parent. There are ways of working around this, but I think the real problem with your code is that you have the structure backwards. The buttons that set the current widget should be controlled by the main-window, and the widgets in the stack should work completely independantly of that.