Реализация флага CheckBox в заголовке Excel таблицы (посредством QHeaderView)
Имеется динамическая exсel таблица, с изменяющимся количеством строк (наименование колонок в процессе обработки таблицы изменить нельзя, скачивается с файла).
Необходимо для дальнейшей обработки exсel таблицы определить, какие строки помечены флагом CheckBox.
Для удобства (поставить/снять все флаги) были добавлены два флага: один реализован через QCheckBox в statusbar’e окна; второй в заголовке таблицы реализован через QHeaderView.
На мой взгляд в коде имеются следующие недочеты:
- при добавлении новой колонки под
checkbox’ы (помимо уже имеющихся в exсel таблице), визуально создается впечатление, что добавлено две колонки; checkboxв заголовке иcheckbox«Весь список» в statusbar’e между собой связи не имеют, т.е. при снятии флага с одного из них (на скриншоте пример с checkbox «Весь список»), другой флаг (в checkbox в заголовке) остается;
Вопрос:
- возможно ли убрать визуальное отображение второй колонки в таблице? (если возможно, то как);
- возможно ли связать флаг
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 шт):
Вопрос:
- возможно ли убрать визуальное отображение второй колонки в таблице? (если возможно, то как);
Ответ: возможно, например так:
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())



