链接来自两个QTableView的行以进行可视化比较

时间:2017-09-05 09:29:10

标签: python pyqt pyqt4 qtableview

我正在尝试为管理科学结果开发GUI。为此,我想呈现两个数据集的结果。用户将有一个直观的表示,以帮助他在两个QTableView中比较这些结果。

图片:comparer结果,

我想链接两张桌子中的线条,所以他们总是面对面。 当一个表中的订单发生变化时,另一个表将遵循并调整其顺序仍然具有面对面的链接线。 最后我想将空行面添加到另一个表中没有相对行的行。

我认为使用QSortFilterProxyModel但我不确定如何使用它。

修改 我的问题似乎不太清楚。我在这里制定。我发现自己是一个解决方案,所以这里是我正在寻找的一个例子。

small Comparaison 在这个示例行中,我根据名称链接行(bla,bli blo,blu)。我们在同一行看到,表格呈现“bla”和“bli”的面对面线,因为左侧模型和右侧都有。 右表中没有“蓝色”。所以我添加一个空行。 在左边的表格中使用“blo”

在此示例项中,使用右表的“configuration”进行排序。左表必须遵循右表所选择的顺序。

这里我的代码没有解决方案

class Result(object):
    def __init__(self, headerOrder, infoResult):
        for n, headerName in enumerate(headerOrder):
            self.__setattr__(headerName, infoResult[n])

        self.diff = self.reference_redshift - self.estimate

ModelClass

class Result_model(QtCore.QAbstractTableModel):

    def __init__(self,  header, parent=None):
        QtCore.QAbstractTableModel.__init__(self, parent)
        self.__datas = []
        self.__headers = header

    def rowCount(self, parent=None):
        return len(self.__datas)

    def columnCount(self, parent=None):
        return len(self.__headers)

    def data(self, index, role):
        if role == QtCore.Qt.ToolTipRole:
            row = index.row()
            column = index.column()
            return "{}: {}".format(self.__headers[column], getattr(self.__datas[row], self.__headers[column]))

        if role == QtCore.Qt.DisplayRole:

            row = index.row()
            column = index.column()
            value = getattr(self.__datas[row], self.__headers[column])

            return value

    def headerData(self, section, orientation, role):

        if role == QtCore.Qt.DisplayRole:

            if orientation == QtCore.Qt.Horizontal:

                if section < len(self.__headers):
                    return self.__headers[section]
                else:
                    return "not implemented"
            else:
                return section

    def supportedDragActions(self):
        return QtCore.Qt.CopyAction

    def supportedDropActions(self):
        return Qt.CopyAction | Qt.MoveAction

    def getResult(self, index):
        row = index.row()
        return self.__datas[row]

    def sort(self, Ncol, order):
        """Sort table by given column number.
        """
        self.emit(QtCore.SIGNAL("layoutAboutToBeChanged()"))
        attribut = self.__headers[Ncol]
        self.__datas = sorted(
            self.__datas, key=lambda x: getattr(x, attribut), reverse=(order == QtCore.Qt.DescendingOrder))

        self.emit(QtCore.SIGNAL("layoutChanged()"))

    def addResults(self, results):
        self.beginInsertRows(QtCore.QModelIndex(), len(
            self.__datas), len(self.__datas) + len(results))
        for res in results:
            self.__datas.append(res)
        self.endInsertRows()

仅限TableView拖动

class TableResult(QtGui.QTableView):

    def __init__(self, parent=None):
        QtGui.QTableView.__init__(self, parent)
        self.setDragEnabled(True)
        self.setDragDropMode(QtGui.QAbstractItemView.InternalMove)
        self.header, self.aid_index = [["aid", "estimate", "reference_redshift", "diff", "amazed_executable_id", "amazed_configuration_id",
                                        "astronomical_object_name", "star_forming_rate", "magnitude", "log_f_halpha", "emission_velocity_dispersion", "res_dir"], 0]
        self.tag_info_result = ["aid", "estimate", "reference_redshift",  "amazed_executable_id", "amazed_configuration_id",
                                "astronomical_object_name", "star_forming_rate", "magnitude", "log_f_halpha", "emission_velocity_dispersion", "res_dir"]
        self.setItemDelegateForColumn(self.aid_index, ButtonDelegate(self))
        self.parent = parent

    def startDrag(self, dropAction):
        if(self.parent is not None and hasattr(self.parent, "selection")):
            # create mime data object
            mime = QtCore.QMimeData()
            # start drag
            drag = QtGui.QDrag(self)
            drag.setMimeData(mime)
            drag.start(QtCore.Qt.CopyAction | QtCore.Qt.CopyAction)
        else:
            print("Drag impossible")

    def mouseMoveEvent(self, event):
        self.startDrag(event)

TableView with drop

