Форум сайта python.su
Подскажите, пожалуйста, каким образом в PyQt задаются условия фильтрации по столбцам таблицы?
Вот в экселе появляется окошко, где мы ставим / убираем галочки, и строки фильтруются. А какой аналог этому окошку есть в PyQt?
В голову приходит два варианта развития событий:
1) Над каждым столбцом таблицы сделать по 1 комбобоксу со множественным выбором (хотя это сложная задача, насколько я понимаю). В комбобоксах будут списки уникальных строк по каждому столбцу. Выбираем нужные значения, по ним происходит фильтрация.
2) Где-нибудь сбоку от таблицы сделать QListView. Кликая по нужному столбцу, в QListView будет появляться список уникальных значений выбранного столбца. Далее понятно.
Но может в Qt уже заложен какой-то простой инструмент?
Прикреплённый файлы:
11-1.png (31,5 KБ)
Офлайн
есть метод QTableView.setRowHidden это проще всего
список значения для фильтра надо самому делать, я применял QLineEdit/QComboBox
а вообще применяется модель-посредник
http://doc.crossplatform.ru/qt/4.8.x/html-qt/itemviews-customsortfiltermodel.html
Отредактировано vic57 (Авг. 27, 2017 18:08:56)
Офлайн
vic57, а QComboBox с выбором одного значения?
Офлайн
KyrymЭто похоже на дерево с чекбоксами. QTreeView + QCheckBox.
А какой аналог этому окошку есть в PyQt?
Офлайн
Думается мне, что дерево с чекбоксами - это громко сказано. Дерево может быть актуально только для столбцов с датами, где идёт группировка по годам, а так формировать QTreeView особо не из чего. Хотя стоит об этом поразмышлять - унификация и всё такое…
Офлайн
Kyrymможно и так
vic57, а QComboBox с выбором одного значения?
Отредактировано vic57 (Авг. 28, 2017 16:57:23)
Офлайн
Подскажите, пожалуйста, для класса QAbstractItemView в чём разница между сигналами: clicked и pressed? По описанию, вроде бы, одно и тоже, да и в их работе я разницы не заметил.
Отредактировано Kyrym (Авг. 31, 2017 12:49:39)
Офлайн
ну если в терминах JavaScript
clicked <-> onmousedown + onmouseup
pressed <-> onmousedown
Офлайн
Делаю QTableView с фильтрами. Сейчас пытаюсь это сделать через QListWidget. Смысл следующий. Запустили программу, данные базы данных автоматом подгрузились в QTableView. Теперь щёлкая по нужному столбцу таблицы, к которому требуется применить фильтр, в QListWidget будет вставляться список уникальных значений по выбранному столбцу. Теперь в QListWidget нажимаем по требуемому элементу, и таблица фильтруется.
Например, в таблице нажали на первый столбец, в QListWidget появилось 4 элемента. Выбрали мандарины, тогда в таблице останется 2 строки с мандаринами. В этот же момент создаётся список индексов открытых (нескрытых) строк. Далее жмём второй столбец, QListWidget обновляется, и теперь в нём 2 строки с числами 16 и 4. Выбирая одно из них осуществляется дальнейшая фильтрация. Так можно пройтись по всем столбцам.
Проблема в том, что почему-то не формируется список индексов открых строк.
Вот конструкция:
self.lst_open_row = [] # обнуление списка for i in range(len(db_tb)): if self.table.isRowHidden(i) == False: # строка не скрыта self.lst_open_row.append(i) # в список открытых строк / индексов print('i_hidden = ',i) print('self.lst_open_row =',self.lst_open_row)
# Python 3 # -*- coding: utf-8 -*- version = '2017.12.11' # замена листвью на листвиджет import sys, pickle from PyQt4 import QtCore, QtGui from PyQt4.QtGui import (QWidget, qApp, QAction, QApplication, QHBoxLayout, QVBoxLayout, QGridLayout, QLabel, QLineEdit, QTextEdit, QPushButton, QComboBox, QCheckBox, QRadioButton, QFrame, QScrollArea, QTabWidget, QSizePolicy, QGroupBox, QFileDialog, QMessageBox, QTableView, QHeaderView, QStandardItemModel, QStandardItem, QListView, QListWidget, QSortFilterProxyModel, QBrush, QColor) from PyQt4.QtGui import QIcon, QPixmap, QPalette, QTextCursor # ДАННЫЕ db_tb = [{'магазин 2': '12', 'магазин 3': '7', 'Фрукты': 'апельсины', 'магазин 1': '4'}, {'магазин 2': '3', 'магазин 3': '9', 'Фрукты': 'яблоки', 'магазин 1': '16'}, {'магазин 2': '44', 'магазин 3': '3', 'Фрукты': 'груши', 'магазин 1': '8'}, {'магазин 2': '2', 'магазин 3': '15', 'Фрукты': 'мандарины', 'магазин 1': '16'}, {'магазин 2': '56', 'магазин 3': '8', 'Фрукты': 'яблоки', 'магазин 1': '2'}, {'магазин 2': '74', 'магазин 3': '5', 'Фрукты': 'мандарины', 'магазин 1': '4'}] lsl_vib_column = [] # ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ columnName = ["Фрукты", "магазин 1", "магазин 2", "магазин 3"] # заголовки табл rowCount = len(db_tb) # число строк columnCount = len(columnName) # число столбцов rowHeight = 20 # высота строки db_tb_i = {} # ЦВЕТА ПОЛЕЙ sss_vivod = ("background-color: #456173; color: #f2f2f0; font: 10pt 'Courier New'") sss = ("background-color: #456173; color: #f2f2f0; font: 10pt 'Arial'") sss_err = ("color: #ff6161; font: bold 10pt 'Courier New'") # ГРАФИКА class Window(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) x_win = 500 # ширина окна # self.setMinimumSize(250, 300) # Ширина и высота окна self.resize(x_win, 800) # шир / выс окна self.setWindowTitle('QTableView + фильтры') # Заголовок self.setWindowIcon(QIcon('icon.png')) # Иконка # ПОДКЛЮЧЕНИЕ СТИЛЕЙ sss = open('dark_blue.stylesheet', 'r') self.styleData = sss.read() sss.close() self.setStyleSheet(self.styleData) # БЛОК РАЗМЕТКИ vbox_os = QVBoxLayout() # Создали объект вертикальный контейнер # >>> УПРАВЛЕНИЕ ТАБЛИЦЕЙ grid_upr_1 = QGridLayout() # сетка навигации 1 grid_upr_1.setSpacing(5) # --- --- self.pole_find = QLineEdit() grid_upr_1.addWidget(self.pole_find, 0,0,1,3) # --- self.button_up_row = QPushButton('▲') self.button_up_row.clicked.connect(self.on_up_row) grid_upr_1.addWidget(self.button_up_row, 0,4) # --- self.button_down_row = QPushButton('▼') self.button_down_row.clicked.connect(self.on_down_row) grid_upr_1.addWidget(self.button_down_row, 0,5) # --- self.button_prnt = QPushButton('Print ДБ') self.button_prnt.clicked.connect(self.on_prnt) grid_upr_1.addWidget(self.button_prnt, 0,6,1,3) # --- self.lbl_predup = QLabel('') self.lbl_predup.setStyleSheet(sss_err) grid_upr_1.addWidget(self.lbl_predup, 0,9) # --- настройка self.lbl_nas = QLabel(' ') grid_upr_1.addWidget(self.lbl_nas, 0,10) grid_upr_1.setColumnStretch(10, 1) # --- --- # >>> КОНЕЦ: УПРАВЛЕНИЕ ТАБЛИЦЕЙ # >>> ПАНЕЛЬ ПОИСКА ПО ТАБЛИЦЕ grid_find = QGridLayout() # сетка навигации 1 grid_find.setSpacing(1) # --- --- self.pix_f = QPixmap("search_512") scaled_pix = self.pix_f.scaled(25, 25) #lbl_f = QLabel('<h3><font face="Webdings">L</font></h3>') lbl_f = QLabel(self,alignment=QtCore.Qt.AlignCenter) lbl_f.setPixmap(scaled_pix) grid_find.addWidget(lbl_f, 0,0) # --- for i in range(1,columnCount+1): self.pole_find_i = QLineEdit() grid_find.addWidget(self.pole_find_i, 0,i) # >>> КОНЕЦ: ПАНЕЛЬ ПОИСКА ПО ТАБЛИЦЕ # МОДЕЛЬ (вставка данных) self.model = QStandardItemModel(rowCount,columnCount) self.model.blockSignals(True) # блокировка сигналов for row in range(0,rowCount): # вставить базу данных в таблицу for column in range(0,columnCount): item = QStandardItem(db_tb[row][columnName[column]]) self.model.setItem(row, column, item) self.model.blockSignals(False) # отмена блокировки сигналов for i in range(0, columnCount): # подписи шапки self.model.setHeaderData(i, QtCore.Qt.Horizontal, columnName[i]) self.model.itemChanged.connect(self.predup) # предупреждение об изменении ячейки # КОНЕЦ: МОДЕЛЬ # ПРЕДСТАВЛЕНИЕ (ТАБЛИЦА) self.table = QTableView() # создаём экз таблицы self.table.setModel(self.model) # применяем модель к таблице self.table.setSortingEnabled(True) # можно сортировать кликом мышки self.table.setWordWrap(True) # текст можно перенести на другую строку self.table.setTextElideMode(3) # текст не обрезается # self.table.horizontalHeader().setStyleSheet("QHeaderView::section{font-weight:bold; color:#46647F; height:26px}") self.table.horizontalHeader().setResizeMode(QHeaderView.Stretch) # растянуть таблицу по горизонтали self.table.clicked.connect(self.lst_filter_lw) # создание списка фильтра # Устанавливаем высоту строк for i in range(0, rowCount): self.table.setRowHeight(i, rowHeight) # КОНЕЦ: ПРЕДСТАВЛЕНИЕ (ТАБЛИЦА) grid_down = QGridLayout() # нижняя сетка для фильтра и карточки grid_down.setSpacing(5) # настройка grid_down.setColumnMinimumWidth(0, 100) # ширина панели фильтрации grid_down.setColumnMinimumWidth(1, x_win-100) # ширина панели фильтра grid_down.setColumnStretch(1, 1) # растяжение карточки # >>> ПАНЕЛЬ ФИЛЬТРА self.list = QListWidget() grid_down.addWidget(self.list, 0,0) # --- --- self.button_del_filter = QPushButton('Сбросить фильтр') self.button_del_filter.clicked.connect(self.del_filter) grid_down.addWidget(self.button_del_filter, 1,0) # >>> КОНЕЦ: ПАНЕЛЬ ФИЛЬТРА # >>> КАРТОЧКА grid_cart = QGridLayout() grid_cart.setSpacing(5) grid_down.addLayout(grid_cart, 0,1,1,3) # --- --- lbl_1 = QLabel(columnName[0]) grid_cart.addWidget(lbl_1, 0,0) # --- self.pole_1 = QLineEdit() grid_cart.addWidget(self.pole_1, 0,1) # --- --- lbl_2 = QLabel(columnName[1]) grid_cart.addWidget(lbl_2, 1,0) # --- self.pole_2 = QLineEdit() grid_cart.addWidget(self.pole_2, 1,1) # --- --- lbl_3 = QLabel(columnName[2]) grid_cart.addWidget(lbl_3, 2,0) # --- self.pole_3 = QLineEdit() grid_cart.addWidget(self.pole_3, 2,1) # --- --- lbl_4 = QLabel(columnName[3]) grid_cart.addWidget(lbl_4, 3,0) # --- self.pole_4 = QLineEdit() grid_cart.addWidget(self.pole_4, 3,1) # --- --- lbl_end = QLabel('') grid_cart.addWidget(lbl_end, 20,0) # >>> КОНЕЦ: КАРТОЧКА # >>> ПАНЕЛЬ УПРАВЛЕНИЯ КАРТОЧКОЙ grid_upr_cart = QGridLayout() grid_upr_cart.setSpacing(5) grid_down.addLayout(grid_upr_cart, 1,1,1,3) # --- --- self.button_save_db = QPushButton('Сохр БД') self.button_save_db.clicked.connect(self.save_in_file) grid_upr_cart.addWidget(self.button_save_db, 0,0,1,3) # --- --- self.button_edit_row = QPushButton('Ред стр') self.button_edit_row.clicked.connect(self.on_edit_row) grid_upr_cart.addWidget(self.button_edit_row, 0,3,1,3) # --- self.button_save_row = QPushButton('Сохр стр') self.button_save_row.clicked.connect(self.on_save_row) grid_upr_cart.addWidget(self.button_save_row, 0,6,1,3) # --- self.button_del_row = QPushButton('Удалить стр') self.button_del_row.clicked.connect(self.on_del_row) grid_upr_cart.addWidget(self.button_del_row, 0,9,1,3) # --- настройка self.lbl_nas = QLabel('') grid_upr_cart.addWidget(self.lbl_nas, 0,12) grid_upr_cart.setColumnStretch(12, 1) # >>> КОНЕЦ: ПАНЕЛЬ УПРАВЛЕНИЯ КАРТОЧКОЙ # --- --- vbox_os.addLayout(grid_upr_1) vbox_os.addLayout(grid_find) vbox_os.addWidget(self.table) vbox_os.addLayout(grid_down) # --- --- self.setLayout(vbox_os) # --- переменные --- self.lst_filter_lw_uni = [] self.lst_open_row = [] self.model_filter_lw = None self.model_filter_tv = None # ========= ФУНКЦИИ ТАБЛИЦЫ def db_tb_in_cell_tabl(self, i,j): # из базы данных в ячейку print('i =',i) print('j =',j) print('columnName[j] =',columnName[j]) print('db_tb =',db_tb) item = Qself.tableWidgetItem() item.setText(str(db_tb[i][columnName[j]])) self.self.table.setItem(i,j,item) def on_add_row (self): # добавить строку в базу # сбор данных для 1 строки db_tb_i = dict(zip(columnName, [polya.text() for polya in self.polya])) # конец: сбор данных db_tb.append(db_tb_i) # данные для новой строки табл row_index = len(db_tb)-1 self.table.insertRow(row_index) self.paste_row(row_index,db_tb_i) self.predup() def paste_row(self, row,text): self.table.blockSignals(True) # блокировка сигналов self.table.setRowHeight(row, rowHeight) # высота строки for j in range(0,columnCount): # в таблицу вставляем значения self.db_in_cell_tabl(row,j) self.table.blockSignals(False) # отмена блокировки сигналов self.clear_polya() # очищаем поля def cell_tabl_in_db(self): # из ячейки табл в базу данных i = self.table.currentRow() # индекс выделенной строки j = self.table.currentColumn() # индекс выделенной строки text = self.table.item(i,j).text() # извлечь текст из ячейки db_tb[i][columnName[j]] = text # текст в базу def predup (self): # предупреждение об изменении данных ячейки self.lbl_predup.setText('Изменения не сохранены') def on_up_row (self): # перемещение строки вверх row_index = self.table.currentRow() # индекс выделенной строки if row_index == 0: pass else: db_tb.insert(row_index-1,db_tb[row_index]) # вставить строку в базу del db_tb[row_index+1] # удалить старую строку из базы self.table.insertRow(row_index-1) # вставить строку в табл self.table.removeRow(row_index+1) # удалить старую строку из табл self.paste_row(row_index-1,db_tb[row_index-1]) self.table.selectRow(row_index-1) self.predup() def on_down_row (self): # перемещение строки вниз row_index = self.table.currentRow() # индекс выделенной строки if row_index+1 == len(db_tb): pass else: db_tb.insert(row_index+2,db_tb[row_index]) # вставить строку в базу del db_tb[row_index] # удалить старую строку из базы self.table.insertRow(row_index+2) # вставить строку в табл self.table.removeRow(row_index) # удалить старую строку из табл self.paste_row(row_index+1,db_tb[row_index+1]) self.table.selectRow(row_index+1) # выделяет передвинутую строку self.predup() def on_del_row (self): row_index = self.table.currentRow() # индекс выделенной строки del db_tb[row_index] # удалить строку в базе self.table.removeRow(row_index) # удалить строку в таблице self.predup() def on_edit_row(self): # правка строки в полях ввода row_index = self.table.currentRow() # индекс выделенной строки db_tb_i = db_tb[row_index] for i in range(0,len(self.polya)): # вставить строку из таблицы в поля self.polya[i].setText(db_tb_i[columnName[i]]) def on_save_row(self): # изм строку в полях отправить в таблицу # сбор данных для 1 строки db_tb_i = dict(zip(columnName, [polya.text() for polya in self.polya])) # конец: сбор данных row_index = self.table.currentRow() # индекс выделенной строки db_tb[row_index].update(db_tb_i) # обновление словаря self.paste_row(row_index,db_tb_i) self.clear_polya() self.predup() def save_in_file(self): # сохранить базу в файл with open('database4.db', 'wb') as f: pickle.dump(db_tb, f) self.lbl_predup.setText('') def clear_polya(self): # очистить поля ввода for i in range(0,len(self.polya)): self.polya[i].clear() # === >>> ФИЛЬТР LISTWIDGET === def lst_filter_lw(self): # формирование списка значений фильтра index_column = self.table.currentIndex().column() lst_filter_lw = [] if self.model_filter_tv == None: # если фильтр сброшен или отсутствует for i in range(0, rowCount): lst_filter_lw.append(db_tb[i][columnName[index_column]]) else: for i in range(len(self.lst_open_row)): lst_filter_lw.append(db_tb[self.lst_open_row[i]][columnName[index_column]]) #for i in range(0, rowCount): #lst_filter_lw.append(db_tb[i][columnName[index_column]]) self.lst_filter_lw_uni = [] # уникальный список print('self.lst_filter_lw_uni =',self.lst_filter_lw_uni) for i in lst_filter_lw: # формирование уникального списка if i not in self.lst_filter_lw_uni: self.lst_filter_lw_uni.append(i) self.lst_filter_lw_uni.sort() model_list = QStandardItemModel(self.list) for i in range(0, len(self.lst_filter_lw_uni)): item = QStandardItem(self.lst_filter_lw_uni[i]) model_list.appendRow(item) self.list.clear() self.list.addItems(self.lst_filter_lw_uni) self.list.clicked.connect(self.tb_filter_lw) #self.list.activated.connect(self.tb_filter_lw) # сигнал по Enter def tb_filter_lw(self): index_row = self.list.currentIndex().row() fil = self.lst_filter_lw_uni[index_row] print('fil =',fil) # >>> МОДЕЛЬ-ПОСРЕДНИК (Фильтр) self.model_filter_tv = QSortFilterProxyModel() self.model_filter_tv.setSourceModel(self.model) self.model_filter_tv.setDynamicSortFilter(True) # фильтр работает на лету self.table.setModel(self.model_filter_tv) # включение фильтра regExp = QtCore.QRegExp(fil, QtCore.Qt.CaseInsensitive) # подставляем ID в фильтр self.model_filter_tv.setFilterRegExp(regExp) # применяем фильтр # >>> КОНЕЦ: МОДЕЛЬ-ПОСРЕДНИК self.lst_open_row = [] # обнуление списка for i in range(len(db_tb)): if self.table.isRowHidden(i) == False: # строка не скрыта self.lst_open_row.append(i) # в список открытых строк / индексов print('i_hidden = ',i) print('self.lst_open_row =',self.lst_open_row) def del_filter(self): # сброс фильтра self.table.setModel(self.model) self.list.clear() self.lst_filter_lw_uni = [] self.model_filter_tv = None # === >>> КОНЕЦ: ФИЛЬТР LISTWIDGET === # ========= КОНЕЦ: ФУНКЦИИ ТАБЛИЦЫ def on_prnt(self): # вывод БД в Shell print(db_tb_i) for i in range(0,len(db_tb)): print(i,':',db_tb[i],sep='') # КОНЕЦ if __name__ == "__main__": app = QApplication(sys.argv) window = Window() window.move(40, 20) # сдвиг окна от верхнего левого угла экрана pal = window.palette() pal.setBrush(QPalette.Window, QBrush(QColor("#222831"))) window.setPalette(pal) # передаёт изменёный цвет окну window.show() # запускает окно sys.exit(app.exec_())
Отредактировано Kyrym (Дек. 11, 2017 16:17:57)
Прикреплённый файлы:
QTableView + фильтры_007.png (45,8 KБ)
Офлайн
Kyrymэто потому что вы формируете ХЗ что, но не список открытых строк . Если бы вы делали фильтр с помощью setRowHidden() тогда оно бы вам честно говорило что строка (не)скрыта. Вы же делаете фильтр через QSortFilterProxyModel , оно там по другому работает. И когда вы перебираете построчно вашу таблицу, например при фильтре по апельсинам, то оно считает что у вас всего одна строка в таблице “6, апельсины,……”. естественно она видимая, а все остальные строки пустые , но тоже видимые. както так.
Проблема в том, что почему-то не формируется список индексов открых строк.
Вот конструкция:
[code python][/code]
Офлайн