Strange grafical artifacts while re-inserting widgets into gridlayout [duplicate] - python

I have a QVBox layout that houses a QVBox layout and a QHBox layout. I use the other QVBox layout to hold dynamically created GUI objects and the QHBox layout to hold the buttons that add/remove those objects. Everything works correctly if I position the QHBox on top of the QVBox, but when I try to position the QHBox beneath the QVBox the objects aren't removed correctly but stay there "lingering" on top of the QHBox. I'll upload pictures to demonstrate the problem. First picture is before taking action, second is after creating a new object and third is after deleting the object
Here is the code that creates and deletes the new objects
def addClient(self):
if (len(self.clients) < 5):
client = clientComponent(self)
self.clients.append(client)
index = len(self.clients)-1
self.vLayout3.addWidget(self.clients[index])
client.setIndex(index)
self.clients[index].startButton.clicked.connect(partial(self.threadcontrol, '2', client.getIndex()))
self.clients[index].stopButton.clicked.connect(partial(self.clientstop, '0', client.getIndex()))
def deleteClient(self):
if (len(self.clients) > 1):
self.vLayout3.removeWidget(self.clients.pop())
This is where I complete the layout
def initializeUi(self):
self.mainWidget = QWidget(self)
self.setCentralWidget(self.mainWidget)
self.mainLayout = QVBoxLayout(self.mainWidget)
self.hLayout1 = QHBoxLayout()
self.hLayout2 = QHBoxLayout()
self.vLayout1 = QVBoxLayout()
self.vLayout2 = QVBoxLayout()
self.vLayout3 = QVBoxLayout()
self.addServer()
self.addClient()
self.serverBox = QGroupBox('Server')
self.clientBox = QGroupBox('Client')
self.traffic1 = QLabel('0.0Mb/s', self)
self.traffic1.setAlignment(Qt.AlignRight)
self.traffic2 = QLabel('0.0Mb/s', self)
self.traffic2.setAlignment(Qt.AlignCenter)
self.traffic3 = QLabel('0.0Mb/s', self)
self.traffic3.setAlignment(Qt.AlignLeft)
self.newClientButton = QPushButton('+', self)
self.deleteClientButton = QPushButton('-', self)
self.hLayout1.addWidget(self.traffic1)
self.hLayout1.addWidget(self.traffic2)
self.hLayout1.addWidget(self.traffic3)
self.hLayout2.addWidget(self.newClientButton)
self.hLayout2.addWidget(self.deleteClientButton)
self.vLayout2.addLayout(self.vLayout3)
self.vLayout2.addLayout(self.hLayout2)
self.mainLayout.addWidget(self.plot)
self.mainLayout.addLayout(self.hLayout1)
self.serverBox.setLayout(self.vLayout1)
self.mainLayout.addWidget(self.serverBox)
self.clientBox.setLayout(self.vLayout2)
self.mainLayout.addWidget(self.clientBox)

This is happening because your main window remains the parent of the client widgets after you remove them from the layout. You will see similar behaviour if you assign a widget a parent widget without adding it to any layout.
Removing the parent should resolve the issue.
def deleteClient(self):
if (len(self.clients) > 1):
client = self.clients.pop()
self.vLayout3.removeWidget(client)
client.setParent(None)
You may also need to make a call to adjustSize to resize the window to fit the remaining widgets

When you delete a widget from layout it still remains in parent widget's
object tree, so it gets displayed outside of any layout.
To remove a widget from the object tree call widget.setParent(None).
widget.deleteLater() also works.
Here is my MCVE(Qt4, Py2.7):
from PyQt4.QtGui import (QApplication, QWidget, QPushButton,
QVBoxLayout, QHBoxLayout)
app=QApplication([])
self = QWidget()
main_layout = QVBoxLayout(self)
clients = []
l2 = QHBoxLayout()
main_layout.addLayout(l2)
b_add = QPushButton('add', self)
l2.addWidget(b_add)
def addClient():
b = QPushButton(str(len(clients)), self)
clients.append(b)
main_layout.addWidget(b)
b_add.clicked.connect(addClient)
b_rm = QPushButton('rm', self)
l2.addWidget(b_rm)
def deleteClient():
b = clients.pop()
main_layout.removeWidget(b)
# comment out two following lines to get the behavior you observe
b.setParent(None)
self.adjustSize()
b_rm.clicked.connect(deleteClient)
self.show()
app.exec_()
On my system I also have to call self.adjustSize() after deletion to resize the main window

