2025, Dec 07 09:00

How to Get Flat, Full-Cell Backgrounds in QTableWidget/QTableView on Windows 11 (Qt 6.7+)

Windows 11's Qt 6.7 style clips QTableWidget/QTableView backgrounds. Fix it by switching to Fusion or using a custom delegate to paint full-cell backgrounds.

Windows 11 introduces rounded corners all over the place — and if you’re using Qt 6.7 or newer, that aesthetic shows up inside QTableWidget/QTableView items too. The catch: when you set a background with item.setBackground(QColor(...)), the color no longer fills the whole cell; it’s clipped with internal padding and rounded edges. Stylesheets can flood-fill cells, but then you lose the ability to color only specific rows via model data. This guide shows what’s going on and how to get a flat, full-cell background without breaking everything else.

Reproducing the issue

The following minimal snippet colors a cell using the item background role. On Windows 11 with Qt’s Windows 11 style, you’ll see the rounded padding around the color.

from PySide6.QtWidgets import QMainWindow, QApplication, QTableWidget, QTableWidgetItem, QVBoxLayout
from PySide6.QtGui import QColor
class AppFrame(QMainWindow):
    def __init__(self):
        super().__init__()
        host_layout = QVBoxLayout()
        grid = QTableWidget()
        grid.setRowCount(1)
        grid.setColumnCount(1)
        cell = QTableWidgetItem()
        grid.setItem(0, 0, cell)
        cell.setBackground(QColor(255, 0, 0))
        self.setCentralWidget(grid)
        host_layout.addWidget(grid)
        self.setLayout(host_layout)
if __name__ == "__main__":
    qtapp = QApplication([])
    ui = AppFrame()
    ui.show()
    qtapp.exec()

Why this happens

Starting with Qt 6.7, a new QStyle was added to better match Windows 11. That style, internally referred to as "windows11", deliberately paints rounded corners for item views. The default QStyledItemDelegate uses the style to render items (via the CE_ItemViewItem control). In that style implementation, items often get rounded shapes whenever QStyleOptionViewItem.viewItemPosition isn’t Middle, and even the rectangular path includes vertical margins that keep backgrounds from filling the full cell area.

The relevant rendering path for the rectangular case reduces the paint rect with top and bottom margins, which explains the visible padding inside the cell.

...
} else {
    painter->drawRect(rect.marginsRemoved(QMargins(0, 2, 0, 2)));
}

Solution 1: switch QStyle

The most straightforward fix is to avoid the Windows 11 style altogether. You can set a different style for the whole app or just for a specific widget. Fusion is a solid, cross-platform choice that doesn’t add rounded corners to item backgrounds. Apply it as early as possible in your startup sequence to avoid inconsistent behavior.

from PySide6.QtWidgets import QApplication, QStyleFactory
# per-widget
some_view.setStyle(QStyleFactory.create('fusion'))
# application-wide
QApplication.setStyle('fusion')

This keeps behavior predictable, but it also makes the UI look less "native" on Windows 11. If used per widget, remember that setStyle() doesn’t propagate to child widgets; scrollbars, headers and the viewport might still use the application style. It may also influence size calculations such as size hints for rows/columns.

Solution 2: a delegate that paints a flat background

If you want to keep the application style but still get full-cell backgrounds, use a delegate. The item delegate is responsible for drawing items. There are two angles here. First, setting viewItemPosition to Middle reduces rounded behavior; however, the Windows 11 style still applies top/bottom margins internally, so this alone doesn’t fully flatten the background.

from PySide6.QtWidgets import QStyledItemDelegate, QStyleOptionViewItem
class PositionDelegate(QStyledItemDelegate):
    def initStyleOption(self, opt, idx):
        super().initStyleOption(opt, idx)
        opt.viewItemPosition = QStyleOptionViewItem.ViewItemPosition.Middle
# usage: table_view.setItemDelegate(PositionDelegate(table_view))

The reliable approach is to paint the background yourself before letting the default delegate draw the content. This uses the item’s BackgroundRole as-is, so you can still color specific rows or cells through the model or item API.

from PySide6.QtCore import Qt
from PySide6.QtGui import QColor, QBrush, QGradient
from PySide6.QtWidgets import QStyledItemDelegate
class FlatFillDelegate(QStyledItemDelegate):
    def paint(self, painter, option, index):
        bg = index.data(Qt.ItemDataRole.BackgroundRole)
        if isinstance(bg, (QBrush, QColor, QGradient, int)):
            painter.fillRect(option.rect, bg)
        super().paint(painter, option, index)
# install for a view, e.g.:
# grid.setItemDelegate(FlatFillDelegate(grid))

At this point, the style can still add a rounded highlight or hover overlay on top. With an opaque background color, that results in a visible rounded shape "inside" the flat fill when the item is selected or hovered. With a semi-transparent background, blending will make that shape look more pronounced. To neutralize that effect, pair the delegate with a minimal stylesheet that keeps normal items transparent and restores the highlight only when selected.

QTableView::item {
    background: transparent;
}
QTableView::item:selected {
    background: palette(highlight);
}

This combination preserves your full-rectangle background fill and still provides a standard highlight when the user selects an item.

Why it’s worth knowing

Qt leans on QStyle to deliver native look & feel across platforms. When a platform style changes — as it did for Windows 11 — the visual contract of common widgets can shift in subtle ways. Relying entirely on style-driven painting means your UI inherits those changes automatically; sometimes that’s perfect, other times, like with table cell backgrounds, it’s not. Understanding where the style draws the line lets you decide whether to swap styles for predictability or selectively override behavior with a delegate.

Practical considerations

Style choice affects the whole application. Setting a global style like fusion ensures consistent rendering across all widgets but differs from the OS look. Setting a style for a single widget won’t propagate to its components such as headers or scrollbars, which can lead to mixed aesthetics. Delegates give you surgical control over painting without abandoning the platform style, but when stylesheets enter the mix, remember that item views are composite widgets and partial styling can have side effects; keep styles minimal and focused on what you actually need.

Availability of styles also depends on platform and Qt version. There’s only one universally available Windows style explicitly named windows. The windowsxp style exists only for Qt 5 on Windows. The windowsvista style is available on Windows, including newer Qt releases and OS versions. The windows11 style targets Windows 11 and later. If you plan to set styles in code, guard it appropriately and do it as early as possible in application startup.

Summary

If setBackground() no longer fills QTableWidget/QTableView cells on Windows 11, it’s the platform style at work. To get flat, full-cell backgrounds, either change the style to something like fusion, or keep the native look and use a delegate that paints the BackgroundRole across the full rect, optionally pairing it with a small stylesheet to control selection overlays. Choose the approach that best fits your UI consistency requirements and deployment targets, and apply it early in your initialization so that behavior remains predictable.