Chapter 4. A Guided Tour Through the Simple Widgets
Qt is a rich library and as a beginner, you could easily find yourself reinventing the wheel when a Qt class that meets your needs already exists. This is why we present widgets that are already available with their most useful methods in this chapter and in Chapter 5. We also provide guidelines that specify when to use a certain widget.
This widget tour has two parts. This chapter presents so-called simple widgets, such as push buttons and labels. The next chapter contains the predefined dialog boxes that Qt provides for common tasks such as opening files, as well as the building blocks for defining your own dialogs. Some widgets with a more specific task, such as those used for displaying database data, are described in other chapters to stay with their underlying classes.
For most of the widgets, we show screenshots depicting them in both Windows and Motif styles because these styles are used most frequently. In other styles like the SGI style, the widgets may look somewhat different. To ensure that you can find them easily in the reference, most screenshots are taken directly from the Qt reference documentation, with the kind permission of Trolltech AS.
For your convenience, Table 4-1 contains all widgets that ship with Version 3 of Qt. They are listed alphabetically, each with a short description and a reference to the section of this book where they are explained further.
Widget | Description | Section |
QButton
|
A common base class for button-like widgets |
See Section 4.3. |
QButtonGroup
|
Organizes QButton widgets in a group |
See Section 4.8.2. |
QCanvasView
|
A widget that displays a canvas with items on it |
See Section 9.7 in Chapter 9. |
QCheckBox
|
A checkbox with a label—either a text or a pixmap |
See Section 4.3. |
QComboBox
|
A combined button and pop-up list |
See Section 4.4.2. |
QDataBrowser
|
A form-like editable view of database records |
See Chapter 19. |
QTable
|
A spreadsheet-like table widget for database data |
See Chapter 19. |
QDataView
|
A form-like noneditable view of database records |
See Chapter 19. |
QDateEdit
|
A widget for entering date values |
See Section 4.5.4. |
QDateTimeEdit
|
A widget for entering date and time values |
See Section 4.5.4. |
QDesktopWidget
|
A widget that represents the desktop on which your application runs | |
QDial
|
A potential meter-like control for entry of numerical data |
See Section 4.5.2. |
QDockArea
|
A container for dockable widgets |
See Section 4.12.5. |
QFrame
|
The base class of widgets that have an (optional) frame |
See Section 4.8.1. |
QGLWidget
|
A widget for displaying OpenGL graphics |
See Section 24.1 in Chapter 24 . |
QGrid
|
A widget that lays its children out in a grid and has no visual appearance of its own |
See Chapter 6. |
QGridView
|
A general base class for tabular data |
See Section 4.17. |
QGroupBox
|
A group box frame with a title |
See Section 4.8.2. |
QHBox
|
A widget that lays its children out in a row and has no visual appearance of its own |
See Chapter 6. |
QHButtonGroup
|
A button group that automatically arranges its children in a row |
See Section 4.8.2. |
QHeader
|
A header for tabular data displays |
See Section 4.17. |
QHGroupBox
|
A group box that automatically arranges its children in a row |
See Section 4.8.2. |
QIconView
|
Icons in a grid, optionally labelled |
See Section 4.16. |
QLabel
|
Displays a static text, a pixmap, or an animation |
See Section 4.11.1. |
QLCDNumber
|
Displays a number with LCD-like digits |
See Section 4.11.2. |
QLineEdit
|
A simple line editor for inputting text |
See Section 4.10. |
QListBox
|
A single-column list of items that can be scrolled |
See Section 4.4.1. |
QListView
|
Implements a list/tree view |
See Section 4.15. |
QMainWindow
|
A typical application window with a menu bar, some toolbars, and a status bar |
See Section 4.12.1. |
QMenuBar
|
A horizontal menu bar |
See Section 4.7. |
QNPWidget
|
A widget used for writing Netscape plug-ins |
See Section 24.2 in Chapter 24. |
QPopupMenu
|
A pop-up menu widget |
See Section 4.7. |
QProgressBar
|
A horizontal progress bar |
See Section 4.13. |
QPushButton
|
A push button with a text or pixmap label |
See Section 4.3. |
QRadioButton
|
A radio button with a label—either text or a pixmap |
See Section 4.3. |
QScrollBar
|
A vertical or horizontal scrollbar |
See Section 4.6. |
QScrollView
|
Provides a view of contents, as well as scrollbars to move through the view |
See Section 4.14. |
QSizeGrip
|
A handle for resizing a window |
See Section 4.8.5. |
QSlider
|
A vertical or horizontal slider |
See Section 4.5.1. |
QSpinBox
|
A spin box widget, sometimes called an up-down widget, a little arrows widget, or a spin button |
See Section 4.5.3. |
QSplitter
|
A splitter widget for distributing the space between two widgets |
See Section 4.8.3. |
QStatusBar
|
A horizontal bar suitable for presenting status messages |
See Section 4.12.3. |
QTabBar
|
The bar with the tabs in a tab window—useful for writing your own tab-like widgets |
See Section 4.9. |
QTable
|
A spreadsheet-like table widget |
See Section 4.17. |
QTabWidget
|
The bar with the tabs in a tab window as well as the window itself—useful for writing your own tab-like widgets |
See Section 4.9. |
QTextBrowser
|
Displays stylized text and provides features for hyperlinking |
See Section 4.18. |
QTextEdit
|
An editor for inputting text, even with different text properties |
See Section 4.10. |
QTextView
|
Displays stylized text |
See Section 4.18. |
QTimeEdit
|
A widget for entering time values |
See Section 4.5.4. |
QToolBar
|
A simple toolbar |
See Section 4.12.2. |
QToolButton
|
A push button whose appearance has been
tailored for use in a |
See Section 4.12.2. |
QToolTip
|
Provides tiny information windows about other widgets |
See Section 4.12.6. |
QVBox
|
A widget that lays its children out in a column and has no visual appearance of its own |
See Chapter 6. |
QVButtonGroup
|
A button group that arranges its children in a column |
See Section 4.8.2. |
QVGroupBox
|
A group box that arranges its children in a column |
See Section 4.8.2. |
QXtWidget
|
A widget for integrating Xt widgets into Qt applications |
See Section 24.3 in Chapter 24. |
QWhatsThis
|
Provides information windows about other widgets
that are larger than those created by |
See Section 4.12.6. |
QWidgetStack
|
Shows one of its children at a time |
See Section 4.8.4. |
QWorkspace
|
Enables the use of the MDI paradigm: subwindows in a parent window that can be independently moved, resized, and managed |
See Section 4.12.4. |
General Widget Parameters
All widgets in this chapter serve as building blocks
for your application; most of them are commonly found in dialogs.
Of course, these widgets all have the general properties of Qt widgets.
You can, for example, set their fonts and colors. See the reference
documentation for QWidget
for these methods.
The method setEnabled()
, which accepts a bool
parameter
for influencing whether a widget is available for user interaction,
is very useful. Enabling and disabling widgets, depending on the
situation, is very important for creating good user
interfaces. Disabled widgets are displayed “grayed out.” All
too often, you see programs in which you can choose some settings,
and then you are told “Sorry, this choice is not possible.”
Worse, you may be offered choices that have absolutely no effect.
These choices waste the user’s time. Remember this when you design
your application—your users will thank you for it.
Other settings that apply to all widgets are the font, which
is set with setFont()
, and the palette for drawing,
which is set with setPalette()
(you can learn more
about palettes in Section 9.3.3 in Chapter 9).
Note the uniformity of the constructors of widgets in
Qt. Most widgets have a constructor with three parameters: a QWidget*
that
specifies the parent, a name (see Chapter 21 to find out how this
is used), and widget flags, which only apply
to top-level widgets and are rarely needed even for them.
You can create a top-level widget without passing any parameters
to the constructor because all three parameters usually default to 0
. You can create any other widget simply by passing
the parent.
Some widgets also provide additional overloaded constructors.
These constructors provide parameters for setting additional initial properties.
For example, you can pass the label for the push button
as a parameter to the constructor of the class QPushButton
.
Any additional parameters usually come before the
standard ones.
Widget Styles
Qt uses GUI emulation, so it can use widgets with a Windows style in Unix and widgets with a Motif style in Windows—or you can use a different style altogether. You could even mix widgets with different styles in your application, but doing so is not recommended.
There are several ways to set the style of widgets:
You can set the style for an individual widget by calling
setStyle()
on the widget object and passing a pointer to an object of a class that inherits fromQStyle
. The two most frequently used subclasses areQWindowsStyle
andQMotifStyle
, but there is alsoQCDEStyle
(which emulates the look and feel of the Common Desktop Environment in a way that’s similar toQMotifStyle
),QMotifPlusStyle
(a Motif-like style with better bevelling and highlighting buttons when the mouse rolls over them),QSGIStyle
(a Motif-like style that looks like systems running the Unix-variant Irix from Silicon Graphics), andQPlatinumStyle
(which emulates the look of the standard style of the Java Swing toolkit). Qt/Embedded also includesQCompactStyle
, which is similar toQWindowsStyle
, but which occupies less screen real estate (useful on embedded devices with limited screen resolutions). Finally, in the Qt version of MacOS X,QAquaStyle
emulates the default Aqua style used in MacOS X. If you build Qt on Windows XP, you can configure it to use an additionalQXPStyle
.As noted before, setting a certain style for an individual widget is not recommended.
You can set the default for all widgets by calling the static method
QApplication::setStyle()
. You should call this method before any widget is created or repaint all the widgets afterwards. This repainting can be done with the following code:// set the default QApplication::setStyle( new QWindowsStyle ); // or any other style you like // get a list of all top-level widgets of the application QWidgetList* widgetlist = QApplication::topLevelWidgets(); // create an iterator over the list QWidgetListIt widgetlist_it( *widgetlist ); // do the following for each top-level widget while( widgetlist_it.current() ) { // take one widget from the list QWidget* widget = widgetlist_it.current(); // advance the iterator to the next element ++widgetlist_it; // apply the new style to this widget widget->setStyle( newstyle ); // get a list of all descendants of this widget that are widgets // themselves QObjectList* objectlist = widget->queryList( "QWidget", 0, 0, true ); // create an iterator over this list QObjectListIt objectlist_it( *objectlist ); // apply the new style to all descendant widgets while ( objectlist_it.current() ) { ++objectlist_it; // advance iterator to next element, // the current one is already changed QWidget *child = (QWidget *)( objectlist_it.current() ); child->setStyle( newstyle ); } delete objectlist; } delete widgetlist;
You might be surprised about the two
delete
calls that are here when nonew
call is seen. That’s because Qt has allocated these objects itself.The last (and often the most advisable) option is that you can set the widget style on the command line. Of course, this only works if none of the other two methods is used. You can call any Qt program with command-line switches such as
-style=windows
,-style=platinum
, or-style=motif
.
Properties
Objects of all classes that inherit from
QObject
can have properties, but properties are mostly used in
connection with widgets. A property is nothing more than a name for a
certain setting that a widget has, together with a method used to set this
setting, a method used to query it, and other features. Properties are
defined with the macro Q_PROPERTY
. For most
things you can set in a widget, there is a corresponding property, and
instead of calling the set method directly, you could also use
setProperty()
. Instead of calling:
myWidget->setDefault( true );
you could also call:
myWidget->setProperty( "default", true );
This code is more cumbersome to write and less readable, so you will rarely use it. Properties are meant for development tools that need to know what can be set in a widget like Qt Designer.
Buttons
Buttons are probably the most common GUI element. You can group them according to how they are used: push buttons yield some kind of action such as closing a dialog box or invoking a program-specific function. On the other hand, radio buttons and checkboxes, which are collectively known as option buttons, are used to select options rather than to invoke actions. With push buttons, this is usually not possible because something happens as soon as they are clicked.[16] There is also a visual difference: push buttons appear “pressed in” only while clicked, while option buttons change appearance when clicked and keep that appearance.
Push Buttons
Push buttons are represented by the class
QPushButton
(see
Figure 4-1) in Qt. These buttons yield some kind
of action, such as closing a dialog box or invoking a program-specific
function. They can be labeled with either text or pixmaps, using
the method setText()
or setPixmap()
.
A text string can also be passed in the constructor. You usually
connect a slot to the signal clicked()
, which
is emitted when the mouse is pressed and released again over the
button.
The signals pressed()
and released()
are
also available, but you should be careful with them, since you
are probably on your way to designing a
hard-to-use GUI if you use them. There is also a signal
called toggled()
, which is only emitted in a
special mode of a QPushButton
object (we will
discuss this signal later).
A push button that resides in a dialog can become
the default push button. This push button is “clicked” when the user
presses the Enter key. Of course, there can only be one default
button in a dialog box. Make a push button the default
button by calling setDefault()
on it. A related
method is setAutoDefault()
, which makes a button
an autodefault button. Unlike the default button, there
can be more than one autodefault button. An autodefault
button becomes the new default button when it gets the keyboard
focus, and it loses this property when the keyboard focus moves
to a different widget.
It is good practice to always have a default button to facilitate use of your dialog box with the keyboard only.
Menu buttons are a special case of push buttons. These
buttons display a menu when clicked. Of course, menu
buttons still emit the same signals, but they are rarely used;
instead, the signals connected to the menu items are typically used because we are only interested in a menu button if it displays
the menu. Make a push button a menu button by calling
QPushButton::setPopup()
and passing a pointer to a
QPopupMenu
. The menu that is pointed at is
the menu that will be opened when the button is clicked.
Radio Buttons and Checkboxes
Option buttons are represented in Qt by the classes
QCheckBox
(see Figure 4-2) and
QRadioButton
(see Figure 4-3). These buttons differ mainly in how
the choices for the user are restricted. Checkboxes are
used for “many of many” choices; any number of checkboxes
in a group, including none, can be checked. Radio buttons are for “one
of many” choices, so exactly one radio button in a group can be
checked. Both styles of buttons have special appearances that reflect
their choice model, for example, in MS Windows, checkboxes
are square and radio buttons are circular. In MS Windows, your users
would be confused if you used square buttons that have
radio button behavior.
As a programmer, you should know whether radio buttons and
checkboxes are checked. You find this information with the methods
QCheckBox::isChecked()
and
QRadioButton::isChecked()
. When you want to set or
unset an option button programmatically
(for example, when setting a default value), use
QCheckBox::setChecked()
or QRadioButton::setChecked()
.
These methods accept a bool
parameter that indicates
whether the button should be set or unset.
Like push buttons, option buttons can have text or a pixmap as a label. Unlike push buttons, they are not shown in the button itself, but close to it. Clicking on the text has the same effect as clicking on the button.
Normally, you just check for the values of the option
buttons in a dialog box when the dialog box has been closed with OK. Sometimes,
however, you may want to react immediately to user interaction.
This could be the case when setting or unsetting an option button
influences the availability of other buttons. In this case, you
can connect to the signals clicked()
,
released()
, pressed()
,
and toggled()
, just as you do with push buttons.
To achieve “one of many behavior” with radio buttons, you have to do more work than simply create them. Your application must know which buttons form a group because there could be two or more groups of radio buttons, each with exactly one button checked.
You can group your radio buttons with a QButtonGroup
object.
This should also be used to visually identify your buttons as a
group, but for now we will just talk about enforcing radio button
behavior. You have two options for using QButtonGroup
. You
can make your radio buttons children of a QButtonGroup
object,
which automatically enforces radio button behavior:
QButtonGroup* bgroup = new QButtonGroup( "Heading", parent ); QRadioButton* radio1 = new QRadioButton( "Choice1", bgroup ); QRadioButton* radio2 = new QRadioButton( "Choice2", bgroup ); QRadioButton* radio3 = new QRadioButton( "Choice3", bgroup );
The other approach is to insert the radio buttons into
a button group by calling QButtonGroup::insert()
.
Note how the parents differ:
QButtonGroup* bgroup = new QButtonGroup( "Heading", parent ); QRadioButton* radio1 = new QRadioButton( "Choice1", parent ); bgroup->insert( radio1 ); QRadioButton* radio2 = new QRadioButton( "Choice2", parent ); bgroup->insert( radio2 ); QRadioButton* radio3 = new QRadioButton( "Choice3", parent ); bgroup->insert( radio3 );
Sometimes you want a button that looks like a push button but acts like an option button. An example would be a dialog box in a word processor that lets you choose the alignment (flush left, flush right, centered, or justified) of a paragraph. The four choices could be depicted by icons, and it would be graphically more attractive (and more obvious to the user) if the icons themselves could be clicked.
To achieve this functionality, use QPushButton
objects
and make them toggle buttons with setToggleButton( true )
. You can then also connect the signal toggled()
to
your slot.
While radio buttons can only be on or off, checkboxes can have three different states: on, off, and “not
changed.” To use this feature, call
QCheckBox::setTristate( true )
first, and then
set the checkbox into “no change” mode by calling
QCheckBox::setNoChange()
. The typical use for
this “not changed” option is for font styles in, for example, word processors: if you select an area of
text, some text in this area might be italic and some text
might not be. In this case, you would set the checkbox into “not changed” mode, which could also be interpreted as
“both.” If the user does not do anything with the checkbox, nothing is changed with respect to italicization. However, once the
user clicks the checkbox, the whole area will either be made italic
or nonitalic. Note that it is not possible to bring the checkbox back
into “not changed” mode from the user interface (at least
not directly by manipulating the checkbox). Changing the mode can
only be done programmatically.
Selection Widgets
This section discusses widgets that let the user choose one (or several) items from a given selection. These widgets include combo boxes and list boxes, which are mainly used for selecting text items, but can also present graphical data to the user.
List Boxes
List boxes are represented by the class QListBox
(see
Figure 4-4) in Qt. List boxes let the user select one or more entries
from a list of items. If there are more items than can fit into
the screen space allocated for the list box, the user can use scrollbars to look through the items.
There are
three techniques used to insert items into a list box. The most
general is the method QListBox::insertItem()
,
which exists in three variants used to insert strings, pixmaps, or
items of type QListBoxItem
. The latter can be
used to define items different from text and pixmaps, but it is most
commonly used to define items that can show text and a pixmap at
the same time. If you want to insert several strings in a row and
already have them in a QStringList
(see Chapter
8), you can use insertStringList()
. You can then
sort the items in the box according to the texts of the items by using
the sort()
method.
After inserting entries, you need to be informed about the
entries that have been selected. If the list box allows selection
of only one entry at a time, (for example, the default), you can simply ask for the currently selected
item with QListBox::currentItem()
. This yields
a zero-based position number, which you can pass to
QListBox::text()
or
QListBox::pixmap()
to
retrieve the text or pixmap at that position.
If you switched your list box to multiselection
mode by calling QListBox::setMultiSelection( true )
,
things are more difficult. You can loop over all the entries
and ask each of them if the entries are selected, for example:
for( uint i = 0; i < listbox->count(); i++ ) { if( listbox->isSelected( i ) // item i (whose text is listbox->text( i ) or whose // pixmap is listbox->pixmap( i )) is selected }
You can also be notified when an item is
selected. When an item is highlighted with a single mouseclick,
the signals highlighted( int )
and highlighted( const QString& )
are emitted. When an item is selected
by double-clicking it or by pressing the Enter key, the signals
selected( int )
and selected( const QString& )
are
emitted. Variants with an int
parameter pass
the position number, while the variants with the const QString&
parameter pass the text of the item.
In multiselection list boxes, the signal selectionChanged()
can
be useful. When several items are selected in a row by dragging
the mouse over them, selectionChanged()
is emitted
only once.
There are also a couple of signals at a slightly lower
level. They pass at least a pointer to the
QListBoxItem
if all you put in the list box were
text and pixmaps and you did not use any
QListBoxItems
(Qt will create these for each
item anyway). These signals report mouse actions on the items. The
available signals are clicked()
,
pressed()
, doubleClicked()
,
returnPressed()
,
rightButtonClicked()
,
rightButtonPressed()
,
mouseButtonPressed()
, and
mouseButtonClicked()
in various incarnations.
QListBox
contains many methods for customizing
its operation. Please refer to the reference documentation for more
details.
Combo Boxes
A combo box is an alternative to a single-selection list box. Its main advantage is the lower amount of screen real estate that it occupies. When not in use, a combo box shows only the currently selected item with a downward arrow. When the user clicks on the combo box, a list pops up, from which the user can choose an item. After the user has chosen an item by clicking on it, the combo box folds together again and shows the new selection.
Combo boxes are represented by the class QComboBox
(see
Figure 4-5 and
Figure 4-6) in Qt. They can contain
text and pixmaps, but no
user-defined item types like those contained by list boxes. You can have read-only
and read-write combo boxes. Read-write combo boxes
allow users to insert new items at runtime. These combo boxes have
a text-entry field that displays the currently selected
item and can also be used for entering new text. Whether the user
is allowed to do so, and where new items are inserted in the list,
is governed by an “insertion policy” and set with
QComboBox::setInsertionPolicy()
.
See the reference documentation for the available policies.
Note that if you use read-write combo boxes, you cannot have pixmaps as items since it is not possible to edit the pixmaps in a text-entry field. When you try to insert a pixmap as an item, nothing will happen.
If you use Motif-like styles, you can choose between old-style combo boxes (read-only) and new-style combo boxes. The old ones look like those found in Motif 1 programs; the new ones look like those in Motif 2 programs. Windows-like styles have only one style of combo boxes. The constructor determines whether an old-style or a new-style combo box is created. If you create your combo boxes with:
QComboBox* combo = new QComboBox( parent );
you get an old-style combo box. If the first parameter is a Boolean value, a new-style combo box is created. This Boolean value determines whether or not the combo box will be read-write:
QComboBox* combo1 = new QComboBox( true, parent ); // read-write QComboBox* combo2 = new QComboBox( false, parent ); // read-only
The interface used for inserting items into combo boxes is very similar to the insertion interface for list boxes: you can either insert
your text or pixmap items with insertItem()
,
or you can insert whole string lists in the form of QStringList
objects
with insertStringList()
.
As with list boxes, you can ask the combo box for the currently
selected item with QComboBox::currentItem()
.
You can also be notified when an item has been highlighted (the
mouse is over the item but still pressed) or selected (the mouse
has been released over an item) with the signals
highlighted()
and activated()
.
These signals come in an int
- and a const QString&
- version, as with
QListBox
.
When you use read-write combo boxes, you may want
to check user input for validity before accepting it as a new item
in the combo box. Checking user input can be done with validators, which
are represented in Qt by the class QValidator
.
We will talk about this class in the “Validating User Input” section in Chapter 10.
Like list boxes, combo boxes have a lot of methods with which you can customize their appearance and behavior. You should at least skim the reference documentation to see what is in store for you.
There sometimes is confusion about whether to use a list box or a combo box. Choosing the wrong box for a given situation can lead to an awkward user interface. Here are some guidelines, but remember, there is no absolute truth:
When you need nonstandard items (items that are neither text nor a pixmap), you have to use a list box.
When you want to allow the user to add new items at runtime, you have to use a read-write combo box or provide extra widgets for adding and removing items.
When screen real estate is scarce and dialog boxes are already crowded, use a combo box. On the other hand, think about whether your dialog box is really well designed. Dialog boxes with too many options are incomprehensible to casual users. Consider splitting up the dialog box.
When you want to permit the user to choose more than one item, you must use a list box.
When you have just a few fixed items to choose from, use neither a combo box nor a list box, but a group of radio buttons or checkboxes.
Widgets for Bounded-Range Input
The slider, dial, and spin box widgets allow you to choose a numerical value from a given set. With a slider or a dial, the minimum and maximum values are fixed. A spin box need not be bounded. Finally, three widgets are used to enter date and time values.
Sliders
While it is not always obvious whether you should use a list
box or a combo box, when you should
use a slider is almost always obvious. In Qt, sliders are represented by the class QSlider
(see
Figure 4-7). A slider is a longish
widget with a knob that you can drag to choose a numeric value from
a predefined range. The position of the knob is proportional to
the value with respect to the range. Early versions of Qt did not
have a slider, which is why some programmers still use a scrollbar to achieve the same effect. Don’t use a scrollbar—your users
will become confused.
Sliders have several parameters. The minimum value and the maximum value define the range from which the value can be selected; the step width determines how much the knob moves when you click in the slider trough instead of dragging the knob; and the initial value determines the initial position of the knob. Finally, the orientation says whether the slider should be arranged horizontally or vertically. Since these values usually don’t change over the lifetime of a slider, they should be set in the constructor:
QSlider* slider = new QSlider( 0, 200, 5, 100, QSlider::Horizontal, parent, "myslider" );
This code example creates a slider with which you can choose an integer between 0 and 200. Clicking in the trough decreases or increases the slider value by 5. The initial value is 100, and the slider is positioned horizontally.
QSlider
inherits from
QRangeControl
, which allows methods such as
setRange()
, setSteps()
, and
setValue()
to
change these parameters. QSlider
adds
setOrientation()
.
Remember that changing this visual parameter after the slider has
been shown can be very irritating to the user.
If the slider is long, it can be useful to provide visual
clues as to where a certain value might be on the slider “scale.”
These clues are tickmarks, which you can add to a slider by calling
setTickmarks()
.
You can choose whether you want the tickmarks to be above, below,
or on both sides of the slider, or whether you want none
at all. There is no general rule about when to use tickmarks, but
we suggest you use them only when the slider is longer than 200
pixels or so to avoid visual overload.
To retrieve the slider’s set value, you can
call either QSlider::value()
, or you can be notified
when the slider value changes. The signal sliderMoved( int )
is emitted whenever the knob is moved so much that
a new value is chosen. The parameter contains the new value.
If you want to be notified only when the user is done selecting
a value (i.e., when the user has released the knob), connect to the signal
valueChanged( int )
. When
you set setTracking( true )
, valueChanged( int )
behaves just like sliderMoved( int )
.
If you are interested in knowing when the user has pressed or released
the knob, you can also connect to the signals
sliderPressed()
or
sliderReleased()
, but you will rarely need to do so.
It’s interesting and useful to connect a QSlider
object
to a QLCDNumber
object. You will learn about
QLCDNumber
later
in this chapter, but for now you only need to know that it shows
a value with a predefined number of digits in the form of the
seven-segment LCD displays. Such a display can be used
to represent the value chosen with the slider:
QSlider slider = new QSlider( 0, 9, 1, 1, QSlider::Horizontal, parent ); QLCDNumber number = new QLCDNumber ( 1, parent ); QObject::connect( slider, SIGNAL( sliderMoved( int ) ), number, SLOT( display( int ) ) );
This combination is so useful that you will probably see more
combinations of sliders with QLCDNumber
objects
than single sliders. For a full example, see the section
“Another Example for Signals and Slots” in Chapter 2.
Dials
Dials (see Figure 4-8) are very similar to sliders. They are a recent addition to Qt, and most people could probably just get along with using sliders exclusively. There are two differences between a slider and a dial. First, the slider is long, while the dial is square; second, a dial can “wrap around.” If the user has reached the maximum value and tries to increase the value further, he will end up with the minimum value again and can continue to select from there. Whether this is useful or not depends on the application; in most situations, this wrapping around is probably not what the user expects, but in some rare cases, such as when selecting a direction, it might be just what you need.
If you do not need the wrapping around, then whether you pick a slider or a dial is mostly a question of dialog box layout. You can use whatever looks nicer in your dialog box. Some users think that sliders are easier to operate, however. Also, there are some patterns from real life that are more connected to either a slider or a dial. Take a stereo for example: the volume knob is usually round like a dial and does not wrap around, fortunately. The various controls for the equalizer are usually sliders, however. If you write a front end to a sound card, you might want to follow these patterns; doing so will make it easier for your user to recognize the functionality of each control.
As for the programming interface, QDial
, which
implements dials in Qt, has almost the same interface as
QSlider
so you can read about most methods
there. One method that QSlider
does not have, of
course, is setWrapping( bool )
, which specifies
whether the dial should wrap around or not. By default, wrapping is
off. When wrapping is off, the dial will not be a complete circle, but
it will leave out a segment at the bottom to indicate that a
“barrier” is present.
Two methods control the dial’s appearance:
setNotchesVisible( bool )
specifies whether small
lines should indicate the various positions and
the method setNotchTarget( double )
specifies a size for these
lines. Depending on the size of the dial and its other parameters,
QDial
will sometimes have to use a slightly
different value.
QDial
has the same signals as
QSlider
, but they are of course not named
sliderMoved()
, etc., but
dialMoved()
, etc.
Spin Boxes
Spin Boxes (see Figure 4-9),
implemented by the class QSpinBox
, consist of a
text-entry field and two arrow buttons—one for increasing the numerical value in the text field and one for decreasing it.
This combination is convenient for entering integer values because
the UI element can be operated easily with the mouse. But if doing so becomes
cumbersome (for example, the value desired is far away from the
current value), the keyboard can be used as well.
Note that without any extra precautions, this is not a foolproof method for inputting integer numbers in a given range because the user can enter any text whatsoever into the widget. You can use the methods described in the “Validating User Input” section to make sure that the users enter only integer numbers in the desired range.
The most obvious methods are setValue()
and
value()
for setting and retrieving the current
value, but there are more. You can change the display in various
ways. For example, you can set a prefix and a suffix that are
displayed in front of and behind the value in question,
respectively. This is done with setPrefix()
and
setSuffix()
.
A related method that also influences the display, but not the value
itself, is setSpecialValueText()
.
Say that you
want to let the user choose a font size, but that you also want to provide
some kind of “default value,” which could mean “take
the font from the previous paragraph.” You can have a value like
0
, which
is an impossible value for the font size as a marker for the font,
but it would be difficult to explain this to the user. This is where
setSpecialValueText()
comes
in. With this method, you set text that is shown, instead of the
actual value, when the smallest possible value has been chosen. In our
example, this could be <default>
.
Two more methods to change the value programmatically are
stepUp()
and stepDown()
,
which simulate clicking the up arrow and the down arrow buttons,
respectively.
Normally, when the user clicks on one of the arrow buttons
and the minimum or maximum value that is set together with setRange()
is
reached, the value does not change any more. But if you have called
setWrapping( true )
, the values wrap around and start at the maximum
or minimum value again.
For the buttons that let the user increase or decrease
the selected value, there are two different styles. By default, a
small up and a small down arrow are shown, but you can change this arrow to
plus and minus signs by calling QSpinBox::setButtonSymbols( PlusMinus )
.
Finally, you will find two signals, both with the name valueChanged()
.
One has an integer parameter and the other has a const QString&
parameter.
By connecting to these signals, your program can be informed whenever
the value of the spin box changes. By choosing the right signal,
you can either get just the numerical value or the whole string
in the input field, including the prefix and suffix, if any exist.
Widgets for Entering Date and Time
A particular case of bounded range input is the input of date and time
values. It is not so much the bounding of the range that is
interesting here (even though there are boundaries, like the
fact that the month number in a date value must be between 1 and 12), but
rather the format. Qt provides three widgets for entering date and
time values: QDateEdit
(see Figure 4-10) is for entering dates,
QTimeEdit
(see Figure 4-11) is for entering time values,
and QDateTimeEdit
(see Figure 4-12) obviously is the simple
concatenation of the two. The way these three widgets are used is very
similar; QDateEdit
works on a
QDate
object, QTimeEdit
on a
QTime
object, and QDateTimeEdit
on a QDateTime
object. You can construct all three
widgets with the date or time value in question, but be informed about
changes with respective valueChanged(...)
signals
and specify—at least for QDateEdit
and
QTimeEdit
—minimum and maximum values that are
permissible. All three also have an auto-advance feature that is
turned on by calling setAutoAdvance( true )
and
that moves the focus to the next entry section when one is
completed.
These three widgets are pretty simple—they are not much more than a couple of spin boxes in a row. However, third-party components that provide more fancy input (like calendar input) of date and time values are available.
Scrollbars
Like buttons, scrollbars are intuitively understood by most
users. You can either use them directly by creating objects of type
QScrollBar
(see
Figure 4-13), or use them by means of
QScrollView
. Usually, you’ll do the latter
because it relieves you from all the
geometry management related to scrollbars that are attached to an
application window and because it supports automatic scrollbar
policies.
Like sliders, scrollbars have a minimum value, a maximum value, a current value, and an orientation. There is a “line step size,” which is the amount the scrollbar knob moves when you click on one of the buttons at the end of the scrollbar. The “page step size” is the amount the knob moves when you click in the trough of the scrollbar.
It is most convenient to set these values when creating the scrollbar:
QScrollBar* scrollbar = new QScrollBar( 0, /* minimum value */ 500, /* maximum value */ 1, /* line step value */ 10, /* page step value */ 0, /* initial value */ QScrollBar::Vertical, /* orientation */ parent );
but you can also set the values with setRange()
,
setSteps()
, setValue()
,
and setOrientation()
. The latter accepts the
values QScrollBar::Vertical
and QScrollBar::Horizontal
.
To use the scrollbar, you must know when it is operated by
the user, no matter whether the user operates it with the knob, by
clicking on the arrows, or by clicking in the trough. Your application
can be notified of any changes by a plethora of signals. The most
important are sliderMoved( int )
and
valueChanged( int )
. With these signals, the parameter contains the
new scrollbar value. The sliderMoved( int )
signal
is emitted whenever the value changes, no matter whether the user
drags the knob or clicks on some part of the slider. The
valueChanged( int )
signal is emitted only when the user clicks on the
scrollbar or drags the slider and releases the knob again, unless
setTracking( bool )
has been called before. In this case, valueChanged( int )
behaves just like sliderMoved( int )
.
Four other signals are emitted when the slider
value changes by a line step or a page step:
nextPage()
, nextLine()
, prevPage()
,
and prevLine()
. The last two remaining
signals, sliderPressed()
and sliderReleased()
,
are probably less useful. They are emitted when the user has started
or quit operating the knob. You could use these signals when you have to
prepare your application window for scrolling.
The most common use for scrollbars is moving the view that
an application window provides on the application data. There is
usually a horizontal scrollbar below the application window and
a vertical scrollbar to the right of the application window. Sometimes
the scrollbars are only shown on demand—for example, only when
the representation of the application data is larger than the visible
area. You could arrange the scrollbars yourself, but it is much easier to
use the class QScrollView
. We will cover the use of QScrollView
later in this chapter.
Menu-Related Widgets
To program menus, Qt provides the four classes QPopupMenu
(see
Figure 4-14), QMenuBar
(see
Figure 4-15), QMenuData
, and
QCustomMenuItem
.
You have already seen the basic use of pull-down menus and pop-up menus in Section 3.1 in Chapter 3, so we will focus on some special uses here.
Providing accelerators in menus is easy. Accelerators are key combinations used to directly access a function bound to a menu item without popping up the menu first. They should not be confused with underlined letters in menu entries, which can be used to jump to the entries when the menu is already open.
Set an accelerator for a menu item by calling QMenuData::setAccel()
,
which accepts a key code and a numeric identifier. The identifier
is the value returned by QMenuData::insertItem()
when
you insert the menu entry. The keycode is an expression like
CTRL + Key_O
or SHIFT + ALT + Key_P
.
Refer to the header file qkeycode.h for a complete
list of keycodes. Qt automatically generates a string like Ctrl-O
,
which is appended to the menu-item label.
Until now, you have mostly seen first-level menus,
which pop up in response to a user event like a mouseclick
or from a menu bar. But you don’t have to learn
anything new to add second-level menus, which pop up from
another pop-up menu. Simply add the second-level
menu with insertItem()
to the first-level
menu.
It is possible to implement dynamic menus—that is, menus that change contents as the application changes—in Qt. To do this, connect a slot to the signal aboutToShow()
of the menu in question. In this slot, you can change the menu dynamically by calling QMenuData::removeItem()
, QMenuData::updateItem()
, QMenuData::setItemEnabled()
, or whatever you like. The only problem is that you cannot get the
QPopupMenu
object corresponding to the ID, so you have to keep a
table that maps IDs to QPopupMenu
objects
somewhere.
Apart from bringing up pop-up menus synchronously
with exec()
, as described in the “Adding
Menus” section in Chapter 3, you can also pop them up asynchronously. This makes
it possible to continue background processing while the menu is
popped up, but since the menu has to grab the mouse, the next mouseclick leads to closing the menu, no matter where it occurs.
If you use asynchronous menus, you won’t be able to check
the selected menu item via the return code, but only via the signal
activated( int )
.
If you have used Motif before, you
will certainly know the concept of tear-off menus. A tear-off menu
has a special menu item (usually a dashed line). When this item is
selected, a copy of the menu is created and put in its own top-level
window, which can be moved independently of the application window and
stays open until it is closed explicitly. This can be achieved by
calling insertTearOffHandle()
on the
QPopupMenu
object in question. You specify the
position where the tear-off item should be added. If you do not
specify anything and just accept the defaults, this will be the very
first item, which is also what users accustomed to Motif will
expect. Note, however, that people used to Windows platforms will
probably not know what this dashed line means and will be irritated when
they select it and a new window pops up. Therefore, it might be a good
idea to use tear-off menus only in Motif style and other Unix
styles (or not at all).
If you need more than text display, pixmaps, and an accelerator, you can
also take over the drawing of menu items completely by yourself. This is
done by subclassing from QCustomMenuItem
and
reimplementing at least the two methods—paint()
,
which should do the actual painting, and sizeHint()
,
which should return the preferred size of the menu item. You can also
override fullSpan()
and return
true
if the menu item should span the entire menu,
as well as override isSeparator()
and
return true
to mark the item as a separator. You can
then insert an object of your subclass of
QCustomMenuItem
with
QMenuData::insertItem()
, just like you do with
ordinary items.
Arrangers
Qt contains ten classes that are mainly meant for arranging
other widgets: QFrame
, QGroupBox
,
QButtonGroup
, QHGroupBox
,
QVGroupBox
, QHButtonGroup
, and
QVButtonGroup
draw a frame around their contents,
while QSplitter
lets the user change the relative
position of two widgets to each other. QWidgetStack
arranges
widgets on top of each other so that only one widget is
visible at any time. Finally, QSizeGrip
helps
resize other top-level widgets. With one exception (see the next
section), QFrame
is
only useful as a base class for your own widgets. It serves as a
base class for several built-in Qt widgets as well. On the other hand, you
will rarely derive classes from QGroupBox
or
QButtonGroup;
these are consumed as
is, just like QSplitter
and
QWidgetStack
.
Frames
QFrame
draws a frame and then calls
drawContents()
.
You should reimplement this method in the classes that you derive
from it; the default implementation does nothing. The most useful
method of QFrame
is setFrameStyle()
,
which accepts a large number of flags. These flags come in two groups:
frame shapes and shadow styles. You can pick one flag from each group and join them with bitwise-or
. The frame
shapes group contains flags like Box
for a simple
box and WinPanel
for Windows-like
panels. The shadow styles group contains the three flags
QFrame::Plain
, QFrame::Raised
, and
QFrame::Sunken
.
It is hard to imagine how a frame shape and shadow style will look when combined. The Qt reference documentation contains an image that shows all possible combinations. You simply have to pick one!
If you want to customize your frames further, you can also
use the methods QFrame::setLineWidth()
and
QFrame::setMidLineWidth()
, but using these methods leads to
nonstandard interfaces.
You can also make the contents of the frame keep a certain
distance from the frame itself by calling setMargin()
and
passing the distance as an integer value. This margin is then subtracted
from the contents rectangle reported by contentsRect()
,
and that subtraction should be respected in your implementation of drawContents()
.
Using a QFrame
object
alone is useful in one case: QFrame
objects make fine separator
lines. The object must be enabled to have a line shape, but since
this enablement is the default in the constructor, you usually don’t
have to worry about this requirement.
Group Boxes
While QFrame
is a building block for your
widgets, QGroupBox
(see Figure 4-16) and QButtonGroup
can be used
as is. They simply draw a frame that can visually group widgets
that are related logically, such as a group of mutually exclusive
radio buttons. Both widgets show up the same way, but QButtonGroup
has additional facilities for working with buttons, like enforcing
radio button behavior.
It is almost always a good idea to add a description to widgets
that are grouped together by a QGroupBox
or
QButtonGroup
object.
You can add a description by calling QGroupBox::setTitle()
,
which adds a label to the upper frame line. You can also pass this title
as the first argument in the constructor. While most group boxes
have their title on the left side of the upper frame, you can also
change this by calling QGroupBox::setAlignment()
with
the parameter Qt::AlignCenter
or Qt::AlignRight
.
QButtonGroup
is used mainly to enforce
radio button behavior so that only one button in a group can be
checked at a time. You can either make your radio buttons children
of a QButtonGroup
object or insert them manually
with QButtonGroup::insert()
. Inserting a radio
button into a button group automatically makes it exclusive. If
you want to have the same behavior for nonradio buttons,
you have to call QButtonGroup::setExclusive( true )
explicitly.
Another useful application of QButtonGroup
is
connecting one slot to the signals of several buttons. See the “Connecting
Several Buttons to One Slot” section to learn how to do this.
There are special versions of QGroupBox
and
QButtonGroup
that are called
QHGroupBox
, QVGroupBox
,
QHButtonGroup
, and
QVButtonGroup
. The only difference to the basic
versions of these widgets is that they perform layout management on
their children automatically. This difference is advantageous because the widgets can
automatically take care of not overwriting the frame and the
caption. You can read more about this concept in Chapter 6.
Splitters
Splitters manifest themselves as small knobs (see
Figure 4-17) that can be used to distribute
the available space between two widgets. In Qt, this distribution is implemented by
the class QSplitter
, which also contains the
machinery for resizing the widgets. Example 4-1 contains
a very simple program for using QSplitter
.
#include <qapplication.h> #include <qlabel.h> #include <qsplitter.h> int main( int argc, char* argv[] ) { QApplication a( argc, argv ); QSplitter* splitter = new QSplitter(); a.setMainWidget( splitter ); QLabel* label1 = new QLabel( "Label 1", splitter ); QLabel* label2 = new QLabel( "Label 2", splitter ); splitter->show(); return a.exec(); }
Two label widgets are created as children of the splitter
and are thus arranged by the splitter automatically.
Of course, a horizontal arrangement is also possible. This arrangement can be
achieved by passing QSplitter::Vertical
either
as an additional first parameter to the QSplitter
constructor
or by passing it to setOrientation()
. This might sound confusing,
but if the splitter’s orientation is vertical, the two widgets will be
in a row and vice versa.
When you compile and run the program in Example 4-1,
you will find it possible to make one widget almost completely disappear.
You may want a certain amount of space to be available for
each widget. This is easily done by calling setMinimumSize()
at
the widget as usual:
label1->setMinimumSize( label1->sizeHint() );
Calling setMinimumSize()
ensures that label1
will always be
large enough to display its contents.
QSplitter
respects this allotted space if you call setMaximumSize()
as well.
Also, you can determine whether a widget should also be resized
when the whole splitter is resized by calling setResizeMode()
and passing the pointer to the widget in question as the first parameter,
and then by passing either QSplitter::Stretch
,
QSplitter::KeepSize
, or
QSplitter::FollowSizeHint
as the second
parameter.
Normally, QSplitter
resizes the widgets
it arranges at the end of a resizing operation when the user stops
dragging the knob. If you call setOpaqueSize( true )
on
the QSplitter
object, the widgets get all the
resize events. This is useful for providing the user with immediate
feedback, but it can slow down the resizing operation if the resized widgets
are large, complex, or slow to redraw.
Widget Stacks
The last of the widgets that arrange other widgets is
QWidgetStack
.
Except for a frame drawn around it, a widget stack simply shows one of its children at a time, as specified by the
user. This is useful when you have several widgets but want
to make only one visible at a time.
After creating
an object of this class, create the widgets that you want arranged by the widget stack, and then add
these widgets to the widget stack with addWidget()
. This
method expects a pointer to the widget in question and an integer
ID. The widget should be a child of the QWidgetStack
object.
If it is not, it will be made into one. You can also
remove widgets from the widget stack again by calling removeWidget()
.
To show a certain widget on the stack, call raiseWidget()
.
You can either pass the method a pointer to the widget that you want to
make visible or pass the integer ID that you passed to addWidget()
.
Size Grips
Size grips (see Figure 4-18) are small handles that let the user resize a top-level window; they are usually located in the lower-right corner of the window. If you write a standard-style application, you probably do not need to include a size grip yourself. Since you will already have a status bar, and status bars include size grips, you are already set.
Should you need to create a size grip explicitly, create
an object of class QSizeGrip
anywhere
in the widget hierarchy and position it as
desired (anything but the lower-right corner is likely to surprise
your users). The size grip will find out what the top-level window is
and let the user resize.
Tab-Related Widgets
When you have a lot of user-interface elements in a main window or a dialog box, you might consider using tabs. Tabs resemble the filing cabinets where each folder has a small heading cardboard that explains what is in the folder.
Qt provides three classes for achieving this:
QTabBar
, QTabWidget
, and
QTabDialog
. There is also the class
QTab
, which, though public, is mostly undocumented.
Since most uses of tabs are in the form of a freestanding
dialog box, I will cover the use of tabs in the next chapter (where
QTabDialog
is described). Everything it says there
is also valid for QTabWidget
, with the exception
that a QTabWidget
object is not a freestanding
dialog box, but is meant to be included elsewhere in a window. In other words, it has
no buttons. To put it
another way, QTabDialog
is a combination of
QTabWidget
and QDialog
. You can
see a tab widget in Figure 4-19.
QTabBar
is the class that provides the
actual tab row (QTabWidget
is a combination of
QTabBar
and
QWidgetStack
). Usually, you will only work with
QTabDialog
or QTabWidget
and not
use QTabBar
directly, but if you have special
requirements like special styles, you can make a subclass from
QTabBar
and draw the tabs yourself. The most
important methods in QTabBar
are
setShape()
, used to select one of the four predefined
tab styles (tabs at the top with rounded corners, triangular tabs at
the top, tabs at the bottom with rounded corners, and triangular tabs
at the bottom); setTabEnabled()
, which lets you
control the accessibility of a tab page; and
setCurrentTab()
, used to select the
current tab page programmatically. QTabBar
also emits the signal
selected( int )
when one of the tab pages is
selected.
Text-Entry Fields
Text entry in Qt is done via the two widgets QLineEdit
(see
Figure 4-20) and QTextEdit
(see
Figure 4-21) for single-line
and multiline text entry fields, respectively. Their use is
very simple. In most cases, it is sufficient to create the widget. You can
preset text with setText()
and
retrieve the entered text with text()
. You can restrict
the maximum number of characters entered in QLineEdit
objects
and decide whether the characters entered should be shown, not shown,
or replaced by asterisks. The latter two options are useful mainly
for entering passwords. QTextEdit
does not
have these options.
QTextEdit
also lets you insert text
programmatically with QTextEdit::insertAt()
and QTextEdit::insertLine()
,
and remove it again with QTextEdit::removeLine()
.
In addition, you can use this widget as a simple text viewer by calling the method QTextEdit::setReadOnly( true )
.
QTextEdit
and QLineEdit
can work with the system clipboard. Simply use the methods
cut()
, copy()
,
and paste()
. You do not need to create a
QClipboard
object for this.
Both widgets report state changes via the signals
returnPressed()
and
textChanged()
; QTextEdit
also has a couple of other, less-often used signals.
The textChanged
signal is emitted whenever the
text in the text-entry field changes. It has a const QString&
parameter with QLineEdit
that
contains the new contents. Retrieving the text from a QTextEdit
is
an expensive operation that should not be done often. Note that
if you want to connect to textChanged()
to validate
the input, you should use a QValidator
object
instead (see Section 10.2 in Chapter 10).
One of the interesting things about
QTextEdit
is that it not only supports
displaying and entering normal unformatted text, but also so-called
rich text. If you want to use rich text instead of plain text, call setTextFormat( Qt::RichText )
. How
rich text is handled in Qt is described in Section 10.5 in Chapter 10.
Labels
Labels are widgets that display information. The user cannot
interact with them. In Qt, two classes are provided for this:
QLabel
, which can
display text, a pixmap, or animation; and QLCDNumber
, which shows
a numerical value with a predefined number of digits.
Simple Labels
QLabel
(Figure 4-22) is probably the simplest widget
in all of Qt. It can display a piece of text (including rich text), a
pixmap, a vector graphic, or an animation
that you set via QLabel::setText()
,
QLabel::setPixmap()
, QLabel::setPicture()
,
or QLabel::setMovie()
. If you want to set text,
you can also pass it as the first argument in the constructor.
You can set the
alignment—that is, indicate where in the space for the label the contents
should be located. This is done by calling setAlignment()
,
which accepts several flags, including AlignLeft
and
AlignVCenter
.
See the reference documentation for QLabel
for
the full details.
The setAlignment()
method also knows about
two flags that are not directly related to alignment: ExpandTabs
tells
the label to expand tab characters to spaces, and WordBreak
tells
it to automatically break the lines at the border of the label.
These two flags make sense only with labels that show
text. They have no effect when the label shows a pixmap or an animation.
Since QLabel
inherits
from QFrame
, it can have various frame styles
(see the section about QFrame
).
Another interesting QLabel
feature is
the ability to assign “buddies.” You already know that
most intrinsically labeled widgets, such as buttons, can
have an ampersand in their label. This is rendered as an underscore
below the next letter after the ampersand. This letter serves as
a shortcut key for this widget. Labels can have shortcut keys, too,
but they would not make much sense without the buddy mechanism because
labels don’t provide any means of user interaction. Instead,
when you set a buddy widget with QLabel::setBuddy()
,
keyboard focus is transferred to the buddy widget whenever the shortcut
key of the label is pressed. The usual applications of this feature
are labeled text-entry fields or combo boxes:
QLabel* label = new QLabel( "&labeltext", parent ); label->setGeometry( ... ); label->setAlignment( AlignRight \(md AlignVCenter ); QLineEdit* edit = new QLineEdit( parent ); edit->setGeometry( ... ); // position right to the label label->setBuddy( edit );
The fact that labels are the easiest
way to show some graphics is often overlooked. For example, Example 4-2 shows a complete graphics viewer that
already supports several formats. Note that in this example, qDebug()
is
used for a diagnostic message. This is very useful for messages
that you only want to see during development. In Unix, these messages
go to standard error; in Windows, they go to the debugger. Where
they are shown depends on your IDE. In Visual C++,
they are shown in the Debug window that is by default
located in one of the tabs at the bottom of the screen.
#include <qapplication.h> #include <qlabel.h> #include <qpixmap.h> int main( int argc, char* argv[] ) { QApplication myapp( argc, argv ); if( argc != 2 ) { qDebug( "Usage: pixview filename\n" ); exit( 1 ); } QPixmap mypixmap; mypixmap.load( argv[1] ); QLabel* mylabel = new QLabel( 0 ); mylabel->resize( mypixmap.size() ); mylabel->setPixmap( mypixmap ); myapp.setMainWidget( mylabel ); mylabel->show(); return myapp.exec(); }
A QLabel
object
is not always the best choice for displaying static text: if you want to
let the user copy text from the label to the clipboard, use a QLineEdit
set to read-only mode
instead. For multiline labels, a read-only QTextEdit
can
give you more flexibility at the cost of higher memory requirements.
QLCDNumber
QLCDNumber
(see Figure 4-23) is a nice little widget
for displaying numeric information. It can display numbers with
a fixed, but arbitrary, number of digits in decimal, octal, hexadecimal,
and binary representation, and can add a decimal point and display a restricted number of strings. It uses a so-called “seven
segment display,” which should be well-known to everyone
who has ever seen a digital clock. QLCDNumber
is commonly used in connection
with a QSlider
object. (See Section 4.4 earlier in this chapter for details.)
You can create, size, and show a QLCDNumber
like
any other widget. You can either pass the number of digits as the
first argument to the constructor or set it afterwards with
QLCDNumber::setNumDigits()
.
The number or text to be shown is set with display()
,
to which you can pass either an int
, a double
,
or a const QString&
parameter. The reference
documentation for QLCDNumber
contains the letters
that can be displayed. All others are replaced by spaces.
When you display a numeric value, you can determine the base
in which it will be displayed. This base can either be chosen by calling
setMode()
with QLCDNumber::Bin
,
QLCDNumber::Dec
, QLCDNumber::Hex
,
or QLCDNumber::Oct
as parameter, or you can use the
slotssetBinMode()
, setDecMode()
,
setHexMode()
, setOctMode()
,
or setDecMode()
, respectively. Decimal representation
is the default.
You can influence the appearance of the displayed data in
two ways. You can choose a style for the segments by calling setSegmentStyle()
with
one of the parameters Outline
, Filled
,
or Flat
. In addition, you can determine whether
a possible decimal point will be shown in a position of its own,
or between two positions, by calling setSmallDecimalPoint()
and
passing either true
or false
. Often you’ll want to know whether a given number
will fit into the available digits. This information can be determined by calling
QLCDNumber::checkOverflow()
, to which you can pass
either a double
or an int
parameter. It will return a bool
that is true if
the number cannot be displayed within the available digits.
If this is not an option for you because the value is set
in a slot called by a signal from somewhere else, you can at least
take action afterwards by connecting to the signal overflow()
that
is emitted whenever a number or a string that does not fit into
the available digits is passed to display()
.
Widgets for the Office
The Microsoft Office suite has set an interface standard that most Windows applications follow today. This is becoming the standard even on Unix systems. On top of the application window is a menu bar, below which you can find toolbars—rows of buttons that provide quick access to frequently used functionality. Buttons in toolbars are usually labeled with images rather than text. Because of their small size, these images are not always easy to recognize. Tooltips were invented to remedy this problem. A tooltip is a short phrase that is shown in a small window over a button when the user places the mouse over the button. Most programs have only one toolbar, but it is not uncommon to have three or even more of them. Below the toolbar is the main application area. In a spreadsheet application, this would be the spreadsheet that you are working on. At the bottom margin, a status bar is often found that contains information about the current state of the application.
Recently, docking windows have become a common addition to this “office style.” Docking windows are internal windows that can be “docked” into certain positions, but that also float freely “on top of” the rest of the application.
This section explains the widgets that Qt provides for creating standard application windows. For a more extensive example, see the directory examples/application in your Qt distribution.
The Main Window
QMainWindow
organizes a menu bar, any number
of toolbars, a status bar, and a central widget,
and manages their geometry.
QMainWindow
is very easy to use. If you
add children of the classes QMenuBar
, QToolBar
,
or QStatusBar
, QMainWindow
automatically
detects and uses them for their respective purposes. You can also have QMainWindow
create a menu bar or
status bar on demand by calling menuBar()
or
statusBar()
,
respectively. These methods either return a previously created menu
bar or status bar, or create a new one. Note that you always have to
create toolbars yourself; they are never generated on the fly.
Toolbars are added by hand. First, you have to create the
toolbar yourself (see the next section for this), and then add it with
addToolbar()
(or simply make the main window a
parent of the toolbar),
which accepts a few more parameters. The second parameter is a label
that is used as a tooltip and description for the toolbar; the third indicates where you want the tool
bar to dock with the main window. Possible values are
Top
, Bottom
,
Left
, Right
,
Unmanaged
, and TornOff
.
The first four options should be obvious. The fifth does not show
the toolbar, and the last one makes it “float” over the
application. Finally,
the fourth parameter of addToolbar()
indicates
whether the toolbar should occupy a new row in the main window
(pass true
) or whether it should be set to the right
of the last toolbar added if there is enough space for it (this
is the default).
You can add as many toolbars as you like with addToolbar()
,
and you can also take them away with removeToolbar()
.
Two more methods in QMainWindow
influence
the look of all the toolbars: setUsesBigPixmaps( true )
makes the toolbars use larger pixmaps than usual, and
setRightJustification( true )
makes the toolbar extend all the way to the right
edge of the main window. This behavior is common, so you might
want to always use this setting.
A menu bar, some toolbars, and a status bar do not make a useful
application: the “content” is missing. You can use
any widget for this content—either predefined Qt widgets, or widgets
that you have written yourself. This widget should be a child of
the main window and should be registered with
setCentralWidget()
. QMainWindow
does
nothing with this widget except manage its geometry. You could,
for example, put a QTextEdit
widget in as
a central widget and have a text editor. However, doing so will not be useful
yet because it lacks loading and saving capabilities. These features
are simple to add, though.
All in all, QMainWindow
makes it easy to
achieve a standard look for your applications. Unless your application
has special UI needs, you should use QMainWindow
.
Toolbars
Toolbars are one of the preeminent features of today’s
GUI applications. Qt provides the class QToolBar
,
which is used in conjunction with the class QToolBarButton
.
When working with toolbars, you normally don’t need
to do anything but create objects. The parent-child relationship
helps Qt figure out where to put which widget. You can create a
QToolBar
object
as a child of a QMainWindow
object, and as many
QToolButton
objects
as needed as children of the QToolBar
object.
That’s all. Here is some sample code:
... QMainWindow* mainwindow = new QMainWindow; QToolBar* toolbar = new QToolBar( mainwindow ); QToolButton* toolbutton1 = new QToolButton( somepixmap, "statusbartext", 0, this, SLOT( someSlot() ), toolbar, "tooltiptext" ); QToolButton* toolbutton2 = new QToolButton( someotherpixmap, "statusbartext", 0, this, SLOT( someOtherSlot() ), toolbar, "tooltiptext" ); ...
You might want to use four other methods in
QToolBar
. The addSeparator()
method inserts space that is not occupied by toolbar
buttons and can be used to group toolbar buttons
together visually. The setStretchableWidget()
method marks
one widget as stretchable. This method is used if the
QMainWindow
that contains the toolbar is
configured to have one toolbar over its whole width. The marked
widget then takes all remaining space. If you don’t do this, the
toolbar will occupy only the space needed for all its children,
unless you call setHorizontalStretchable( true )
, in
which case the toolbar will extend over all the available space as
well. There is also setVerticalStretchable()
, which
is less useful.
QToolButton
also has some interesting methods.
Normally, only the image is displayed, but when you call setUsesTextLabel( true );
on a toolbar button, it displays text below the
image. This text is set with setTextLabel()
.
In the previous section, you saw that a whole toolbar can
be configured to use large pixmaps. This can also be done for each
toolbar button by calling setUsesBigPixmap( true )
on the button.
Finally, you can also make a toolbar button
toggle with QToolButton::setToggleButton()
.
This is rarely used, but some buttons toggle font styles
such as italics or bold in word processing applications.
Status Bars
A status bar (see Figure 4-24)
contains messages for the user. These messages normally consist of text only.
Qt, which implements status bars in the form of the class QStatusBar
,
distinguishes three different types of messages in the status bar:
- Temporary
Temporary messages are displayed only briefly. Most are explanatory texts for menu entries or toolbar buttons. Temporary messages are shown by calling
QStatusBar::message()
. You can use the variant that accepts only the text to be shown and then remove the temporary message afterwards withQStatusBar::clear()
. However, it’s easier to specify a time in milliseconds as the second argument. In this case, the temporary message is removed automatically. There is never more than one temporary message; ifQStatusBar::message()
is called again while the old one is still showing, the old one is deleted.- Normal
Normal messages are shown to the left of the first permanent message (see the next item). They can be obscured temporarily by temporary messages. You can put any widget as a normal message into the status bar, but usually
QLabel
is used. Another widget that might be useful in a status bar isQProgressBar
. You put a widget into a status bar as a normal message by callingQStatusBar::addWidget()
and then passing a pointer to the widget as the first parameter. The second parameter is an integer stretch factor that is used to compute the space that is available for each normal and permanent message in the status bar. This value is not an actual width in pixels, but it indicates the relative size in relation to the other widgets. The third parameter isfalse
.Normal messages are removed again with
removeWidget()
. Some things that might be useful as normal widgets are information items that are usually displayed but often change, and information items that are not crucial for allowing the user to operate the application. Examples would be line and column indicators in a spreadsheet or a word processor, or an indicator that shows whether the current document has been saved.- Permanent
Messages are made permanent by passing
true
as the third argument. Permanent messages are arranged to the right of the status bar, and can never be obscured by temporary messages. It is customary to put information there that is relatively static or that is needed for the user to operate the application, such as mode indicators. Though it does not fit the pattern, many applications also put a clock there.
Multiple Document Interface (MDI)
On the Windows platform, the so-called Multiple Document Interface (MDI) is a popular UI paradigm. MDI programs have a subwindow for each document that is confined to the extent of the main window. Each subwindow behaves a lot like the main window itself: they have a titlebar and can be resized, minimized, maximized, and closed. The opposite of this paradigm is the Single Document Interface (SDI) in which you have a new main window for each new document—or can only work with more than one document at a time.
UI research has shown that MDI is probably a bad idea and should be used with care; many users never grasp the concept of two levels of windows (main windows and subwindows) and consequently never open more than one document at a time or do not manage to switch between them. Even Microsoft, previously the strongest proponent of MDI, seems to be backing out slowly.
Nevertheless, Qt supports MDI in
case you need it. The class that achieves this support is called
QWorkspace
. Just create
one object of QWorkspace
. Then, for each MDI
window you want to have, create an ordinary Qt widget
(QWidget
or subclass thereof) and let it have the
QWorkspace
object as its parent. Usually, you will
want the QWorkspace
object
to be the central widget in a QMainWindow
.
You can manipulate the subwindows as usual; you can even call show()
,
hide()
, showMaximized()
, and
showNormal()
. All these operations will operate
within the workspace. An additional feature of
QWorkspace
is that it can tile the subwindows
(arrange them so that each one is visible and there are no overlaps;
if there are many windows or the workspace is small, then
the subwindows will be very small) or cascade them (arrange them so
that only the titlebars of the subwindows are visible; the next subwindow starts right under the titlebar of the previous one, slightly
offset to the right; the last subwindow might be fully visible if
there is enough space for it). Tiling subwindows is done with the slots
QWorkspace::cascade()
and
QWorkspace::tile()
.
If you need to know when a subwindow has been activated
(moved to the front and given focus) because the window should be
changed somehow to reflect this, you can connect to the signal
QWorkspace::windowActivated()
. Also, QWorkspace::activeWindow()
returns the
currently active window. The convenience method
QWorkspace::windowList()
returns a list with
pointers to all subwindows.
Docking Windows
Docking windows are windows that can either be “docked” into certain positions (so-called “docks”) in a container window or be “floating”—i.e., be top-level windows on top of the application window. This style was first made popular by IDEs like Visual Studio that need a large number of child windows that are mostly independent of each other, but recently, the style has been used in other types of programs as well. Even Qt uses it for its helper programs like Qt Assistant. Actually, even toolbars are a kind of docking window; in the current Qt version, they are treated like any other docking window.
Docking in Qt is provided by the two classes
QDockArea
and
QDockWindow
. QDockArea
provides
a container or “dock” into which instances of
QDockWindow
can dock. Since
QMainWindow
already provides four
QDockArea
instances, one at each side, and you
typically use QMainWindow
in applications that use
docking anyway, you rarely need to instantiate
QDockWindow
yourself.
QDockWindow
is, on the other hand,
something that you need each time you want to implement docking
windows. Your window needs to be derived from
QDockWindow
to be able to dock into a dock
area. As you would expect, QToolBar
inherits
QDockWindow
.
When you create a docking window, you need to specify
whether it should be docked or not initially. If it should be docked,
then the parent must either be a QDockArea
or a
QMainWindow
. If the docking window is not docked at
the beginning, this docking is not required; however it won’t be able to dock
unless it is reparented, so that would not be a good idea.
The typical way to create a docking window is to create
it docked, make the QMainWindow
instance the
parent, and specify with
QMainWindow::moveDockWindow()
, into which dock the
docking window should go. This is done as follows:
QDockWindow* myDockWindow = new QDockWindow( InDock, mainwindow ); mainwindow->moveDockWindow( myDockWindow, Bottom );
Here, the window would be docked at the bottom of the main window. If the window should not be docked initially, you would use:
QDockWindow* myDockWindow = new QDockWindow( OutsideDock, mainwindow );
In this case, the dock window would initially be floating.
Of course, you also want some docked contents. The
contents are specified with
QDockWindow::setWidget()
. It is not possible to
dock the window into a specific dock after it has been created. The method QDockWindow::dock()
does
not take any parameters, but rather docks the window into the dock it
was docked in before. However, the actual docking should be left to
the user, who can interact with the docking window with the
mouse. Docking or undocking programmatically will probably confuse
the user and contradicts the whole idea.
A couple of properties can be set in a
dock window; e.g., you can set preferred sizes with
setFixedExtentWidth()
and
setFixedExtentHeight()
and specify whether the
docking window can stretch with
setHorizontallyStretchable()
and
setVerticallyStretchable()
. You can also specify to
which extent the user can interact and manipulate the dock window with
setCloseMode()
,
setResizeEnabled()
, and
setMovingEnabled()
.
Tooltips and “What’s This” Windows
Tooltips, also known as bubble help, quick help, or Hoover help, are small, rectangular windows with short descriptive text that pop up when you rest the mouse over a widget for a short time. The descriptive text should explain in as few words as possible the meaning of the widget the mouse is over. When the mouse is moved away or a mouse button or a key is pressed, the tooltip disappears.
“What’s This” windows (sometimes also called bubble help) have a similar purpose, but they are larger and allow more text to be shown. They bridge the gap between the low impact and low information content of tooltips and the high impact and high information content of online help systems.
You usually don’t
have to hassle with creating tooltip widgets or with
sizing and showing them. All you have to do is call the static method
QToolTip::add()
:
QLineEdit* edit = new QLineEdit( parent ); QToolTip::add( edit, "Description of edit field" );
This method adds a tooltip to the text-entry field. If
you are tired of the tooltip, you can remove it by calling QToolTip::remove()
.
Note that these two methods of QToolTip
are static. You
don’t have to create QToolTip
objects
yourself; this is done internally for you.
Tooltips that are added with the method shown are always available when the mouse is over a widget. You can also restrict the area that is sensitive to tooltips to a certain rectangle within the widget:
QLineEdit* edit = new QLineEdit( parent ); QToolTip::add( edit, QRect( 0, 0, edit->width()/2, edit->height() ), "Description of edit field" );
Restricting the area ensures that the tooltip pops up only when the mouse is over the left half of the widget.
There are cases when the text of the tooltip, or even whether
a tooltip should be shown at all, can only be determined dynamically.
An example would be a scrollbar in a word processor that shows
the current page in a tooltip. To show the tooltip, you have to derive a
class from QToolTip
that reimplements the virtual
method maybeTip()
. A QPoint
reference
that contains the mouse position relative to the window is passed
to this method. On the basis of this position, and possibly the
current application state, you can decide whether a tooltip should
be shown or not. You can also access the widget
associated
with the tooltip by calling QToolTip::parentWidget()
.
If you should decide to pop up the tooltip, you have to call QToolTip::tip()
with
a QRect
and a QString
that
indicate size, position, and the text to be shown, respectively. When
the mouse is moved out of the widget and back to the same point,
maybeTip()
is
called again. Depending on your application, you might want to do
some caching to avoid expensive recalculations. The maybeTip()
method
can be called often and should therefore be as fast as possible.
In applications that have a status bar or other means of displaying
temporary information, it could be useful to display the text shown
in a tooltip in this other widget as well. You can use QToolTipGroup
to
leverage the mechanisms of deciding whether information about a
widget that is in QToolTip
should be provided.
Since status bars usually have more space available
than tooltips, you can even show other, possibly longer, text
here. To achieve this, you have to do two things: you must pass
a pointer to the QToolTipGroup
object and the
second string to an overloaded form of the method QToolTip::add()
;
then you must connect the signal QToolTipGroup::showTip( const QString& )
to a slot that will show the text
and the signal QToolTipGroup::clear()
to a slot
that will remove it. These slots are usually the
setText()
and clear()
methods
of a label. Here is a code snippet that shows the whole procedure:
QToolTipGroup * ttgroup = new QToolTipGroup( parent ); QLabel* mylabel = new QLabel( parent ); // should be in e.g., a status bar QObject::connect( ttgroup, SIGNAL( showTip( const QString& ) ), mylabel, SLOT( setText( const QString & ) ) ); QObject::connect( ttgroup, SIGNAL( removeTip() ), mylabel, SLOT( clear() ) ); QLineEdit* myedit = new QLineEdit( this ); QToolTip::add( myedit, "short description", ttgroup, "longer description" );
Tooltips are very efficient means of communicating to the user the meaning of the elements in a GUI. They are very easy to program and normally don’t have much overhead. You should use them liberally.
At the beginning of this section, we mentioned that you can
also use “What’s This” windows. These windows are represented in
Qt by the class QWhatsThis
and are used
the same way that you use QToolTip
. To associate
a “What’s This” window containing the text text
with
a widget mywidget
, simply call:
QWhatsThis::add( mywidget, text );
To remove such an association from a widget, call QWhatsThis::remove()
, to which you
simply pass a pointer to the widget in question.
Tooltip windows are shown automatically.
“What’s This” windows are only shown on
user request. The user can press Shift and F1 together on the keyboard,
in which case the “What’s This” window for the
widget that has the keyboard focus is shown; or the user can click on
a special toolbar button and then click on the widget
whose “What’s This” window she wants to see. Such a special
toolbar button should not be created explicitly, but by
calling QWhatsThis::whatsThisButton()
, to which
you pass the parent for the button (normally a QToolBar
widget).
This method creates and returns a button configured for invoking
the “What’s This” feature. You can invoke this method as
many times as you like and get a new toolbar button each
time. Here is a small example:
QToolBar* toolbar = new QToolBar( this ); QToolButton* contexthelpbutton = QWhatsThis::whatsThisButton( toolbar );
Progress Bars
Progress bars are represented in Qt by the class QProgressBar
(see
Figure 4-25). They are used to show
that the program still works during lengthy operations. Normally,
you will use the more convenient encapsulation QProgressDialog
, which
you will learn about in the “Predefined Dialogs” section.
Sometimes it can be useful to integrate a progress bar into a dialog box,
however, or into the top-level window of your application.
In these cases, QProgressBar
is used directly.
You can create,
size, and show it the usual way, and then set the total number of
steps that the application will have to perform with setTotalSteps()
.
During this operation, call setProgress()
with
the number of currently completed steps. QProgressBar
will
automatically compute the percentage, and update the progress bar accordingly.
Remember that you have to give the progress bar a chance to update
its display either by returning to the event loop from time to time
or by calling QApplication::processEvents()
so
that the display can be refreshed and the progress bar can be redrawn.
Scrolled Views
A scrolled view can be any widget with a view that might be
smaller than its physical size. Users are then provided with scroll
bars to select the part of the widget that they want to see. This
can be cumbersome to program, but fortunately Qt provides the class
QScrollView
,
which does almost everything automatically. You have already seen how
to use it in the “Adding a Scrolled View” section in Chapter 3, so I
will only mention characteristics here that have not yet been
discussed. Please note that other
widgets, including QListView
(see Section 4.15), inherit QScrollView
.
QScrollView
can
contain multiple child widgets. You add one child widget with addChild()
,
and then you pass a pointer to the child as well as the
x and y positions
where the widget should be located on the overall canvas that the
scrolled view uses. Children can be moved with moveChild()
,
removed with removeChild()
, and made visible
and invisible with showChild()
.
If you don’t want to have widgets in your scrolled
view, but want to draw on the canvas provided by QScrollView
instead,
you must derive a class from QScrollView
and
reimplement drawContentsOffset()
.
The reference documentation for this method contains an example
that shows how to deal with the coordinates. If you draw on the canvas
this way, you might want to resize the canvas, too. Resizing can be
done with resizeContents()
. This method can also
be used when you simply add widgets to the canvas.
It might not always be obvious when to use child widgets and
when to draw directly into the canvas provided by QScrollView
.
Here is a possible rule of thumb: whenever you would put in
a QWidget
as a child (as opposed to a more specialized
widget), you might as well use the canvas directly because
the most commonly used overloaded methods for receiving low-level
events are available in the methods
viewportPaintEvent()
,
viewportMousePressEvent()
,
viewportMouseReleaseEvent()
, viewportMouseMoveEvent()
,
and viewportMouseDoubleClickEvent()
. These methods are
called by QScrollView
and you must reimplement
them as needed in a class derived from QScrollView
.
If you need any special low-level events, such as
enterEvent()
or leaveEvent()
,
you must provide a widget of your own.
Scrolled views can have one of three possible scrollbar
policies. These scrollbar policies are set with
setHScrollBarMode()
and setVScrollBarMode()
for
the horizontal and vertical scrollbar, respectively. The possible
values are AlwaysOn
(scrollbar is always shown,
even if it would not be necessary), Auto
(scrollbar is shown only when the view is smaller than the physical size
of the contents), and AlwaysOff
(scrollbar is never
shown, even if it is necessary). Note that AlwaysOff
should
be used only when you provide some other means of moving the view;
otherwise the user will have no way to get to the parts of the view
that are not visible from the beginning.
The view on the canvas can also be done programmatically.
Two methods, setContentsPos()
and center()
,
use absolute coordinates and scroll the view so that the coordinates
passed as parameters are visible in the upper-left corner
or in the center of the view, respectively. There is also an overloaded
version of center()
that allows you to specify
some tolerance as to the center position. Finally, scrollBy()
allows
you to use relative coordinates and thus mimics what the user does
when clicking on the arrows of the scrollbars. QScrollView
has
only one signal, contentsMoving()
. It is emitted
just before the contents are moved to their new position.
List Views
QListView
(see Figure 4-26) is a very complex widget that
can display data in rows and columns, as well as in hierarchical
form. Entries that contain subentries can be opened and closed
by the user or done programmatically.
QListView
and its helper class QListViewItem
have
complex interfaces, and it takes time and experience to master them. But
if you confine yourself to the default settings at first and use
some things that are done “automagically,” simple operations
with QListView
are just that.
To demonstrate some features of QListView
,
we will build a list of countries, their capitals, and their populations.
There will be one country per row and three columns: one each for
the name of the country, the capital, and the population. To add hierarchical structure to this example, we will group
the countries by continent.[17] Before we dive into the workings, let’s
look at the code shown in Example 4-3.
#include <qapplication.h> #include <qlistview.h> int main( int argc, char* argv[] ) { QApplication a( argc, argv ); QListView* listview = new QListView(); listview->addColumn( "Country" ); listview->addColumn( "Capital" ); listview->addColumn( "Population in mio." ); listview->setRootIsDecorated( true ); QListViewItem* europe = new QListViewItem( listview, "Europe" ); QListViewItem* america = new QListViewItem( listview, "Americas" ); QListViewItem* africa = new QListViewItem( listview, "Africa" ); QListViewItem* asia = new QListViewItem( listview, "Asia" ); QListViewItem* item = new QListViewItem( america, "Brazil", "Brasilia", "161.8" ); item = new QListViewItem( europe, "Scotland", "Edinburgh", "057.6" ); item = new QListViewItem( africa, "Morocco", "Rabat", "030.4" ); item = new QListViewItem( europe, "Norway", "Oslo", "004.4" ); item = new QListViewItem( europe, "Italy", "Rome", "052.2" ); item = new QListViewItem( america, "Chile", "Santiago", "014.2" ); item = new QListViewItem( africa, "Cameroon", "Yaounde", "013.2" ); item = new QListViewItem( europe, "Austria", "Vienna", "008.1" ); item = new QListViewItem( asia, "Saudi Arabia", "Riyadh", "020.0" ); item = new QListViewItem( europe, "Denmark", "Copenhagen", "005.2" ); item = new QListViewItem( europe, "France", "Paris", "058.0" ); item = new QListViewItem( africa, "South Africa", "Pretoria", "042.3" ); item = new QListViewItem( america, "Paraguay", "Asuncion", "005.6" ); item = new QListViewItem( europe, "Bulgaria", "Sofia", "008.8" ); item = new QListViewItem( europe, "Spain", "Madrid", "039.1" ); item = new QListViewItem( africa, "Nigeria", "Abuja", "107.1" ); item = new QListViewItem( asia, "South Korea", "Seoul", "045.9" ); item = new QListViewItem( america, "Mexico", "Mexico City", "097.6" ); item = new QListViewItem( europe, "Netherlands", "Amsterdam", "015.6" ); item = new QListViewItem( europe, "Belgium", "Brussels", "010.1" ); item = new QListViewItem( europe, "Yugoslavia", "Belgrade", "010.8" ); item = new QListViewItem( asia, "Iran", "Tehran", "067.3" ); item = new QListViewItem( europe, "Germany", "Berlin", "081.2" ); item = new QListViewItem( america, "USA", "Washington", "268.0" ); item = new QListViewItem( europe, "England", "London", "048.5" ); item = new QListViewItem( africa, "Tunisia", "Tunis", "009.2" ); item = new QListViewItem( europe, "Romania", "Bucharest", "022.5" ); item = new QListViewItem( america, "Colombia", "Bogota", "035.1" ); item = new QListViewItem( america, "Argentina", "Buenos Aires", "034.6" ); item = new QListViewItem( asia, "Japan", "Tokyo", "125.1" ); item = new QListViewItem( america, "Jamaica", "Kingston", "002.4" ); item = new QListViewItem( europe, "Croatia", "Zagreb", "004.5" ); listview->resize( 400, 300 ); listview->show(); a.setMainWidget( listview ); return a.exec(); }
Apart from the mass of data needed to make experimenting with
the list view more interesting, this example features several
things. Probably the most prominent feature is the lack of an explicit
call to a method like insertItem()
. The whole
hierarchy is built on the normal parent-child widget hierarchy
of Qt (even though technically, a QListViewItem
is
not a widget).
We have four objects of the class QListViewItem
,
which are children of the list view itself and are thus top-level
items. We have 32 more QListViewItem
objects
that are each children of one of the four top-level items.
Note
Every piece of data displayed in a list view belongs
to an object of the class QListViewItem
. To fully
exploit QListView
, you need to master not only the
interface of this class, but that of QListViewItem
as
well.
For now, let’s get back to our example. In the first
few lines of the main
function, we set up the
list view by adding column headers and telling the widget that we
want the top-level items to be expandable. If we did not,
there would be no way for the user to get at the country entries,
but you could still expand one entry programmatically by calling
setOn( true )
at the corresponding QListViewItem
.
The headers are more than descriptions of what can be found
in the columns. Clicking on them sorts the data by that column.
You could also sort the data programmatically by calling setSorting()
,
where the first parameter is the number of the column and the second
parameter is a Boolean value that indicates whether sorting should
be in ascending (true
) or descending (false
)
order.
Before we go into the details about how to create items for
the list view, let’s look at more general formatting features.
One of these features is column width. Column widths can be configured in
two modes: Maximum
and Manual
.
These modes are set with setColumnWidthMode()
.
In Maximum
mode, the column gets
the size needed for the widest entry in that column automatically. In Manual
mode,
the width is set via setColumnWidth()
.
Three more methods affect the graphical representation: setTreeStepSize()
,
setAllColumnsShowFocus()
, and
setRootIsDecorated()
.
The first determines how far children are indented below their parent
entry (the default is 20 pixels); the second determines whether
all or only the first column of an entry should be highlighted when
that entry has the keyboard focus; and the third specifies whether
even the root items (the ones highest in the hierarchy) should have
the boxes with plus and minus signs (in Windows-like styles)
or arrows (in Unix-like styles).
With the latter method, we come to the subject of keyboard
focus and selection. QListView
allows four selection
policies: single selection, multiselection, extended
selection, and no selection at all.
The default is single selection: selecting one entry automatically
deselects the previously selected entry. You can switch to multiselection
with setMultiSelection()
. With
multiselection and extended selection,
keyboard focus and selection are two different things that can
be set with two different methods. The setSelected()
method
accepts a pointer to a QListViewItem
and a bool
,
which indicates whether or not the item in question should be selected. Keyboard focus, on the other hand, is set with
setCurrentItem()
. The difference between
multiselection and extended selection is that in
multiselection, the items are completely independent of one another. In other words, selecting one does not have any influence whatsoever on the
selection states of the others. In extended selection mode, however,
selecting one item deselects all others unless the Ctrl or the Shift
key is held down while selecting. Ctrl allows selecting
separate items, while Shift selects a range. A good rule of thumb
is to use extended selection mode for things of which you usually want one
or more, and multiselection
mode for things of which you usually want many.
When you want to iterate over all items in a list view, the
easiest way is to retrieve the first item with
firstChild()
, and then use
QListViewItem::itemBelow()
to iterate over the
items. Unfortunately, this only works if all items with children are
expanded. If you cannot guarantee this, you will have to use a
combination of the various navigation functions of
QListViewItem()
.
QListView
emits several signals. The
selectionChanged()
signal
notifies the program that more, fewer, or other items are selected.
If the selection policy is single-selection, selectionChanged( QListViewItem* )
, which makes the newly selected
item readily available, is also present. currentChanged( QListViewItem* )
, in contrast, indicates a change in keyboard focus. Since even in
a multiselection list view only one item at a time can
have the keyboard focus, this signal can always pass the newly focused
item. Besides these higher-level signals, several somewhat
lower-level signals all pass a QListViewItem*
as well. These signals are emitted when the user clicks or presses the mouse
on an item. See the Qt reference documentation for all the details.
Finally three signals could be considered
lower-level and report manipulations on the items: doubleClicked( QListViewItem* )
, returnPressed( QListViewItem* )
, and rightButtonClicked( QListViewItem*, const QPoint&, int )
.
The first two should usually be connected to the same slot because users
normally expect double-clicking and pressing Enter to invoke
the same functionality. The rightButtonClicked()
signal
is mainly meant for popping up context menus. The second and third
parameters specify the mouse coordinates (which are a good candidate
for the menu coordinates) and the column in which the mouse was clicked.
Note that there is a signal rightButtonPressed()
,
but no rightButtonReleased()
.
QListViewItem
has many methods, but most
of them are only useful for special cases. If you want only text
entries, the constructors might be all you ever need. You can pass the parent
as the first argument: either a QListView*
for
top-level items, or a QListViewItem*
for
non-top-level items. In addition, you can pass
up to eight strings, which serve as values for the respective columns.
If you need more than eight columns, or if you want to have pixmaps
in your columns, you will need additional methods. The setText( int, const QString& )
method adds a text entry to
a column specified by the first argument. The setPixmap( int, const QPixmap& )
method does the same for pixmaps.
Three more methods allow entry configuration. The setSelected( bool )
method selects or deselects an entry; setExpandable( bool )
determines whether it should be possible for the
user to expand or fold back the entry by means of a little icon
next to it; and setSelectable( bool )
determines
whether the item can be selected by the user.
It is even possible to let the user rename an item in
place. By default, this functionality is turned off, but if you call
QListViewItem::setRenameEnabled()
, the user can rename the text value (of course, this does not work for pixmaps). For the first argument, pass the column where the item you would like to be changed resides, and for the second argument, pass true
. Note that you need to call
setRenameEnabled()
once for each column in each
item that you want to let the user rename; this can result in a lot of calls. The
QListView
object itself renames an
itemRenamed()
signal whenever an item is renamed.
Furthermore, QListView
also supports
drag-and-drop—or rather, provides you with the means to implement
this functionality. To use drag-and-drop in a list view, you need to
do two things:
On each item that may be dragged, call
setDragEnabled( true )
. On each item onto which data may be dropped, callsetDropEnabled( true )
.Subclass from
QListView
and override the methoddragObject
to return aQDragObject
object contained the data to transport. For dropping, connect to the signalQListView::dropped()
, which gets passed aQDropEvent
, which in turn provides you with all necessary information.
For more information about drag-and-drop and about what to do with the QDragObject
and
QDropEvent
, please see Section 12.2.
Note that when you build very large hierarchies, creating all items at startup or widget creation
time may not be a good idea because it may take too long. For example, file systems
can be very large. It would be better to create the root entries
first, and then create all other entries only when they are needed (for
instance, when their parent is expanded). To do so, create a class
derived from QListViewItem
that reimplements
setOpen( bool )
and setup()
. The setup()
virtual
method is called before the item is displayed at first and also
after attributes such as the font or the widget style have changed.
You can take care of any initializations here that might have an
effect on the item’s visual appearance. With a file system,
this process might include checking whether the directory in question is
empty and calling setExpandable( true )
if the user cannot open it. When this item is opened,
setOpen( bool )
, in which you can read the directory and
create items for the files or subdirectories, is called. This way, there is never
more file system activity than needed. Since there is already a
good dirview example in the Qt distribution,
read through it if you want to use QListView
.
Icon Views
Icon views are widgets that show a number of icons in a grid that allow you to manipulate them in various ways, e.g., selecting them or dragging them. The icons can have an optional text label that augments the data representation that the icon provides.
Typical uses for icon views are file browsers in which each file or directory is represented by an icon and the optional label that shows the file or directory name, or thumbnail viewers, in which each icon represents an image (often in the form of a file as well).
Qt implements icon views by means of the class
QIconView
. If you have read through the previous
sections about list views, there won’t be many surprises for you
here. The simplest way to use the icon view is to create a
QIconView
object, and then a number of
QIconViewItem
objects that have the
QIconView
object as parents. For each
QIconViewItem
, you should specify a
QPixmap
. You can also specify a
QString
as the optional label.
By default, the icons are inserted left to right, top to bottom, and one after the other into the icon view. However, there are several ways to change this default behavior. First, when creating an icon, you can specify another icon that should be right before the one you are creating. This way, you can insert an icon somewhere in the middle instead of only at the end.
You can also change the insertion order to top to bottom,
left to right. This is done by calling setArrangement( TopToBottom )
before inserting the icons.
Finally, you can sort the icons or have them inserted
from the start. Sorting afterwards is done by calling
QIconView::sort()
; turning on sorting for insertion
is done with QIconView::setSorting()
. With both
methods, you can specify whether sorting should be in ascending or
descending order. The sorting order is determined by the return value
of the method QIconViewItem::key()
, which is a
QString
. By subclassing from
QIconViewItem
, you can return your own string here
and thus define your own sorting order for the icons—e.g., by size.
QIconView
has, like
QListBox
, four different selection modes:
Single
lets you select only one icon; selecting
another one deselects the previously selected
icon. Multi
lets you select more than one icon.
Selecting one icon has no influence whatsoever on the selection state
of any of the other icons. Extended
also lets you
select more than one icon; this is done by means of holding the
Shift or the Ctrl key, as you do in listboxes. Finally,
NoSelection
disables the selection of icons
completely.
QIconView
emits a large number of
signals, many of which are similar or identical to the signals emitted
by QListView
. The reference documentation lists
them all.
A special feature of QIconView
is the
in-place renaming of icons. This renaming applies to the associated label, of
course. This feature can be enabled with
QIconViewItem::setRenameEnabled( true )
for an
item. You can rename an item by first selecting it and then clicking on
the name (or pressing F2), upon which the name
label turns into a line editor with which you
can change it. When you are done editing, the signal
itemRenamed()
is emitted by the
QIconView
.
Widgets for Tabular Material
The widget QListView
presented in the previous
section has a limited capability for displaying tabular data. If
you need more flexibility and do not care about the possibility
of having hierarchical displays, QListView
is
probably not the best choice. Qt also provides two widgets especially
geared toward displaying tabular material: QGridView
(see
Figure 4-27) and QTable
(see
Figure 4-28). QGridView
does not
have any visual representation itself, but rather helps you develop
your own widgets that can display tables. QTable
,
on the other hand, displays a spreadsheet-like table with cells.
QGridView
is an abstract base class, so
it cannot be used by itself. Instead, you have to derive another
class from QGridView
. There is a comprehensive
example of how to use QGridView
in Section 14.2 in Chapter 14.
QHeader
is another very useful widget for
displaying tabular material. QListView
and
QTable
use
it for displaying their column or row headers, but you can also
use QHeader
for your own widgets if they display
tabular data.
The most important thing to understand with QHeader
is
that you do not have one QHeader
object per column
or row, but one for all columns or rows. You add column or row headers
with addLabel()
and set the orientation (Horizontal
for
column headers and Vertical
for rows) with setOrientation()
.
Should you need to change header labels afterwards, you can do so
with setLabel()
.
Most methods in QHeader
deal with sizes
and are seldom used, but the three signals are very
important. sectionClicked()
is
emitted when one of the header parts (labels) has been clicked.
It has become customary for programs to sort the data by the column or
row in question when the user clicks on the header. QListView
does
exactly this. On the other hand, spreadsheets like MS Excel
simply select the column or row in question when the header is clicked.
The other two signals are emitted when headers are manipulated.
When the user clicks with the mouse exactly between two header labels
and then moves it, sizeChanged ( int, int, int )
is emitted. The first parameter of size changed is the logical
column or row number, the second is the old size in pixels, and
the third is the new size. If you click and drag in one label
instead, you can change the physical positions of the labels. When
this happens, the signal moved( int, int )
is
emitted, in which the first parameter specifies the column being moved and the second specifies its new position.
These three actions—clicking on a column or row header, resizing
a header, and moving a header—can be enabled or disabled by
calling setClickEnabled( bool )
, setResizeEnabled( bool )
, and
setMovingEnabled( bool )
, respectively. Finally, if you want the signal
sizeChanged()
to
be emitted while the sizes are changed, you will have to call setTracking( true )
. This could be useful for online resizing of columns or
rows, but this feature should be used carefully because it needs
a lot of computational resources.
Let’s now move away from the do-it-yourself widgets
QGridView
and QHeader
and go to
something more readily useable. If you do not want to implement your own widget, but
simply want to display and manipulate data in a spreadsheet-like fashion,
QTable
is the widget to use. It conserves a lot of memory since it only allocates memory for a cell if there is actually data in the cell.
You can specify the size of the table in the constructor;
if you do not specify anything, you get a table with 10 × 10
cells. You can put four things into a cell: a string, a
pixmap (also both a string and a pixmap), a user-defined item, and a
widget. Putting a string or a pixmap into a cell is easy: just
call QTable::setText()
or
QTable::setPixmap()
and specify the coordinates of
the cell as well as the string or pixmap to insert. If you insert both
a string and a pixmap into a cell, the pixmap is displayed to the left
of the text.
To put something that is user-defined into a cell, you need to
define a class that inherits from QTableItem
. Then
you can put any object of this class into the table by calling
QTable::setItem()
and passing the coordinates of
the cell and a pointer to the QTableItem
object.
Finally, you can put a Qt widget into a cell by calling
QTable::setCellWidget()
. Of course, not everything
is suitable to be put into a cell, but push buttons and checkboxes make good widgets to put into a table cell.
Actually, when you insert a string or a pixmap into a
cell, a QTableItem
is created as well. The
QTableItem
object is also responsible for editing.
By double-clicking a cell, you can put it into “edit mode”
(if the cell’s contents can be edited). In edit mode, the normal
representation of the cell is replaced by an editor widget. This widget can
be any Qt widget, but not all widgets are useful
here. QTableItem
itself uses a
QLineEdit
object that lets you edit the text in the
cell; but a pixmap in a cell cannot be edited unless you develop a
subclass of QTableItem
that lets you do that. However, developing this subclass would be pretty hard. By subclassing from
QTableItem
you can define other editors. For
example, it can be useful to put a slider into a cell. The
most important methods to override in QTableItem
are createEditor()
, which should return the editor
widget and will be called when a cell goes into edit mode, and
setContentsFromEditor()
, which should copy the data
in the editor widget back into the cell and will be called when a cell
leaves edit mode. If all you want is to put a combo box or a checkbox into a cell, you can use the ready-made QTableItem
subclasses QComboTableItem
and QCheckTableItem
.
Note the difference between a cell widget and
an editor widget. A cell widget is always present; if you use a cell
widget, then this cell will never go into edit mode. (Of course,
you could, for example, put a push button into a cell and pop up
a dialog to edit something when the button is clicked.) An editor
widget is only present when the cell is in edit mode, however; when it is
not in edit mode, the normal representation as defined by
QTableItem
or its subclass is used.
QTable
supports a lot of methods and
signals for selecting, highlighting, and sorting, but most of them are
self-explanatory and follow the same pattern as
QListView
and QIconView
, so
instead of repeating them here, I would like to refer you to the
reference documentation.
Widgets for Displaying Rich Text
Qt provides two widgets for displaying so-called “rich text.” Rich text is text that can have different styles like different fonts, colors, sizes, etc. At the extreme, each letter can have a different style. Rich text is a larger subject that we will cover in Section 10.5 in Chapter 10.
While some of the traditional widgets such as QLabel
, QMessageBox
, and
QWhatsThis
can display rich text as well, there are
two classes in Qt that are especially geared towards displaying rich
text: QTextView
and
QTextBrowser
.
QTextView
is much like
QLabel
because it simply displays the text. Since QLabel
can also display rich text, you might not even need to use QTextView
for
simple cases. QTextView
provides additional
facilities that might be useful for more advanced uses. For example,
you can set the stylesheet with setStyleSheet()
(please see Section 10.5 for an explanation of
stylesheets). To give the whole widget different colors, use setPaperColorGroup()
. You can
also set the color and the brush of the background individually by
using setPaper()
. You can configure two more styles: the color of hyperlinks and whether hyperlinks
should be underlined so that they are easier to
recognize. QTextView
does nothing to support
hyperlinks; supporting hyperlinks is the domain of
QTextBrowser
.
QTextBrowser
augments
QTextView
by hyperlink support
facilities. QTextBrowser
is not meant to be a
full-featured web browser, but rather a lean and fast documentation
viewer. For example, the documentation browser that comes with Qt
Designer (see the second part of this book) is based on
QTextBrowser
. You can provide
QTextBrowser
with the text to be displayed by calling setText()
and passing the text as a
string or by calling setSource()
and passing the
name of a document to be displayed.
Writing a documentation browser can be done by following these easy steps:
Create a
QMainWindow
with an optional menu bar, a toolbar with buttons for Back, Forward, Home, and aQLineEdit
for entering the document.Create a
QTextBrowser
and make it the central widget of the main window.Connect the
clicked()
signal of the Back button to thebackward()
slot of the text browser, the Forward button’sclicked()
signal of the Forward button to theforward()
signal, and the Home button’sclicked()
signal to thehome()
slot of the text browser.Connect the text browser’s
backwardAvailable( bool )
signal to thesetEnabled( bool )
slot of the Back button and the text browser’sforwardAvailable( bool )
signal to thesetEnabled( bool )
slot of the Forward button.Connect the
textChanged()
signal of the text browser to a slot in your application (e.g., in your subclass ofQMainWindow
) that first queries the current document name by callingQTextBrowser::source()
and then displays it in the line-entry field in the toolbar. This connection ensures that this field always displays the correct document name.Connect the
returnPressed()
signal of the line-entry field in the toolbar to a slot in your application that reads the entered text and callsQTextBrowser::setSource()
with this text. This connection ensures that you can use the line-entry field for navigation.
You now have a working documentation browser. Of course, several different kinds bells and whistles could be added, such as a history list, bookmarks, etc. See, for example, the helpwindow example that comes with Qt, which explains a lot of these enhancements.
[16] Of course, when clicking a push button invokes a dialog box, closing with Cancel effectively undoes the action. The user interface has changed, however—at least temporarily.
[17] I chose these countries because they are the 32 nations that qualified for the world soccer championships in the summer of 1998—an event that has more than once stopped me from continuing the work on the first edition of this book.
Get Programming with Qt, 2nd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.