Реализация флага CheckBox в заголовке Excel таблицы (посредством QHeaderView)

Имеется динамическая exсel таблица, с изменяющимся количеством строк (наименование колонок в процессе обработки таблицы изменить нельзя, скачивается с файла).
Необходимо для дальнейшей обработки exсel таблицы определить, какие строки помечены флагом CheckBox.
Для удобства (поставить/снять все флаги) были добавлены два флага: один реализован через QCheckBox в statusbar’e окна; второй в заголовке таблицы реализован через QHeaderView.

На мой взгляд в коде имеются следующие недочеты:

  1. при добавлении новой колонки под checkbox’ы (помимо уже имеющихся в exсel таблице), визуально создается впечатление, что добавлено две колонки;
  2. checkbox в заголовке и checkbox «Весь список» в statusbar’e между собой связи не имеют, т.е. при снятии флага с одного из них (на скриншоте пример с checkbox «Весь список»), другой флаг (в checkbox в заголовке) остается;

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

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

Вопрос:

  1. возможно ли убрать визуальное отображение второй колонки в таблице? (если возможно, то как);
  2. возможно ли связать флаг QCheckBox и флаг QHeaderView, что бы статус (поставили / сняли флаг) у них менялся одновременно? (если да, то как).

Пример таблицы: https://dropmefiles.com/Y0hBz (либо с другого файлообменника https://transfiles.ru/xkzyk). Таблица для примера, можно взять любую.

Пример кода:

m5.py

import os, sys
import openpyxl
import pathlib

from PyQt5 import QtCore, QtWidgets

from PyQt5.QtWidgets import (QApplication, QHeaderView, QTableWidget, QTableWidgetItem, QPushButton,
                             QVBoxLayout, QStyle, QStyleOptionButton, QWidget, QGridLayout, QCheckBox,)
from PyQt5.QtCore import (pyqtSignal, Qt, QRect, )

class TableWidget(QTableWidget):
    def edit(self, index, trigger, event):
        if index.column() == -1:
            trigger = self.NoEditTriggers
        return super().edit(index, trigger, event)

