Question

I created a model which list the existing configurations (let's say it lists "files", as this doesn't really matter here). So far, it works well when attached to a QListView.

Example:

--- ListView ---
- file #1      -
- file #2      -
- file #3      -
- file #4      -
----------------

Is it possible to use the same model for a dynamically updated QMenu ?

Something like:

Menu
-> Submenu #1
-> Submenu #2
-> File-submenu
  -> file #1
  -> file #2
  -> file #3
  -> file #4
-> Submenu #3

In short: is there any way to create a list of dynamicaly updated QActions (grouped into the same QMenu) depending on a model (derived from QAbstractListModel) ?

Was it helpful?

Solution

If your objective is just to update your menu actons with the item text that are available in the QAbstractListModel, then the answer is Yes.

Here is a way..

Individual item's index can be obtained by using the following function.

QModelIndex QAbstractListModel::index ( int row, int column = 0, 
const QModelIndex & parent = QModelIndex() ) const   [virtual]

With the obtained index, the data can be obtained by,

 QVariant QModelIndex::data ( int role = Qt::DisplayRole ) const

Then the text availalble in the index can be obtained by using,

QString QVariant::toString () const

Now with the obtained QString you can add an action to the menu.

QAction * QMenu::addAction ( const QString & text )

The thing you have to make sure is that, you should be able to traverse through all the items in the Model, so that you can obtain the index of the each and every item. Hope it helps..

OTHER TIPS

Unfortunately there is no QMenuView class but I found this promising implementation on the net: QMenuView (qmenuview.h, qmenuview.cpp).

To answer your short question, yes, there is. But you'll have to write it yourself.

The easy part would be to create a subclass of QAbstractListModel.

The hard part would be when you create your own view. Qt will let you create your own view, just like if you were to create your own model, but it would become so much more complex, since you've got to handle everything yourself.

It's entirely doable for a specific purpose, but it's also much more work than I think you want. So, like Gianni was saying, Qt's model-view framework isn't meant to be used this way.

No. Models can only be used with Views, as per the Model-View framework that Qt uses.

You can create a menu item and put QListView into it using QWidgetAction. Of course this menu cannot have submenus. The example below is in Python, but I hope it doesn't matter in this case.

enter image description here

from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import Qt


class QListViewMenu(QtWidgets.QMenu):
    """
    QMenu with QListView.
    Supports `activated`, `clicked`, `doubleClicked`. `setModel`.
    """
    max_visible_items = 16

    def __init__(self, parent=None):
        super().__init__(parent)
        self.listview = lv = QtWidgets.QListView()
        lv.setFrameShape(lv.NoFrame)
        lv.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        pal = lv.palette()
        pal.setColor(pal.Base, self.palette().color(pal.Window))
        lv.setPalette(pal)
        lv.setEditTriggers(lv.NoEditTriggers)  # disable edit on doubleclick

        act_wgt = QtWidgets.QWidgetAction(self)
        act_wgt.setDefaultWidget(lv)
        self.addAction(act_wgt)

        self.activated = lv.activated
        self.clicked = lv.clicked
        self.doubleClicked = lv.doubleClicked
        self.setModel = lv.setModel

        lv.sizeHint = self.size_hint
        lv.minimumSizeHint = self.size_hint
        lv.mousePressEvent = lambda event: None  # skip
        lv.mouseMoveEvent = lambda event: None  # skip
        lv.mouseReleaseEvent = self.mouse_release_event

    def size_hint(self):
        lv = self.listview
        width = lv.sizeHintForColumn(0)
        width += lv.verticalScrollBar().sizeHint().width()
        if isinstance(self.parent(), QtWidgets.QToolButton):
            width = max(width, self.parent().width())
        visible_rows = min(self.max_visible_items, lv.model().rowCount())
        return QtCore.QSize(width, visible_rows * lv.sizeHintForRow(0))

    def mouse_release_event(self, event):
        if event.button() == Qt.LeftButton:
            idx = self.listview.indexAt(event.pos())
            if idx.isValid():
                self.clicked.emit(idx)
            self.close()
        super(QtWidgets.QListView, self.listview).mouseReleaseEvent(event)


class Form(QtWidgets.QDialog):
    def __init__(self):
        super().__init__()
        words = "ability able about above accept according account across"
        model = QtCore.QStringListModel(words.split())
        # fake icons to take space
        def data(index, role):
            if role == Qt.DecorationRole:
                pixm = QtGui.QPixmap(40, 40)
                pixm.fill(Qt.transparent)
                return QtGui.QIcon(pixm)
            return QtCore.QStringListModel.data(model, index, role)
        model.data = data

        self.btn = btn = QtWidgets.QToolButton(self)
        btn.setText("QListView menu")
        btn.setPopupMode(btn.MenuButtonPopup)
        root_menu = QtWidgets.QMenu(btn)
        menu = QListViewMenu(btn)
        menu.setTitle('submenu')
        menu.setModel(model)
        menu.clicked.connect(self.item_clicked)
        root_menu.addMenu(menu)
        btn.setMenu(root_menu)

    def item_clicked(self, index):
        self.btn.menu().hide()
        print(index.data())

app = QtWidgets.QApplication([])
f = Form()
f.show()
app.exec()
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top