Programming a GUI with PyQt in Python3 - python

I'm asked to create a GUI with PyQt without using any Qtdesigner for my assignment. But now I'm facing a problem. In this pic GUI screenshoot
as you can see, there is a spinbox "Anzahl der Schicht". What I want to do is, when the user sets the value for this spinbox, the area which below it will show the corresponding rows of the input(the combination of QLineEdit,QSlider Widget,QLineEdit and 2 QSpinboxes in a row).
For example, the pic that I uploaded, means the value of spinbox "Anzahl der Schicht" is 3, so there are 3 rows below it. If the value is 4, there should be 4 rows. There is no limited value for the spinbox. How can I make this kind of dynamic effect for the GUI?
Update on 05.07.2019
Thanks for all useful replies. The following code and pic GUI version2 is my current status. So far I can't connect the Qspinbox to the class Widget() for adding or deleting the rows. So I just use a button "add widget" to implement what I want.
class ExampleWidget(QtWidgets.QGroupBox):
def __init__(self, numAddWidget):
QtWidgets.QGroupBox.__init__(self)
self.numAddWidget = numAddWidget
self.initSubject()
self.organize()
self.setFlat(True)
self.setStyleSheet("border: 1px solid transparent")
def initSubject(self):
self.shiftname =QtWidgets.QLineEdit() # Eingabefeld init
self.shiftname.setText('0')
self.shiftpercent = QtWidgets.QSlider()
self.shiftpercent.setOrientation(QtCore.Qt.Horizontal)
self.carnum =QtWidgets.QLineEdit() # Eingabefeld init
self.carnum.setText('0')
self.start = QtWidgets.QTimeEdit()
self.start.setDisplayFormat("HH:mm")
self.end = QtWidgets.QTimeEdit()
self.end.setDisplayFormat("HH:mm")
def organize(self):
grid = QtWidgets.QGridLayout(self)
self.setLayout(grid)
grid.addWidget(self.shiftname, 0,0)
grid.addWidget(self.shiftpercent, 0,1)
grid.addWidget(self.carnum, 0,2)
grid.addWidget(self.start, 0,3)
grid.addWidget(self.end, 0,4)
class Widget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.numAddWidget = 1
self.initUi()
def initUi(self):
self.layoutV = QtWidgets.QVBoxLayout(self)
self.area = QtWidgets.QScrollArea(self)
self.area.setWidgetResizable(True)
self.scrollAreaWidgetContents = QtWidgets.QWidget()
self.layoutH = QtWidgets.QHBoxLayout(self.scrollAreaWidgetContents)
self.gridLayout = QtWidgets.QGridLayout()
self.layoutH.addLayout(self.gridLayout)
self.area.setWidget(self.scrollAreaWidgetContents)
self.add_button = QtWidgets.QPushButton("Add Widget")
self.layoutV.addWidget(self.add_button)
self.layoutV.addWidget(self.area)
self.add_button.clicked.connect(self.addWidget)
self.widget = ExampleWidget(self.numAddWidget)
self.gridLayout.addWidget(self.widget)
def addWidget(self):
self.numAddWidget += 1
self.widget = ExampleWidget(self.numAddWidget)
self.gridLayout.addWidget(self.widget)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())

