Status bar and elided label

Many applications use a QStatusBar to display various information at the bottom of the window. The good thing about this widget is that it can contain various other widgets, not only labels, but also buttons, progress bars, etc. But in the most common case the status bar simply contains a few labels, and lays them out in a horizontal bar layout.

Everything is fine when the information displayed in the status bar is short and the window is large enough. However one thing that we should remember about the QLabel is that it can be expanded as necessary, but in the default configuration it cannot shrink — unless it has word wrapping or auto-scaling (in case of graphic content) enabled, its preferred size is also the minimum size. The result is that the window must be at least as wide as the total widths of all status labels, and the user cannot make it smaller. But in some cases the status text can be arbitrarily long — take a web browser which displays the URL of the hovered link in the status bar, and we cannot make any assumptions about the maximum length of the link.

Qt has a powerful mechanism of layouts which offers various solutions to this problem. One solution is to set the horizontal size policy of the label to Ignored. In that case both the preferred size and the minimum size is ignored. That's fine when the status bar contains only a single label. But in many cases, there are several additional labels on the right, that should only take as much space as they need, but should also shrink when there is not enough space. The solution is to keep the Preferred size policy, but also force the label to have zero minimum width by using setMinimumWidth. (Edited on 2012-03-23: actually it must be a small value greater than zero; setting minimum width to zero has no effect.)

There is still a minor issue — when the QLabel shrinks, it doesn't elide the text (by appending ... to indicate that the text was truncated), but simply clips it. The QLabel can be extended to support elided text quite easily, and there are many ways to do that. When I first did that, based on some code that I found, it worked nicely, until I placed a few such labels side by side in a status bar. I couldn't get the layout to behave correctly, and finally I found that this problem was related to the mechanism of eliding text.

So in case you need to put together an elided label on your own: do this by simply overriding the paintEvent, and do not try to mess with setText or resizeEvent, because that may break the automatically calculated preferred size of the label. Here's a code snippet:

void ElidedLabel::paintEvent( QPaintEvent* /*e*/ )
{
    QPainter painter( this );
    drawFrame( &painter );

    QRect cr = contentsRect();
    cr.adjust( margin(), margin(), -margin(), -margin() );

    QString fullText = text();

    if ( fullText != m_lastText || cr.width() != m_lastWidth ) {
        m_elidedText = fontMetrics().elidedText( fullText, Qt::ElideRight,
            cr.width() );
        m_lastText = fullText;
        m_lastWidth = cr.width();
    }

    QStyleOption opt;
    opt.initFrom( this );

    style()->drawItemText( &painter, cr, alignment(), opt.palette, isEnabled(),
        m_elidedText, foregroundRole() );
}

Note that this code caches the elided text for better performance, using the m_elidedText, m_lastText and m_lastWidth member variables. Also note that it always uses Qt::ElideRight — you can customize this if you need. Of course this will only work in case of a single-line, plain text with no special formatting.

Filed under: Blog