w
wolkov9118 сентября 2018 г. 9:38

Прокси-модель, содержащая на 1 столбец больше, чем модель-источник.

qt5, model, proxy, QIdentityProxyModel, column, PyQt5

Здравствуйте!

Пытаюсь переопределить QIdentityProxyModel так, чтобы она имела на 1 столбец больше, чем ее модель-источник, но никак не могу добиться успешного результата.

Вот заготовка, очищенная от мусора:


#!/usr/bin/python3
# -*- coding: utf-8 -*-

"""Попытка реализовать прокси-модель, содержащую на 1 столбец больше, чем модель-источник."""
import sys
import typing

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *


app = None


class MyProxyModel(QIdentityProxyModel):
    """Прокси-модель, содержащая на 1 столбец больше, чем модель-источник."""

    VALUE = "==="

    def columnCount(self, parent: QModelIndex = None) -> int:
        """Переопределяет соответствующий родительский метод.
        columnCount(self, parent: QModelIndex = QModelIndex()) -> int
        """
        if not self.sourceModel():
            return 0
        return super().columnCount() + 1

    def data(self, proxyIndex: QModelIndex, role: int = None) -> typing.Any:
        """Переопределяет соответствующий родительский метод.
        data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> Any
        """
        assert self.checkIndex(proxyIndex)
        if role is None:
            role = Qt.DisplayRole

        if not self.sourceModel():
            return None

        if role == Qt.DisplayRole:
            if proxyIndex.column() == self.columnCount() - 1:
                # text = ""
                # for column in range(self.columnCount() - 1):
                #     # index = self.index(proxyIndex.row(), column, proxyIndex.parent())
                #     index = self.sibling(proxyIndex.row(), column, proxyIndex)
                #     text += "->" + str(super().data(index))
                # return text
                return self.VALUE

        return super().data(proxyIndex, role)

    def headerData(self, section: int, orientation: int, role: int = None) -> typing.Any:
        """Переопределяет соответствующий родительский метод.
        headerData(self, section: int, orientation: Qt.Orientation, role: int = Qt.DisplayRole) -> Any
        """
        if role is None:
            role = Qt.DisplayRole

        if orientation == Qt.Horizontal and role == Qt.DisplayRole and section == self.columnCount() - 1:
            return "New column"

        return super().headerData(section, orientation, role)

    # def mapFromSource(self, sourceIndex: QModelIndex) -> QModelIndex:
    #     # return super().mapFromSource(sourceIndex)
    #     if not self.sourceModel() or not sourceIndex.isValid():
    #         return QModelIndex()
    #
    #     assert sourceIndex.model() is self.sourceModel()
    #     return self.createIndex(sourceIndex.row(), sourceIndex.column(), sourceIndex.internalPointer())
    #
    # def mapToSource(self, proxyIndex: QModelIndex) -> QModelIndex:
    #     # return super().mapToSource(proxyIndex)
    #     if not self.sourceModel() or not proxyIndex.isValid():
    #         return QModelIndex()
    #
    #     assert proxyIndex.model() is self
    #     return self.sourceModel().createIndex(proxyIndex.row(), proxyIndex.column(), proxyIndex.internalPointer())


def main():
    global app
    app = QApplication(sys.argv)

    sourceModel = QFileSystemModel(app)
    sourceModel.setRootPath(".")

    proxyModel = MyProxyModel(app)
    proxyModel.setSourceModel(sourceModel)

    treeView = QTreeView()
    treeView.setMinimumWidth(800)
    treeView.setModel(proxyModel)
    treeView.setColumnWidth(0, 400)
    # TODO: По прежнему выделяет только ячейку. Надо переопределить MyProxyModel.mapSelection<From/To>Source()!
    treeView.setSelectionBehavior(treeView.SelectRows)
    # treeView.setSelectionMode(treeView.SingleSelection)
    treeView.show()

    def checkProxyModel():
        print("BEGIN checkProxyModel()")

        assert proxyModel.columnCount() == sourceModel.columnCount() + 1

        proxyIndexWithZeroColumn = proxyModel.index(0, 0)
        sourceIndexWithZeroColumn = sourceModel.index(0, 0)
        assert proxyModel.data(proxyIndexWithZeroColumn) == sourceModel.data(sourceIndexWithZeroColumn)

        proxyIndexWithPenultimateColumn = proxyModel.index(0, proxyModel.columnCount() - 2)
        sourceIndexWithLastColumn = sourceModel.index(0, sourceModel.columnCount() - 1)
        assert proxyModel.data(proxyIndexWithPenultimateColumn) == sourceModel.data(sourceIndexWithLastColumn)

        proxyIndexWithLastColumn = proxyModel.index(0, proxyModel.columnCount() - 1)
        assert proxyModel.data(proxyIndexWithLastColumn, Qt.DisplayRole) == proxyModel.VALUE

        print("END checkProxyModel()")

    QTimer.singleShot(0, checkProxyModel)

    sys.exit(app.exec())


if __name__ == '__main__':
    main()

В отображении появляется новый столбец с нужным заголовком, но без данных.

Я не понимаю, каким образом мне нужно переопределить методы mapFromSource/mapToSource, чтоб все заработало...  Или же вообще нужно переопределять index(), parent() и прочие?..

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

10
Evgenii Legotckoi
  • 18 сентября 2018 г. 13:56

Добрый день.

Вот рабочий вариант

#!/usr/bin/python3
# -*- coding: utf-8 -*-

"""Попытка реализовать прокси-модель, содержащую на 1 столбец больше, чем модель-источник."""
import sys
import typing


from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *


app = None


class MyProxyModel(QIdentityProxyModel):
    """Прокси-модель, содержащая на 1 столбец больше, чем модель-источник."""

    VALUE = "==="

    def columnCount(self, parent: QModelIndex = None) -> int:
        """Переопределяет соответствующий родительский метод.
        columnCount(self, parent: QModelIndex = QModelIndex()) -> int
        """
        if not self.sourceModel():
            return 0
        return super().columnCount() + 1

    def data(self, proxyIndex: QModelIndex, role: int = None) -> typing.Any:
        """Переопределяет соответствующий родительский метод.
        data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> Any
        """

        print(proxyIndex.column())
        if role is None:
            role = Qt.DisplayRole

        if not self.sourceModel():
            return None

        if role == Qt.DisplayRole:
            if proxyIndex.column() == self.columnCount() - 1:
                return self.VALUE

        return super().data(proxyIndex, role)

    def index(self, row: int, column: int, parent: QModelIndex = QModelIndex()) -> typing.Any:
        if column == self.columnCount(parent) - 1:
            return self.createIndex(row, column)

        return super().index(row, column, parent)

    def headerData(self, section: int, orientation: int, role: int = None) -> typing.Any:
        """Переопределяет соответствующий родительский метод.
        headerData(self, section: int, orientation: Qt.Orientation, role: int = Qt.DisplayRole) -> Any
        """
        if role is None:
            role = Qt.DisplayRole

        if orientation == Qt.Horizontal and role == Qt.DisplayRole and section == self.columnCount() - 1:
            return "New column"

        return super().headerData(section, orientation, role)


def main():
    global app
    app = QApplication(sys.argv)

    sourceModel = QFileSystemModel(app)
    sourceModel.setRootPath(".")

    proxyModel = MyProxyModel(app)
    proxyModel.setSourceModel(sourceModel)

    treeView = QTreeView()
    treeView.setMinimumWidth(800)
    treeView.setModel(proxyModel)
    treeView.setColumnWidth(0, 400)
    # TODO: По прежнему выделяет только ячейку. Надо переопределить MyProxyModel.mapSelection<From/To>Source()!
    treeView.setSelectionBehavior(treeView.SelectRows)
    treeView.show()

    def checkProxyModel():
        print("BEGIN checkProxyModel()")

        assert proxyModel.columnCount() == sourceModel.columnCount() + 1

        proxyIndexWithZeroColumn = proxyModel.index(0, 0)
        sourceIndexWithZeroColumn = sourceModel.index(0, 0)
        assert proxyModel.data(proxyIndexWithZeroColumn) == sourceModel.data(sourceIndexWithZeroColumn)

        proxyIndexWithPenultimateColumn = proxyModel.index(0, proxyModel.columnCount() - 2)
        sourceIndexWithLastColumn = sourceModel.index(0, sourceModel.columnCount() - 1)
        assert proxyModel.data(proxyIndexWithPenultimateColumn) == sourceModel.data(sourceIndexWithLastColumn)

        proxyIndexWithLastColumn = proxyModel.index(0, proxyModel.columnCount() - 1)
        assert proxyModel.data(proxyIndexWithLastColumn, Qt.DisplayRole) == proxyModel.VALUE

        print("END checkProxyModel()")

    QTimer.singleShot(0, checkProxyModel)

    sys.exit(app.exec())


if __name__ == '__main__':
    main()

Вам надо было метод index переопределить, чтобы создавался QModelIndex для вашей спец-колонки



    w
    • 19 сентября 2018 г. 6:17

    Огромное спасибо за ответ!:)

    Но вариант все же не является рабочим.

    Почти всегда дерево отображается корректно и даже раскрываются новые уровни, если нажать точно на треугольник слева. Но если сменить выделенный элемент, например, щелкнуть мышкой на какую-нибудь ячейку, то приложение падает, и единственным выводом в консоль является:

    Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)

    qVersion() == 5.11.1, OS == Kubuntu 18.04

    Также приложение падает, если вернуть в метод data() строчку

    assert self.checkIndex(proxyIndex)

    Изучение исходников метода index() в QFileSystemModel и QStandardItemModel ничем пока не помогло. :(

      Evgenii Legotckoi
      • 19 сентября 2018 г. 6:22

      Падение с assert - это уже питоновские заморочки, дайте туда более корректную проверку, поскольку проверка на None просто не работает адекватно в том случае.

      Что касается установки информации, то возможно, что ещё потребуется перепределить метод setData. Переопределите его и посмотрите через print, что туда прилетает в индексе и роли.

      print(proxyIndex.row(), proxyIndex.column(), role)

        w
        • 19 сентября 2018 г. 6:44

        До setData() я еще не добрался, так как пока что даже reed only модель не работает.

        В конкретном данном случае в

        assert self.checkIndex(proxyIndex)

        всегда передается экземпляр QModelIndex. Поэтому это не является проблемой обработки None.
        Да и на падения при смене текущей ячейки это не влияет.

        Я Вам очень благодарен за то, что все равно пытаетесь помочь!

          w
          • 19 сентября 2018 г. 8:00
          • (ред.)

          Если определить метод index() таким костыльным и затратным образом:

          def index(self, row: int, column: int, parent: QModelIndex = QModelIndex()) -> QModelIndex:
              assert self.checkIndex(parent)
              if column == self.columnCount() - 1:
                  return self.createIndex(row, column, QPersistentModelIndex(parent))
          
              return super().index(row, column, parent)
          

          то приложение уже перестает падать, но строку вьюха выделить все еще не может, так как для модельного индекса из моего столбца internalPointer() хранит данные, отличающиеся от данных для других столбцов.

          Также в этом случае в консоль выводится соответствующее сообщение:

          Can't select indexes from different model or with different parents

            w
            • 20 сентября 2018 г. 5:15

            В итоге, эмулировать существование дополнительных столбцов через QIdentityModel на PyQt5(5.11.1) получается только для плоских (табличных, списочных) моделей.

            Для древовидных моделей при создании модельного индекса в internalPointer необходимо хранить некие данные о родителе. Но если установить в него данные можно, то при считывании PyQt не может определить их тип из-за того, что в С++ они объявлены как void*, если считывать через internalPointer(), и qintptr, если считывать через internalId().

            В следствие чего, программа крашится.

              Evgenii Legotckoi
              • 21 сентября 2018 г. 4:25

              Попробуйте ещё PySide 2 - это официально поддерживаемый пакет привязок Python к Qt, возможно, что там не будет таких проблем.

                w
                • 1 октября 2018 г. 6:21

                Вот работающий вариант переопределения метода index():

                def index(self, row: int, column: int, parent: QModelIndex = QModelIndex()) -> QModelIndex:
                    """Переопределяет соответствующий родительский метод.
                    index(self, row: int, column: int, parent: QModelIndex = QModelIndex()) -> QModelIndex
                    """
                    if column == self.columnCount() - 1:
                        assert self.checkIndex(parent)
                        return self.createIndex(row, column, super().index(row, 0, parent).internalId())
                
                    return super().index(row, column, parent)
                

                Теперь корректно устанавливаются дынные об элементе-предке и программа больше не крашится.

                Но остается вопрос, как заставить вьюху менять выделение/текущий индекс при щелчке мышкой по ячейкам из нового столбца?..

                  w
                  • 1 октября 2018 г. 9:30

                  Изучение исходников QTreeView и QAbstractItemView показало, что для осуществления обработки нажатий по элементам нового столбца необходимо переопределить метод sibling():

                  def sibling(self, row: int, column: int, index: QModelIndex) -> QModelIndex:
                      """Переопределяет соответствующий родительский метод.
                      index(self, row: int, column: int, parent: QModelIndex = QModelIndex()) -> QModelIndex
                      """
                      if column == self.columnCount() - 1:
                          assert self.checkIndex(index)
                          assert index.isValid()  # Это можно будет проверить в строке выше, когда в PyQt5 реализуют соотв. параметры.
                          return self.createIndex(row, column, super().index(row, 0, index.parent()).internalId())
                      return super().sibling(row, column, index)
                  
                    Evgenii Legotckoi
                    • 1 октября 2018 г. 9:32

                    Полагаю, что вам придётся везде создавать соответствующий валидный индекс для дополнительного столбца, если что-то не работает

                      Комментарии

                      Только авторизованные пользователи могут публиковать комментарии.
                      Пожалуйста, авторизуйтесь или зарегистрируйтесь
                      B

                      C++ - Тест 002. Константы

                      • Результат:16баллов,
                      • Очки рейтинга-10
                      B

                      C++ - Тест 001. Первая программа и типы данных

                      • Результат:46баллов,
                      • Очки рейтинга-6
                      FL

                      C++ - Тест 006. Перечисления

                      • Результат:80баллов,
                      • Очки рейтинга4
                      Последние комментарии
                      k
                      kmssr9 февраля 2024 г. 2:43
                      Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
                      АК
                      Анатолий Кононенко5 февраля 2024 г. 9:50
                      Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
                      EVA
                      EVA25 декабря 2023 г. 18:30
                      Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
                      J
                      JonnyJo25 декабря 2023 г. 16:38
                      Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
                      G
                      Gvozdik19 декабря 2023 г. 5:01
                      Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
                      Сейчас обсуждают на форуме
                      P
                      Pisych27 февраля 2023 г. 12:04
                      Как получить в массив значения из связанной модели? Спасибо, разобрался:))
                      AC
                      Alexandru Codreanu19 января 2024 г. 19:57
                      QML Обнулить значения SpinBox Доброго времени суток, не могу разобраться с обнулением значение SpinBox находящего в делегате. import QtQuickimport QtQuick.ControlsWindow { width: 640 height: 480 visible: tr…
                      BlinCT
                      BlinCT27 декабря 2023 г. 16:57
                      Растягивать Image на парент по высоте Ну и само собою дял включения scrollbar надо чтобы был Flickable. Так что выходит как то так Flickable{ id: root anchors.fill: parent clip: true property url linkFile p…
                      Дмитрий
                      Дмитрий10 января 2024 г. 12:18
                      Qt Creator загружает всю оперативную память Проблема решена. Удалось разобраться с помощью утилиты strace. Запустил ее: strace ./qtcreator Начал выводиться весь лог работы креатора. В один момент он начал считывать фай…
                      Evgenii Legotckoi
                      Evgenii Legotckoi12 декабря 2023 г. 14:48
                      Побуквенное сравнение двух строк Добрый день. Там случайно не высылается этот сигнал textChanged ещё и при форматировани текста? Если решиать в лоб, то можно просто отключать сигнал/слотовое соединение внутри слота и …

                      Следите за нами в социальных сетях