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





Прежде всего, я настоятельно рекомендую вам внимательно прочитать все Руководство по программированию модели/представления.
При использовании прокси-моделей любое изменение фильтра может привести к аннулированию текущих индексов при их удалении.
Ваш первый вызов 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 Вся концепция прокси заключается в том, что он действует как его источник, возможно, полностью скрывая его и изменяя его поведение. Насколько известно представлению, существует только одна модель. При использовании сортировки порядок индексов изменяется, но длина — нет. Если вы отфильтруете источник, прокси-сервер принципиально удалит индекс, тем самым сместив все последующие. Рассматривайте модель как массив указателей: изменение порядка этих указателей не меняет размер массива. Фильтрация меняет размер, а это означает, что отмена фильтрации приводит к появлению новых индексов.
Я знаю концепцию прокси. Но знание этого не объясняет, какое поведение изменилось.
Для меня нет большой разницы между сортировкой и фильтрацией. При сортировке создается таблица перевода строк. Итак, после сортировки вам нужно предоставить представление для строки 1 еще одной строки ([b, a] => [a, b]). Фильтрация делает почти то же самое, с той лишь разницей, что таблица перевода короче количества строк данных ([b, a] => [a]). Это можно сделать, управляя количеством строк в представлении. Нет необходимости в новых индексах. Но я не нашел описания того, как работает прокси, поэтому предположил, что оба работают одинаково.
Теперь я использую ваше первое решение. Чтобы использовать подкласс, мне нужно изменить автоматически сгенерированный пользовательский интерфейс (не очень хорошая идея) или добавить новый класс в дизайнер (не знаю как).
@GePe Модель прокси предоставляет свои собственные индексы. Как уже говорилось, представление знает только о своей собственной модели и своих индексах, его не заботит (и не должно) о возможной базовой структуре данных. С точки зрения представления элемента фильтруемая модель аналогична удалению строки (или столбца), поэтому «восстановление» (путем отмены фильтрации) — это то же самое, что создание новой строки/столбца. Итак, ЕСТЬ необходимость в новых индексах, поскольку это и есть «нефильтрованные» индексы. Для пояснения рассмотрим случай, когда модель фильтруется перед установкой в представлении: для представления »
@GePe » модель содержит только количество строк, соответствующее ее фильтрации (например, 5 строк вместо 8). Теперь предположим, что вы программно отключили прокси-фильтр после установки модели: для представления модель изменила количество строк, она ничего не знала о «нефильтрованном состоянии». Вот почему создаются новые индексы (в этом цель сигналов rowsInserted), и это причина, по которой ваш код не работает. Вы правы, что не редактируете файлы пользовательского интерфейса, но вам не нужно этого делать, если вы хотите использовать подклассы: вместо этого изучите продвигаемые виджеты.
Значит, у прокси есть индексы для общения с представлением и другие для общения с моделью? Это означает, что он выполняет некоторый перевод. Почему он не может сделать то же самое для количества строк? Модель использует сигналы, чтобы оставаться в курсе изменений данных. Таким образом, прокси-сервер также знает об этом и может обновить представление, применив фильтр или сортировку к новым данным. Если я вызываю InsertRows через прокси, он также переводит индексы.
При изменении фильтра модель данных не изменяется прокси, вы не можете удалить данные, отфильтровав их представление. Таким образом, количество строк модели остается прежним, и если вы удалите фильтр, прокси-сервер отправит в представление количество строк модели, а не отфильтрованное количество строк. Поэтому нет необходимости уничтожать индексы модели данных. И если постоянный редактор подключен к индексу модели данных, он останется активным.
@GePe Нет. Это так не работает. Прокси не изменяет исходные данные (или индексы). Он имеет свои собственные индексы*, которые в конечном итоге будут «переведены» в исходные данные с помощью внутреннего сопоставления. Фактически, базовая модель элементов Qt может фильтровать собственное содержимое, просто удаляя или создавая новые индексы. Кажется, вы немного не понимаете, как вообще работают модели данных и как они реализованы в Qt. Я настоятельно рекомендую вам не торопиться и внимательно изучить сопутствующие материалы, включая официальное Руководство по программированию Qt Model/View.
Спасибо. Я попробую это завтра. Я уже прочитал рекомендованное руководство, но не обнаружил, что прокси-модель также меняет индексы своей исходной модели. Я думал, что эти индексы изменяются только в том случае, если изменяются данные модели.