On Windows 11, when I set the background color of items in my QTableWidget using item.setBackground(QColor(210,210,210))
, the color doesn't fill the cell entirely, there is some sort of padding with rounded corners:
Is there a way to get rid of that "rounded style"? The person in this post is also using .setBackground()
but doesn't have this issue.
When using QSS to change the background color of the table items, all the cells are filled-in correctly:
However I want to be able to choose which rows to apply the background color to. This is the QSS used resulting in the image above:
QTableView::item {
color: rgb(50, 50, 50);
background-color: rgb(0, 180, 0);
}
QTableView {
background-color: rgb(240, 240, 240);
font: 16px;
}
I also tried to fiddle with the border and padding, but it just removes all background colors, no matter the values.
QTableView::item {
color: rgb(50, 50, 50);
border: 0px;
padding: 4px;
}
Minimal reproducible example:
from PySide6.QtWidgets import QMainWindow, QApplication, QTableWidget, QTableWidgetItem, QVBoxLayout
from PySide6.QtGui import QColor
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
layout = QVBoxLayout()
table = QTableWidget()
table.setRowCount(1)
table.setColumnCount(1)
item = QTableWidgetItem()
table.setItem(0, 0, item)
item.setBackground(QColor(255, 0, 0))
self.setCentralWidget(table)
layout.addWidget(table)
self.setLayout(layout)
if __name__ == "__main__":
app = QApplication([])
window = MainWindow()
window.show()
app.exec()
Since Qt 6.7 a new QStyle has been introduced in order to make its look more "native" when run on Windows 11. The new changes include rounded corners in many UI elements, including items in Qt item views.
The whole implementation can be seen in the source code, check for the line containing CE_ItemViewItem
, which is what the default QStyledItemDelegate uses to draw items.
There are basically two ways to make those items rectangular, without (theoretically) breaking other functionalities.
Qt has many ways to make programming easier to cross-platform development. One of them is using the QStyle class, which is used as an abstraction layer that allows proper display and usage of UI elements while keeping development as clean as possible.
Since Qt 6.7 and on Windows 11, a default QStyle named "windows11" has been introduced, which is the actual culprit of the rounded rectangles you're seeing.
The simplest solution, then, is to use another QStyle, either for the widget, or application wide. The common suggestion is to switch to the "fusion" style, which is one of the few available on all Qt platforms.
# widget only
table.setStyle(QStyleFactory.create('fusion'))
# otherwise, application-wide
QApplication.setStyle('fusion')
Note that the usage of the fusion
style is because it's normally considered the more compliant style in Qt. A further alternative is to use the windowsvista
style instead. See the notes below.
If you're setting the style for the application, it is quite important to do it as early as possible: either before or exactly right after creating the QApplication instance; doing it later (especially after setting style sheets in any way or after creating widgets) may result in unexpected behavior.
This has a few drawbacks, though.
In the case of an application wide style, the result is obvious: it makes the whole program less "native"; the appearance is consistent within every UI element of the program, but not with the OS.
In the widget-only case, the new style may not be completely applied to the widget (so it may look a bit inconsistent); this is because QWidget.setStyle()
does not cause any propagation (as opposed to calls like setFont()
, setPalette()
or setStyleSheet()
), and item views are complex widgets containing other widgets: the viewport (where the scrollable contents are shown), the scroll bars, and its headers (if any) will still use the application style.
In fact, the style is fundamentally applied to the item view (the outer frame) and the items, because the delegate normally uses the style of the view for its needs, no matter if the viewport has a different style.
Also, changing the style doesn't only change the way items are drawn, but other aspects as well: for instance, adjusting the sizes of rows and columns to their contents may give different results.
The above may be acceptable, but with the drawbacks it has it may be better to consider other solutions.
Another alternative is to use a custom delegate, which is the object responsible of providing proper UI interaction of items in their views.
Among other aspects, Qt item delegates are used to properly draw items in their views, and the default QStyledItemDelegate also calls an "initialize" function initStyleOption()
that eventually sets further aspects of the given specialized QStyleOption. The paint()
function firstly calls initStyleOption()
, in order to properly paint what it needs.
After checking the code of the "windows11" style above, we can see that it basically draws rounded rectangles only in some circumstances: whenever the QStyleOptionViewItem.viewItemPosition
is anything that is not QStyleOptionViewItem.ViewItemPosition.Middle
.
By default, QTableView does not set that variable, but we can locally overwrite it to Middle
:
class BackgroundDelegate(QStyledItemDelegate):
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
option.viewItemPosition = QStyleOptionViewItem.ViewItemPosition.Middle
...
table.setItemDelegate(BackgroundDelegate(table))
Unfortunately, this doesn't solve all our problems. The default behavior of the above still causes an unwanted margin, which can be seen in these lines of the Qt sources:
...
} else {
painter->drawRect(rect.marginsRemoved(QMargins(0, 2, 0, 2)));
}
This will then draw the item background as a rectangle, but still with margins on top and bottom (Qt sides/margins start from the left side and go clockwise).
The only way to work around this is to drop the above attempt and partially override the paint()
function instead.
For this, we may take my answer to How to return a QColor as the QBackgroundRole in QTableView which has preset stylesheet in PyQt5?, which does the following:
class BackgroundDelegate(QStyledItemDelegate):
def paint(self, qp, opt, index):
background = index.data(Qt.ItemDataRole.BackgroundRole)
if isinstance(background, (QBrush, QColor, QGradient, int)):
qp.fillRect(opt.rect, background)
super().paint(qp, opt, index)
...
table.setItemDelegate(BackgroundDelegate(table))
What it does is to automatically fill the item rectangle with the color set in the model, and then proceed with the default implementation, which draws the contents as expected.
Note that this means that the style will still draw a rounded rectangle for the item. That background will be either the one set for the item, or even the "highlight" background.
Here is what may happen:
The only way to overcome these issues (other than fully implement item painting, which is normally discouraged) is by also using stylesheets; a basic QSS could then be the following:
QTableView::item {
background: transparent;
}
QTableView::item:selected {
background: palette(highlight);
}
The above will (theoretically) override the default style painting
Remember though that, with complex widgets, whenever a property or subcontrol is set, [almost] all other properties and subcontrols must be set as well (as explained in the docs. I don't have a Windows 11 machine, so I cannot check if the above QSS may cause further problems.
None of the above suggestions comes without possible drawbacks. The Qt team always tries to keep things clean and make UI look&feel as consistent as possible, meaning that further customization from the developer potentially breaks those efforts.
The first thing to do, in case your program is meant to be used on older Windows versions or even other OSes is to properly check the environment:
windows
;This means that:
At the very least, check the current platform and OS version at the very beginning of your program initialization (or class definition/constructor) before trying to apply any customized behavior.