class Tabl(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

    def setupTab(self):
        self.tableW = TableWidget()
        self.layout = QVBoxLayout(self)
        self.layout.addWidget(self.tableW)

        file = pathlib.Path("testing.xlsx")
        wb = openpyxl.load_workbook(os.path.join(os.getcwd(), file), read_only=True)
        ws = wb.active

        try: # закрыть .xlsx файл
            # headers = ["Выбрать:"] + [item.value for item in ws[1] if item.value is not None]
            headers = [item.value for item in ws[1] if item.value is not None]
            data = ws.iter_rows(min_row=2, max_col=9)

            columns = self.tableW.columnCount()
            self.tableW.setColumnCount(len(headers))
            self.tableW.setHorizontalHeaderLabels(headers)
            self.tableW.insertColumn(columns)

            chekHeader = ChekHeader(Qt.Horizontal, self)
            self.tableW.setHorizontalHeader(chekHeader)

            for x, rows in enumerate(data):
                if rows[0].value is not None:
                    self.tableW.setRowCount(self.tableW.rowCount() + 1)
                    for y, cell in enumerate(rows):
                        val = cell.value
                        if val is not None:
                            item = QTableWidgetItem(str(val))
                            self.tableW.setItem(self.tableW.rowCount() - 1, (y+1), item)
                            self.tableW.resizeColumnsToContents()

            for i in range(self.tableW.rowCount()):
                ch = QTableWidgetItem()
                ch.setFlags(ch.flags() | Qt.ItemIsUserCheckable)
                ch.setCheckState(Qt.Checked)
                self.tableW.setItem(i, 0, ch)
            return ch
        finally:
            wb.close()
        return

# __________________________________

class ChekHeader(QHeaderView):
    clicked = pyqtSignal(int)

    _x_offset = 4
    _y_offset = 0
    _width = 20
    _height = 20

    def __init__(self, orientation=Qt.Horizontal, parent=None):
        super(ChekHeader, self).__init__(orientation, parent)
        self.one = True

    def paintSection(self, painter, rect, logicalIndex):
        painter.save()
        super(ChekHeader, self).paintSection(painter, rect, logicalIndex)
        painter.restore()
        self._y_offset = int((rect.height() - self._width) / 2.)

        if logicalIndex == 0:
            try:
                option = QStyleOptionButton()
                option.rect = QRect(rect.x() + self._x_offset,
                                    rect.y() + self._y_offset, self._width, self._height)
                option.state = QStyle.State_Enabled | QStyle.State_Active
                if self.one:
                    option.state |= QStyle.State_On
                else:
                    option.state |= QStyle.State_Off
                self.style().drawControl(QStyle.CE_CheckBox, option, painter)
            except Exception as exc_:
                print('#_________________________________________')
                print(' class ChekHeader -> def paintSection -> ошибка exc_: ', exc_)
                print('#_________________________________________')

    def mousePressEvent(self, event):
        print()
        try:
            index = self.logicalIndexAt(event.pos())
            if 0 == index:
                x = self.sectionPosition(index)
                if x + self._x_offset < event.pos().x() < x + self._x_offset + self._width \
                        and self._y_offset < event.pos().y() < self._y_offset + self._height:
                    if self.one:
                        self.one = False
                        self.updateSection(0)
                    else:
                        self.one = True
                        self.updateSection(0)
                    try:
                        print('class ChekHeader -> def mousePressEvent -> self.one =', self.one)
                        self.clicked.emit(ex.state_changed(self.one))
                        self.update()
                    except Exception as exc_0:
                        print('#_________________________________________')
                        print(' class ChekHeader -> def mousePressEvent -> ошибка exc_0: ', exc_0)
                        print('#_________________________________________')
            super(ChekHeader, self).mousePressEvent(event)

        except Exception as exc_1:
            print('#_________________________________________')
            print(' class ChekHeader -> def mousePressEvent -> ошибка exc_1: ', exc_1)
            print('#_________________________________________')
# __________________________________

class Example(QtWidgets.QMainWindow):
    def __init__(self):
        super(Example, self).__init__()
        self.centralWidget = QtWidgets.QWidget()
        self.setCentralWidget(self.centralWidget)
        self.tabl = Tabl(self)
        self.tabl.setupTab()
        self.initUI()
        self.gridLayout = QGridLayout(self.centralWidget)
        self.gridLayout.addWidget(self.tabl, 0, 0, 0, 0)

    def initUI(self):
        self.btn_che = QPushButton('Счет CheckBox')
        self.btn_che.clicked.connect(self.excelCheck)

        self.excelCheckBox = QCheckBox('Весь список')
        self.excelCheckBox.stateChanged.connect(self.state_changed)
        self.excelCheckBox.setChecked(True)

        self.statusBar().addPermanentWidget(self.excelCheckBox, 1)
        self.statusBar().addPermanentWidget(self.btn_che)

    def state_changed(self, state):
        print()
        if state:
            for r in range(self.tabl.tableW.rowCount()):
                print('class Example -> def state_changed -> Стоит галка на строке: r1 =', r+1)
                item = self.tabl.tableW.item(r, 0)
                item.setCheckState(Qt.Checked)
        else:
            for r in range(self.tabl.tableW.rowCount()):
                print('class Example -> def state_changed -> Сняли галку на строке: r2 =', r+1)
                item = self.tabl.tableW.item(r, 0)
                item.setCheckState(Qt.Unchecked)

        if state == 2 or state == True:
            self.excelCheckBox.setChecked(True)
        else:
            self.excelCheckBox.setChecked(False)

    # @QtCore.pyqtSlot()
    def excelCheck(self):
        print()
        items = []
        for r in range(self.tabl.tableW.rowCount()):
            item = self.tabl.tableW.item(r, 0)
            if item.checkState() == Qt.Checked:
                items.append(item)
            print(f'class Example ->  def excelCheck -> строка {r+1} флаг {item.checkState()}')

        print()
        for it in items:
            r = it.row()
            c = it.column()
            #v = self.tabl.tableW.horizontalHeaderItem(c).text()
            #h = it.text()
            print(f'class Example ->  def excelCheck -> Выбрана строка = {r + 1} ')
        print()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    ex.resize(350, 250)
    ex.show()
    sys.exit(app.exec())

Ответы (1 шт):

Автор решения: S. Nick

Вопрос:

  • возможно ли убрать визуальное отображение второй колонки в таблице? (если возможно, то как);

Ответ: возможно, например так:

import sys
import os
import openpyxl
import pathlib
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.Qt import *


class TableWidget(QTableWidget):

    def edit(self, index, trigger, event):
        if index.column() == -1:
            trigger = self.NoEditTriggers
        return super().edit(index, trigger, event)
        

class Tabl(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

    def setupTab(self):
        self.tableW = TableWidget()
        self.layout = QVBoxLayout(self)
        self.layout.addWidget(self.tableW)

        file = pathlib.Path("testing.xlsx")
        wb = openpyxl.load_workbook(os.path.join(os.getcwd(), file), read_only=True)
        ws = wb.active

        try:# закрыть .xlsx файл
            # headers = ["Выбрать:"] + [item.value for item in ws[1] if item.value is not None]
            headers = [item.value for item in ws[1] if item.value is not None]
            data = ws.iter_rows(min_row=2, max_col=9)

            columns = self.tableW.columnCount()
            self.tableW.setColumnCount(len(headers))
            self.tableW.setHorizontalHeaderLabels(headers)
            self.tableW.insertColumn(columns)

            chekHeader = ChekHeader(Qt.Horizontal, self)
            self.tableW.setHorizontalHeader(chekHeader)

            for x, rows in enumerate(data):
                if rows[0].value is not None:
                    self.tableW.setRowCount(self.tableW.rowCount() + 1)
                    for y, cell in enumerate(rows):
                        val = cell.value
                        if val is not None:
                            item = QTableWidgetItem(str(val))
                            self.tableW.setItem(self.tableW.rowCount() - 1, (y+1), item)
                            self.tableW.resizeColumnsToContents()
            
            for row in range(self.tableW.rowCount()):
                ''' убираем
                ch = QTableWidgetItem()
                ch.setFlags(ch.flags() | Qt.ItemIsUserCheckable)
                ch.setCheckState(Qt.Checked)
                self.tableW.setItem(row, 0, ch)
                '''
# !!! +++       vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv       добавляем
                widget   = QWidget()
                checkbox = QCheckBox()
                checkbox.setCheckState(Qt.Checked)                
                layoutH = QHBoxLayout(widget)
                layoutH.addWidget(checkbox)
                layoutH.setAlignment(Qt.AlignCenter)
                layoutH.setContentsMargins(0, 0, 0, 0)
                self.tableW.setCellWidget(row, 0, widget) 
# ???            return ch
        finally:
            wb.close()
# ???        return


class ChekHeader(QHeaderView):
    clicked = pyqtSignal(int)

    _x_offset = 4
    _y_offset = 0
    _width = 20
    _height = 20

    def __init__(self, orientation=Qt.Horizontal, parent=None):
        super(ChekHeader, self).__init__(orientation, parent)
        self.one = True

    def paintSection(self, painter, rect, logicalIndex):
        painter.save()
        super(ChekHeader, self).paintSection(painter, rect, logicalIndex)
        painter.restore()
        self._y_offset = int((rect.height() - self._width) / 2.)

        if logicalIndex == 0:
            try:
                option = QStyleOptionButton()
                option.rect = QRect(rect.x() + self._x_offset,
                                    rect.y() + self._y_offset, self._width, self._height)
                option.state = QStyle.State_Enabled | QStyle.State_Active
                if self.one:
                    option.state |= QStyle.State_On
                else:
                    option.state |= QStyle.State_Off
                self.style().drawControl(QStyle.CE_CheckBox, option, painter)
            except Exception as exc_:
                print('#_________________________________________')
                print(' class ChekHeader -> def paintSection -> ошибка exc_: ', exc_)
                print('#_________________________________________')

    def mousePressEvent(self, event):
        print()
        try:
            index = self.logicalIndexAt(event.pos())
            if 0 == index:
                x = self.sectionPosition(index)
                if x + self._x_offset < event.pos().x() < x + self._x_offset + self._width \
                        and self._y_offset < event.pos().y() < self._y_offset + self._height:
                    if self.one:
                        self.one = False
                        self.updateSection(0)
                    else:
                        self.one = True
                        self.updateSection(0)
                    try:
                        print('class ChekHeader -> def mousePressEvent -> self.one =', self.one)
                        self.clicked.emit(ex.state_changed(self.one))
                        self.update()
                    except Exception as exc_0:
                        print('#_________________________________________')
                        print(' class ChekHeader -> def mousePressEvent -> ошибка exc_0: ', exc_0)
                        print('#_________________________________________')
            super(ChekHeader, self).mousePressEvent(event)

        except Exception as exc_1:
            print('#_________________________________________')
            print(' class ChekHeader -> def mousePressEvent -> ошибка exc_1: ', exc_1)
            print('#_________________________________________')


class Example(QtWidgets.QMainWindow):
    def __init__(self):
        super(Example, self).__init__()
        
        self.centralWidget = QtWidgets.QWidget()
        self.setCentralWidget(self.centralWidget)
        
        self.tabl = Tabl(self)
        self.tabl.setupTab()
        
        self.initUI()
        
        self.gridLayout = QGridLayout(self.centralWidget)
        self.gridLayout.addWidget(self.tabl, 0, 0, 0, 0)

    def initUI(self):
        self.btn_che = QPushButton('Счет CheckBox')
        self.btn_che.clicked.connect(self.excelCheck)

        self.excelCheckBox = QCheckBox('Весь список')
        self.excelCheckBox.stateChanged.connect(self.state_changed)
        self.excelCheckBox.setChecked(True)

        self.statusBar().addPermanentWidget(self.excelCheckBox, 1)
        self.statusBar().addPermanentWidget(self.btn_che)

    def state_changed(self, state):
# !!! +++      # v^v^v^v^v
        if state:
            for r in range(self.tabl.tableW.rowCount()):
                print('Example -> state_changed -> Стоит галка на строке: r1 =', r+1)
#-                item = self.tabl.tableW.item(r, 0)
#-                item.setCheckState(Qt.Checked)
# !!! +++ 
                self.tabl.tableW.cellWidget(r, 0).findChild(type(QCheckBox())).\
                    setCheckState(Qt.Checked)
        else:
            for r in range(self.tabl.tableW.rowCount()):
                print('class Example -> def state_changed -> Сняли галку на строке: r2 =', r+1)
#-                item = self.tabl.tableW.item(r, 0)
#-                item.setCheckState(Qt.Unchecked)
# !!! +++
                self.tabl.tableW.cellWidget(r, 0).findChild(type(QCheckBox())).\
                    setCheckState(Qt.Unchecked)
                    
        if state == 2 or state == True:
            self.excelCheckBox.setChecked(True)
        else:
            self.excelCheckBox.setChecked(False)

    # @QtCore.pyqtSlot()
    def excelCheck(self):
        items = []
        print()
        for r in range(self.tabl.tableW.rowCount()):
#-            item = self.tabl.tableW.item(r, 0)
#-            if item.checkState() == Qt.Checked:
# !!! +++
            if self.tabl.tableW.cellWidget(r, 0).findChild(type(QCheckBox())).isChecked():
#-               items.append(item)
# !!! +++
                items.append([r, 0])
                print(f'Example -> excelCheck 111-> строка {r+1} флаг `Checked`')
# !!! +++
        if not items:
            print(f'Example -> excelCheck    -> НЕТ Выбранных строк\n')
            return
        
#-        for it in items:
# !!! +++
        for r, c in items:
#-            r = it.row()
#-            c = it.column()
            print(f'Example -> excelCheck 222-> Выбрана строка = {r + 1} ')


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Example()
    ex.resize(350, 250)
    ex.show()
    sys.exit(app.exec())

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

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

→ Ссылка