Here is a way. In this example the widgets (QLineEdits in this case) are stored in a list, Widget.items. When the value of the spin box changes extra widgets are appended to this list and added to the vertical layout if necessary. The widgets are then shown or hidden depending whether the current number of visible widgets is greater or smaller than the value of the spin box.
from PyQt5 import QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super().__init__()
self.resize(500,500)
self.items = []
self.item_count = 0
self.item_factory = QtWidgets.QLineEdit
group_box = QtWidgets.QGroupBox()
self.item_layout = QtWidgets.QVBoxLayout(group_box)
self.item_layout.addStretch(2)
self.spin_box = QtWidgets.QSpinBox(self)
self.spin_box.valueChanged.connect(self.set_item_count)
h_layout = QtWidgets.QHBoxLayout(self)
h_layout.addWidget(group_box, 2)
h_layout.addWidget(self.spin_box, 0)
def set_item_count(self, new_count:int):
n_items = len(self.items)
for ii in range(n_items, new_count):
item = self.item_factory(self)
self.items.append(item)
self.item_layout.insertWidget(n_items, item)
for ii in range(self.item_count, new_count):
self.item_layout.itemAt(ii).widget().show()
for ii in range(new_count, self.item_count):
self.item_layout.itemAt(ii).widget().hide()
self.item_count = new_count
if __name__ == "__main__":
app = QtWidgets.QApplication([])
window = Widget()
window.show()
app.exec()

Related

QToolBar size and alignment with hidden actions