class Selection_receiver(TableResult):
    "Add the drop possibility from TableResult"

    def __init__(self, setResultFunction, parent=None):
        TableResult.__init__(self, parent)
        self.setAcceptDrops(True)
        self.setResultFunction = setResultFunction

    def dragEnterEvent(self, event):
        if (isinstance(event.source(), TableResult)):
            event.accept()
            event.acceptProposedAction()
        else:
            event.ignore()

    def dropEvent(self, event):
        print("dropEvent")
        if (isinstance(event.source(), TableResult)):
            event.acceptProposedAction()
            model_result = event.source().parent.resModel
            self.setResultFunction(model_result)
        else:
            event.ignore()

呈现两个表的小部件

class Comparater_result_widget(QtGui.QWidget):
    """
    Present two table for easy comparaison.
    """

    def __init__(self,parent=None):
        super(self.__class__, self).__init__(parent)
        self.setWindowTitle("Result Comparer")
        main_layout = QtGui.QVBoxLayout()

        receiverSplitter = QtGui.QSplitter()
        receiverSplitter.setOrientation(QtCore.Qt.Horizontal)
        self.left_receiver = Selection_receiver(self.setLeftResult)
        receiverSplitter.addWidget(self.left_receiver)

        self.right_receiver = Selection_receiver( self.setRightResult)
        receiverSplitter.addWidget(self.right_receiver)

        main_layout.addWidget(receiverSplitter)

        self.left_receiver.horizontalScrollBar().valueChanged.connect(
            self.right_receiver.horizontalScrollBar().setValue)
        self.right_receiver.horizontalScrollBar().valueChanged.connect(
            self.left_receiver.horizontalScrollBar().setValue)
        self.left_receiver.verticalScrollBar().valueChanged.connect(
            self.right_receiver.verticalScrollBar().setValue)
        self.right_receiver.verticalScrollBar().valueChanged.connect(
            self.left_receiver.verticalScrollBar().setValue)

        self.right_results = None
        self.left_results = None
        self.setLayout(main_layout)

    def setLeftResult(self, model_result):
        print("setLeftResult []".format(model_result))
        self.left_results = model_result
        self.add_model_result(self.left_receiver, model_result)

    def setRightResult(self, model_result):
        print("setRightResult {}".format(model_result))
        self.right_results = model_result
        self.add_model_result(self.right_receiver, model_result)

    def add_model_result(self, receiver, model_result):
        receiver.setModel(model_result)

        if(self.right_results is not None and self.left_results is not None):
            self.link_result()

    def link_result(self):
        # parse the two model and link results if the have equal on one
        # particular attribut
        pass

    def OnLeftChangeOrder(self):
        # somthing like right_proxy.reorder(left_order)
        pass

    def OnRightChangeOrder(self):
        # something link left_proxy.reorder(right_order)
        pass

1 个答案:

答案 0 :(得分:0)

这是我创建的解决方案。