Related

Add dockable widgets in quadrants using PyQt

I'm creating a GUI using PyQt, to display 4 images in a window, positioned in this way:
Top left
Top right
Bottom left
Bottom right
I'd like to be able to undock them, but also to redock them back to any of the 4 available space.
My final goal is to set it up so that moving an undocked image where another one is already placed, would move that second image out of the way (docking it in another free quadrant or undocking it), or that placing it in the center would make it occupy all the 4 quadrants (undocking all the others).
I've tried achieving this with QDockWidget, but so far I'm not achieving good results.
My current code:
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Window")
grid_layout = QGridLayout()
dock_window_1 = QMainWindow()
docked = QDockWidget("Dockable", self)
dock_window_1.addDockWidget(Qt.DockWidgetArea.TopDockWidgetArea, docked)
dockedWidget = QWidget(self)
docked.setWidget(dockedWidget)
dockedWidget.setLayout(QVBoxLayout())
dockedWidget.layout().addWidget(QPushButton("1"))
dock_window_2 = QMainWindow()
docked_2 = QDockWidget("Dockable_2", self)
dock_window_2.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, docked_2)
dockedWidget_2 = QWidget(self)
docked.setWidget(dockedWidget_2)
dockedWidget_2.setLayout(QVBoxLayout())
dockedWidget_2.layout().addWidget(QPushButton("2"))
dock_window_3 = QMainWindow()
docked_3 = QDockWidget("Dockable_3", self)
dock_window_3.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, docked_3)
dockedWidget_3 = QWidget(self)
docked.setWidget(dockedWidget_3)
dockedWidget_3.setLayout(QVBoxLayout())
dockedWidget_3.layout().addWidget(QPushButton("3"))
dock_window_4 = QMainWindow()
docked_4 = QDockWidget("Dockable_4", self)
dock_window_4.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, docked_4)
dockedWidget_4 = QWidget(self)
docked.setWidget(dockedWidget_4)
dockedWidget_4.setLayout(QVBoxLayout())
dockedWidget_4.layout().addWidget(QPushButton("4"))
grid_layout.addWidget(dock_window_1, 0, 0)
grid_layout.addWidget(dock_window_2, 1, 0)
grid_layout.addWidget(dock_window_3, 0, 1)
grid_layout.addWidget(dock_window_4, 1, 1)
widget = QWidget()
widget.setLayout(grid_layout)
self.setCentralWidget(widget)
This kind of works, but I'm only able to redock a widget in its original place.
Can anyone help me getting on the right road here?
Thanks in advance!
Most of the behavior you are describing is actually already implemented by Qt using the QMainWindow and the QDockWidget.
The issue with your code is you are creating a unique QMainWindow for each of the QDockWidgets when the program only needs one QMainWindow. Additionally you are adding each of the QDockWidgets to the layout after you have already added them using the standard dockAreas surrounding the central widget.
If you eliminate the unneeded QMainWindows and instead assign each of the QDockWidgets to the your MainWindow, your desired functionality should work automatically.
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Window")
grid_layout = QGridLayout()
docked = QDockWidget("Dockable", self)
docked.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
dockedWidget = QWidget(self)
docked.setWidget(dockedWidget)
dockedWidget.setLayout(QVBoxLayout())
dockedWidget.layout().addWidget(QPushButton("1"))
docked_2 = QDockWidget("Dockable_2", self)
docked_2.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
dockedWidget_2 = QWidget(self)
docked_2.setWidget(dockedWidget_2)
dockedWidget_2.setLayout(QVBoxLayout())
dockedWidget_2.layout().addWidget(QPushButton("2"))
docked_3 = QDockWidget("Dockable_3", self)
docked_3.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
dockedWidget_3 = QWidget(self)
docked_3.setWidget(dockedWidget_3)
dockedWidget_3.setLayout(QVBoxLayout())
dockedWidget_3.layout().addWidget(QPushButton("3"))
docked_4 = QDockWidget("Dockable_4", self)
docked_4.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
dockedWidget_4 = QWidget(self)
docked_4.setWidget(dockedWidget_4)
dockedWidget_4.setLayout(QVBoxLayout())
dockedWidget_4.layout().addWidget(QPushButton("4"))
self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, docked)
self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, docked_2)
self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, docked_3)
self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, docked_4)
widget = QWidget()
widget.setLayout(grid_layout)
self.setCentralWidget(widget)

