Silmor . de
Site Links:
Impressum / Publisher

Qt: TreeViews as Data Tables

A Nicer View for Hierarchical Tables

Qt has two views to display 2-dimensional data: QTableView and QTreeView. The QTableView class is ideal for displaying tabular data with nicely separated cells:


However, it is not able to display hierarchical data. For that we have QTreeView:


On the other hand QTreeView visually packs the displayed data closer together and does not draw any separator lines. What is often needed for complex hierarchical data is a combination of both:


While not ideal this view can be accomplished relatively easily by creating a new delegate that derives from the default delegate class:

//file: GridDelegate.h
#include <QStyledItemDelegate>

///Helper Delegate to paint frames around cells in a tree view
class GridDelegate : public QStyledItemDelegate
{
    bool mtopframe=true,mgoleft=false;
public:
    ///instantiate
    explicit GridDelegate(QObject * parent = 0) : QStyledItemDelegate(parent) { }

    ///draws a grid and then delegates to QStyledItemDelegate
    virtual void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const override;

    ///increases vertical height of each row by a few pixels to make it look nicer
    virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;

    ///if enabled: paint a frame around top level rows; default: enabled
    void setTopLevelFrame(bool enabled){mtopframe=enabled;}

    ///if enabled: paint the frame of the leftmost cell from the absolute left, including the arrow; if disabled: start the frame where the cell starts at the current hierarchy level, just right of the arrow
    void setStartFrameLeft(bool enabled){mgoleft=enabled;}
};
//file: GridDelegate.cpp
#include <QPainter>
#include <QDebug>
#include <QSize>

#include "GridDelegate.h"


void GridDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const
{
    if(index.parent().isValid() || mtopframe){
        painter->save();
        painter->setPen(QColor(Qt::gray));
        auto rect=option.rect;
        if(mgoleft && index.column()==0)rect.setX(0);
        painter->drawRect(rect);
        painter->restore();
    }

    QStyledItemDelegate::paint(painter, option, index);
}

QSize GridDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
{
    QSize sz=QStyledItemDelegate::sizeHint(option,index);
    sz.setHeight(sz.height()+6);
    return sz;
}

To use it one simply sets the delegate of the treeview:

auto *view = new QTreeView(myparent);
view->setItemDelegate(new GridDelegate(myparent));

In addition to creating a visible grid this delegate also offers the option to not paint a grid on top level rows and to paint the grid always to the complete left of the widget instead of following the hierarchy for the leftmost cell.

Note: feel free to use this code in your own projects under any license you like, I hereby place it in the public domain.

How it Works

The function of the GridDelegate is relatively simple: the constructor simply refers to the default delegate, the "magic" is in the paint and sizeHint methods.

The paint method is called for each visible cell. It first paints a gray box around the current cell and then calls the inherited version of this method. The code checks whether to paint around top level rows (hint: top level model items do not have a parent item) and it also checks the option to paint to the absolute left of the widget if the item is in column 0. The QPainter::save and restore calls are simply there to make sure the state of the painter is clean after this extension is done. Last it simply calls the inherited method to do the normal painting to show the data.

The sizeHint method simply adds 6 pixels to the height of each row, so that there is enough space for the box.

Possible Enhancements

This implementation uses a fixed color and a simple rectangle to paint the cell borders. It is quite possible to use different colors, check the palette or the current style for this task. It would also be quite possible to use different line styles to make it more pleasing.

The overridden sizeHint only adds some fixed amount of space in the vertical direction, if you need more space you can easily adapt the code to leave a bigger margin. You will need some margine, otherwise the cells will look quite claustrophobic.


Webmaster: webmaster AT silmor DOT de