Как вставлять виджеты между QHeaderView и QTableView?

Я хотел бы отобразить виджеты между QHeaderView и остальной частью QTableView, как на приведенном ниже примере изображения (создано с помощью Photoshop), так как это кажется естественным способом включения ввода для фильтрации столбцов.


Есть ли у кого-нибудь идеи о том, как вставлять виджеты между ними?


введите здесь описание изображения


person timmwagener    schedule 03.06.2017    source источник


Ответы (2)


Ниже представлена ​​демонстрация класса FilterHeader, которую я написал для одного из своих проектов. Возможно, вам придется адаптировать его под свои нужды, но он уже должен делать то, что вам нужно. Отступы вокруг полей фильтра вряд ли будут работать одинаково на всех платформах, поэтому вам может потребоваться настроить код в методе adjustPositions.

скриншот

import sys
from PyQt4 import QtCore, QtGui

class FilterHeader(QtGui.QHeaderView):
    filterActivated = QtCore.pyqtSignal()

    def __init__(self, parent):
        super().__init__(QtCore.Qt.Horizontal, parent)
        self._editors = []
        self._padding = 4
        self.setStretchLastSection(True)
        self.setResizeMode(QtGui.QHeaderView.Stretch)
        self.setDefaultAlignment(
            QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
        self.setSortIndicatorShown(False)
        self.sectionResized.connect(self.adjustPositions)
        parent.horizontalScrollBar().valueChanged.connect(
            self.adjustPositions)

    def setFilterBoxes(self, count):
        while self._editors:
            editor = self._editors.pop()
            editor.deleteLater()
        for index in range(count):
            editor = QtGui.QLineEdit(self.parent())
            editor.setPlaceholderText('Filter')
            editor.returnPressed.connect(self.filterActivated.emit)
            self._editors.append(editor)
        self.adjustPositions()

    def sizeHint(self):
        size = super().sizeHint()
        if self._editors:
            height = self._editors[0].sizeHint().height()
            size.setHeight(size.height() + height + self._padding)
        return size

    def updateGeometries(self):
        if self._editors:
            height = self._editors[0].sizeHint().height()
            self.setViewportMargins(0, 0, 0, height + self._padding)
        else:
            self.setViewportMargins(0, 0, 0, 0)
        super().updateGeometries()
        self.adjustPositions()

    def adjustPositions(self):
        for index, editor in enumerate(self._editors):
            height = editor.sizeHint().height()
            editor.move(
                self.sectionPosition(index) - self.offset() + 2,
                height + (self._padding // 2))
            editor.resize(self.sectionSize(index), height)

    def filterText(self, index):
        if 0 <= index < len(self._editors):
            return self._editors[index].text()
        return ''

    def setFilterText(self, index, text):
        if 0 <= index < len(self._editors):
            self._editors[index].setText(text)

    def clearFilters(self):
        for editor in self._editors:
            editor.clear()


class Window(QtGui.QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.view = QtGui.QTableView()
        layout = QtGui.QVBoxLayout(self)
        layout.addWidget(self.view)
        header = FilterHeader(self.view)
        self.view.setHorizontalHeader(header)
        model = QtGui.QStandardItemModel(self.view)
        model.setHorizontalHeaderLabels('One Two Three Four Five'.split())
        self.view.setModel(model)
        header.setFilterBoxes(model.columnCount())
        header.filterActivated.connect(self.handleFilterActivated)

    def handleFilterActivated(self):
        header = self.view.horizontalHeader()
        for index in range(header.count()):
            print((index, header.filterText(index)))


if __name__ == '__main__':

    app = QtGui.QApplication(sys.argv)
    window = Window()
    window.setGeometry(600, 100, 600, 300)
    window.show()
    sys.exit(app.exec_())
person ekhumoro    schedule 03.06.2017
comment
Я протестировал его (с PySide), и он работает, и это отличное начало. Тем не менее, я заметил некоторые незначительные неудобства отображения с hoverEvents (в основном просто то, что фокус исчезает/слегка мерцает без видимой причины, когда мышь находится над QLineEdit). Итак, я зарегистрировал доставку событий в QLineEdits, и кажется, что все события перехватываются FilterView. (Что, вероятно, молча распределяет его по QLineEdits, поскольку набор текста и т. Д. Все еще работает). Тем не менее, это заставляет меня задаться вопросом, связано ли это с sizeHint() FilterView, охватывающим область QLineEdits, и может ли это вызвать проблемы? - person timmwagener; 07.06.2017
comment
@тимвагенер. У меня все отлично работает как с PyQt4, так и с PySide (в Linux). Я думаю, что проблема должна быть на вашей стороне. В коде, который я разместил, нет ничего, что могло бы вызвать описанную вами проблему. - person ekhumoro; 07.06.2017
comment
Хорошо, проблема возникает в Windows 7 с включенным рабочим столом Aero (по умолчанию). Редактирование строк в представлении заголовка отображает поведение выделения, отличное от редактирования строк, добавленных в обычный макет. Вот небольшой мод. вашего примера, чтобы смоделировать его. Когда рабочий стол Aero не включен, редактирование строк не выделяется, поэтому разница не видна. Меня беспокоит менее просто визуальный шум (хотя я не мог использовать его таким образом), а больше то, что, возможно, намекает на какую-то проблему с порядком update/paintEvent!? - person timmwagener; 07.06.2017
comment
@тимвагенер. Боюсь, мне нечего добавить, так как я не могу тестировать в Windows. Может быть, попробуйте проверить, как часто вызывается updateGeometries. Кажется, вы обнаружили возможную ошибку Qt в плагине стиля Aero. Но вам нужно намного сократить пример, чтобы вы могли выделить реальную причину. Для начала я хотел бы посмотреть, возможно ли воспроизвести проблему вне представления заголовка. - person ekhumoro; 07.06.2017
comment
Я мог бы исправить эту проблему с подсветкой, создав LineEdits в TableView, а не в HeaderView. Теперь они получают все обновления от сигналов и размещаются правильно, но отображают то же поведение подсветки, что и обычно. Я подозреваю, что это как-то связано с updateGeometries() запуском событий/обновлений для всех дочерних элементов HeaderView, что приводит к тому, что редактирование строки теряет фокус или около того. Но тут только угадаю. - person timmwagener; 08.06.2017
comment
@тимвагенер. Я изменил свой демонстрационный код в соответствии с вашим предложением, поскольку он не влияет на то, как он работает в Linux, и поэтому должен дать более общий ответ. Проблема, безусловно, вызвана ошибкой в ​​стилевом плагине Aero, поскольку поведение фокуса не должно произвольно зависеть от того, какой родительский виджет используется. - person ekhumoro; 08.06.2017
comment
Вы когда-нибудь пытались установить view.setSortingEnabled(True) в табличном представлении, содержащем новый экземпляр представления заголовка? У меня возникла проблема, что представление больше не хочет сортироваться. Как будто сигнал из нового представления заголовка не подключен к измененному слоту сортировки представления после вызова view.setHorizontalHeader(header). Это также не относится к FilterHeader, но происходит только при установке новых ванильных экземпляров QtGui.QHeaderView. Индикатор сортировки отображается, но никакой сортировки не происходит. Если новый вид заголовка не установлен, сортировка работает должным образом. - person timmwagener; 10.06.2017
comment
Я разместил проблему сортировки с конкретным демонстрационным кодом в качестве нового вопроса здесь - person timmwagener; 10.06.2017

Вот ответ @ekhumoro, перенесенный на PyQt5 и с небольшим изменением - добавлено поле со списком в 4-м столбце.

введите здесь описание изображения

    import sys
    from PyQt5 import QtCore, QtGui
    from PyQt5.QtWidgets import QHeaderView, QWidget, QLineEdit, QApplication, QTableView, QVBoxLayout, QLineEdit, QComboBox
    from PyQt5.QtCore import pyqtSignal
    
    class FilterHeader(QHeaderView):
        filterActivated = QtCore.pyqtSignal()
    
        def __init__(self, parent):
            super().__init__(QtCore.Qt.Horizontal, parent)
            self._editors = []
            self._padding = 4
            self.setStretchLastSection(True)
            #self.setResizeMode(QHeaderView.Stretch)
            self.setDefaultAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
            self.setSortIndicatorShown(False)
            self.sectionResized.connect(self.adjustPositions)
            parent.horizontalScrollBar().valueChanged.connect(self.adjustPositions)
    
        def setFilterBoxes(self, count):
            while self._editors:
                editor = self._editors.pop()
                editor.deleteLater()
            for index in range(count):
                if index == 3:
                    editor = QComboBox(self.parent())
                    #editor.returnPressed.connect(self.filterActivated.emit)
                    editor.addItems(["One","Two"])
                else:
                    editor = QLineEdit(self.parent())
                    editor.setPlaceholderText('Filter')
                    editor.returnPressed.connect(self.filterActivated.emit)                
                self._editors.append(editor)
            self.adjustPositions()
    
        def sizeHint(self):
            size = super().sizeHint()
            if self._editors:
                height = self._editors[0].sizeHint().height()
                size.setHeight(size.height() + height + self._padding)
            return size
    
        def updateGeometries(self):
            if self._editors:
                height = self._editors[0].sizeHint().height()
                self.setViewportMargins(0, 0, 0, height + self._padding)
            else:
                self.setViewportMargins(0, 0, 0, 0)
            super().updateGeometries()
            self.adjustPositions()
    
        def adjustPositions(self):
            for index, editor in enumerate(self._editors):
                height = editor.sizeHint().height()
                editor.move( self.sectionPosition(index) - self.offset() + 2, height + (self._padding // 2))
                editor.resize(self.sectionSize(index), height)
    
        def filterText(self, index):
            if 0 <= index < len(self._editors):
                return self._editors[index].text()
            return ''
    
        def setFilterText(self, index, text):
            if 0 <= index < len(self._editors):
                self._editors[index].setText(text)
    
        def clearFilters(self):
            for editor in self._editors:
                editor.clear()
    
    
    class Window(QWidget):
        def __init__(self):
            super(Window, self).__init__()
            self.view = QTableView()
            layout = QVBoxLayout(self)
            layout.addWidget(self.view)
            header = FilterHeader(self.view)
            self.view.setHorizontalHeader(header)
            model = QtGui.QStandardItemModel(self.view)
            model.setHorizontalHeaderLabels('One Two Three Four Five'.split())
            self.view.setModel(model)
            header.setFilterBoxes(model.columnCount())
            header.filterActivated.connect(self.handleFilterActivated)
    
        def handleFilterActivated(self):
            header = self.view.horizontalHeader()
            for index in range(header.count()):
                print((index, header.filterText(index)))
    
    
    if __name__ == '__main__':
    
        app = QApplication(sys.argv)
        window = Window()
        window.setGeometry(600, 100, 600, 300)
        window.show()
        sys.exit(app.exec_())
person Oak_3260548    schedule 10.08.2020