Core Requirements
- Colorize row text based on numeric value direction (positive = green, negative = red)
- Disable sorting on specific columns (e.g., symbol, name)
- Render custom ascending/descending icons in headers
- Ensure text and icons do not overlap in narrow columns
- Suport per-column text alignment (left, center, right)
- Handle percentage values with special parsing for sorting
Custom Header for Selective Sorting
To prevent sorting on designated columns, we subclass QHeaderView and override mouseReleaseEvent. Instead of disabling sorting globally, we intercept clicks on restricted columns before the sort logic triggers.
class StockHeaderView : public QHeaderView
{
Q_OBJECT
public:
explicit StockHeaderView(Qt::Orientation orientation, QWidget* parent = nullptr);
void setSortEnabled(int column, bool enabled);
protected:
void mouseReleaseEvent(QMouseEvent* event) override;
private:
QMap<int, bool> m_sortEnabled;
};
void StockHeaderView::mouseReleaseEvent(QMouseEvent* event)
{
int column = logicalIndexAt(event->pos().x());
if (m_sortEnabled.contains(column) && !m_sortEnabled[column])
return; // Block sorting on this column
QHeaderView::mouseReleaseEvent(event);
}
Custom Header Painting with Icon Overlay
Overiding paintSection allows us to draw custom sort indicators without interfering with Qt’s default rendering. We first paint the base header, then overlay the arrow if the colum is sorted.
void StockHeaderView::paintSection(QPainter* painter, const QRect& rect, int logicalIndex) const
{
// Fill background to ensure consistent color under text
painter->fillRect(rect.adjusted(-1, 0, -1, -1), QColor("#212121"));
QRect adjustedRect = rect;
if (sortIndicatorSection() == logicalIndex)
{
// Shift text area left to avoid overlapping with icon
adjustedRect.adjust(0, 0, -20, 0);
// Draw sort arrow
QRect iconRect = rect;
iconRect.setLeft(rect.right() - 16);
iconRect.setWidth(14);
iconRect.setHeight(10);
iconRect.moveTop((rect.height() - iconRect.height()) / 2);
QPixmap icon = sortIndicatorOrder() == Qt::AscendingOrder
? QPixmap(":/icons/arrow_down")
: QPixmap(":/icons/arrow_up");
painter->drawPixmap(iconRect, icon);
}
// Delegate base header painting
QHeaderView::paintSection(painter, adjustedRect, logicalIndex);
// Draw subtle border
painter->setPen(QColor("#2B2B2B"));
painter->drawRect(rect.adjusted(-1, 0, -1, -1));
}
Text Alignment Control
Per-column alignment is managed via a custom model that overrides headerData() to return stored alignment flags. The same alignment is applied to both header and data cells.
QVariant StockModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::TextAlignmentRole)
{
auto it = m_columnAlignments.find(section);
if (it != m_columnAlignments.end())
return it.value();
return Qt::AlignCenter;
}
return QStandardItemModel::headerData(section, orientation, role);
}
// Usage:
model->setColumnAlignment(0, Qt::AlignRight | Qt::AlignVCenter);
model->setColumnAlignment(1, Qt::AlignRight | Qt::AlignVCenter);
model->setColumnAlignment(3, Qt::AlignLeft | Qt::AlignVCenter);
Intelligent Sorting with Value Types
The filtering model distinguishes between numeric types: integers, percentages, and strings. Percentage values are parsed by stripping the '%' symbol and converting to double for comparison.
bool StockFilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) const
{
switch (m_sortType)
{
case SortType::Integer:
{
double l = left.data().toDouble();
double r = right.data().toDouble();
return l < r;
}
case SortType::Percentage:
{
QString lStr = left.data(Qt::UserRole + 2).toString();
QString rStr = right.data(Qt::UserRole + 2).toString();
double l = lStr.remove('%').toDouble();
double r = rStr.remove('%').toDouble();
return l < r;
}
default:
return left.data().toString() < right.data().toString();
}
}
Dynamic Text Coloring in Cells
Row text color is determined at the model level using the Qt::ForegroundRole. Positive values trigger green, negative values trigger red.
QVariant StockModel::data(const QModelIndex& index, int role) const
{
if (role == Qt::ForegroundRole)
{
QVariant value = QStandardItemModel::data(index, Qt::DisplayRole);
if (value.type() == QVariant::Double || value.type() == QVariant::Int)
{
double num = value.toDouble();
return num >= 0 ? QColor("#4CAF50") : QColor("#F44336");
}
}
return QStandardItemModel::data(index, role);
}
Styling and Consistency
Application-wide styling ensures visual cohesion:
tableView->setStyleSheet(
"QTableView { background-color: #333333; gridline-color: #444; }"
"QHeaderView { background-color: #212121; color: #cccccc; }"
"QHeaderView::section { padding: 6px; border: none; }"
);
By decoupling sorting logic from the default header behavior, overriding painting for precise visual control, and leveraging role-based data styling, we achieve a professional, responsive financial table that behaves like a native trading terminal.