-2

CustomMenu class inherits from QMenu. Its custom setData() method accepts an argument and sets it to a class variable model. I did this because QToolButton, QToolMenu and QAction do not support model/view framework. From what I know aside from all QList-QTable-QTree Views and Trees only the QComboBox does support model. So the question: would it be possible to extend the functionality of other non-model widgets so they could be used as driven-by-model widgets too?

enter image description here

from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys, os

class CustomMenu(QMenu):
    def __init__(self):
        super(CustomMenu, self).__init__()
        self.model=None

    def setModel(self, model):
        self.model=model
        self.pupulate()
    def pupulate(self):
        for item in self.model.items:
            self.addAction(item)

class Model(QAbstractTableModel):
    def __init__(self, parent=None, *args):
        QAbstractTableModel.__init__(self, parent, *args)
        self.items = ['Row0_Column0','Row1_Column0','Row2_Column0']

    def flags(self, index):
        return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
    def rowCount(self, QModelIndex):
        return len(self.items)
    def columnCount(self, QModelIndex):
        return 1

    def data(self, index, role):
        if not index.isValid(): return QVariant()
        if role == Qt.DisplayRole:
            return QVariant(self.items[index.row()])
        return QVariant()
    def setData(self, index, value, role=Qt.EditRole):
        if index.isValid():            
            if role == Qt.EditRole:                
                self.items[index.row()]=value  
                return True
        return False
class MyWindow(QWidget):
    def __init__(self, *args):
        QWidget.__init__(self, *args)
        layout=QVBoxLayout(self)
        self.setLayout(layout)

        tablemodel=Model(self)

        tableView=QTableView() 
        tableView.horizontalHeader().setStretchLastSection(True)
        tableView.setModel(tablemodel)         
        layout.addWidget(tableView)      

        combo=QComboBox()
        combo.setModel(tablemodel)
        layout.addWidget(combo)

        toolButton=QToolButton(self)
        toolButton.setText('Tool Button')
        toolButton.setPopupMode(QToolButton.InstantPopup)
        toolButton.setSizePolicy(QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed))

        menu=CustomMenu()
        menu.setModel(tablemodel)
        toolButton.setMenu(menu)
        layout.addWidget(toolButton)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = MyWindow()
    w.show()
    sys.exit(app.exec_())

EDITED LATER: An attempt to implement a QDataWidgetMapper():

from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys, os

class CustomMenuButton(QWidget):
    def __init__(self, parent):
        super(CustomMenuButton, self).__init__(parent)
        self.dataMapper=QDataWidgetMapper()
        layout=QVBoxLayout()
        self.setLayout(layout)

        toolButton=QToolButton(self)
        toolButton.setText('Tool Button')
        toolButton.setPopupMode(QToolButton.InstantPopup)

        self.menu=QMenu(toolButton)
        toolButton.setMenu(self.menu)

    def setModel(self, model):
        self.dataMapper.setModel(model)

        for row in range(model.rowCount()):
            action=self.menu.addAction('Item')

            self.dataMapper.addMapping(action, row)

class Model(QAbstractTableModel):
    def __init__(self, parent=None, *args):
        QAbstractTableModel.__init__(self, parent, *args)
        self.items = ['Row0_Column0','Row1_Column0','Row2_Column0']

    def flags(self, index):
        return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
    def rowCount(self, parent=QModelIndex()):
        return len(self.items)
    def columnCount(self, parent=QModelIndex()):
        return 1

    def data(self, index, role):
        if not index.isValid(): return QVariant()
        if role == Qt.DisplayRole:
            return QVariant(self.items[index.row()])
        return QVariant()
    def setData(self, index, value, role=Qt.EditRole):
        if index.isValid():            
            if role == Qt.EditRole:                
                self.items[index.row()]=value  
                self.dataChanged.emit(index, index)
                return True
        return False

class MyWindow(QWidget):
    def __init__(self, *args):
        QWidget.__init__(self, *args)
        layout=QVBoxLayout(self)
        self.setLayout(layout)

        tablemodel=Model(self)

        tableView=QTableView() 
        tableView.horizontalHeader().setStretchLastSection(True)
        tableView.setModel(tablemodel)         
        layout.addWidget(tableView)      

        combo=QComboBox()
        combo.setModel(tablemodel)
        layout.addWidget(combo)

        menuButton=CustomMenuButton(self)
        layout.addWidget(menuButton)

        menuButton.setModel(tablemodel)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = MyWindow()
    w.show()
    sys.exit(app.exec_())

