Прокси-фильтр PySide6 QSortFilterProxyModel удаляет постоянный редактор

У меня есть QTableView, содержащий столбец с флажками. Я использую делегат для флажка, потому что хочу выровнять его по центру столбца. Тот, который генерируется через CheckStateRole, — нет. Редактор флажков для этого столбца становится постоянным.

Для сортировки и фильтрации таблицы я использую QSortFilterProxyModel. Сортировка работает и фильтрация с использованием подстановочных знаков. Но если я удалю фильтр с подстановочными знаками, флажки ранее отфильтрованных строк не будут отображаться. Вместо этого в столбце отображается 0 или 1.

Код:

import sys
import pandas as pd
from PySide6.QtCore import Qt, Property, Signal, QAbstractTableModel
from PySide6.QtCore import QSortFilterProxyModel, QSignalBlocker
from PySide6.QtWidgets import (QCheckBox, QHBoxLayout, QStyledItemDelegate,
                               QTableView, QWidget, QApplication, QMainWindow,
                               QVBoxLayout, QLineEdit, QFrame)


class CenterCheckBox(QWidget):
    toggled = Signal(bool)

    def __init__(self, parent):
        super().__init__(parent)
        layout = QHBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        self.check = QCheckBox()
        layout.addWidget(self.check, alignment=Qt.AlignmentFlag.AlignCenter)
        self.check.setFocusProxy(self)
        self.check.toggled.connect(self.toggled)
        # set a 0 spacing to avoid an empty margin due to the missing text
        self.check.setStyleSheet('color: red; spacing: 0px;')
        self.setAutoFillBackground(True)

    @Property(bool, user=True)   # note the user property parameter
    def checkState(self):
        return self.check.isChecked()

    @checkState.setter
    def checkState(self, state):
        with QSignalBlocker(self.check) as blocker:
            self.check.setChecked(state)


class MyCheckboxDelegate(QStyledItemDelegate):
    def __init__(self, parent):
        super().__init__(parent)

    def createEditor(self, parent, option, index):
        check = CenterCheckBox(parent)
        check.toggled.connect(lambda: self.commitData.emit(check))
        return check

    def setModelData(self, editor, model, index):
        model.setData(index, editor.checkState)


class TableModel(QAbstractTableModel):
    def __init__(self, data):
        super().__init__()
        print(data)
        self._data = data

    def rowCount(self, index=None):
        return self._data.shape[0]

    def columnCount(self, parent=None):
        return self._data.shape[1]

    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if index.isValid():
            if role == Qt.ItemDataRole.DisplayRole or role == Qt.ItemDataRole.EditRole:
                if self._data.columns[index.column()]=='Delete':
                    return ''
                value = self._data.iloc[index.row(), index.column()]
                return str(value)


class MyWindow(QMainWindow):
    def __init__(self, *args):
        QMainWindow.__init__(self, *args)
        self.frame = QFrame(self)
        self.verticalLayout = QVBoxLayout(self.frame)
        self.lineEdit = QLineEdit(self.frame)
        self.verticalLayout.addWidget(self.lineEdit)
        self.table_view = QTableView(self.frame)
        self.verticalLayout.addWidget(self.table_view)

        table_model = TableModel(pd.DataFrame([['1', '1'],
                                               ['0', '0'],
                                               ['2', '0']]))
        proxy_model = QSortFilterProxyModel()
        proxy_model.setSourceModel(table_model)
        self.table_view.setModel(proxy_model)
        self.table_view.setSortingEnabled(True)
        self.lineEdit.textChanged.connect(proxy_model.setFilterWildcard)

        self.table_view.setItemDelegateForColumn(1, MyCheckboxDelegate(self))
        for row in range(0, proxy_model.rowCount()):
            self.table_view.openPersistentEditor(proxy_model.index(row, 1))

        self.setCentralWidget(self.frame)


app = QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec())

Я уже искал в инете такое поведение, но ничего не нашел. Что я могу сделать, чтобы снова увидеть флажки после очистки фильтра подстановочных знаков? Возможно, стоит снова вызвать openPersistentEditor, но как узнать, когда?

КСТАТИ: Есть ли описание взаимодействия между делегатами, представлениями, моделями и так далее? До сих пор я нашел только документацию по классам и очень простое описание. архитектуры представления модели.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
56
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Прежде всего, я настоятельно рекомендую вам внимательно прочитать все Руководство по программированию модели/представления.

При использовании прокси-моделей любое изменение фильтра может привести к аннулированию текущих индексов при их удалении.

Ваш первый вызов openPersistentEditor() создаст редакторы для существующих индексов, но изменение фильтра прокси также приведет к уничтожению этих индексов, что автоматически удалит связанные с ними редакторы.

При изменении фильтра (возможно, «снятии фильтрации» ранее скрытых индексов) будут показаны новые индексы. Неважно, что они виртуально существовали до этого: с точки зрения QTableView это новые индексы, а значит, для них необходимо создавать новые постоянные редакторы.

Таким образом, возможное решение — сделать то, что вы уже делаете при инициализации, а именно вызвать openPersistentEditor() для любого нового индекса. Если подумать, это имеет смысл: вы открываете редакторы, которых раньше не существовало, поэтому вы должны делать то же самое всякий раз, когда отображается «новый» индекс (то есть, восстановленный фильтром).

Этого легко достичь, подключившись к сигналу QAbstractItemModel rowsInserted.