I'm adding a toolbar to a widget, and dynamically hiding actions (they are also used in context menus). I'm encountering some problem with alignment/size. In the image below, Test 1 is "correct", and I want the other two to have only the "C" button, right-aligned (as in Test 3, but without the extra left space).
The code shows my attempt. Test 2 simply uses .setVisible(False) on the action A and B, while Test 3 does the same and adds a couple of empty QLabel on the left (if I add only one), the C does not end up right-aligned. It looks like QToolBar wants a minimum of 3 buttons or something.
Any idea how to fix this?
#!/usr/bin/env python
from PyQt4.QtGui import *
import sys
class MainWindow(QMainWindow):
def __init__(self):
super(QMainWindow, self).__init__()
stylesheet = 'QToolButton{padding: 0; margin: 0} QToolBar{border: 1px solid black}'
layout = QVBoxLayout()
a = QAction('A', self)
b = QAction('B', self)
c = QAction('C', self)
toolbar = QToolBar()
toolbar.addAction(a)
toolbar.addAction(b)
toolbar.addAction(c)
toolbar.setStyleSheet(stylesheet)
hbox = QHBoxLayout()
hbox.addWidget(QLabel('Test 1'))
hbox.addStretch(1)
hbox.addWidget(toolbar)
group = QGroupBox()
group.setLayout(hbox)
layout.addWidget(group)
a = QAction('A', self)
b = QAction('B', self)
c = QAction('C', self)
toolbar = QToolBar()
toolbar.addAction(a)
toolbar.addAction(b)
toolbar.addAction(c)
a.setVisible(False)
b.setVisible(False)
toolbar.setStyleSheet(stylesheet)
hbox = QHBoxLayout()
hbox.addWidget(QLabel('Test 2'))
hbox.addStretch(1)
hbox.addWidget(toolbar)
group = QGroupBox()
group.setLayout(hbox)
layout.addWidget(group)
a = QAction('A', self)
b = QAction('B', self)
c = QAction('C', self)
toolbar = QToolBar()
toolbar.addWidget(QLabel(''))
toolbar.addWidget(QLabel(''))
toolbar.addAction(a)
toolbar.addAction(b)
toolbar.addAction(c)
a.setVisible(False)
b.setVisible(False)
toolbar.setStyleSheet(stylesheet)
hbox = QHBoxLayout()
hbox.addWidget(QLabel('Test 3'))
hbox.addStretch(1)
hbox.addWidget(toolbar)
group = QGroupBox()
group.setLayout(hbox)
layout.addWidget(group)
layout.addStretch(1)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
self.show()
app = QApplication(sys.argv)
win = MainWindow()
sys.exit(app.exec_())
To dynamically hide actions in a QToolbar, you cannot use setVisible(False) on the action since it will only "hide" the action from view but the button is inherently still there which is why you have the extra white space. Even though the action is hidden, there is still padding from a hidden object which gives you the undesired empty space. Similarly, when you add empty QLabels, this "hidden" widget also leaves white space since the physical area of the widget is still there.
Considering this, a solution is to remove the action from the QToolbar using removeAction(). This way, the actual object will be removed from the layout so there is no white space. Here's an example that shows how to add and remove actions in a QToolbar using handlers. You can check what objects are in the Qtoolbar with .actions() and depending on what action you want to remove, you can simply pass this to removeAction(). I'll leave it up to you to implement error handling when there are no more actions to add or no more actions to remove.
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys
class MainWindow(QMainWindow):
def __init__(self):
super(QMainWindow, self).__init__()
self.stylesheet = 'QToolButton{padding: 0; margin: 0} QToolBar{border: 1px solid black}'
self.main_layout = QGridLayout()
self.layout = QVBoxLayout()
self.a = QAction('A', self)
self.b = QAction('B', self)
self.c = QAction('C', self)
self.action_table = {1: self.a,
2: self.b,
3: self.c
}
self.test1_toolbar = QToolBar()
self.test1_toolbar.addAction(self.a)
self.test1_toolbar.addAction(self.b)
self.test1_toolbar.addAction(self.c)
self.test1_toolbar.setStyleSheet(self.stylesheet)
self.test1_hbox = QHBoxLayout()
self.test1_hbox.addWidget(QLabel('Test 1'))
self.test1_hbox.addStretch(1)
self.test1_hbox.addWidget(self.test1_toolbar)
self.test1_group = QGroupBox()
self.test1_group.setLayout(self.test1_hbox)
self.layout.addWidget(self.test1_group)
self.test2_toolbar = QToolBar()
self.test2_toolbar.addAction(self.a)
self.test2_toolbar.addAction(self.b)
self.test2_toolbar.addAction(self.c)
self.test2_toolbar.setStyleSheet(self.stylesheet)
self.test2_add_button = QPushButton('Add')
self.test2_add_button.clicked.connect(self.test2_add_action)
self.test2_remove_button = QPushButton('Remove')
self.test2_remove_button.clicked.connect(self.test2_remove_action)
self.test2_hbox = QHBoxLayout()
self.test2_hbox.addWidget(QLabel('Test 2'))
self.test2_hbox.addStretch(1)
self.test2_hbox.addWidget(self.test2_toolbar)
self.test2_group = QGroupBox()
self.test2_group.setLayout(self.test2_hbox)
self.layout.addWidget(self.test2_group)
self.test3_toolbar = QToolBar()
self.test3_toolbar.addAction(self.a)
self.test3_toolbar.addAction(self.b)
self.test3_toolbar.addAction(self.c)
self.test3_toolbar.setStyleSheet(self.stylesheet)
self.test3_hbox = QHBoxLayout()
self.test3_hbox.addWidget(QLabel('Test 3'))
self.test3_hbox.addStretch(1)
self.test3_hbox.addWidget(self.test3_toolbar)
self.test3_group = QGroupBox()
self.test3_group.setLayout(self.test3_hbox)
self.layout.addWidget(self.test3_group)
self.layout.addStretch(1)
self.widget = QWidget()
self.main_layout.addLayout(self.layout,0,0,1,1)
self.main_layout.addWidget(self.test2_add_button,0,1,1,1)
self.main_layout.addWidget(self.test2_remove_button,0,2,1,1)
self.widget.setLayout(self.main_layout)
self.setCentralWidget(self.widget)
self.show()
def test2_add_action(self):
objects = len(self.test2_toolbar.actions())
self.test2_toolbar.addAction(self.action_table[objects + 1])
def test2_remove_action(self):
objects = len(self.test2_toolbar.actions())
self.test2_toolbar.removeAction(self.action_table[objects])
app = QApplication(sys.argv)
win = MainWindow()
sys.exit(app.exec_())

PyQt accessing Label and LineEdit that are dynamically created

