Why does this PyQt5 Qlistwidget signal + slot not working? - python

I've been trying to make this work, but it just doesn't. I get no errors, just plain noncompliance. It just does not want to add the QListWidget items, or change the QLabel.
I made a MainWindowClass. It it's main widget I have a layout and a button. In it's dockwidget I have a QListWidget.
I made a signal from the button and connected it to a slot in the dockwidget's list.
The connection is there. When I press the button, the method in the dockwidgetcontents class is running.
But it does not add the items to the listwidget. And does not produce any errors.
Here is the code:
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class AppClass:
def __init__(self):
super().__init__()
app = QApplication(sys.argv)
window = MainWindowClass()
window.show()
sys.exit(app.exec_())
class MainWindowClass(QMainWindow):
def __init__(self):
super().__init__()
self.init_UI()
self.TheDockWidget()
def init_UI(self):
self.setGeometry(100, 100, 400, 200)
self.setWindowTitle("Test App")
self.setCentralWidget(MainWidgetClass())
def TheDockWidget(self):
self.dockWidget = QDockWidget('Status:')
self.dockWidget.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable)
self.addDockWidget(Qt.RightDockWidgetArea, self.dockWidget)
self.dockWidget.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
self.dockWidget.setWidget(DockWidgetContents())
class DockWidgetContents(QWidget):
def __init__(self):
super().__init__()
self.init_UI()
def init_UI(self):
layout = QVBoxLayout()
self.setLayout(layout)
self.button3 = QPushButton()
self.button3.setText("button3")
layout.addWidget(self.button3)
self.listwidget = QListWidget()
layout.addWidget(self.listwidget)
self.listwidget.addItem("Ready.")
self.label = QLabel("initial")
layout.addWidget(self.label)
#pyqtSlot(str, str, int)
def _add_item(self, strA, strB, int1):
self.label.setText("yes") # why does this not work??
self.listwidget.addItem(strA) # why does this not work??
self.listwidget.addItem(strB) # why does this not work??
self.listwidget.addItem(str(int1)) # why does this not work??
print(strA, strB, int1) # but this works fine.
class MainWidgetClass(QWidget):
def __init__(self):
super().__init__()
self.init_UI()
def init_UI(self):
mainLayout = QGridLayout()
self.setLayout(mainLayout)
mainLayout.addWidget(TopLeftWidgetClass(), 0, 0)
class TopLeftWidgetClass(QWidget):
signal = pyqtSignal(str, str, int)
def __init__(self):
super().__init__()
self.init_UI()
def init_UI(self):
layout = QHBoxLayout()
self.setLayout(layout)
self.button1 = QPushButton("button1")
layout.addWidget(self.button1)
self.button1.clicked.connect(self.start)
def start(self):
otherClass = DockWidgetContents()
self.signal.connect(otherClass._add_item)
self.signal.emit("one", "two", 3)
if __name__ == '__main__':
AppClass()
I also read all the suggested questions when I typed my question into the form, but I must be not understanding something vital or prerequisite to this.
While I find all your answers to other questions extremely valuable, I'd appreciate if the answer would point me to the reference that I'm missing, so I can understand the problem, rather than just copy/paste the fixed code.

The main problem is that you're trying to connect to a new instance of DockWidgetContents, which gets also immediately deleted as soon as start() returns.
That new instance is obviously useless, as one already exists, but you have no direct ways to get it, because you create all classes "on the fly" when you add widgets to layouts and parents.
Remember that, while creating "instances on the fly" is not technically a problem, it doesn't allow you to keep references to those instances.
You have to create appropriate references to widgets and correctly connect them.
Here you can see the modifications required:
class MainWindowClass(QMainWindow):
def __init__(self):
super().__init__()
self.init_UI()
self.TheDockWidget()
self.mainWidget.topLeftWidget.signal.connect(
self.dockWidgetContents._add_item)
def init_UI(self):
self.setGeometry(100, 100, 400, 200)
self.setWindowTitle("Test App")
self.mainWidget = MainWidgetClass()
self.setCentralWidget(self.mainWidget)
def TheDockWidget(self):
self.dockWidget = QDockWidget('Status:')
self.dockWidget.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable)
self.addDockWidget(Qt.RightDockWidgetArea, self.dockWidget)
self.dockWidget.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
self.dockWidgetContents = DockWidgetContents()
self.dockWidget.setWidget(self.dockWidgetContents)
class MainWidgetClass(QWidget):
def __init__(self):
super().__init__()
self.init_UI()
def init_UI(self):
mainLayout = QGridLayout()
self.setLayout(mainLayout)
self.topLeftWidget = TopLeftWidgetClass()
mainLayout.addWidget(self.topLeftWidget, 0, 0)

Related

QStackedWidget opening multiple windows

I have build a small application using PyQt and as far as I know QStackedWidget is used for making multipage applications. The issue is that it is opening multiple pages and also I'm unable to redirect to class Xyz using button in class Abc.
main.py
from PyQt4 import QtGui, QtCore
import sys
class Window(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.stacked_widget = QtGui.QStackedWidget()
layout = QtGui.QVBoxLayout()
layout.setContentsMargins(0,0,0,0)
layout.addWidget(self.stacked_widget)
widget = QtGui.QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
widget_1 = Abc(self.stacked_widget)
widget_2 = Xyz(self.stacked_widget)
self.stacked_widget.addWidget(widget_1)
self.stacked_widget.addWidget(widget_2)
self.showMaximized()
class Abc(QtGui.QWidget):
def __init__(self, stacked_widget, parent=None):
super(Abc, self).__init__(parent)
self.stacked_widget = stacked_widget
self.frame = QtGui.QFrame()
self.frame_layout = QtGui.QVBoxLayout()
self.button = QtGui.QPushButton('Click me!')
self.button.clicked.connect(self.click)
self.frame_layout.addWidget(self.button)
self.frame.setLayout(self.frame_layout)
self.layout = QtGui.QVBoxLayout()
self.layout.addWidget(self.frame)
self.setLayout(self.layout)
self.showMaximized()
def click(self):
print("You clicked me!")
self.stacked_widget.setCurrentIndex(1)
class Xyz(QtGui.QWidget):
def __init__(self, parent=None):
super(Xyz, self).__init__(parent)
self.frame_layout = QtGui.QStackedLayout()
self.page1 = Page("Page 1", self.frame_layout)
self.page2 = Page("Page 2", self.frame_layout)
self.frame_layout.addWidget(self.page1)
self.frame_layout.addWidget(self.page2)
class Page(QtGui.QWidget):
def __init__(self, text, frame_layout, parent=None):
super(Page, self).__init__(parent)
print(self.width(), self.height())
self.frame_layout = frame_layout
self.frame = QtGui.QFrame()
self.frame_layout = QtGui.QVBoxLayout()
self.frame.setStyleSheet("background-color: rgb(191, 191, 191)")
self.label = QtGui.QLabel()
self.label.setText(text)
self.frame_layout.addWidget(self.label, alignment=QtCore.Qt.AlignCenter)
self.frame.setLayout(self.frame_layout)
self.layout = QtGui.QVBoxLayout()
self.layout.addWidget(self.frame)
self.layout.setContentsMargins(0,0,0,0)
self.setLayout(self.layout)
self.showMaximized()
print(self.width(), self.height())
if __name__ == "__main__":
app = QtGui.QApplication([])
window = Window()
window.show()
sys.exit(app.exec_())
The main problem is that you are not setting the QStackedLayout for the Xyz widget, the result is that all pages will actually appear as top level windows.
When a widget is added to a layout, it takes ownership of it; if the layout is already set to a widget, then the widget that was added to the layout becomes reparented to the other. The same happens if you set the layout afterwards.
Why does it show the first page as a separate window? And why doesn't it show the second?
When a new widget is created without a parent, it becomes a top level window; when a widget is added to a new stacked layout, Qt automatically tries to show it; you didn't set the layout to anything (which would reparent it as explained before), and the result is that the first page is shown as a standalone window.
Now, since the first "screen" is going to be shown only the first time, you can set that widget as the central widget, and then set a Xyz instance (which is actually a QStackedWidget subclass) as a new central widget.
Note that you don't need to use QWidget to add a QFrame if that's the only parent widget shown: you can just subclass QFrame.
This is a much simpler and cleaner version of your code:
from PyQt4 import QtGui, QtCore
class Window(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.startPage = Abc()
self.setCentralWidget(self.startPage)
self.startPage.startRequest.connect(self.buildPages)
def buildPages(self):
self.pages = Xyz()
self.setCentralWidget(self.pages)
class Abc(QtGui.QFrame):
startRequest = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(Abc, self).__init__(parent)
layout = QtGui.QVBoxLayout(self)
self.button = QtGui.QPushButton('Click me!')
self.button.clicked.connect(self.startRequest)
layout.addWidget(self.button)
class Xyz(QtGui.QStackedWidget):
def __init__(self, parent=None):
super(Xyz, self).__init__(parent)
self.page1 = Page("Page 1")
self.page2 = Page("Page 2")
self.addWidget(self.page1)
self.addWidget(self.page2)
self.page1.switchRequest.connect(lambda: self.setCurrentIndex(1))
self.page2.switchRequest.connect(lambda: self.setCurrentIndex(0))
class Page(QtGui.QFrame):
switchRequest = QtCore.pyqtSignal()
def __init__(self, text, parent=None):
super(Page, self).__init__(parent)
layout = QtGui.QVBoxLayout(self)
# set the background only for QFrame subclasses (which also includes
# QLabel), this prevents setting the background for other classes,
# such as the push button
self.setStyleSheet('''
QFrame {
background-color: rgb(191, 191, 191)
}
''')
# when adding a widget to a layout, the layout tries to automatically
# make it as big as possible (based on the widget's sizeHint); so you
# should not use the alignment argument for layout.addWidget(), but for
# the label instead
self.label = QtGui.QLabel(text, alignment=QtCore.Qt.AlignCenter)
layout.addWidget(self.label)
self.switchButton = QtGui.QPushButton('Switch')
layout.addWidget(self.switchButton)
self.switchButton.clicked.connect(self.switchRequest)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())

Use a variable/data in two tabs in PyQt5 / Python

I am trying to get input from a user in one tab then show it in the second tab. I could not find a question or an example about how to do that. This is an example of the code that I want to use, How can I show the data from Tab(1) Qlabel in QTextEdit box in Tab(2), I am a beginner in pyqt5 and not sure how this would work:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class TabWidget(QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle('Tab Widget Application')
tabwidget = QTabWidget()
tabwidget.addTab(FirstTab(), 'First Tab')
tabwidget.addTab(SecondTab(),'Second Tab')
vbox=QVBoxLayout()
vbox.addWidget(tabwidget)
self.setLayout(vbox)
class FirstTab(QWidget):
def __init__(self):
super().__init__()
self.nameLabel = QLabel(self)
self.nameLabel.setText('Name:')
self.line = QLineEdit(self)
self.line.move(80, 20)
self.line.resize(200, 32)
self.nameLabel.move(20, 20)
self.btn=QPushButton('switch',self)
self.btn.move(80, 50)
self.btn.clicked.connect(lambda: SecondTab.display(SecondTab(),self.nameLabel.text()))
class SecondTab(QWidget):
def __init__(self):
super().__init__()
self.layout = QVBoxLayout()
self.editor=QTextEdit()
self.layout.addWidget(self.editor)
self.setLayout(self.layout)
def display(self,text):
self.editor.setText(text)
if __name__ == '__main__':
app=QApplication(sys.argv)
tabwidget = TabWidget()
tabwidget.resize(500,500)
tabwidget.show()
app.exec()
In order to track changes amongst child widgets, you'll need a main "controller".
Your QTabWidget will suffice, as long as you implement it in the correct way:
class TabWidget(QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle('Tab Widget Application')
# if the target widget of the layout is provided as an init argument, the
# layout will be automatically set to it
vbox = QVBoxLayout(self)
tabwidget = QTabWidget()
vbox.addWidget(tabwidget)
firstTab = FirstTab()
tabwidget.addTab(firstTab, 'First Tab')
secondTab = SecondTab()
tabwidget.addTab(secondTab,'Second Tab')
firstTab.line.textChanged.connect(secondTab.editor.setPlainText)
firstTab.btn.clicked.connect(lambda: tabwidget.setCurrentWidget(secondTab))

Add/Remove layout containing multiple widgets pyqt

I'm trying to make a gui that allows the addition/removal of multiple QtWidgets. The user selects a file and has the option off adding a comment in the lineedit next to it. A quick search on this site has so far given me the following code which allows me to add rows widgets.
import os
import sys
from PyQt5 import QtGui, QtCore, QtWidgets
class Additional(QtWidgets.QMainWindow):
def __init__(self):
super(Additional, self).__init__()
self.addButton = QtWidgets.QPushButton(' + ')
self.delButton = QtWidgets.QPushButton(' - ')
self.acceptButton = QtWidgets.QPushButton('Accept')
self.cancelButton = QtWidgets.QPushButton('Cancel')
self.addButton.clicked.connect(self.addWidget)
self.delButton.clicked.connect(self.delWidget)
self.acceptButton.clicked.connect(self.acceptValues)
self.cancelButton.clicked.connect(self.cancel)
self.scrollLayout = QtWidgets.QFormLayout()
self.scrollWidget = QtWidgets.QWidget()
self.scrollWidget.setLayout(self.scrollLayout)
self.scrollArea = QtWidgets.QScrollArea()
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setWidget(self.scrollWidget)
self.button_layout = QtWidgets.QVBoxLayout()
self.mainLayout = QtWidgets.QHBoxLayout()
self.button_layout.addWidget(self.addButton)
self.button_layout.addWidget(self.delButton)
self.button_layout.addWidget(self.acceptButton)
self.button_layout.addWidget(self.cancelButton)
self.mainLayout.addLayout(self.button_layout)
self.mainLayout.addWidget(self.scrollArea)
self.centralWidget = QtWidgets.QWidget()
self.centralWidget.setLayout(self.mainLayout)
self.setCentralWidget(self.centralWidget)
self.resize(800, 200)
self.setFixedSize(self.size())
self.show()
def addWidget(self):
self.scrollLayout.addRow(AddRow())
def delWidget(self):
# Would this call another class to remove the row? If so, how?
pass
def acceptValues(self):
# Would I count the widgets in the 'scroll' area and get the data from that?
pass
def cancel(self):
QtCore.QCoreApplication.instance().quit()
# BTW, is this the right way to close the window/instance?
class AddRow(QtWidgets.QWidget):
def __init__( self, parent=None):
super(AddRow, self).__init__(parent)
self.button = QtWidgets.QPushButton('Select file')
self.label = QtWidgets.QLabel('Selection will go here')
self.lineedit = QtWidgets.QLineEdit()
self.lineedit.setPlaceholderText("Rename (optional)...")
# Would I add the button callback here?
layout = QtWidgets.QHBoxLayout()
layout.addWidget(self.button)
layout.addWidget(self.label)
layout.addWidget(self.lineedit)
self.setLayout(layout)
app = QtWidgets.QApplication(sys.argv)
myApp = Additional()
app.exec_()
I'm trying to figure out:
How to delete the last row that's been added.
How to assign the action to the button (this be done in the 'AddRow'
class likeself.buttton.clicked.callback(self.selectfile)
How to collect the data from the rows (ie, after 'accept' has been
clicked)
Any ideas would be welcome!
Before pointing to the solution, I'm just going to change the name of the AddRow class to RowWidget because a class should not indicate action.
class RowWidget(QtWidgets.QWidget):
def __init__( self, parent=None):
super(RowWidget, self).__init__(parent)
...
How to delete the last row that's been added ?
Since you are using QFormLayout and assuming that you are using a version PyQt5>=5.8 you can use the removeRow() method:
#QtCore.pyqtSlot()
def delWidget(self):
if self.scrollLayout.rowCount() > 0:
self.scrollLayout.removeRow(self.scrollLayout.rowCount()-1)
How to assign the action to the button (this be done in the 'AddRow' class likeself.buttton.clicked.callback(self.selectfile)?
Each part of your application must be independent so the slot that you select the file must be part only of RowWidget, and RowWidget must have a method that returns that value:
class RowWidget(QtWidgets.QWidget):
def __init__( self, parent=None):
super(RowWidget, self).__init__(parent)
self.button = QtWidgets.QPushButton('Select file')
self.label = QtWidgets.QLabel('Selection will go here')
self.lineedit = QtWidgets.QLineEdit()
self.lineedit.setPlaceholderText("Rename (optional)...")
self.button.clicked.connect(self.on_select_file)
layout = QtWidgets.QHBoxLayout(self)
layout.addWidget(self.button)
layout.addWidget(self.label)
layout.addWidget(self.lineedit)
#QtCore.pyqtSlot()
def on_select_file(self):
filename, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Open File")
if filename:
self.lineedit.setText(filename)
def get_filename(self):
return self.lineedit.text()
How to collect the data from the rows (ie, after 'accept' has been clicked)?
The widgets attached to a layout are children of the widget where the layout has been added, that widget can be obtained through parentWidget(), having that parent we can obtain their children through findChildren():
#QtCore.pyqtSlot()
def acceptValues(self):
l_values = []
for w in self.scrollLayout.parentWidget().findChildren(RowWidget):
l_values.append(w.get_filename())
print(l_values)
The previous method may fail if the parentWidget() has other children that belong to RowWidget.
Another option, and that does not fail is to iterate through the QLayoutItems:
#QtCore.pyqtSlot()
def acceptValues(self):
l_values = []
for i in range(self.scrollLayout.rowCount()):
layout_item = self.scrollLayout.itemAt(i)
if isinstance(layout_item.widget(), RowWidget):
l_values.append(layout_item.widget().get_filename())
print(l_values)

How to avoid double inhertince

I am trying to devlop a qt application and I have decided to make an abstract class named window that inherts from QWidget each window, diaoluge will inhert from her, and will include the basic proprties that window should have. Right now one of the classes that inherts from window also inhert from QMainWindow. My problam is that both QMainWindow and window inherts from QWidget. That makes me think that my way of soltion is sub-optimal and there is a proper way to do the genraliztion I try to do, any advice will be welcomed.
Heres the code for refrence (delted unimportent parts) with the double inhertince:
class Window(QtGui.QWidget , ):
_metaclass__ = ABCMeta
def __init__(self, win_name):
super(Window, self).__init__()
self.name = win_name
self.initUI()
def initUI(self, ):
self.setWindowTitle(self.name)
self.show()
class Main_Window(QtGui.QMainWindow, Window):
def __init__(self, win_name):
super(Main_Window, self).__init__(win_name)
self.initUI()
def initUI(self, ):
self.statusBar().showMessage('chk')
self.setGeometry(300, 300, 250, 150)
super(Main_Window, self).initUI()
Another option instead using inheritance would be using composition, here's a little example:
import sys
from PyQt5.Qt import * # noqa
class GuiBuilder():
def __init__(self, widget, name):
self.widget = widget
self.name = name
self.init_ui()
def init_ui(self):
self.widget.setWindowTitle(self.name)
class MainWindow(QMainWindow):
def __init__(self, name):
super().__init__()
self.gui_builder = GuiBuilder(self, name)
self.init_ui()
def init_ui(self):
self.statusBar().showMessage('chk')
self.setGeometry(300, 300, 250, 150)
self.gui_builder.init_ui()
def main():
app = QApplication(sys.argv)
for title in ["window1", "window2", "window3"]:
ex = MainWindow(title)
ex.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
As you can see I've refactored a bit your posted code, changed Window by GuiBuilder, Pep8 style, ... Of course, the above example doesn't make too much sense and you'd just use this approach if there was a good reason for it. KISS

How to access variables from one class to another class in PyQt4?

I want to get a string from the main window to use in a window triggered with a click. I know how to do it by putting all statements into a single class, but now I'm trying to do the same thing with one class per window. Here is the code:
import sys
from PyQt4 import QtGui
class Window(QtGui.QWidget):
def __init__(self, parent=None):
super().__init__()
self.initUI()
def initUI(self):
self.value = QtGui.QLineEdit('23')
self.button = QtGui.QPushButton('Open Dialog')
self.button.clicked.connect(self.openDialog)
vbox = QtGui.QVBoxLayout()
vbox.addWidget(self.value)
vbox.addWidget(self.button)
self.setLayout(vbox)
def openDialog(self):
self.entry = self.value.text()
print(self.entry)
Dialog().exec_()
class Dialog(QtGui.QDialog):
def __init__(self, parent=Window):
super().__init__()
win = Window()
self.text = win.entry
self.label = QtGui.QLabel(self.text)
hbox = QtGui.QHBoxLayout()
hbox.addWidget(self.label)
self.setLayout(hbox)
def main():
app = QtGui.QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
But I'm getting the error "AttributeError: 'Window' object has no attribute 'entry'" and I don't know any other way to try fix it. Can someone help me with it?
Create an instance of Dialog in the openDialog method, so that you can access its attributes directly. That way, the two classes can operate more independently, and you won't need to access the Window class from within the Dialog class:
def openDialog(self):
dialog = Dialog(self)
dialog.label.setText(self.value.text())
dialog.exec_()
print(dialog.label.text())
class Dialog(QtGui.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.label = QtGui.QLabel(self)
hbox = QtGui.QHBoxLayout()
hbox.addWidget(self.label)
self.setLayout(hbox)
Here
win = Window()
self.text = win.entry
you declare a new window and accesing its entry field but on your window class
class Window(QtGui.QWidget):
def __init__(self, parent=None):
super().__init__()
self.initUI()
the entry field is not constructed.
So
Either you want to create a new window, so you have to put the self.entry on the constructor
You want to use the existing window an access its entry after calling openDialog
Edit:
Maybe here
class Dialog(QtGui.QDialog):
def __init__(self, parent=Window):
super().__init__()
you are initializing the class wrong. The parent constructor should be called with parent=Window too. Then from inside the dialog you could reference the window by doing self.parent

Categories

Resources