Найти - Пользователи
Полная версия: PyQt 4 | QTableView + фильтры
Начало » GUI » PyQt 4 | QTableView + фильтры
1 2
Kyrym
Подскажите, пожалуйста, каким образом в PyQt задаются условия фильтрации по столбцам таблицы?
Вот в экселе появляется окошко, где мы ставим / убираем галочки, и строки фильтруются. А какой аналог этому окошку есть в PyQt?


В голову приходит два варианта развития событий:
1) Над каждым столбцом таблицы сделать по 1 комбобоксу со множественным выбором (хотя это сложная задача, насколько я понимаю). В комбобоксах будут списки уникальных строк по каждому столбцу. Выбираем нужные значения, по ним происходит фильтрация.
2) Где-нибудь сбоку от таблицы сделать QListView. Кликая по нужному столбцу, в QListView будет появляться список уникальных значений выбранного столбца. Далее понятно.

Но может в Qt уже заложен какой-то простой инструмент?
vic57
есть метод QTableView.setRowHidden это проще всего
список значения для фильтра надо самому делать, я применял QLineEdit/QComboBox
а вообще применяется модель-посредник
http://doc.crossplatform.ru/qt/4.8.x/html-qt/itemviews-customsortfiltermodel.html
Kyrym
vic57, а QComboBox с выбором одного значения?
py.user.next
Kyrym
А какой аналог этому окошку есть в PyQt?
Это похоже на дерево с чекбоксами. QTreeView + QCheckBox.
Kyrym
Думается мне, что дерево с чекбоксами - это громко сказано. Дерево может быть актуально только для столбцов с датами, где идёт группировка по годам, а так формировать QTreeView особо не из чего. Хотя стоит об этом поразмышлять - унификация и всё такое…
vic57
Kyrym
vic57, а QComboBox с выбором одного значения?
можно и так
кстати комбобокс можно сделать setEditable(True), получается LineEdit+ComboBox с сохранением истории
Kyrym
Подскажите, пожалуйста, для класса QAbstractItemView в чём разница между сигналами: clicked и pressed? По описанию, вроде бы, одно и тоже, да и в их работе я разницы не заметил.
vic57
ну если в терминах JavaScript
clicked <-> onmousedown + onmouseup
pressed <-> onmousedown
Kyrym
Делаю 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_())
PEHDOM
Kyrym
Проблема в том, что почему-то не формируется список индексов открых строк.
Вот конструкция:
это потому что вы формируете ХЗ что, но не список открытых строк . Если бы вы делали фильтр с помощью setRowHidden() тогда оно бы вам честно говорило что строка (не)скрыта. Вы же делаете фильтр через QSortFilterProxyModel , оно там по другому работает. И когда вы перебираете построчно вашу таблицу, например при фильтре по апельсинам, то оно считает что у вас всего одна строка в таблице “6, апельсины,……”. естественно она видимая, а все остальные строки пустые , но тоже видимые. както так.
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB