w
wolkov9118 вересня 2018 р. 09: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 р. 06: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 р. 06:22

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

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

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

        w
        • 19 вересня 2018 р. 06:44

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

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

        assert self.checkIndex(proxyIndex)

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

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

          w
          • 19 вересня 2018 р. 08: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 р. 05:15

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

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

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

              Evgenii Legotckoi
              • 21 вересня 2018 р. 04:25

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

                w
                • 01 жовтня 2018 р. 06: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
                  • 01 жовтня 2018 р. 09: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
                    • 01 жовтня 2018 р. 09:32

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

                      Коментарі

                      Only authorized users can post comments.
                      Please, Log in or Sign up
                      sf

                      C++ - Тест 004. Указатели, Массивы и Циклы

                      • Результат:90бали,
                      • Рейтинг балів8
                      МВ

                      Qt - Тест 001. Сигналы и слоты

                      • Результат:68бали,
                      • Рейтинг балів-1
                      ЛС

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

                      • Результат:53бали,
                      • Рейтинг балів-4
                      Останні коментарі
                      A
                      ALO1ZE19 жовтня 2024 р. 05:19
                      Читалка файлів fb3 на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
                      ИМ
                      Игорь Максимов05 жовтня 2024 р. 04:51
                      Django - Урок 064. Як написати розширення для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
                      d
                      dblas505 липня 2024 р. 08:02
                      QML - Урок 016. База даних SQLite та робота з нею в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
                      k
                      kmssr08 лютого 2024 р. 15:43
                      Qt Linux - Урок 001. Автозапуск програми Qt під Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
                      АК
                      Анатолий Кононенко04 лютого 2024 р. 22:50
                      Qt WinAPI - Урок 007. Робота з ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
                      Тепер обговоріть на форумі
                      J
                      JacobFib17 жовтня 2024 р. 00:27
                      добавить qlineseries в функции Пользователь может получить любые разъяснения по интересующим вопросам, касающимся обработки его персональных данных, обратившись к Оператору с помощью электронной почты https://topdecorpro.ru…
                      ИМ
                      Игорь Максимов03 жовтня 2024 р. 01:05
                      Реализация навигации по разделам Спасибо Евгений!
                      JW
                      Jhon Wick01 жовтня 2024 р. 12:52
                      Indian Food Restaurant In Columbus OH| Layla’s Kitchen Indian Restaurant If you're looking for a truly authentic https://www.laylaskitchenrestaurantohio.com/ , Layla’s Kitchen Indian Restaurant is your go-to destination. Located at 6152 Cleveland Ave, Colu…
                      КГ
                      Кирилл Гусарев27 вересня 2024 р. 06:09
                      Не запускается программа на Qt: точка входа в процедуру не найдена в библиотеке DLL Написал программу на C++ Qt в Qt Creator, сбилдил Release с помощью MinGW 64-bit, бинарнику напихал dll-ки с помощью windeployqt.exe. При попытке запуска моей сбилженной программы выдаёт три оши…
                      F
                      Fynjy22 липня 2024 р. 01:15
                      при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …

                      Слідкуйте за нами в соціальних мережах