class MyWindow(QMainWindow):
    def __init__(self, *args):
        # you should really use super!!!
        super().__init__(*args)
        ...
        proxy_model.rowsInserted.connect(self.updateEditors)

    def updateEditors(self):
        model = self.table_view.model()
        for row in range(model.rowCount()):
            index = model.index(row, 1)
            if not self.table_view.isPersistentEditorOpen(index):
                self.table_view.openPersistentEditor(index)

Обратите внимание, что более подходящее и динамичное решение должно использовать подкласс QTableView, который будет:

  • установить делегат самостоятельно;
  • реализовать аналогичный updateEditors() метод;
  • переопределить setModel(), возможно, отключить сигнал rowsInserted, если существовала предыдущая модель, вызвать реализацию по умолчанию и, в конечном итоге, подключить сигнал новой модели (если она существует) к соответствующему методу/слоту;

Хотя описанное выше может оказаться более сложным, его преимущество состоит в том, что оно всегда будет работать согласованно, независимо от того, используете ли вы исходный режим, прокси-сервер или даже вложенный прокси-сервер.

Спасибо. Я попробую это завтра. Я уже прочитал рекомендованное руководство, но не обнаружил, что прокси-модель также меняет индексы своей исходной модели. Я думал, что эти индексы изменяются только в том случае, если изменяются данные модели.

GePe 30.05.2024 05:15

Кажется, что для сортировки индексы не меняются. Я подумал, что фильтрация тоже работает.

GePe 30.05.2024 05:30

@GePe Вся концепция прокси заключается в том, что он действует как его источник, возможно, полностью скрывая его и изменяя его поведение. Насколько известно представлению, существует только одна модель. При использовании сортировки порядок индексов изменяется, но длина — нет. Если вы отфильтруете источник, прокси-сервер принципиально удалит индекс, тем самым сместив все последующие. Рассматривайте модель как массив указателей: изменение порядка этих указателей не меняет размер массива. Фильтрация меняет размер, а это означает, что отмена фильтрации приводит к появлению новых индексов.

musicamante 30.05.2024 05:35

Я знаю концепцию прокси. Но знание этого не объясняет, какое поведение изменилось.

GePe 30.05.2024 18:05

Для меня нет большой разницы между сортировкой и фильтрацией. При сортировке создается таблица перевода строк. Итак, после сортировки вам нужно предоставить представление для строки 1 еще одной строки ([b, a] => [a, b]). Фильтрация делает почти то же самое, с той лишь разницей, что таблица перевода короче количества строк данных ([b, a] => [a]). Это можно сделать, управляя количеством строк в представлении. Нет необходимости в новых индексах. Но я не нашел описания того, как работает прокси, поэтому предположил, что оба работают одинаково.

GePe 30.05.2024 18:15

Теперь я использую ваше первое решение. Чтобы использовать подкласс, мне нужно изменить автоматически сгенерированный пользовательский интерфейс (не очень хорошая идея) или добавить новый класс в дизайнер (не знаю как).

GePe 30.05.2024 18:19

@GePe Модель прокси предоставляет свои собственные индексы. Как уже говорилось, представление знает только о своей собственной модели и своих индексах, его не заботит (и не должно) о возможной базовой структуре данных. С точки зрения представления элемента фильтруемая модель аналогична удалению строки (или столбца), поэтому «восстановление» (путем отмены фильтрации) — это то же самое, что создание новой строки/столбца. Итак, ЕСТЬ необходимость в новых индексах, поскольку это и есть «нефильтрованные» индексы. Для пояснения рассмотрим случай, когда модель фильтруется перед установкой в ​​представлении: для представления »

musicamante 31.05.2024 03:05

@GePe » модель содержит только количество строк, соответствующее ее фильтрации (например, 5 строк вместо 8). Теперь предположим, что вы программно отключили прокси-фильтр после установки модели: для представления модель изменила количество строк, она ничего не знала о «нефильтрованном состоянии». Вот почему создаются новые индексы (в этом цель сигналов rowsInserted), и это причина, по которой ваш код не работает. Вы правы, что не редактируете файлы пользовательского интерфейса, но вам не нужно этого делать, если вы хотите использовать подклассы: вместо этого изучите продвигаемые виджеты.

musicamante 31.05.2024 03:11

Значит, у прокси есть индексы для общения с представлением и другие для общения с моделью? Это означает, что он выполняет некоторый перевод. Почему он не может сделать то же самое для количества строк? Модель использует сигналы, чтобы оставаться в курсе изменений данных. Таким образом, прокси-сервер также знает об этом и может обновить представление, применив фильтр или сортировку к новым данным. Если я вызываю InsertRows через прокси, он также переводит индексы.

GePe 01.06.2024 01:18

При изменении фильтра модель данных не изменяется прокси, вы не можете удалить данные, отфильтровав их представление. Таким образом, количество строк модели остается прежним, и если вы удалите фильтр, прокси-сервер отправит в представление количество строк модели, а не отфильтрованное количество строк. Поэтому нет необходимости уничтожать индексы модели данных. И если постоянный редактор подключен к индексу модели данных, он останется активным.

GePe 01.06.2024 01:24

@GePe Нет. Это так не работает. Прокси не изменяет исходные данные (или индексы). Он имеет свои собственные индексы*, которые в конечном итоге будут «переведены» в исходные данные с помощью внутреннего сопоставления. Фактически, базовая модель элементов Qt может фильтровать собственное содержимое, просто удаляя или создавая новые индексы. Кажется, вы немного не понимаете, как вообще работают модели данных и как они реализованы в Qt. Я настоятельно рекомендую вам не торопиться и внимательно изучить сопутствующие материалы, включая официальное Руководство по программированию Qt Model/View.

musicamante 01.06.2024 07:13

Другие вопросы по теме