Trolltech | Documentation | Qt Quarterly | Fields on Forms »

Fancy List Items
by Mark Summerfield
Qt's list view class can provide list and tree views and is capable of handling large amounts of data. But out of the box it can only have one background color and one font for all of its items. The same applies to Qt's list box widget. In this article we present a a list box item subclass and a list view item subclass that can have custom fonts and background colors.

Fancy List Box Items

The list box item subclass is short and simple. Each item can have its own font and background color as the screenshot shows.

Fancylistbox

Here's the definition of the list box item subclass:

    class FancyListBoxText : public QListBoxText
    {
    public:
        FancyListBoxText(QListBox *parent)
            : QListBoxText(parent), mFont(parent->font()),
              mBackground(parent->colorGroup().base())
	{}
    
        void paint(QPainter *painter);
        int width(const QListBox *) const { return QFontMetrics(mFont).width(text()); }
        int height(const QListBox *) const { return QFontMetrics(mFont).height(); }
    
        void setText(const QString &text) { QListBoxText::setText(text); }
        QFont font() const { return mFont; }
        void setFont(const QFont &font) { mFont = font; }
        QColor background() const { return mBackground; }
        void setBackground(const QColor &color) { mBackground = color; }
    
    private:
        QFont mFont;
        QColor mBackground;
    };
    

Apart from the paint() function, all the code is inline. We make sure that we give sensible defaults to the font and background in the constructor. We must reimplement width() and height() because a custom font will affect the list box item's size.

    
    void FancyListBoxText::paint(QPainter *painter)
    {
        QFont font = painter->font();
        painter->setFont(mFont);
        painter->fillRect(painter->viewport(), mBackground);
        QListBoxText::paint(painter);
        painter->setFont(font);
    }
    

In the paint() function we save the painter's font, set its font to the custom font, paint the background and then pass the rest of the work on to the base class. We finish by restoring the painter's original font. A slower alternative would have been to save the painter's state, set its font, and restore its state at the end.

That's all that needs to be done. We can now create FancyListBoxText items instead of plain QListBoxText items.

Fancy List View Items

Providing fancy list view items is slightly more involved than list box items because each item can have any number of columns. Our list view item subclass will add a font and a background color for each column.

Fancylistview

The definition of our subclass looks like this:

    class FancyListViewItem : public QListViewItem
    {
    public:
        FancyListViewItem(QListView *parent, const QString &label1, const QString &label2)
            : QListViewItem(parent, label1, label2)
        {}
    
        FancyListViewItem(QListViewItem *parent, const QString &label1, const QString &label2)
            : QListViewItem(parent, label1, label2)
        {}
    
        void paintCell(QPainter *painter, const QColorGroup &cg,
		       int column, int width, int align);
        int width(const QFontMetrics &fm, const QListView *lv, int column) const;
    
        QFont font(uint column) const;
        void setFont(uint column, const QFont &font);
        QColor background(uint column) const;
        void setBackground(uint column, const QColor &color);
    
    private:
        QValueVector<QFont> fonts;
        QValueVector<QColor> backgrounds;
    };
    

We've only coded constructors for items with two initial strings, but it is easy to make others since they simply call the base class and have no functionality of their own. We store each column's font and background color in the two private QValueVectors.

    QFont FancyListViewItem::font(uint column) const
    {
        if (column < fonts.size())
            return fonts[column];
        return listView()->font();
    }
    

If the font has been set we return it, otherwise we return the QListView's font.

    void FancyListViewItem::setFont(uint column, const QFont &font)
    {
        if (column >= fonts.size())
            fonts.resize(column + 1, listView()->font());
        fonts[column] = font;
    }
    

When a font is set we resize the vector if necessary. By default empty QValueVector<T> elements are populated with T(). If we hadn't specified a font, the QFont() constructor would have been used, giving the application's default font which may differ from the QListView's font. Note that unlike FancyListBoxText items, using large font sizes won't work well since there's no easy way to tell a QListView how much height an item needs.

    QColor FancyListViewItem::background(uint column) const
    {
        if (column < backgrounds.size())
            return backgrounds[column];
        return listView()->colorGroup().base();
    }
    

This uses the same logic as font().

    void FancyListViewItem::setBackground(uint column, const QColor &color)
    {
        if (column >= backgrounds.size())
            backgrounds.resize(column + 1, listView()->colorGroup().base());
        backgrounds[column] = color;
    }
    

Here we've used the QListView's base color as the default background color for empty elements.

    void FancyListViewItem::paintCell(
        QPainter *painter, const QColorGroup &cg,
        int column, int width, int align)
    {
        painter->save();
        if (column >= 0 && column < (int)fonts.size())
            painter->setFont(fonts[column]);
        QColorGroup grp(cg);
        if (column >= 0 && column < (int)backgrounds.size())
            grp.setColor(QColorGroup::Base, backgrounds[column]);
        QListViewItem::paintCell(painter, grp, column, width, align);
        painter->restore();
    }
    

We start by saving the state of the painter. If we have a custom font set, we set it on the painter. We create a new color group that's a copy of the one passed in, and if we have a custom background color we set our color group to use it. We then pass on the burden of painting to the base class. At the end we restore the painter to its original state.

    int FancyListViewItem::width(const QFontMetrics &fm, const QListView *lv, int column) const
    {
        int width;
        if (column >= 0 && column < (int)fonts.size()) {
            QFontMetrics fm2(fonts[column]);
            width = QListViewItem::width(fm2, lv, column);
        }
        else
            width = QListViewItem::width(fm, lv, column);
        return width;
    }
    

If we have used a custom font it is likely that it will be wider or narrower than the QListView's standard font, so the width must be calculated using the appropriate font's metrics.

Qt's Font and Color Names
To produce the screenshots we iterated over the names of the fonts and colors that are known to Qt. For the color names we called the static QColor::colorNames() function. The names in this list can be passed to the QColor constructor. For the list of font names we called QFontDatabase().families(). The names in this list can be passed to the QFont constructor.


This document is licensed under the Creative Commons Attribution-Share Alike 2.5 license.

Copyright © 2003 Trolltech Trademarks Fields on Forms »