abstractTableModel在PySide中显示两组相互依赖的数据

时间:2014-04-06 00:04:27

标签: python qt pyqt pyside data-modeling

我正在构建一个用于电影摄影的python / PySide工具。我创建了一个代表镜头的对象。它具有开始时间,结束时间和actor对象引用列表的属性。 actor对象具有简单的属性(名称,构建,年龄等),并且可以在镜头之间共享。

我想在PySide的两个表视图中显示它。一个表格视图列出镜头(和列中的属性),而另一个表格视图显示选定的镜头中引用的actor。如果未选择镜头,则第二个表视图为空。如果选择了多个镜头,则所有引用的actor都将显示在actor表视图中。

我为我的镜头数据创建了一个abstractTableModel,并且在相应的表视图中,所有内容都能正常运行。但是,我不确定如何甚至接近演员的表格视图。我应该为演员使用另一个abstractTableModel吗?我似乎无法弄清楚如何使用abstractTableModel将数据提供/连接到所选镜头中包含的actor的第二个表视图。

我认为我的一部分问题是我只想显示一次演员信息,无论多个选定的镜头是否引用同一个演员。在多次失败尝试之后,我认为我需要将所选镜头信息(他们的actor列表属性)重定向并解析为主窗口的自定义属性,以包含所有引用的列表actor索引并创建一个abstractTableModel,它使用它来获取显示的实际actor属性。我不完全相信这会有效,更不用说我的直觉告诉我这是一个混乱的方法,所以我来这里寻求建议。

这是正确的做法吗?如果没有,那么“正确的”是什么?在PySide / python中设置它的方法。

请记住,这是我第一次涉足数据模型和PySide。

这是拍摄模型。

class ShotTableModel(QtCore.QAbstractTableModel):
    def __init__(self, data=[], parent=None, *args):
        super(ShotTableModel, self).__init__(parent)
        self._data = data

    def rowCount(self, parent):
        return len(self._data.shots)

    def columnCount(self, parent):
        return len(self._data._headers_shotList)

    def getItemFromIndex(self, index):
        if index.isValid():
            item = self._data.shots[index.row()]   
            if item:
                return item
        return None

    def flags(self, index):
        if index.isValid():
            item = self.getItemFromIndex(index)
            return item.qt_flags(index.column())

    def data(self, index, role):
        if not index.isValid():
            return None

        item = self.getItemFromIndex(index)

        if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
            return item.qt_data(index.column())

        if role == QtCore.Qt.CheckStateRole:
            if index.column() is 0:
                return item.qt_checked

#         if role == QtCore.Qt.BackgroundColorRole:
#             return QtGui.QBrush()

#         if role == QtCore.Qt.FontRole:
#             return QtGui.QFont()

        if role == QtCore.Qt.DecorationRole:
            if index.column() == 0:
                resource = item.qt_resource()
                return QtGui.QIcon(QtGui.QPixmap(resource))

        if role == QtCore.Qt.ToolTipRole:
            return item.qt_toolTip()

        return None

    def setData(self, index, value, role = QtCore.Qt.EditRole):
        if index.isValid():
            item = self.getItemFromIndex(index)
            if role == QtCore.Qt.EditRole:
                item.qt_setData(index.column(), value)
                self.dataChanged.emit(index, index)
                return value

            if role == QtCore.Qt.CheckStateRole:
                if index.column() is 0:
                    item.qt_checked = value
                    return True
        return value

    def headerData(self, section, orientation, role):
        if role == QtCore.Qt.DisplayRole:
            if orientation == QtCore.Qt.Horizontal:
                return self._data._headers_shotList[section]

    def insertRows(self, position, rows, parent = QtCore.QModelIndex()):
        self.beginInsertRows(parent, position, position + rows - 1)
        for row in range(rows):
            newShotName = self._data.getUniqueName(self._data.shots, 'New_Shot')
            newShot = Shot(newShotName)
            self._data.shots.insert(position, newShot)
        self.endInsertRows()
        return True

    def removeRows(self, position, rows, parent = QtCore.QModelIndex()):
        self.beginRemoveRows(parent, position, position + rows - 1)
        for row in range(rows):
            self._data.shots.pop(position)   
        self.endRemoveRows()
        return True

这里是包含镜头和actor实例的数据块。这就是我传递给镜头模型的内容。