I am trying to access dynamically created Labels and LineEdit to change their texts.
I have no idea how is that possible ?
As an example, when Start button is clicked it should change the text of PS1 QLineEdit from XXXX to YYYY .
from PyQt5 import QtCore, QtGui, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("BiPolar Power Supply Testing")
widget_map = {}
tab_widget = QtWidgets.QTabWidget()
self.setCentralWidget(tab_widget)
pstest_widget = QtWidgets.QWidget()
tab_widget.addTab(pstest_widget, "PS Tests")
pstest_vlay = QtWidgets.QVBoxLayout()
for i in range(1, 9):
title = "PS{}".format(i)
group_box = MainWindow.create_pstest_element(title)
pstest_vlay.addWidget(group_box)
self.PSFStart_btn = QtWidgets.QPushButton("Start")
self.PSFStop_btn = QtWidgets.QPushButton("Stop")
pstest_vlay.addWidget(self.PSFStart_btn)
pstest_vlay.addWidget(self.PSFStop_btn)
pstest_vlay.addStretch()
grid_lay_1 = QtWidgets.QGridLayout(pstest_widget)
#grid_lay_1.addWidget(pstest_widget)
grid_lay_1.addLayout(pstest_vlay, 0, 0)
#staticmethod
def create_pstest_element(title):
group_box = QtWidgets.QGroupBox(title)
grid = QtWidgets.QGridLayout()
serial_label = QtWidgets.QLabel("Serial No:")
serial_lineedit = QtWidgets.QLineEdit("XXXX")
grid.addWidget(serial_label, 0, 0)
grid.addWidget(serial_lineedit, 0, 1)
group_box.setLayout(grid)
return group_box
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Here is how the GUI looks like:
The current design makes it difficult to access the widgets since you do not save access to the elements (you could use a filter using findChildren but that method is not scalable or elegant.).
In these cases it is better than creating a class that inherits from QGroupBox by making internal elements such as the QLabel and QLineEdit class members. On the other hand, having many QGroupBox, it is best to use a container that allows us to access each element by means of an index or key, in this one a list is enough.
class GroupBox(QtWidgets.QGroupBox):
def __init__(self, title, parent=None):
super().__init__(title, parent)
grid = QtWidgets.QGridLayout()
self.serial_label = QtWidgets.QLabel("Serial No:")
self.serial_lineedit = QtWidgets.QLineEdit("XXXX")
grid.addWidget(self.serial_label, 0, 0)
grid.addWidget(self.serial_lineedit, 0, 1)
self.setLayout(grid)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("BiPolar Power Supply Testing")
tab_widget = QtWidgets.QTabWidget()
self.setCentralWidget(tab_widget)
pstest_widget = QtWidgets.QWidget()
tab_widget.addTab(pstest_widget, "PS Tests")
pstest_vlay = QtWidgets.QVBoxLayout()
self.group_boxes = []
for i in range(1, 9):
title = "PS{}".format(i)
group_box = GroupBox(title)
pstest_vlay.addWidget(group_box)
self.group_boxes.append(group_box)
self.PSFStart_btn = QtWidgets.QPushButton("Start")
self.PSFStop_btn = QtWidgets.QPushButton("Stop")
pstest_vlay.addWidget(self.PSFStart_btn)
pstest_vlay.addWidget(self.PSFStop_btn)
pstest_vlay.addStretch()
grid_lay_1 = QtWidgets.QGridLayout(pstest_widget)
# grid_lay_1.addWidget(pstest_widget)
grid_lay_1.addLayout(pstest_vlay, 0, 0)
self.PSFStart_btn.clicked.connect(self.on_start_clicked)
#QtCore.pyqtSlot()
def on_start_clicked(self):
group_box = self.group_boxes[0]
group_box.serial_lineedit.setText("YYYY")

AttributeError: 'NoneType' object has no attribute 'scenePos'

