Trolltech | Documentation | Qt Quarterly | « Qt 3.1 -- A Better Qt | Qt 3.1's SQL Sequel »

Diving into Splash Screens
by Trenton Schulz
Most large applications use splash screens to provide feedback to the user, and as advertising space. Despite appearing to be a simple graphical hack, there are some issues to consider when adding a splash screen so that you don't put people off using your program.

Update: Qt now has a QSplashScreen class built-in.


Swim Gear

Most splash screens consist of a graphic that is shown for a few moments before the main window makes its appearance. Here's the header for an example splash screen class:

    #ifndef SPLASHSCREEN_H
    #define SPLASHSCREEN_H

    #include <qpixmap.h>
    #include <qwidget.h>

    class SplashScreen : public QWidget
    {
    public:
        SplashScreen( const QPixmap &pixmap );

        void setStatus( const QString &message, int alignment = AlignLeft, const QColor &color = black );
        void finish( QWidget *mainWin );
        void repaint();

    protected:
        void mousePressEvent( QMouseEvent * );

    private:
        QPixmap pix;
    };

    #endif

Testing the Water

The SplashScreen class makes it easy to create a simple splash screen that shows itself after creation and closes itself just before the main window appears. For example:

    int main( int argc, char **argv )
    {
        QApplication app( argc, argv );
        QPixmap pixmap( "splash.png" );
        SplashScreen *splash = new SplashScreen( pixmap );
        QMainWindow *mainWin = new QMainWindow;
        ...
        app.setMainWidget( mainWin );
        mainWin->show();
        splash->finish( mainWin );
        delete splash;
        return app.exec();
    }

With this, you get a widget displaying splash.png centered on the screen. The call to finish() means that splash will wait until the main widget makes its appearance, and will then close itself.

Snorkeling

Now that we have seen how to use the SplashScreen class, let's look under the hood to see how it works. A splash screen differs from an ordinary widget in many respects.

    #include <qapplication.h>
    #include <qpainter.h>
    #include <qpixmap.h>
    #include "splashscreen.h"

    SplashScreen::SplashScreen( const QPixmap &pixmap )
        : QWidget( 0, 0, WStyle_Customize | WStyle_Splash ),
          pix( pixmap )
    {
        setErasePixmap( pix );
        resize( pix.size() );
        QRect scr = QApplication::desktop()->screenGeometry();
        move( scr.center() - rect().center() );
        show();
        repaint();
    }

The key items in the constructor are the flags passed to the QWidget constructor. New in Qt 3.1 is the WStyle_Splash flag, a combination of five other flags: WStyle_NoBorder, WStyle_StaysOnTop, WStyle_Tool, WWinOwnDC, and WX11BypassWM. X11 only needs the WX11BypassWM flag, which prevents the window manager from decorating or managing the window. The other flags are for Windows. The WStyle_NoBorder flag prevents the window from having resize handles or a title bar, so the user cannot resize or move the window. WStyle_StaysOnTop asks Windows to keep this window on top. The WStyle_Tool flag tells Windows not to add this window in the taskbar. The WWinOwnDC eliminates the Windows XP drop shadow. We must "or" WStyle_Splash with WStyle_Customize to make the windowing system use our flags instead of the defaults.

In the body of the constructor, we set the widget's "erase pixmap." The erase pixmap is used to clear the widget before it is (potentially) painted on. Although the widget is constructed with WStyle_StaysOnTop, it's still possible to put another window on top of it (for example, by pressing Alt+Tab), and when that window goes away, the widget needs to repaint itself. The remainder of the constructor code adjusts the size of the widget, centers it on the screen, calls show(), and then calls repaint().

    void SplashScreen::repaint()
    {
        QWidget::repaint();
        QApplication::flush();
    }

We override QWidget::repaint() to call QApplication::flush(), which gets all the events in the application's event queue processed immediately. This is where the real magic of the splash screen happens, and why we can paint on the screen without having any sort of event loop.