class ShotManagerData(BaseObject):
    def __init__(self, name='', shots=[], actors=[]):
        super(ShotManagerData, self).__init__(name)
        self._shots = shots # Shot(name="New_Shot", start=0, end=100, actors=[])
        self._actors = actors # Actor(name="New_Actor", size="Average", age=0)

        self.selectedShotsActors = [] #decided to move to this data block

        self._headers_shotList = ['Name', 'Start Frame', 'End Frame']
        self._headers_actorList = ['Name', 'Type', 'RootNode', 'File']

    def save(self, file=None):
        mEmbed.save('ShotManagerData', self)

    @classmethod
    def load(cls, file=None):
        return(mEmbed.load('ShotManagerData'))

    @staticmethod
    def getUniqueName(dataList, baseName='New_Item'):
        name = baseName
        increment = 0
        list_of_names = [data.name if issubclass(data.__class__, BaseObject) else str(data) for data in dataList] 
        while name in list_of_names:
            increment += 1
            name = baseName + '_{0:02d}'.format(increment)
        return name

    @property
    def actors(self):
        return self._actors

    @property
    def shots(self):
        return self._shots

    def actorsOfShots(self, shots):
        actorsOfShots = []
        for shot in shots:
            for actor in shot.actors:
                if actor not in actorsOfShots:
                    actorsOfShots.append(actor)
        return actorsOfShots

    def shotsOfActors(self, actors):
        shotsOfActors = []
        for actor in actors:
            for shot in self.shots:
                if actor in shot.actors and actor not in shotsOfActors:
                    shotsOfActors.append(shot)
        return shotsOfActors

最后是主要工具。

class ShotManager(form, base):
    def __init__(self, parent=None):
        super(ShotManager, self).__init__(parent)
        self.setupUi(self)

        #=======================================================================
        # Properties
        #=======================================================================


        self.data = ShotManagerData() #do any loading if necessary here
        self.actorsInSelectedShots = []

        #test data
        actor1 = Actor('Actor1')
        actor2 = Actor('Actor2')
        actor3 = Actor('Actor3')

        shot1 = Shot('Shot1', [actor1, actor2])
        shot2 = Shot('Shot2', [actor2, actor3])
        shot3 = Shot('Shot3', [actor1])

        self.data.actors.append(actor1)
        self.data.actors.append(actor2)   
        self.data.actors.append(actor3)

        self.data.shots.append(shot1)
        self.data.shots.append(shot2)
        self.data.shots.append(shot3)

        #=======================================================================
        # Models
        #=======================================================================
        self._model_shotList = ShotTableModel(self.data)
        self._proxyModel_shotList = QtGui.QSortFilterProxyModel()
        self._proxyModel_shotList.setSourceModel(self._model_shotList)
        self.shotList.setModel(self._proxyModel_shotList)  #this is the QTableView
        self._selModel_shotList = self.shotList.selectionModel()
        self.shotList.setSortingEnabled(True)
        self._proxyModel_shotList.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)


        self._model_actorList = SelectedShotsActorTableModel(self.data)
        self._proxyModel_actorList = QtGui.QSortFilterProxyModel()
        self._proxyModel_actorList.setSourceModel(self._model_actorList)
        self.actorList.setModel(self._proxyModel_actorList)
        self._selModel_actorList = self.actorList.selectionModel()

        #=======================================================================
        # Events
        #=======================================================================
        self.addShot.clicked.connect(self.addShot_clicked)
        self.delShot.clicked.connect(self.delShot_clicked)


        self._selModel_shotList.selectionChanged.connect(self.shotList_selectionChanged)

    #===========================================================================
    # General Functions    
    #===========================================================================
    def getSelectedRows(self, widget):
        selModel = widget.selectionModel()
        proxyModel = widget.model()
        model = proxyModel.sourceModel()
        rows = [proxyModel.mapToSource(index).row() for index in selModel.selectedRows()]
        rows.sort()
        return rows

    def getSelectedItems(self, widget):
        selModel = widget.selectionModel()
        proxyModel = widget.model()
        model = proxyModel.sourceModel()
        indices = [proxyModel.mapToSource(index) for index in selModel.selectedRows()]
        items = [model.getItemFromIndex(index) for index in indices]
        return items
    #===========================================================================
    # Event Functions    
    #===========================================================================
    def addShot_clicked(self):
        position = len(self.data.shots)
        self._proxyModel_shotList.insertRows(position,1)

    def delShot_clicked(self):
        rows = self.getSelectedRows(self.shotList)
        for row in reversed(rows):
            self._proxyModel_shotList.removeRows(row, 1)

    def shotList_selectionChanged(self, selected, deselected):
        selectedShots = self.getSelectedItems(self.shotList)
        print 'SelectedShots: {}'.format(selectedShots)
        self.data.selectedShotsActors = self.data.actorsOfShots(selectedShots)
        print 'ActorsOfShots: {}'.format(self.data.selectedShotsActors)

        self._proxyModel_actorList.setData() # this line reports missing variables