EDIT 2: Defined a class CustomAction(QAction) with dataChanged = pyqtSignal(QModelIndex,QModelIndex)...

from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys, os

class CustomAction(QAction):
    dataChanged = pyqtSignal(QModelIndex,QModelIndex)
    def __init__(self, name, parent):
        super(CustomAction, self).__init__(name, parent)

class CustomMenuButton(QWidget):
    def __init__(self, parent):
        super(CustomMenuButton, self).__init__(parent)
        self.dataMapper=QDataWidgetMapper()
        layout=QVBoxLayout()
        self.setLayout(layout)

        toolButton=QToolButton(self)
        toolButton.setText('Tool Button')
        toolButton.setPopupMode(QToolButton.InstantPopup)

        self.menu=QMenu(toolButton)
        toolButton.setMenu(self.menu)

    def setModel(self, model):
        self.dataMapper.setModel(model)
        for row in range(model.rowCount()):
            index=model.index(row,0)
            itemName=model.data(index, Qt.DisplayRole).toPyObject()

            actn=CustomAction(itemName, self.menu)
            self.menu.addAction(actn)

            self.dataMapper.addMapping(actn, row)

class Model(QAbstractTableModel):
    def __init__(self, parent=None, *args):
        QAbstractTableModel.__init__(self, parent, *args)
        self.items = ['Row0_Column0','Row1_Column0','Row2_Column0']

    def flags(self, index):
        return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
    def rowCount(self, parent=QModelIndex()):
        return len(self.items)
    def columnCount(self, parent=QModelIndex()):
        return 1

    def data(self, index, role):
        if not index.isValid(): return QVariant()
        if role == Qt.DisplayRole:
            return QVariant(self.items[index.row()])
        return QVariant()
    def setData(self, index, value, role=Qt.EditRole):
        if index.isValid():            
            if role == Qt.EditRole:                
                self.items[index.row()]=value  
                self.dataChanged.emit(index, index)
                return True
        return False

class MyWindow(QWidget):
    def __init__(self, *args):
        QWidget.__init__(self, *args)
        layout=QVBoxLayout(self)
        self.setLayout(layout)

        tablemodel=Model(self)

        tableView=QTableView() 
        tableView.horizontalHeader().setStretchLastSection(True)
        tableView.setModel(tablemodel)         
        layout.addWidget(tableView)      

        combo=QComboBox()
        combo.setModel(tablemodel)
        layout.addWidget(combo)

        menuButton=CustomMenuButton(self)
        layout.addWidget(menuButton)

        menuButton.setModel(tablemodel)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = MyWindow()
    w.show()
    sys.exit(app.exec_())
alphanumeric
  • 17,967
  • 64
  • 244
  • 392

1 Answers1

1

This is what the QDataWidgetMapper class was designed for. The following blog has a great tutorial covering exactly how to implement it: http://www.yasinuludag.com/blog/?p=98

EDIT: I should add that QDataWidgetMapper works with QWidget and its subclasses, so QAction would not apply. For classes that are not QWidget or its subclasses, you might have to implement your own custom model-view binding.

nb1987
  • 1,400
  • 1
  • 11
  • 12
  • I just posted an edited version of the same code this time with an attempt to implement `QDataWidgetMapper`. Getting `QDataWidgetMapper.addMapping(QWidget, int, QByteArray): argument 1 has unexpected type 'QAction' Object::connect: No such signal QObject::dataChanged(QModelIndex,QModelIndex)` – alphanumeric Jan 28 '15 at 15:08
  • As I mentioned in the edit to my post, `QDataWidgetMapper` only works with `QWidget` and with `QWidget` subclasses such as `QLineEdit`, `QComboBox`, etc. If you want to use other classes such as `QAction`, you can't use QDataWidgetMapper; you would need to implement your own custom model-view binding. I can see that you have added a custom `dataChanged` signal to your `CustomAction` class, but this will never emit because the actions are not part of your table model's dataset (`items`); also, I do not know how data could be changed in the actions since they are non-editable `QAction` objects. – nb1987 Jan 29 '15 at 07:51