Here is my sample code,i want to print the my coordinates of x,y positions by using the mouse press event,i got this error can any one please help me and i want to display the my graphics view in middle of the scrollArea.
Given bellow is my code:
from pyface.qt import QtGui, QtCore
import sys
class MyView(QtGui.QGraphicsView):
def __init__(self):
QtGui.QGraphicsView.__init__(self)
self.row = 2
self.cols = 4
self.scene = QtGui.QGraphicsScene(0,0,500,500)
self.List = []
for i in range(self.row):
for j in range(self.cols):
item = self.scene.addRect(QtCore.QRectF(0,0,30,30))
item.setPos(30+j*30,500-i*30-60)
print item.scenePos()
self.List.append(item)
self.setScene(self.scene)
def mousePressEvent(self,event):
super(MyView,self).mousePressEvent(event)
p = QtCore.QPointF(event.pos())
print "positonnnnnnnnnnnnnnnn", p
item = self.scene.itemAt(p)
print "#########################2"
print item.scenePos()
class Settings(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Settings, self).__init__(parent)
spacer = QtGui.QWidget(self)
spacer.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
QtGui.QToolTip.setFont(QtGui.QFont('SansSerif', 10))
self.vbox = QtGui.QVBoxLayout()
self.save = QtGui.QPushButton("save")
self.open= QtGui.QPushButton("open")
self.folder= QtGui.QPushButton("Folder")
self.folder.clicked.connect(self.showSettings)
self.vbox.addWidget(self.save)
self.vbox.addWidget(self.open)
self.vbox.addWidget(self.folder)
self.grid = QtGui.QGridLayout()
self.grid.addLayout(self.vbox,0,0)
self.scrollArea = QtGui.QScrollArea()
self.scrollArea.setBackgroundRole(QtGui.QPalette.Light)
self.scrollArea.setWidgetResizable(True)
self.grid.addWidget(self.scrollArea,0,1)
self.setCentralWidget(QtGui.QWidget(self))
self.centralWidget().setLayout(self.grid)
self.setGeometry(200,100,300,300)
self.show()
def showSettings(self):
self.newwidget = QtGui.QWidget()
self.glayout = QtGui.QGridLayout(self.newwidget)
self.MyView = MyView()
self.glayout.addWidget(self.MyView,0,1)
self.scrollArea.setWidget(self.newwidget)
def main():
app = QtGui.QApplication(sys.argv)
ex = Settings()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The coordinates of the items are different to the coordinates of the window, in the case of event.pos() returns the position of the mouse with respect to the viewport of the QGraphicsView so you will have to convert it to coordinates of the scene with mapToScene(), by other side when using itemAt() could return None since in certain areas of the scene there are no items so it is advisable to verify
def mousePressEvent(self, event):
super(MyView,self).mousePressEvent(event)
p = self.mapToScene(event.pos())
item = self.scene.itemAt(p)
if item is not None:
print(item.scenePos())
To understand the different coordinate systems that handle the QGraphicsView, QGraphicsScene and the QGraphicsItems, I recommend reading Graphics View Framework.

What kind of signal is emitted when QScrollArea entry is selected/clicked?