这是selectedShotActors模型:

class SelectedShotsActorTableModel(QtCore.QAbstractTableModel):
    def __init__(self, data=[], headers=[], parent=None, *args):
        super(SelectedShotsActorTableModel, self).__init__(parent)
        self._data = data

    def rowCount(self, parent):
        return len(self._data.selectedShotsActors)

    def columnCount(self, parent):
        return len(self._data._headers_actorList)

    def getItemFromIndex(self, index):
        if index.isValid():
            item = self._data.selectedShotsActors[index.row()]   
            if item:
                return item
        return None

    def flags(self, index):
        if index.isValid():
            item = self.getItemFromIndex(index)
            return item.qt_flags(index.column())

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if not index.isValid():
            return None

        item = self.getItemFromIndex(index)
        if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
            return item.qt_data(index.column())

        if role == QtCore.Qt.CheckStateRole:
            if index.column() is 0:
                return item.qt_checked
#         
#         if role == QtCore.Qt.BackgroundColorRole:
#             return QtGui.QBrush()
#         
#         if role == QtCore.Qt.FontRole:
#             return QtGui.QFont()

        if role == QtCore.Qt.DecorationRole:
            if index.column() == 0:
                resource = item.qt_resource()
                return QtGui.QIcon(QtGui.QPixmap(resource))

        if role == QtCore.Qt.ToolTipRole:
            return item.qt_toolTip()

    def setData(self, index, value, role = QtCore.Qt.EditRole):
        if index.isValid():
            item = self.getItemFromIndex(index)
            if role == QtCore.Qt.EditRole:
                item.qt_setData(index.column(), value)
                self.dataChanged.emit(index, index)
                return value
            if role == QtCore.Qt.CheckStateRole:
                if index.column() is 0:
                    item.qt_checked = value
                    return True
        return value

    def headerData(self, section, orientation, role):
        if role == QtCore.Qt.DisplayRole:
            if orientation == QtCore.Qt.Horizontal:
                return self._data._headers_actorList[section]

2 个答案:

答案 0 :(得分:1)

我的建议是更新来自selectionChanged() selectionModel的{​​{1}}信号的演员模型。

每次发出信号时(更改镜头选择时),您需要重置actor模型,然后迭代选择中的选定模型索引,并获取每个选定行的镜头对象的引用。你的模特。对于每个镜头对象,您可以获得一个actor列表。然后你需要检查actor是否在actor模型中,如果不是,则将其添加进去。

现在这样效率有点低,因为每次更改镜头模型的选择时都会重置actor模型。您确实从QTableView信号中获取了有关取消选择哪些行的信息,因此您可以在取消选择镜头模型的行时从您的actor模型中删除条目,但仅当没有其他选定的镜头行包含从你取消选择的镜头中给出演员

这个代码可以在你的项目中存在一些选项,但我可能会在你已经可以访问演员和镜头的视图和模型的地方。这可能在你的QMainWindow的子类中,但是没有看到代码就很难说出来了!

希望有所帮助:)

答案 1 :(得分:1)

我建议您使用代理模型。代理模型不保存数据,它们链接到现有模型,并提供根据需要对数据进行排序,过滤或重组的机会。

具体而言,您可以使用QSortFilterProxyModel的{​​{1}}作为来源创建QTableView对象。然后,您可以创建一个自定义过滤器,根据所选镜头构建一个actor列表。此方法的优点是代理模型和视图将随着选择的更改而自动更新。我认为这种方法比将代码添加到MainWindow更符合MVC的意图。

有关如何执行此操作的详细信息,请参阅自定义排序/过滤器模型示例。 http://qt-project.org/doc/qt-5/qtwidgets-itemviews-customsortfiltermodel-example.html

如果您需要一些背景信息(我也是Qt MVC的新手;我感到很痛苦)这里有一些其他有用的链接:

模型 - 视图编程:代理模型 http://qt-project.org/doc/qt-5/model-view-programming.html#proxy-models

QSortFilterProxyModel http://qt-project.org/doc/qt-5/qsortfilterproxymodel.html