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

0 投票
2 回答
1527 浏览
提问于 2025-04-18 01:31

我正在制作一个用于电影摄影的Python/PySide工具。我创建了一个对象来表示一个镜头。这个对象有一些属性,比如开始时间、结束时间,还有一个演员对象的引用列表。演员对象有一些简单的属性(比如名字、身材、年龄等),并且可以在多个镜头之间共享。

我想在PySide中显示这两个表格视图。一个表格视图列出所有镜头(并在列中显示它们的属性),另一个表格则显示在选中的镜头中提到的演员。如果没有选中任何镜头,第二个表格就会是空的。如果选中了多个镜头,演员表格会显示所有被提到的演员。

我为我的镜头数据创建了一个抽象表模型,镜头数据在对应的表格视图中一切正常。但是我不太确定该如何处理演员的表格视图。我是否应该为演员再使用一个抽象表模型?我似乎无法弄清楚如何将选中镜头中的演员数据连接到第二个演员表格视图。

我觉得我的问题部分在于,我只想显示一次演员的信息,无论多个选中的镜头是否引用了同一个演员。在多次尝试失败后,我在想我需要将选中镜头的信息(它们的演员列表属性)重定向并解析到主窗口的一个自定义属性中,以包含所有被引用的演员索引,并创建一个抽象表模型来使用这些索引获取实际的演员属性进行显示。我对这个方法并不是很有信心,而且我感觉这可能是个乱七八糟的做法,所以我来这里寻求建议。

这是正确的方法吗?如果不是,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

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

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

这是选中镜头演员的模型:

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 个回答

1

我的建议是,在你的 QTableViewselectionModel 发出 selectionChanged() 信号时,更新演员模型。

每当这个信号被触发(也就是选择的内容发生变化时),你需要重置演员模型,然后遍历你选择的模型索引,获取每一行所对应的镜头对象。对于每个镜头对象,你可以得到一份演员的列表。接着,你需要检查这个演员是否已经在演员模型里,如果没有,就把它添加进去。

这样做有点低效,因为每次镜头模型的选择变化时,你都在重置演员模型。其实你可以利用 selectionChanged() 信号提供的信息,知道哪些行被取消选择,这样你可以在镜头模型的行被取消选择时,从演员模型中移除相应的条目,但前提是没有其他被选中的镜头行包含你要移除的演员

关于这段代码放在哪里,你有几个选择,但我建议放在你已经可以访问演员和镜头视图及模型的地方。这可能是在你自定义的 QMainWindow 类里,不过不看代码的话,很难确定!

希望这能帮到你 :)

1

我建议你使用代理模型。代理模型本身不存储数据,它只是链接到一个已经存在的模型,并提供了对这些数据进行排序、过滤或重新组织的机会。

具体来说,你可以创建一个 QSortFilterProxyModel 对象,并把 QTableViewQItemSelectionModel 作为数据源。然后,你可以创建一个自定义的过滤器,根据选中的镜头来构建一个演员的列表。这样做的好处是,当选择发生变化时,代理模型和视图会自动更新。我觉得这种方法比在主窗口中添加代码更符合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

撰写回答