Karnisov
Karnisov

Reputation: 552

PySide/PyQt4: adding a checkbox to QTableWidget Horizontal (column) Header

I am trying to put a checkbox in the horizontal (column) header of my tablewidget. Based on the other post here (because the base object type is the same), I have tried this:

item = QtGui.QTableWidgetItem()
item.setCheckState(QtCore.Qt.Checked)
self.tableWidget.setHorizontalHeaderItem(1, item)

and I have also tried this:

self.tableWidget.horizontalHeaderItem(1).setCheckState(QtCore.Qt.Checked)

Neither of these produce a checkbox in the horizontal header. Suggestions are appreciated.

Upvotes: 5

Views: 6591

Answers (4)

Jannik
Jannik

Reputation: 38

Adapted from the qt-blog http://blog.qt.io/blog/2014/04/11/qt-weekly-5-widgets-on-a-qheaderview/

This can also be used to place arbitrary widgets into the header

# coding=utf-8
from PySide.QtCore import Qt
from PySide.QtGui import QHeaderView, QCheckBox
from qtpy import QtCore


class CustomHeaderView(QHeaderView):
    def __init__(self, parent=None):
        QHeaderView.__init__(self, Qt.Horizontal, parent)
        self.setMovable(True)
        self.boxes = []

        self.sectionResized.connect(self.handleSectionResized)
        self.sectionMoved.connect(self.handleSectionMoved)

    def scrollContentsBy(self, dx, dy):
        super().scrollContentsBy(dx, dy)
        if dx != 0:
            self.fixComboPositions()

    def fixComboPositions(self):
        for i in range(self.count() + 1):
            self.boxes[i].setGeometry(self.sectionViewportPosition(i), 0,
                                      self.sectionSize(i) - 5, self.height())

    @QtCore.Slot()
    def showEvent(self, e):
        for i in range(self.count() + 1):
            if len(self.boxes) <= i:
                self.boxes.append(QCheckBox(self))
            self.boxes[i].setGeometry(self.sectionViewportPosition(i), 0,
                                      self.sectionSize(i) - 5, self.height())
            self.boxes[i].show()

        super().showEvent(e)

    @QtCore.Slot()
    def handleSectionMoved(self, logical, oldVisualIndex, newVisualIndex):
        for i in range(min(oldVisualIndex, newVisualIndex),self.count()):
            logical = self.logicalIndex(i)
            self.boxes[logical].setGeometry(self.sectionViewportPosition(logical), 0,
                                            self.sectionSize(logical) - 5, self.height())

    @QtCore.Slot()
    def handleSectionResized(self, i):
        for j in range(self.visualIndex(i),self.count()):
            logical = self.logicalIndex(j)
            self.boxes[logical].setGeometry(self.sectionViewportPosition(logical), 0,
                                            self.sectionSize(logical) - 5, self.height())
            self.boxes[logical].updateGeometry()

Upvotes: 0

Pa_
Pa_

Reputation: 521

As a thank to Gary for his answer, here's a modified version of his checkable header, where all the sections are checkable separately

class QCheckableHeader(QHeaderView):

def __init__(self, orientation, parent=None):
    QHeaderView.__init__(self, orientation, parent)
    self.lisCheckboxes = []
    self.sectionCountChanged.connect(self.onSectionCountChanged)

def paintSection(self, painter, rect, logicalIndex):
    print "paintSection", logicalIndex
    painter.save()
    QHeaderView.paintSection(self, painter, rect, logicalIndex)
    painter.restore()
    painter.save()
    painter.translate(rect.topLeft())

    option = QStyleOptionButton()
    option.rect = QRect(10, 10, 10, 10)
    if (len(self.lisCheckboxes) != self.count()):
        self.onSectionCountChanged(len(self.lisCheckboxes), self.count())

    if self.lisCheckboxes[logicalIndex]:
        option.state = QStyle.State_On
    else:
        option.state = QStyle.State_Off
    self.style().drawControl(QStyle.CE_CheckBox, option, painter)
    painter.restore()

def mousePressEvent(self, event):

    iIdx = self.logicalIndexAt(event.pos())
    self.lisCheckboxes[iIdx] = not self.lisCheckboxes[iIdx]
    self.updateSection(iIdx)
    QHeaderView.mousePressEvent(self, event)

@QtCore.Slot()
def onSectionCountChanged(self, oldCount,  newCount):
    if newCount > oldCount:
        for i in range(newCount - oldCount):
            self.lisCheckboxes.append(False)
    else:
        self.lisCheckboxes = self.lisCheckboxes[0:newCount]

Hope it helps someone else other than me too :-)

Upvotes: 3

Karnisov
Karnisov

Reputation: 552

I gave Gary Hughes the credit because his actually puts the checkbox into the header section itself, but I thought I would go ahead an post my simpler solution in case anyone wants to do it the easy way. This is based off the advice I got over at the Qt Developer forums:

I subclassed QTableWidget to make checkboxes child widgets of horizontalHeader(), and to manually reposition the checkboxes when table resizes:

class custom_table(QtGui.QTableWidget):

    def __init__(self, parent=None):
        QtGui.QTableWidget.__init__(self, parent)
        self.chkbox1 = QtGui.QCheckBox(self.horizontalHeader())

    def resizeEvent(self, event=None):
        super().resizeEvent(event)
        self.chkbox1.setGeometry(QtCore.QRect((self.columnWidth(0)/2), 2, 16, 17))

The "(self.columnWidth(0)/2)" keeps the checkbox in the middle of the column header.

Upvotes: 2

Gary Hughes
Gary Hughes

Reputation: 4510

It's not all that pretty, but a solution to this was posted in a FAQ on the qt-project.org site.

I've adapted the solution for Python and made some of the changes suggested in the comments.

from PyQt4.QtCore import Qt, QRect
from PyQt4.QtGui import QTableWidget, QApplication, QHeaderView, QStyleOptionButton, QStyle

import sys

class MyHeader(QHeaderView):

    isOn = False

    def __init__(self, orientation, parent=None):
        QHeaderView.__init__(self, orientation, parent)

    def paintSection(self, painter, rect, logicalIndex):
        painter.save()
        QHeaderView.paintSection(self, painter, rect, logicalIndex)
        painter.restore()

        if logicalIndex == 0:
            option = QStyleOptionButton()
            option.rect = QRect(10, 10, 10, 10)
            if self.isOn:
                option.state = QStyle.State_On
            else:
                option.state = QStyle.State_Off
            self.style().drawControl(QStyle.CE_CheckBox, option, painter)

    def mousePressEvent(self, event):
        self.isOn = not self.isOn
        self.updateSection(0)
        QHeaderView.mousePressEvent(self, event)

class MyTable(QTableWidget):
    def __init__(self):
        QTableWidget.__init__(self, 3, 3)

        myHeader = MyHeader(Qt.Horizontal, self)
        self.setHorizontalHeader(myHeader)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    myTable = MyTable()
    myTable.show()
    sys.exit(app.exec_())

Upvotes: 6

Related Questions