I'm having though time figuring out what kind of signal is emitted in following situation:
Basicly that's QScrollArea that holds multiple QTableWidgets:
class ScrollArea(QtGui.QScrollArea):
def __init__(self):
super(ScrollArea, self).__init__()
self.scroll_widget = QtGui.QWidget()
self.scroll_layout = QtGui.QVBoxLayout()
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setWidgetResizable(True)
self.__create_content()
self.setWidget(self._content_widget)
self.scroll_layout.addWidget(self)
self.scroll_widget.setLayout(self.scroll_layout)
def __create_content(self):
self._content_widget = QtGui.QWidget()
self._content_widget_layout = QtGui.QVBoxLayout()
self._content_widget.setLayout(self._content_widget_layout)
def add_item(self, item):
self._content_widget_layout.addWidget(item)
I'm using Plastique style for QApplication. As it can be seen from the above picture, when an item is clicked inside QScrollArea, blue border appears. What I would like to know is which signal is emitted when the border is drawn? I need this information so I can append a row to the selected QTableWidget whenever a button (on the left side) is clicked.
Also you can see that there is a 'x' inside each table, when 'x' is pressed that QTableWidget gets removed from QScrollArea. If there is a solution for previous problem, I could also remove QTableWidget depending on user selection rather than user clicking the 'x'.
To get the widget that has the focus you can use the focusChanged signal of QApplication:
from PyQt4 import QtCore, QtGui
class HorizontalHeader(QtGui.QHeaderView):
def __init__(self, parent=None):
super(HorizontalHeader, self).__init__(QtCore.Qt.Horizontal, parent)
self.button = QtGui.QToolButton(self, text="x")
self.sectionResized.connect(self.handleSectionResized)
def handleSectionResized(self):
last_ix = self.count() - 1
pos = QtCore.QPoint(self.sectionViewportPosition(last_ix) + self.sectionSize(last_ix) , 0)
self.button.move(pos)
def showEvent(self, event):
self.handleSectionResized()
super(HorizontalHeader, self).showEvent(event)
class TableView(QtGui.QTableView):
def __init__(self, *args, **kwargs):
super(TableView, self).__init__(*args, **kwargs)
header = HorizontalHeader(self)
header.button.clicked.connect(self.deleteLater)
self.setHorizontalHeader(header)
QtGui.qApp.focusChanged.connect(self.onFocusChanged)
def onFocusChanged(self, old, new):
if new == self:
self.deleteLater()
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
scrollArea = QtGui.QScrollArea()
scrollArea.setWidgetResizable(True)
widget = QtGui.QWidget()
scrollArea.setWidget(widget)
lay = QtGui.QVBoxLayout(widget)
for i in range(10):
w = TableView()
model = QtGui.QStandardItemModel(4, 2, w)
w.setModel(model)
lay.addWidget(w)
scrollArea.show()
sys.exit(app.exec_())

PyQt, layout.addItem does not return a reference

I stumbled upon a problem with PyQt5.
After subclassing a horizontal box layout and populating it with three widgets (a label, a checkbox and a slider) I am adding an instance of this layout to my MainWindow application. Later in the application I need to access the values of the sliders which does not work for now.
The instantiated single_slider whose reference is stored under self.slider_1 will be None. Therefore the attributes of this single_slider, especially the modular slider with its PyQt5 attribute slider.value() are unaccessible.
I am sure that there is something wrong with the logic, but cannot get to it.
Any help is much appreciated.
Cheers,
Pa
class single_slider(QHBoxLayout):
def __init__(self):
QHBoxLayout.__init__(self)
self.label = self.addWidget(QCheckBox(), 1)
self.cbox = self.addWidget(QLabel(), 1)
self.slider = self.addWidget(QSlider(orientation=Qt.Horizontal), 5)
class MyApp(QMainWindow):
def __init__(self):
super().__init__()
self.setCentralWidget(QWidget(self))
self.grid = QGridLayout()
self.slider_1 = self.grid.addItem(single_slider())
print(self.slider_1)
self.centralWidget().setLayout(self.grid)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MyApp()
win.show()
ret = app.exec_()
sys.exit(ret)
The addItem() method does not return any value, nor does the addWidget() method, what you have to do is create the object before and then assign them with those methods:
class single_slider(QHBoxLayout):
def __init__(self):
QHBoxLayout.__init__(self)
self.cbox = QCheckBox()
self.label = QLabel("label")
self.slider = QSlider(orientation=Qt.Horizontal)
self.addWidget(self.cbox)
self.addWidget(self.label)
self.addWidget(self.slider, 5)
class MyApp(QMainWindow):
def __init__(self):
super().__init__()
self.setCentralWidget(QWidget(self))
self.grid = QGridLayout()
self.slider_1 = single_slider()
self.grid.addItem(self.slider_1)
self.slider_1.slider.valueChanged.connect(lambda val: print(val))
self.centralWidget().setLayout(self.grid)

Categories

Resources