如何扩展默认不支持模型的Qt Widgets

How to extend Qt Widgets that do not have model support by default

CustomMenu class 继承自 QMenu。它的自定义 setData() 方法接受一个参数并将其设置为 class 变量 model。我这样做是因为 QToolButtonQToolMenuQAction 不支持 model/view 框架。据我所知,除了 QList-QTable-QTree Views and Trees,只有 QComboBox 支持 model。 所以问题是:是否可以扩展其他 non-model widgets 的功能,以便它们也可以用作 driven-by-model 小部件?

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_())

稍后编辑:尝试实现 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_())

编辑 2:定义了 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_())

这就是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

编辑:我应该补充一点,QDataWidgetMapperQWidget 及其子 类 一起使用,因此 QAction 不适用。对于不是 QWidget 或其子 类 的 类,您可能必须实现自己的自定义模型视图绑定。