This report discusses different ways of binding icons to actions in Qt desktop applications and provides motivation for developing approaches to robust icon implementations. The following was written against Qt 4.7.1 and Qt Creator 2.1.0 and assumes the reader has some familiarity with Qt/C++ development.
In Qt, there are (at least) five different approaches to binding icons1) to actions2), each offering different pros and cons with respect to development time, system integration, and icon rendering quality. The different approaches are described below. For purposes of demonstration in the following discussions, I will assume we have a main window with three actions: action_Open
that opens a file, action_Convert
that converts the open file and saves it, and action_Quit
that exits the application.
The path-of-least-resistance for binding icons to actions when developing Qt applications with Qt Designer3) is to use Qt Resources4). The support for *.qrc
resource files built into Qt Designer makes adding icon resources and binding them to actions very easy and additionally provides immediate visual feedback5).
While this approach has the above stated benefits, it's not optimal for a couple reasons. First, it lacks the ability to specify multiple icon sizes. In a typical application, icons bound to actions will need to be rendered at different sizes. For example, toolbar icons may be rendered at 24×24 pixels while menu item icons will be rendered at 16×16. As far as I can tell, Qt Designer (and possibly all of Qt) does not let you set icons for menu items and toolbar items independently. Rather, this approach relies on Qt's ability to automatically shrink (but not grow) icons to fit the desired space as needed6).
Developers using this approach typically provide an icon in a resource that is as large as the application is likely to require, and then let Qt resize it as needed. Thus, for example, you might bind a 48×48 icon to an action and let Qt shrink it to 24×24 for the toolbar and 16×16 for the menu item. While this works, the shrunk icons often appear fuzzy and/or otherwise suboptimal7).
(an image here would help a lot)
The second issue with the way Qt Designer deals with binding icons to actions is that it doesn't provide support for existing system icons and icon themes. While this may be a behavior that Windows (and possibly OS X users) are accustomed to (and some developers may actually prefer), users of GTK+-based environments will find this disconcerting. As we will see, Qt itself does provide support for system icons, but this is not reflected in Qt Designer.
There is no specific code implementation required to use the above icon bindings apart from what Qt Designer and moc
generate for us automatically. Thus our example's MainWindow constructor using Qt Resources in Qt Designer looks something like:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); ... }
It's possible to build QIcon
instances in code that encapsulate multiple icon sizes. Once this is done, the QIcon
instance can be bound to an action – and Qt will automatically select the best size for a given rendering. This requires that the developer build a resource tree with images for all the required sizes and then do a fair amount of hand-coding for each icon.
In the code below, we are assuming we have a resource in our project root called myicons.qrd
with the needed icons in the specified locations:
<RCC> <qresource prefix="/"> <file>myIcons/16x16/document-open.png</file> <file>myIcons/24x24/document-open.png</file> <file>myIcons/32x32/document-open.png</file> <file>myIcons/16x16/document-save-as.png</file> <file>myIcons/24x24/document-save-as.png</file> <file>myIcons/32x32/document-save-as.png</file> <file>myIcons/16x16/application-exit.png</file> <file>myIcons/24x24/application-exit.png</file> <file>myIcons/32x32/application-exit.png</file> </qresource> </RCC>
Once the above resource has been added to the project, building and binding icons for the three actions in in the constructor would look like:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); QIcon documentOpenIcon; documentOpenIcon.addFile( ":myIcons/16x16/document-open.png",QSize(16, 16)); documentOpenIcon.addFile( ":myIcons/24x24/document-open.png", QSize(24, 24)); documentOpenIcon.addFile( ":myIcons/32x32/document-open.png", QSize(32, 32)); ui->action_Open->setIcon(documentOpenIcon); QIcon saveAsIcon; saveAsIcon.addFile( ":myIcons/16x16/document-save-as.png", QSize(16, 16)); saveAsIcon.addFile( ":myIcons/24x24/document-save-as.png", QSize(24, 24)); saveAsIcon.addFile( ":myIcons/32x32/document-save-as.png", QSize(32, 32)); ui->actionSave_as->setIcon(saveAsIcon); QIcon exitIcon; exitIcon.addFile( ":myIcons/16x16/application-exit.png", QSize(16, 16)); exitIcon.addFile( ":myIcons/24x24/application-exit.png", QSize(24, 24)); exitIcon.addFile( ":myIcons/32x32/application-exit.png", QSize(32, 32)); ui->action_Quit->setIcon(exitIcon); ... }
If you have more than a handful of such icons, you will probably want to write a support method to reduce the repeated data entry.
An alternative to using icon resources is provided by the standardIcon()
method, which is an instance member of the QStyle
abstract base class8). This method provides access to the subset of standard system icons enumerated in StandardPixmap-enum
9).
The main issue with using standardIcon()
is that on my platform at least (Linux, Ubuntu 10.10) it does not provide multi-sized icons. You'll get icons from the system theme, but they will be resized and fuzzy at times. (I haven't tested the behavior on Windows and OS X, so it might not be the case on those platforms.) Another issue is that many common icons are not part of StandardPixmap-enum
.
Implementation requires only a bit of straight-forward hand-coding.
In our example, acceptable StandardPixmap-enum
icons are available for action_Open
and (arguably) action_Convert
. To use the standardIcon() approach for these icons, the resulting MainWindow constructor would look like:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); ui->action_Open->setIcon(this->style()->standardIcon(QStyle::SP_DialogOpenButton)); ui->action_Convert->setIcon(this->style()->standardIcon(QStyle::SP_DialogSaveButton)); ... }
On X11 systems, Qt lets you access all the icons that are part of the system icon theme using the static function QIcon::fromTheme()
10). Icons so accessed will be selected as needed based on the required rendering size.
As was the case with QStyle's standardIcon()
function, implementation with QIcon::fromTheme()
requires a bit of hand-coding.
In our example, using QIcon::fromTheme()
for the three actions in it's simplest form looks like:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); ui->action_Open->setIcon(QIcon::fromTheme("document-open")); ui->action_Convert->setIcon(QIcon::fromTheme("document-save-as")); ui->action_Quit->setIcon(QIcon::fromTheme("application-exit")); ... }
It's typical to test for the icon's existence first11):
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); if (QIcon::hasThemeIcon("document-open")) ui->action_Open->setIcon(QIcon::fromTheme("document-open")); if (QIcon::hasThemeIcon("document-save-as")) ui->action_Convert->setIcon(QIcon::fromTheme("document-save-as")); if (QIcon::hasThemeIcon("application-exit")) ui->action_Quit->setIcon(QIcon::fromTheme("application-exit")); ... }
or provide a fallback in the function call:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); ui->action_Open->setIcon(QIcon::fromTheme("document-open", QIcon(":/icons/open.png"))); ui->action_Convert->setIcon(QIcon::fromTheme("document-save-as", QIcon(":/icons/saveas.png"))); ui->action_Quit->setIcon(QIcon::fromTheme("application-exit", QIcon(":/icons/exit.png"))); ... }
QIcon's determination of the system icon theme is at the time of this writing not reliable under Linux – at least not under Xfce, Openbox, Fluxbox, and IceWM; GNOME seems to work as expected12). A possible workaround is to use as yet undefined HeroicMeasures()
™ to suss out the actual icon theme and then set it13) in the constructor:
QString iconTheme = My::HeroicMeasures(); QIcon::setThemeName(iconTheme);
Until such HeroicMeasures()
™ are worked out or the bug is fixed, this method can't really be recommended.
The static function QIcon::fromTheme()
used above can also be used with custom, locally defined icon themes that are placed into Qt Resource files14). Themes defined this way can be used across all platforms. To accomplish this we need to do three things: Create a custom icon theme, create a Qt Resource with the theme, and implement the code needed to use the custom theme.
To describe the process, I will use an example of a custom theme that provides icons in a variety of sizes for our example application. The icons themselves are taken from the raucously popular, public domain Tango Project15) using files provided in Ubuntu's 10.10 tango-icon-theme package16). Because unlike Bill Bruford I am not good at naming things17), I call the custom theme, “TangoMFK”.
There are several ways to create icon themes18). I will present here a way that I have found works and is relatively easy to set up.
Begin by add the following file and directory structure to the root of your project:
icons (dir) TangoMFK (dir) index.theme (text file) 16x16 (dir) 24x24 (dir) 32x32 (dir) tangomfk.qrc (text file)
Into the file index.theme
place the following text:
[Icon Theme] Name=TangoMFK Comment=Tango Icon Theme MFK Inherits=default Directories=16x16,24x24,32x32 [16x16] Size=16 [24x24] Size=24 [32x32] Size=32
Into the 16×16
, 24×24
and 32×32
subdirectories, place icons in the various sizes that you need for your application. In our case, I added files document-open.png
, document-save-as.png
and exit.png
to each of the subdirectories (nine files in total).
The actual icon theme consists of the directory TangoMFK and its contents. The file tangomfk.qrc
is used to build the custom icon theme as a compiled-in resource and is discussed below.
While you can create the needed *.qrc
file to describe the custom icon theme using only the GUI-based resource manager built into Qt Creator, in this case it's probably easier to create the file using straight text. The file below serves as an example of what you need to put into a *.qrc
file. In particular, note the use of an alias to point to the index.theme
and the syntax for the aliases pointing to the actual files.
<!DOCTYPE RCC><RCC version="1.0"> <qresource prefix="icons/TangoMFK"> <file alias="index.theme">icons/TangoMFK/index.theme</file> <file alias="16x16/exit.png">icons/TangoMFK/16x16/exit.png</file> <file alias="24x24/exit.png">icons/TangoMFK/24x24/exit.png</file> <file alias="32x32/exit.png">icons/TangoMFK/32x32/exit.png</file> <file alias="16x16/document-open.png">icons/TangoMFK/16x16/document-open.png</file> <file alias="24x24/document-open.png">icons/TangoMFK/24x24/document-open.png</file> <file alias="32x32/document-open.png">icons/TangoMFK/32x32/document-open.png</file> <file alias="16x16/document-save-as.png">icons/TangoMFK/16x16/document-save-as.png</file> <file alias="24x24/document-save-as.png">icons/TangoMFK/24x24/document-save-as.png</file> <file alias="32x32/document-save-as.png">icons/TangoMFK/32x32/document-save-as.png</file> </qresource> </RCC>
Once you have created the *.qrc
file with the required content, you will need to add it to your project. In Qt Creator, right-click the name of the project, select Add Existing Files…, and add the *.qrc
file. After doing this, you can use Qt Creator's GUI-based resource manager to verify that all the icons have been correctly included.
Note that in the set of icons I drew from, there were no 48×48 icons. To maintain best compatibility with Windows OSes, you ideally should include 48×48 icons as well19).
The pros and cons associated with this approach are essentially identical to Explicitly build and bind icons at runtime above. There is, however, one important difference: you cannot combine this approach with the Custom theme using QIcon::fromTheme() approach because the setting of icon themes is dynamic: the last theme name you set is the theme with which all icons will be rendered.
Once you have created your custom icon theme and added it to your project as a resource, using it is essentially identical to using the system icon theme except that you must now specify the theme name20).
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); QIcon::setThemeName("TangoMFK"); ui->action_Open->setIcon(QIcon::fromTheme("document-open")); ui->actionSave_as->setIcon(QIcon::fromTheme("document-save-as")); ui->action_Quit->setIcon(QIcon::fromTheme("exit")); ... }
You can (and typically should) use the forms shown above for testing icon existence and/or setting fallback icons.
While Qt offers many approaches to binding icons to actions, there is no single solution that is optimal anywhere and no simple combination that is optimal everywhere. Therefore, robust icon support in Qt requires the use of hybrid approaches. Such approaches will be discussed in a future report.
*.ico
files as Qt Resources and bind those to actions. However, when I tried this on Linux, I got it to show the smallest icon in the set. In other words, Qt doesn't seem to automatically select the best icon size in mult-size *.ico
files for the given situation. This may work on Windows and/or OS X but requires testing.