我使用两个代理:

  • 我自己的地图代理模型(&#34; MixRowProxyModel&#34;)
  • 几乎正常的QSortFilterProxyModel(&#34; MySortProxyModel&#34;)

源模型是MixRowProxyModel的模型。 MixRowProxyModel是MySortProxyModel的模型

表可以处于两种状态,主服务器或从服务器。如果我在一列中对正确的表进行排序,则右侧成为主,左侧为从属。

当表是主表时,其MySortProxyModel处于活动状态,MixRowProxyModel处于非活动状态

当表是slave时,其MySortProxyModel处于非活动状态,MixRowProxyModel处于活动状态

设置两个源模型时。我在源模型的行之间创建了一个映射。对表进行排序时,此映射不会更改。

当表成为slave时,我为其MixRowProxyModel构建映射(请参阅Comparater_result_widget中的方法更改)。为此我使用了在modelsource之间创建的初始映射。

class MixRowProxyModel(QtGui.QAbstractProxyModel):

    def __init__(self, controller, side, parent=None):
        QtGui.QAbstractProxyModel.__init__(self, parent=parent)
        self.controller = controller
        self.nbEmpty = 0
        self.mapProxyToSource = None
        self.isMaster = True
        self.side = side

    def setMap(self, mapping):
        self.isMaster = False
        self.mapProxyToSource = mapping
        self.mapSourceToProxy = {v: k for k,
                                 v in self.mapProxyToSource.items()}

    def mapFromSource(self, sourceIndex):
        #print("MixRowProxyModel Proxy Index model {}".format(sourceIndex.model()))
        if(not sourceIndex.isValid()):
            return self.index(-1, -1)
        if(self.isMaster):
            return self.index(sourceIndex.row(), sourceIndex.column(), parent=QtCore.QModelIndex)
        else:
            row = sourceIndex.row()
            if(row in self.mapSourceToProxy):
                return self.index(self.mapSourceToProxy[row], sourceIndex.column())
            else:
                print("Invalid sourceIndex {}".format(row))
                return self.index(-1, -1)

    def mapToSource(self, proxyIndex):
        if(not proxyIndex.isValid()):
            return self.sourceModel().index(-1, -1)
        if(self.isMaster):
            return self.sourceModel().index(proxyIndex.row(), proxyIndex.column())
        else:
            row = proxyIndex.row()
            if(row in self.mapProxyToSource):
                return self.sourceModel().index(self.mapProxyToSource[row], proxyIndex.column())
            else:
                # print("Invalid proxyIndex {}".format(row))
                return self.sourceModel().index(-1, -1)

    def rowCount(self, parent=None):

        return self.sourceModel().rowCount() + self.nbEmpty

    def columnCount(self, parent=None):

        return self.sourceModel().columnCount()

    def addEmptyRow(self):
        print("addEmptyRow {}".format(self.side))
        self.beginInsertRows(QtCore.QModelIndex(),
                             self.rowCount(),  self.rowCount())

        self.nbEmpty += 1
        self.endInsertRows()
        return -1

    def parent(self, index):
        return QtCore.QModelIndex()

    def index(self, row, column, parent=QtCore.QModelIndex):
        if(row >= self.rowCount() or row < 0 or column < 0 or column >= self.columnCount()):
            return QtCore.QModelIndex()

        return self.createIndex(row, column, parent)

    def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):

        if role == QtCore.Qt.DisplayRole:

            if orientation == QtCore.Qt.Horizontal:

                return self.sourceModel().headerData(section, orientation, role)
            else:
                return section

    def flags(self, index):
        return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable

    def data(self, index, role=QtCore.Qt.DisplayRole):

        if(not index.isValid()):
            return None
        if(self.isMaster):
            if(index.row() < self.sourceModel().rowCount()):
                return self.sourceModel().data(index, role)
            else:
                return None
        else:
            if(not index.row() in self.mapProxyToSource):
                return None
            return self.sourceModel().data(self.sourceModel().index(self.mapProxyToSource[index.row()], index.column()), role)

第二个ProxyModel

class MySortProxyModel(QtGui.QSortFilterProxyModel):
    sortedSignal = QtCore.pyqtSignal(QtCore.Qt.SortOrder)

    def sort(self, column, order):
        self.sourceModel().isMaster = True
        #print("Sort order : {} sort Role {}".format(order, self.sortRole()))
        self.setSortRole(QtCore.Qt.DisplayRole)
        super().sort(column, order)

        self.sortedSignal.emit(order)

the Parent Widget controlling the two tables

class Comparater_result_widget(QtGui.QWidget):

    def __init__(self, controller, parent=None):
        super(self.__class__, self).__init__(parent)
        self.controller = controller
        self.setWindowTitle("Result Comparer")
        main_layout = QtGui.QVBoxLayout()

        receiverSplitter = QtGui.QSplitter()
        receiverSplitter.setOrientation(QtCore.Qt.Horizontal)
        self.left_receiver = Selection_receiver(
            self.controller, self.setLeftResult)
        receiverSplitter.addWidget(self.left_receiver)

        self.right_receiver = Selection_receiver(
            self.controller, self.setRightResult)
        receiverSplitter.addWidget(self.right_receiver)

        main_layout.addWidget(receiverSplitter)

        self.left_receiver.horizontalScrollBar().valueChanged.connect(
            self.right_receiver.horizontalScrollBar().setValue)
        self.right_receiver.horizontalScrollBar().valueChanged.connect(
            self.left_receiver.horizontalScrollBar().setValue)
        self.left_receiver.verticalScrollBar().valueChanged.connect(
            self.right_receiver.verticalScrollBar().setValue)
        self.right_receiver.verticalScrollBar().valueChanged.connect(
            self.left_receiver.verticalScrollBar().setValue)

        self.right_source_model = None
        self.right_proxy_model = None
        self.right_proxy_model2 = None
        self.left_source_model = None
        self.left_proxy_model = None
        self.left_proxy_model2 = None

        self.setLayout(main_layout)

    def setLeftResult(self, model_result):
        self.left_source_model = model_result
        self.left_proxy_model = MixRowProxyModel(
            self.controller, "left", parent=self)
        # self.left_proxy_model.sortedSignal.connect(self.onLeftChange)
        self.left_proxy_model.setSourceModel(self.left_source_model)
        self.left_proxy_model2 = MySortProxyModel(parent=self)
        self.left_proxy_model2.sortedSignal.connect(self.onLeftChange)
        self.left_proxy_model2.setSourceModel(self.left_proxy_model)
        self.add_model_result(self.left_receiver, self.left_proxy_model2)

    def setRightResult(self, model_result):
        self.right_source_model = model_result
        self.right_proxy_model = MixRowProxyModel(
            self.controller, "right", parent=self)
        self.right_proxy_model.setSourceModel(self.right_source_model)
        # self.right_proxy_model.sortedSignal.connect(self.onRightChange)

        self.right_proxy_model2 = MySortProxyModel(parent=self)
        self.right_proxy_model2.sortedSignal.connect(self.onRightChange)
        self.right_proxy_model2.setSourceModel(self.right_proxy_model)
        self.add_model_result(self.right_receiver, self.right_proxy_model2)

    def add_model_result(self, receiver, model_result):
        receiver.setModel(model_result)

        if(self.right_source_model is not None and self.left_source_model is not None):
            self.link_result()

    def link_result(self):
        name_to_row = {}
        for numSourceLeftRow in range(self.left_source_model.rowCount()):
            res = self.left_source_model.getResultNumRow(numSourceLeftRow)
            name = res.astronomical_object_name
            if(name in name_to_row):
                name_to_row[name][0].append(numSourceLeftRow)
            else:
                name_to_row[name] = ([numSourceLeftRow], [])

        for numSourceRightRow in range(self.right_source_model.rowCount()):
            res = self.right_source_model.getResultNumRow(numSourceRightRow)
            name = res.astronomical_object_name
            if(name in name_to_row):
                name_to_row[name][1].append(numSourceRightRow)
            else:
                name_to_row[name] = ([], [numSourceRightRow])
        self.mapLeftToRight = {}  # key = leftRow; value = rightRow
        self.mapRightToLeft = {}  # key = rightRow; value = leftRow
        for list_leftRow, list_rightRow in name_to_row.values():
            if(len(list_rightRow) > 1):
                print(
                    "Error more that index at right for same astronomical name {}".list_rightRow)
            if(len(list_leftRow) > 1):
                print(
                    "Error more that index at left for same astronomical name {}".list_leftRow)
            if(len(list_leftRow) == 0):
                leftRow = self.left_proxy_model.addEmptyRow()
            else:
                leftRow = list_leftRow[0]
            if(len(list_rightRow) == 0):
                rightRow = self.right_proxy_model.addEmptyRow()
            else:
                rightRow = list_rightRow[0]

            self.mapLeftToRight[leftRow] = rightRow
            self.mapRightToLeft[rightRow] = leftRow
        self.left_receiver.rowCountChanged(
            self.left_source_model.rowCount(), self.left_proxy_model.rowCount())
        self.right_receiver.rowCountChanged(
            self.right_source_model.rowCount(), self.right_proxy_model.rowCount())
        print("Link Done : LtoR : {}; RtoL {}".format(
            self.mapLeftToRight, self.mapRightToLeft))

    def onRightChange(self, order):
        print("RightChange")
        self.change(self.left_source_model, self.left_proxy_model, self.left_proxy_model2, self.right_source_model, self.right_proxy_model,
                    self.right_proxy_model2, self.mapLeftToRight, self.left_receiver, order)

    def onLeftChange(self, order):
        print("LeftChange")
        self.change(self.right_source_model, self.right_proxy_model, self.right_proxy_model2, self.left_source_model, self.left_proxy_model,
                    self.left_proxy_model2, self.mapRightToLeft, self.right_receiver, order)

    def change(self, slave_source_model, slave_proxy_model,
               slave_proxy_model2, master_source_model, master_proxy_model, master_proxy_model2,
               map_slave_to_master, slave_receiver, order):
        if(slave_source_model is not None):

            slaveMapping = dict()  # in slave table key = indexProxy , value = index Source
            if(order == QtCore.Qt.AscendingOrder):
                unlinkIndex = 0
            else:
                unlinkIndex = master_source_model.rowCount()
            for slaveSourceRow in range(slave_source_model.rowCount()):

                # this line is link to one in master, so we keep the same
                # proxy number
                master_source_row = map_slave_to_master[slaveSourceRow]
                if(master_source_row != -1):
                    master_source_index = master_source_model.index(
                        master_source_row, 0)
                    master_proxy = master_proxy_model.mapFromSource(
                        master_source_index)
                    master_proxy2 = master_proxy_model2.mapFromSource(
                        master_proxy)
                    slaveProxyRow = master_proxy2.row()  # same as master
                else:
                    slaveProxyRow = unlinkIndex  # we put it at the end or begining depending on order
                    unlinkIndex += 1

                slaveMapping[slaveProxyRow] = slaveSourceRow

            slave_proxy_model.layoutAboutToBeChanged.emit()
            slave_proxy_model.setMap(slaveMapping)
            slave_proxy_model2.setSortRole(
                QtCore.Qt.InitialSortOrderRole)  # proxy 2 is reinitialise
            slave_proxy_model.layoutChanged.emit()