When you're building a table with financial data, you'll want your currency columns right-aligned. It's a small visual detail, but it makes numeric data much easier to scan and compare. If you're using QTableWidget, you can set alignment directly on each QTableWidgetItem. But with QTableView and a custom QAbstractTableModel, the approach is a little different.
The alignment for cells in a QTableView is controlled by the model, specifically by responding to Qt.TextAlignmentRole in your model's data() method. Let's walk through how this works, starting with the basics and building up to a complete working example with a QStyledItemDelegate for displaying currency values.
Returning alignment from the model
Your model's data() method already handles Qt.DisplayRole to return text for each cell. To control alignment, you add another check for Qt.TextAlignmentRole and return the desired alignment flags for specific columns.
Here's the pattern:
def data(self, index, role):
if role == Qt.DisplayRole:
return self._data[index.row()][index.column()]
if role == Qt.TextAlignmentRole:
if index.column() == 2: # e.g., your currency column
return Qt.AlignRight | Qt.AlignVCenter
When Qt asks the model "how should this cell be aligned?", this code answers with the alignment flags. The view takes care of the rest.
A complete working example
Let's put together a full example: a QTableView displaying a list of items with descriptions and prices. The price column will be right-aligned. If you're new to using QTableView with custom models, you may want to read through the Model/View architecture tutorial first.
import sys
from PyQt6.QtCore import QAbstractTableModel, QModelIndex, Qt
from PyQt6.QtWidgets import (
QApplication,
QDoubleSpinBox,
QMainWindow,
QStyledItemDelegate,
QTableView,
QVBoxLayout,
QWidget,
)
SAMPLE_DATA = [
["Espresso", "Coffee", 3.50],
["Croissant", "Pastry", 2.75],
["Orange Juice", "Beverage", 4.00],
["Avocado Toast", "Food", 8.50],
["Cappuccino", "Coffee", 4.25],
]
HEADERS = ["Item", "Category", "Price"]
CURRENCY_COLUMN = 2
class ItemTableModel(QAbstractTableModel):
def __init__(self, data):
super().__init__()
self._data = data
def rowCount(self, parent=QModelIndex()):
return len(self._data)
def columnCount(self, parent=QModelIndex()):
return len(self._data[0]) if self._data else 0
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return None
value = self._data[index.row()][index.column()]
if role == Qt.DisplayRole:
if index.column() == CURRENCY_COLUMN:
return f"${value:.2f}"
return str(value)
if role == Qt.EditRole:
return value
if role == Qt.TextAlignmentRole:
if index.column() == CURRENCY_COLUMN:
return int(Qt.AlignRight | Qt.AlignVCenter)
return None
def setData(self, index, value, role=Qt.EditRole):
if role == Qt.EditRole and index.isValid():
self._data[index.row()][index.column()] = value
self.dataChanged.emit(index, index, [role])
return True
return False
def flags(self, index):
base_flags = super().flags(index)
if index.column() == CURRENCY_COLUMN:
return base_flags | Qt.ItemIsEditable
return base_flags
def headerData(self, section, orientation, role=Qt.DisplayRole):
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
return HEADERS[section]
return None
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Currency Alignment Example")
self.setMinimumSize(450, 300)
# Set up the model and view.
self.model = ItemTableModel(SAMPLE_DATA)
self.table = QTableView()
self.table.setModel(self.model)
# Stretch columns to fill the available space.
header = self.table.horizontalHeader()
header.setStretchLastSection(True)
layout = QVBoxLayout()
layout.addWidget(self.table)
container = QWidget()
container.setLayout(layout)
self.setCentralWidget(container)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
When you run this, you'll see a table where the "Item" and "Category" columns are left-aligned (the default), and the "Price" column is right-aligned. Double-click a price cell to edit it — the spin box editor is also right-aligned, so the experience feels consistent.

How it all fits together
There are three pieces working together here:
-
The model's
data()method handlesQt.TextAlignmentRoleto tell the view how to align each cell. This controls the display alignment — what you see when you're just looking at the table. -
The model's
data()method withQt.DisplayRoleformats the value with a dollar sign and two decimal places for display.
Aligning multiple columns
If you have several numeric columns you want to right-align, you can check against a list or set of column indices:
CURRENCY_COLUMNS = {2, 3, 5} # columns to right-align
def data(self, index, role=Qt.DisplayRole):
# ... other role handling ...
if role == Qt.TextAlignmentRole:
if index.column() in CURRENCY_COLUMNS:
return int(Qt.AlignRight | Qt.AlignVCenter)
This scales nicely — you define your numeric columns once and the alignment follows automatically.
Summary
Aligning columns in a QTableView is handled entirely within the model. By responding to Qt.TextAlignmentRole in your data() method, you can control alignment per-column (or even per-cell) without touching the delegate's paint() method. For more on working with table views — including displaying data from numpy and pandas — see the QTableView with numpy and pandas tutorial. If you need to make your table cells editable, take a look at editing a PyQt6 QTableView. You can also sort and filter your table data using a proxy model for a more polished experience.d
Create GUI Applications with Python & Qt6 by Martin Fitzpatrick
(PyQt6 Edition) The hands-on guide to making apps with Python — Over 15,000 copies sold!