@musicamante Ok thanks for steering me away from QComboBox. Got a working solution "starting from scratch" and it's probably quite a bit easier to code up than the QComboBox contortions - and the two issues in the original question are resolved. Will do some more styling and sizing and such, but:
The code:
# heavily adapted from Google AI Overview: 'pyqt popup under button'
from PyQt5.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QTreeView,QMainWindow,QPushButton,QDialog
)
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtCore import QModelIndex,Qt,QPoint
class MyPopup(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.parent=parent
# Create the TreeView for the dropdown popup
self.tree_view = QTreeView(self)
self.tree_view.setHeaderHidden(True) # Hide the header to look like a simple tree
self.tree_view.setSelectionMode(QTreeView.SingleSelection)
self.tree_view.setEditTriggers(QTreeView.NoEditTriggers)
self.tree_view.setExpandsOnDoubleClick(False)
self.tree_view.setAnimated(True)
self.tree_view.setFixedHeight(300)
# Create a model for the tree view
self.model = QStandardItemModel()
self.tree_view.setModel(self.model)
self.tree_view.entered.connect(self.enteredCB)
self.tree_view.clicked.connect(self.clickedCB)
self.tree_view.expanded.connect(self.expandedCB)
self.tree_view.collapsed.connect(self.collapsedCB)
self.setWindowTitle("Popup Dialog")
self.setWindowFlags(Qt.Popup)
layout = QVBoxLayout(self)
layout.setContentsMargins(0,0,0,0)
layout.addWidget(self.tree_view)
self.setLayout(layout)
self.tree_view.setMouseTracking(True)
def enteredCB(self,i):
print('entered')
self.setFullLabel(i)
def expandedCB(self,i):
print('expanded')
self.collapseOthers(i)
def collapsedCB(self,i):
print('collapsed')
def clickedCB(self,i):
print('clicked')
self.setFullLabel(i)
self.close() # close the popup
def setFullLabel(self,i):
# Get the full hierarchy path for display
current_index = i
path_list = [self.model.data(i)]
while current_index.parent().isValid():
parent_index = current_index.parent()
parent_text = self.model.data(parent_index)
path_list.insert(0, parent_text)
current_index = parent_index
# Join path with a separator and set the text
self.parent.button.setText(' > '.join(path_list))
def populate(self, data):
"""Populates the tree model from a dictionary."""
self.model.clear()
for key, children in data.items():
parent_item = QStandardItem(key)
for child_text in children:
child_item = QStandardItem(child_text)
parent_item.appendRow(child_item)
self.model.appendRow(parent_item)
for r in range(self.model.rowCount()):
for c in range(self.model.columnCount()):
txt=self.model.item(r,c).text()
print(f'row {r} col {c} : {txt}')
def collapseOthers(self,expandedIndex):
QApplication.processEvents()
print('collapse_others called: expandedIndex='+str(expandedIndex))
def _collapse_recursive(parent_index: QModelIndex,sp=' '):
for row in range(self.model.rowCount(parent_index)):
index = self.model.index(row, 0, parent_index)
item=self.model.itemFromIndex(index)
txt=item.text()
print(sp+f'checking r={row} col=0 : {txt}')
if index.isValid() and index!=expandedIndex:
print(sp+' collapsing')
self.tree_view.collapse(index)
# self.tree_view.setExpanded(index,False)
# Recursively process children
if self.model.hasChildren(index):
_collapse_recursive(index,sp+' ')
# Start the recursion from the invisible root item
_collapse_recursive(QModelIndex())
QApplication.processEvents()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Main Window")
self.setGeometry(100, 100, 400, 300)
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
self.button = QPushButton("Show Popup", self)
self.button.clicked.connect(self.show_popup_under_button)
layout.addWidget(self.button)
self.popup = MyPopup(self)
def show_popup_under_button(self):
# Get the global position of the button's top-left corner
button_pos = self.button.mapToGlobal(QPoint(0, 0))
# Calculate the desired position for the popup
popup_x = button_pos.x()
popup_y = button_pos.y() + self.button.height()
# Sample hierarchical data
data = {
"Fruits": ["Apple", "Banana", "Orange"],
"Vegetables": ["Carrot", "Broccoli", "Spinach"],
"Dairy": ["Milk", "Cheese", "Yogurt"]
}
self.popup.populate(data)
self.popup.move(popup_x, popup_y)
self.popup.exec_() # Show as a modal dialog
if __name__ == "__main__":
app = QApplication([])
window = MainWindow()
window.show()
app.exec_()