Is it possible to disable the highlighting effect on a selected cell in a QTableWidget?
Question:
In this example, I have a grid with cells that can be colored and uncolored.
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setGeometry(100, 100, 750, 750)
self.rows=60
self.columns=50
self.tableWidget=QTableWidget(self.rows, self.columns)
self.tableWidget.horizontalHeader().hide()
self.tableWidget.verticalHeader().hide()
self.activated={}
self.item={}
for r in range(self.rows):
for c in range(self.columns):
self.activated[r,c]=False
self.item[r,c]=QTableWidgetItem()
self.tableWidget.setItem(r,c,self.item[r,c])
self.tableWidget.setFocusPolicy(Qt.NoFocus)
#self.tableWidget.setSelectionMode(QAbstractItemView.NoSelection)
#self.tableWidget.setStyleSheet("""QTableView:item:selected{selection-background-color: rgb(100,100,150);}""")
#self.tableWidget.setStyleSheet("""QTableView:item:selected{selection-background-color: transparent;}""")
self.tableWidget.setEditTriggers(QTableWidget.NoEditTriggers)
self.tableWidget.cellClicked.connect(self.dothing)
self.tableWidget.cellDoubleClicked.connect(self.dothing)
self.setCentralWidget(self.tableWidget)
def dothing(self,r,c):
if self.activated[r,c]:
self.activated[r,c]=False
self.item[r,c].setBackground(QColor(255,255,255))
else:
for column in range(60):
self.activated[column]=False
self.item[column,c].setBackground(QColor(255,255,255))
self.activated[r,c]=True
self.item[r,c].setBackground(QColor(100,100,150))
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
However, as you might notice from running the code, the colored-in color is obscured by the highlight color, as a result of selection.
Without changing the selection mode (ie. self.tableWidget.setSelectionMode(QAbstractItemView.NoSelection)
), or adjusting the stylesheet, is it possible for me to completely disable/bypass the highlight color paint event?
I must note that it is important that the selection mode and stylesheets aren’t adjusted, as I intend on using the selection range to create a length of colored cells in a single row at once. If selection is disabled, then this cannot happen at all, and if the stylesheet is edited, then it would show multiple rows of cells being highlighted rather than just one row.
Hence, these solutions from previous SO questions such as this don’t really work for me.
Answers:
The solution is quite simple: use an item delegate.
Just subclass a QStyledItemDelegate and reimplement its initStyleOption()
in order to override the selected state of the item, so that it will be painted just using the default background color:
class DeselectedDelegate(QStyledItemDelegate):
def initStyleOption(self, opt, index):
super().initStyleOption(opt, index)
opt.state &= ~QStyle.State_Selected
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
# ...
self.tableWidget.setItemDelegate(
DeselectedDelegate(self.tableWidget))
Note that setting the backgrounds of all the items in the column is not very effective, especially if it’s only for display reasons and always consistent as your code shows (not to mention that you used the name column
that actually refers to a row).
A more appropriate solution could just use the self.activated
dictionary and keep the same reference in the delegate. By further implementing the above, you could just change the backgroundBrush
of the style option whenever the row/column reference is True
in the dict, and ensure that all items are repainted[1]:
class DeselectedDelegate(QStyledItemDelegate):
def __init__(self, parent, activated):
super().__init__(parent)
self.activated = activated
def initStyleOption(self, opt, index):
super().initStyleOption(opt, index)
opt.state &= ~QStyle.State_Selected
if self.activated[index.row(), index.column()]:
opt.backgroundBrush = QColor(100,100,150)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
# ...
self.tableWidget.setItemDelegate(
DeselectedDelegate(self.tableWidget, self.activated))
# note that added argument
def dothing(self, row, column):
active = not self.activated[row, column]
self.activated[row, column] = active
if active:
for r in range(self.tableWidget.rowCount()):
if r != row:
self.activated[r, column] = False
self.tableWidget.viewport().update()
[1] Note that I specifically called the viewport.update()
, because item views, as all Qt scroll areas paint their contents in their viewport (calling self.tableWidget.update()
would have no result); while repainting the whole viewport might not seem very optimal, it’s certainly better than setting the background of all items in the same column (including those that are not currently shown). It’s worth noticing that it is possible to update only a region of the viewport (in your case, that referring to the column interested by the value changes), but, unless extremely complex painting is required on each item (for instance, custom pixmaps), that solution is not worth it.
In this example, I have a grid with cells that can be colored and uncolored.
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setGeometry(100, 100, 750, 750)
self.rows=60
self.columns=50
self.tableWidget=QTableWidget(self.rows, self.columns)
self.tableWidget.horizontalHeader().hide()
self.tableWidget.verticalHeader().hide()
self.activated={}
self.item={}
for r in range(self.rows):
for c in range(self.columns):
self.activated[r,c]=False
self.item[r,c]=QTableWidgetItem()
self.tableWidget.setItem(r,c,self.item[r,c])
self.tableWidget.setFocusPolicy(Qt.NoFocus)
#self.tableWidget.setSelectionMode(QAbstractItemView.NoSelection)
#self.tableWidget.setStyleSheet("""QTableView:item:selected{selection-background-color: rgb(100,100,150);}""")
#self.tableWidget.setStyleSheet("""QTableView:item:selected{selection-background-color: transparent;}""")
self.tableWidget.setEditTriggers(QTableWidget.NoEditTriggers)
self.tableWidget.cellClicked.connect(self.dothing)
self.tableWidget.cellDoubleClicked.connect(self.dothing)
self.setCentralWidget(self.tableWidget)
def dothing(self,r,c):
if self.activated[r,c]:
self.activated[r,c]=False
self.item[r,c].setBackground(QColor(255,255,255))
else:
for column in range(60):
self.activated[column]=False
self.item[column,c].setBackground(QColor(255,255,255))
self.activated[r,c]=True
self.item[r,c].setBackground(QColor(100,100,150))
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
However, as you might notice from running the code, the colored-in color is obscured by the highlight color, as a result of selection.
Without changing the selection mode (ie. self.tableWidget.setSelectionMode(QAbstractItemView.NoSelection)
), or adjusting the stylesheet, is it possible for me to completely disable/bypass the highlight color paint event?
I must note that it is important that the selection mode and stylesheets aren’t adjusted, as I intend on using the selection range to create a length of colored cells in a single row at once. If selection is disabled, then this cannot happen at all, and if the stylesheet is edited, then it would show multiple rows of cells being highlighted rather than just one row.
Hence, these solutions from previous SO questions such as this don’t really work for me.
The solution is quite simple: use an item delegate.
Just subclass a QStyledItemDelegate and reimplement its initStyleOption()
in order to override the selected state of the item, so that it will be painted just using the default background color:
class DeselectedDelegate(QStyledItemDelegate):
def initStyleOption(self, opt, index):
super().initStyleOption(opt, index)
opt.state &= ~QStyle.State_Selected
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
# ...
self.tableWidget.setItemDelegate(
DeselectedDelegate(self.tableWidget))
Note that setting the backgrounds of all the items in the column is not very effective, especially if it’s only for display reasons and always consistent as your code shows (not to mention that you used the name column
that actually refers to a row).
A more appropriate solution could just use the self.activated
dictionary and keep the same reference in the delegate. By further implementing the above, you could just change the backgroundBrush
of the style option whenever the row/column reference is True
in the dict, and ensure that all items are repainted[1]:
class DeselectedDelegate(QStyledItemDelegate):
def __init__(self, parent, activated):
super().__init__(parent)
self.activated = activated
def initStyleOption(self, opt, index):
super().initStyleOption(opt, index)
opt.state &= ~QStyle.State_Selected
if self.activated[index.row(), index.column()]:
opt.backgroundBrush = QColor(100,100,150)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
# ...
self.tableWidget.setItemDelegate(
DeselectedDelegate(self.tableWidget, self.activated))
# note that added argument
def dothing(self, row, column):
active = not self.activated[row, column]
self.activated[row, column] = active
if active:
for r in range(self.tableWidget.rowCount()):
if r != row:
self.activated[r, column] = False
self.tableWidget.viewport().update()
[1] Note that I specifically called the viewport.update()
, because item views, as all Qt scroll areas paint their contents in their viewport (calling self.tableWidget.update()
would have no result); while repainting the whole viewport might not seem very optimal, it’s certainly better than setting the background of all items in the same column (including those that are not currently shown). It’s worth noticing that it is possible to update only a region of the viewport (in your case, that referring to the column interested by the value changes), but, unless extremely complex painting is required on each item (for instance, custom pixmaps), that solution is not worth it.