PyQt6 deleting custom widget with nested class causes the program to crash

I am using Python 3.9.5.
I have encountered some serious problem in my project and here is a minimum reproducible example code, along with some descriptions.
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
class Editor(QTextEdit):
doubleClicked = pyqtSignal(QTextEdit)
def __init__(self):
super().__init__()
self.setReadOnly(True)
def mouseDoubleClickEvent(self, e: QMouseEvent) -> None:
self.doubleClicked.emit(self)
class textcell(QGroupBox):
def __init__(self, text):
super().__init__()
self.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
self.label = QLabel(text)
self.apply = makebutton('Apply')
self.apply.hide()
self.editor = Editor()
self.editor.doubleClicked.connect(lambda: self.editor.setReadOnly(False))
self.editor.doubleClicked.connect(self.apply.show)
self.hbox = QHBoxLayout()
self.hbox.addSpacerItem(spacer)
self.hbox.addWidget(self.apply)
self.vbox = QVBoxLayout()
self.vbox.addWidget(self.label)
self.vbox.addWidget(self.editor)
self.vbox.addLayout(self.hbox)
self.setLayout(self.vbox)
self.apply.clicked.connect(self.on_ApplyClick)
def on_ApplyClick(self):
self.editor.setReadOnly(True)
self.apply.hide()
def makebutton(text):
button = QPushButton()
button.setFixedSize(60, 20)
button.setText(text)
return button
class songpage(QGroupBox):
def __init__(self, texts):
super().__init__()
self.init(texts)
self.setCheckable(True)
self.setChecked(False)
def init(self, texts):
self.vbox = QVBoxLayout()
artist = textcell('Artist')
artist.editor.setText(texts[0])
album = textcell('Album')
album.editor.setText(texts[1])
title = textcell('Title')
title.editor.setText(texts[2])
self.vbox.addWidget(artist)
self.vbox.addWidget(album)
self.vbox.addWidget(title)
self.setLayout(self.vbox)
spacer = QSpacerItem(0, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
class Ui_MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.resize(405, 720)
self.setWindowTitle('example')
frame = self.frameGeometry()
center = self.screen().availableGeometry().center()
frame.moveCenter(center)
self.move(frame.topLeft())
self.centralwidget = QWidget(self)
vbox = QVBoxLayout(self.centralwidget)
hbox = QHBoxLayout()
add = makebutton('Add')
delete = makebutton('Delete')
hbox.addWidget(add)
hbox.addSpacerItem(spacer)
hbox.addWidget(delete)
vbox.addLayout(hbox)
self.scrollArea = QScrollArea(self.centralwidget)
self.scrollArea.setWidgetResizable(True)
self.scrollAreaWidgetContents = QWidget()
self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
self.verticalLayout = QVBoxLayout(self.scrollAreaWidgetContents)
self.verticalLayout.setAlignment(Qt.AlignmentFlag.AlignTop)
self.scrollArea.setWidget(self.scrollAreaWidgetContents)
self.scrollArea.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
vbox.addWidget(self.scrollArea)
self.setCentralWidget(self.centralwidget)
add.clicked.connect(self.addObj)
delete.clicked.connect(self.deleteObj)
def addObj(self):
Obj = songpage(('AAA', 'BBB', 'CCC'))
self.verticalLayout.addWidget(Obj)
def deleteObj(self):
item = self.verticalLayout.itemAt(0)
widget = item.widget()
self.verticalLayout.removeItem(item)
self.verticalLayout.removeWidget(widget)
app = QApplication([])
window = Ui_MainWindow()
window.show()
app.exec()
The problem is very simple, if I click add button, the widget will be added and everything works fine, if I double click on a QTextEdit, its apply button will show and it will change from read only to editable.
After I click an apply button, the button will hide and the corresponding QTextEdit will be read only again.
I have finally managed to add a doubleClicked signal to QTextEdit.
And, the problem, is if I click delete button, instead of deleting the thing as expected, it crashes the whole application.
I am sorry the minimum reproducible example is so long, but I have only managed to reproduce the issue with all the code and I really don't know what went wrong.
So how to fix it?
Well, I have figured it out, it was caused by the spacer item that I add to every one of horizontal layouts where fixed-sized buttons are used.
All such layouts are using the same spacer item, exactly the same spacer item, not just identical.
From what I have observed, the spacer items are all references to the same object, they are all echos of the same object which lives at a fixed memory address.
I honestly don't understand how such thing works, that the same object can not only be added to multiple layouts and present in all the layouts concurrently, but also be added to the same layout multiple times, yet it always remains the original object, not duplicates of itself.
I thought when I added the same spacer item to multiple layouts, I didn't add the original spacer item, instead duplicates of the original item which are identical but are at different memory addresses, and clearly that isn't how Python works.
So when I remove a widget, everything inside it, everything inside its layout is deleted, and the spacer item is deleted.
Because all the spacer items in all the layouts are references to the original spacer item, when I delete one of the layouts, the original spacer item is deleted as well, and the spacer item is deleted from all other layouts, yet its shadows remain, and the item isn't properly removed from all the other layouts, the layouts contain references to an object that no longer exists, thus the application crashed.
By removing the definition of the spacer item and replacing adding the spacer item with .addStretch(), the bug is fixed.

PyQt "Class" object has no attribute "widget"

In PyQt, I have a basic program. It consists of 2 combo boxes, 1 line edit and 3 checkboxes. What I want to do is, depending on the item of the first combo box, hide / show specific widgets. However, I keep getting an error: 'ExportDialog' object has no attribute 'exportSetDelimiter_lbl'. I have defined this widget above in initUI, and I run initUIininit`, so I'm not sure why I am getting this error. Here is my code:
from PyQt5 import QtGui, QtCore, QtWidgets
import sys
class ExportDialog(QtWidgets.QMainWindow):
def __init__(self,imagePath):
super(ExportDialog, self).__init__()
self.initUI(imagePath)
#Set The GUI Position And Size
self.setGeometry(500, 500, 600, 450)
#Set The GUI Title
self.setWindowTitle("Export Deck")
#Set The GUI Icon
self.setWindowIcon(QtGui.QIcon('MainFlashcardAppIcon.png'))
def initUI(self, PATH):
#Create The New Deck Label
self.exportFormat_lbl = QtWidgets.QLabel(self)
self.exportFormat_lbl.setText("Export Format: ")
exportFormat_font = QtGui.QFont()
exportFormat_font.setPointSize(8)
self.exportFormat_lbl.setFont(exportFormat_font)
self.exportFormat_lbl.adjustSize()
self.exportFormat_combo = QtWidgets.QComboBox()
self.exportFormat_combo.setMinimumHeight(35)
self.exportFormat_combo.setFixedWidth(380)
self.exportFormat_combo.currentTextChanged.connect(self.on_combobox_changed)
self.exportDeckName_lbl = QtWidgets.QLabel(self)
self.exportDeckName_lbl.setText("Include: ")
self.exportDeckName_lbl.setFont(exportFormat_font)
self.exportDeckName_lbl.adjustSize()
self.exportDeckName_combo = QtWidgets.QComboBox()
self.exportDeckName_combo.setMinimumHeight(35)
self.exportDeckName_combo.setFixedWidth(380)
self.exportFormat_combo.addItem(".TXT")
self.exportFormat_combo.addItem(".CSV")
self.exportFormat_combo.addItem(".DB")
self.exportSetDelimiter_lbl = QtWidgets.QLabel()
self.exportSetDelimiter_lbl.setText("Set Delimiter (Leave blank for standard delimited):")
self.exportSetDelimiter_txt = QtWidgets.QLineEdit()
self.exportSetDelimiter_txt.setMaxLength(1)
self.exportSetDelimiter = QtWidgets.QLineEdit()
vboxExport_setDelimiter = QtWidgets.QVBoxLayout()
vboxExport_setDelimiter.addWidget(self.exportSetDelimiter_lbl)
vboxExport_setDelimiter.addWidget(self.exportSetDelimiter_txt)
self.includeMedia_check = QtWidgets.QCheckBox("Include HTML and Media References")
self.includeTags_check = QtWidgets.QCheckBox("Include Tags")
self.includeAllSQL_check = QtWidgets.QCheckBox("Include All SQL Tables")
self.exportFormat_combo.addItem("B3 Biology")
self.exportFormat_combo.addItem("B2 Biology")
self.exportFormat_combo.addItem("B1 Biology")
self.allComboList = ["B3 Biology", "B2 Biology", "B1 Biology"]
self.exportDeckName_combo.setCurrentIndex(self.allComboList.index(PATH))
#Create Confirm Button
self.confirmButton = QtWidgets.QPushButton(self)
self.confirmButton.setText("OK")
self.confirmButton.clicked.connect(self.createDeck)
#Create Cancel Button
self.cancelButton = QtWidgets.QPushButton(self)
self.cancelButton.setText("Cancel")
self.cancelButton.clicked.connect(self.close)
hboxExportFormat = QtWidgets.QHBoxLayout()
hboxExportFormat.addWidget(self.exportFormat_lbl)
hboxExportFormat.addStretch()
hboxExportFormat.addWidget(self.exportFormat_combo)
hboxExportName = QtWidgets.QHBoxLayout()
hboxExportName.addWidget(self.exportDeckName_lbl)
hboxExportName.addStretch()
hboxExportName.addWidget(self.exportDeckName_combo)
hboxButtonsBottom = QtWidgets.QHBoxLayout()
hboxButtonsBottom.addStretch()
hboxButtonsBottom.addWidget(self.confirmButton)
hboxButtonsBottom.addWidget(self.cancelButton)
#Create The VBoxLayout
mainLayout = QtWidgets.QVBoxLayout(self)
mainLayout.addLayout(hboxExportFormat)
mainLayout.addLayout(hboxExportName)
mainLayout.addLayout(vboxExport_setDelimiter)
mainLayout.addWidget(self.includeMedia_check)
mainLayout.addWidget(self.includeTags_check)
mainLayout.addWidget(self.includeAllSQL_check)
mainLayout.addStretch()
mainLayout.addLayout(hboxButtonsBottom)
def on_combobox_changed(self, i):
if i == ".TXT":
self.exportSetDelimiter_lbl.show()
self.exportSetDelimiter_txt.show()
self.includeMedia_check.show()
self.includeTags_check.show()
self.includeAllSQL_check.hide()
elif i == ".CSV":
self.exportSetDelimiter_lbl.hide()
self.exportSetDelimiter_txt.hide()
self.includeMedia_check.show()
self.includeTags_check.show()
self.includeAllSQL_check.hide()
elif i == ".DB":
self.exportSetDelimiter_lbl.hide()
self.exportSetDelimiter_txt.hide()
self.includeMedia_check.show()
self.includeTags_check.show()
self.includeAllSQL_check.show()
def createDeck(self):
print("Exported Sucessfully")
self.close()
#Create A Windows
app = QtWidgets.QApplication(sys.argv)
window = ExportDialog("B1 Biology")
window.show()
sys.exit(app.exec_())
This is my first question, so if you need any additional information, I will add it in. Any help would be much appreciated. Thank you!
When a combobox is newly created, it has an invalid current index (-1) and no current text set. As soon as the first item is added, the index is automatically updated to 0 and the current text changes to that of the item.
You've connected to the currentTextChanged signal before adding new items, and since the function currentTextChanged assumes that the whole ui has been already created (including exportSetDelimiter_lbl), you get the attribute error.
While there's no rule for the placing of signal connections, it's usually a good habit to group all connections at the end of the function that creates them, or anyway, ensure that everything required by their connection has already been created.
So, just move the signal connection at the end of initUI and everything will work fine.
Well... No. Because you didn't set a central widget for the main window and tried to set the layout on it (which is not allowed, since a QMainWindow has a private and unaccessible layout).
Add a QWidget, call self.setCentralWidget(someWidget) and create the layout for that widget.

How to save selected element from combobox as variable in PyQt5?

I'm interested in how to save a selected value from my combobox as variable, so when I press e.g. B then I want it to be saved as SelectedValueCBox = selected value, which would be B in this case.
Thank you for your help
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class App(QMainWindow):
def __init__(self):
super().__init__()
self.title = "PyQt5 - StockWindow"
self.left = 0
self.top = 0
self.width = 200
self.height = 300
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
self.tab_widget = MyTabWidget(self)
self.setCentralWidget(self.tab_widget)
self.show()
class MyTabWidget(QWidget):
def __init__(self, parent):
super(QWidget, self).__init__(parent)
self.layout = QVBoxLayout(self)
#self.layout = QGridLayout(self)
self.tabs = QTabWidget()
self.tab1 = QWidget()
self.tabs.resize(300, 200)
self.tabs.addTab(self.tab1, "Stock-Picker")
self.tab1.layout = QGridLayout(self)
button = QToolButton()
self.tab1.layout.addWidget(button, 1,1,1,1)
d = {'AEX':['A','B','C'], 'ATX':['D','E','F'], 'BEL20':['G','H','I'], 'BIST100':['J','K','L']}
def callback_factory(k, v):
return lambda: button.setText('{0}_{1}'.format(k, v))
menu = QMenu()
self.tab1.layout.addWidget(menu, 1,1,1,1)
for k, vals in d.items():
sub_menu = menu.addMenu(k)
for v in vals:
action = sub_menu.addAction(str(v))
action.triggered.connect(callback_factory(k, v))
button.setMenu(menu)
self.tab1.setLayout(self.tab1.layout)
self.layout.addWidget(self.tabs)
self.setLayout(self.layout)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
Since you're already returning a lambda for the connection, the solution is to use a function instead.
class MyTabWidget(QWidget):
def __init__(self, parent):
# ...
def callback_factory(k, v):
def func():
self.selectedValueCBox = v
button.setText('{0}_{1}'.format(k, v))
return func
# ...
self.selectedValueCBox = None
Note that your code also has many issues.
First of all, you should not add the menu to the layout: not only it doesn't make any sense (the menu should pop up, while adding it to a layout makes it "embed" into the widget, and that's not good), but it also creates graphical issues especially because you added the menu to the same grid "slot" (1, 1, 1, 1) which is already occupied by the button.
Creating a layout with a widget as argument automatically sets the layout to that widget. While in your case it doesn't create a big issue (since you've already set the layout) you should not create self.tab1.layout with self. Also, since you've already set the QVBoxLayout (due to the parent argument), there's no need to call setLayout() again.
A widget container makes sense if you're actually going to add more than one widget. You're only adding a QTabWidget to its layout, so it's almost useless, and you should just subclass from QTabWidget instead.
Calling resize on a widget that is going to be added on a layout is useless, as the layout will take care of the resizing and the previous resize call will be completely ignored. resize() makes only sense for top level widgets (windows) or the rare case of widgets not managed by a layout.
self.layout() is an existing property of all QWidgets, you should not overwrite it. The same with self.width() and self.height() you used in the App class.
App should refer to an application class, but you're using it for a QMainWindow. They are radically different types of classes.
Finally, you have no combobox in your code. A combobox is widget that is completely different from a drop down menu like the one you're using. I suggest you to be more careful with the terminology in the future, otherwise your question would result very confusing, preventing people to actually being able to help you.

Why is an absolute positioned widget not shown?

Using Qt5 I am trying to make a widget work using absolute positioning. The code below is a minimum working example of something I am trying to do. A quick walk through of the code:
CentralWidget is the central widget of the main window and holds MyWidget using absolute positioning, e.g. without using any layouts.
MyWidget does not set its child widgets immediately but provides a SetData method which first removes all current child widgets and then adds the new child widgets to its layout.
SetData is triggered using a timer in the main window.
I commented out two lines of code. The first "enables" relative positioning using layouts by adding a layout to CentralWidget. This line shows what I am trying to achieve but with absolute positioning. The second comment enables some debug information:
MyWidget
layout.count: 3
size: PyQt5.QtCore.QSize(-1, -1)
sizeHint: PyQt5.QtCore.QSize(200, 100)
CentralWidget
size: PyQt5.QtCore.QSize(18, 18)
sizeHint: PyQt5.QtCore.QSize(18, 18)
What I am doing wrong in order for MyWidget to be visible using absolute positioning?
Code:
from PyQt5 import QtCore, QtWidgets
import sys
class MyWidget(QtWidgets.QWidget):
z = 0
def __init__(self, parent):
super().__init__(parent)
self._layout = QtWidgets.QVBoxLayout(self)
def SetData(self):
while self._layout.count() > 0:
widget = self._layout.takeAt(0).widget()
widget.hide()
widget.deleteLater()
for i in range(3):
self._layout.addWidget(QtWidgets.QLabel(str(MyWidget.z * 10 + i)))
MyWidget.z += 1
class CentralWidget(QtWidgets.QWidget):
def __init__(self, parent):
super().__init__(parent)
self._myWidget = MyWidget(self)
# QtWidgets.QHBoxLayout(self).addWidget(self._myWidget)
def SetData(self):
self._myWidget.SetData()
# print("MyWidget\n layout.count: {}\n size: {}\n sizeHint: {}\n\nCentralWidget\n size: {}\n sizeHint: {}\n\n".format(self._myWidget.layout().count(), self.sizeHint(), self.size(), self._myWidget.sizeHint(), self._myWidget.size()))
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
centralWidget = CentralWidget(self)
self.setCentralWidget(centralWidget)
self._timer = QtCore.QTimer(self)
self._timer.timeout.connect(centralWidget.SetData)
self._timer.start(500)
def main():
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
app.exec_()
if __name__ == "__main__":
main()
The reason for this behavior is directly related to the fact that the widget is not added to a layout and its contents are added after being shown.
In fact, if you call centralWidget.SetData() upon initialization and before mainWindow.show(), it will work as expected.
A lot of things happen when you add a child widget to a layout, and this usually involves multiple calls to the children size hints, allowing the parent to adapt its own size hint, and, after that, adapt its size and that of its children.
If that "container widget" is itself contained in another layout, that widget will be automatically resized (based on its hint) in the next cycle of events, but this doesn't happen in your case, since yours is a "free" widget.
The function you are looking for is QWidget.adjustSize(), but, for the aforementioned reasons, you cannot call it immediately after adding the children widgets.
To overcome your issue, you can call QApplication.processEvents() before adjustSize(), or, eventually, use a 0-based single shot QTimer:
def SetData(self):
while self._layout.count() > 0:
widget = self._layout.takeAt(0).widget()
widget.hide()
widget.deleteLater()
for i in range(3):
self._layout.addWidget(QtWidgets.QLabel(str(MyWidget.z * 10 + i)))
MyWidget.z += 1
QtCore.QTimer.singleShot(0, self.adjustSize)

Categories

Resources