In our example main(), there was also a call to the SplashScreen::finish() function. This function is not very pretty and reveals some of the issues that library writers face when implementing a multiplatform toolkit:

    #if defined(Q_WS_X11)
    void qt_wait_for_window_manager( QWidget *widget );
    #endif

    void SplashScreen::finish( QWidget *mainWin )
    {
    #if defined(Q_WS_X11)
        qt_wait_for_window_manager( mainWin );
    #endif
        close();
    }

The qt_wait_for_window_manager() function is an internal platform-specific function. It is necessary here because of the way that X11 works. When we call show() on the widget passed in, we have no idea when it will actually be shown, since it is under the control of the window manager. This can result in the splash screen disappearing, a short pause with nothing on the screen, and then the main widget appearing. The qt_wait_for_window_manager() function causes the splash screen to wait until the other widget is actually shown before it closes itself.

Taking the Plunge

Now, with the addition of some artwork, we are able to present simple splash screens to our users. But we can go further than this, and create splash screens that are both more informative and more responsive to the user. Browsing through the demo example's main() function in main.cpp, you will find this comment:

    // How about a splash screen?

Splash

So, let's add one. But instead of just displaying a simple splash screen, let's try to inform our users about what is happening during the initialization process. Also, if the process takes too long (e.g. on slow machines) it would be nice to get rid of the splash screen before the application has been loaded. We'll look at how to do both these things.

First we will look at the setStatus() method that we put in the splash screen class. This function paints text onto the image using the message, alignment, and color that are passed in to it.

    void SplashScreen::setStatus( const QString &message, int alignment, const QColor &color )
    {
        QPixmap textPix = pix;
        QPainter painter( &textPix, this );
        painter.setPen( color );
        QRect r = rect();
        r.setRect( r.x() + 10, r.y() + 10, r.width() - 20, r.height() - 20 );
        painter.drawText( r, alignment, message );
        setErasePixmap( textPix );
        repaint();
    }

This is fairly simple drawing code. Note that we pass this to the QPainter, so that the widget's attributes are copied to the painter. This means that if we call setFont() on the widget, the new font will be used the next time setStatus() is called.

Some users don't like splash screens, and for them we could easily implement a -nosplash command-line option. Alternatively, we can make the splash screen disappear when the user clicks it. Achieving this appears to be straightforward:

    void SplashScreen::mousePressEvent( QMouseEvent * )
    {
        hide();
    }

But if we use the splash screen as shown earlier, it will not disappear when it is clicked on. This is because we don't have an event loop to process events. It may look like we may need to use threads to deal with this problem, but there is a much simpler solution: We must explicitly process events ourselves. We can do this by calling qApp->processEvents() periodically in the main() function.

Here's a snippet based on the demo example's main() function, with the irrelevant parts removed:

    int main( int argc, char **argv )
    {
        QApplication app( argc, argv );
        ...
  +     QPixmap pixmap( "splash.png" );
  +     SplashScreen *splash = new SplashScreen( pixmap );
  +     splash->setFont( QFont("Times", 10, QFont::Bold) );
  +     splash->setStatus( "Initializing..." );
  +     app.processEvents();
        Frame frame;

  +     splash->setStatus( "Creating Widgets Page..." );
        ...
  +     app.processEvents();
  +     splash->setStatus( "Creating Database Page..." );
        ...
  +     app.processEvents();
        ...
        // etc.
        ...
  +     splash->setStatus( "Creating Games Page..." );
        ...
  +     app.processEvents();
        if ( !category.isEmpty() )
            frame.setCurrentCategory( category );

        app.setMainWidget( &frame );
        frame.show();
  +     splash->finish( &frame );
  +     delete splash;
        return app.exec();
    }

With these changes (indicated by +), the user gets feedback about what is going on while the demo is loading, and they can also hide the splash screen, if they want.

Developers looking for a splash screen solution are encouraged to try the class described here. Your feedback is especially welcome since we hope to add a splash screen class with similar functionality to Qt 3.2.


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

Copyright © 2002 Trolltech. Trademarks Qt 3.1's SQL Sequel »