Is there a straightforward (without reimplementing too many members, I mean) way to use a QComboBox as QComboBox Item?
The effect I would like to achieve is a plain QComboBox showing a single value when closed, when opened it should display the normal list; some (or all) list items could be nested QComboBox which can be opened to select nested entries in a tree-like logic. When closed the QComboBox should display (and QSignal) just the selected item in the selected sub-combo.
Is this possible (without rewriting the whole widget, of course)?
I am using PyQt6, if relevant.
I am aware of possibility to use a QTreeView as QComboBox via:
...
wh = QComboBox()
tv = QTreeView()
wh.setView(tv)
wh.setModel(QStandardItemModel())
...
This seems much simpler than the pointed "solving answer", but it leads to inconsistent behavior and clipped lists (exactly as solving answer original code; @musicamante comments are completely right).
What I'm aiming at is something different, I have the following (completely unsatisfactory, see below) code:
from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt
from PyQt6.QtWidgets import QPushButton
class RecursiveComboBox(QPushButton):
clicked = pyqtSignal(str)
def __init__(self, parent=None):
super().__init__(parent)
self.model_data = None
self.level = parent.level + 1 if parent else 0
self.w = QWidget()
self.w.hide()
def set_my_data(self, model_data: dict):
self.model_data = model_data
l = QVBoxLayout()
for k, v in self.model_data.items():
if isinstance(v, dict):
i = RecursiveComboBox(self)
i.setText(k)
i.clicked.connect(self.node_clicked)
print(f'{" "*self.level}[{self.level} -- {self.text()}]: node_clicked conected')
i.set_my_data(v)
else:
i = QPushButton(text=k, parent=self)
i.setProperty('user_data', v)
i.clicked.connect(self.leaf_clicked)
print(f'{" " * self.level}[{self.level} -- {self.text()}]: leaf_clicked conected')
l.addWidget(i)
self.w.setLayout(l)
def mouseReleaseEvent(self, event):
if event.button() == Qt.MouseButton.LeftButton and self.rect().contains(event.pos()):
self.setEnabled(False) # release is implicit
self.repaint()
self.w.show()
else:
super().mouseReleaseEvent(event)
@pyqtSlot(str)
def node_clicked(self, txt):
print(f'node_clicked({txt}): [{self.level} -- {self.text()}]')
self.clicked.emit(txt)
self.w.hide()
self.setEnabled(True)
@pyqtSlot(bool)
def leaf_clicked(self, _):
child = self.sender()
print(f'leaf_clicked({child.text()}): [{self.level} -- {self.text()}]')
self.clicked.emit(child.text())
self.w.hide()
self.setEnabled(True)
if __name__ == '__main__':
import sys
from PyQt6.QtWidgets import QApplication, QWidget, QVBoxLayout
main_course = {
'meat': {
'steak': 'steak',
'meatballs': 'meatballs',
},
'fish': {
'cod': 'cod',
'mullet': 'mullet',
'scallops': 'scallops'
},
'vegetarian': {
'salad': {
'mixed': 'mixed',
'cesar': 'cesar'
},
'flan': 'flan'
}
}
app = QApplication(sys.argv)
mw = QWidget()
lo = QVBoxLayout()
rc = RecursiveComboBox()
rc.setText('Main course')
lo.addWidget(rc)
mw.setLayout(lo)
rc.set_my_data(main_course)
rc.clicked.connect(lambda s: print(f'click event for "{s}"'))
mw.show()
sys.exit(app.exec())
This code has the correct (for me) behavior:
- it shows a single "button"
- it if "button" is clicked it opens a list of sub-buttons
- this behavior is recursive recursive
- when clicking on a "leaf" button the whole hierarchy is closed and the single leaf is returned via
pyqtSignal
OTOH this code has several issues I need help with: essentially it doesn't look like a combobox at all.
It opens unrelated windows for each nested list; this is because I don't set a parent in the constructor: self.w = QWidget() should be self.w = QWidget(self), but that leads to a clipped visualization.
There are other minor issues I think I can handle (or open separate questions for them), but the main issue is: How do I self.w.show() in the same place as self resizing it according to contents?