[Bottom of Page] [MN Home Page]

Looking through wxWindows

An intro to the portable C++ and Python GUI toolkit

M. Neifer
Software engineer
February 2001 (first published at IBM developerWorks)

M. Neifer gives an overview of wxWindows, the portable C++ and Python GUI toolkit. He discusses the library's architecture, talks about how to deal with multi-platform file handling and the directory separation character, and touches on wxHTML, image file formats, and Unicode. He also walks you through some helpful wxWindows debugging tips and talks a bit about porting MFC applications to Linux.

The wxWindows library, whether or not it's compiled as a Dynamic Link Library (DLL), makes it possible to have very small executives. It also offers various goodies for multi-platform development: you get an OpenGL interface and built-in support for HTML, Unicode, and internationalization. And it helps you to port applications from Windows-only MFC (Microsoft Foundation Classes) to Linux, for example. One of the main goals of wxWindows is to run on as many platforms as possible, so it supports nearly every C++ compiler available. It does not yet use all features of Standard C++ (such as namespaces, std::string class, and STL container). But Standard C++ is on the to-do list and there's already some support for the new cast syntax and std::string.

A bit of history

Julian Smart started wxWindows in 1992 at the Artificial Intelligence Applications Institute, University of Edinburgh. In 1995 Markus Holzem released his port of wxWindows to Xt, the X toolkit. After a brief period of stagnation, in May 1997 the Windows and GTK+ ports were merged and put into a CVS repository made available to all contributors to wxWindows. Towards the end of '97, Julian Smart started distributing a CD-ROM of wxWindows, including the complete source, compiler stuff, and much more.

wxWindows is now released under the GPL, with one exception: You can distribute binary executables without the source code. That's a nice option for commercial projects. It's currently available for various flavors of UNIX and Microsoft Windows, and for Macintosh OS. Ports to OS/2 and other operating systems are under development. And now, let's get to the meaty stuff....

Platforms to go

wxWindows takes a different approach to widgets than most other multi-platform GUI libraries because it uses the native controls whenever possible. Controls that are not available, like tree controls under UNIX, are emulated. This will give the user of your application the familiar look and feel. The wxWindows library currently supports the following platforms:

The wxBase library with only the non-GUI classes can also be built under UNIX/Win32 and (with some limitation) BeOS. And even if you're not compiling wxWindows as a DLL, you can get very small executables. For example, the minimal sample application compiled with Microsoft Visual C++ for the Windows platform is smaller than 400 KByte. And because of wxWindows' small executable sizes, you can usually avoid the so-called "DLL hell".

And now, onto the multi-platform goodies...

Architecture-independent types

In order to avoid architecture dependencies, the library offers various architecture-independent types and macros that handle bit-swapping with respect to the applications endianness. These are:

The bit-swapping macros are available for integer and unsigned integer (where xx stands for 16 or 32, BE for big endian, and LE for little endian).

Usage is straightforward here, as the following example shows:

Swapping the bytes of a 32-bit signed integer variable

wxInt32 old_var = 0xF1F2F3F4; wxInt32 new_var = wxINT32_SWAP_ALWAYS( old_var )

Besides these macros, wxWindows offers #defines to determine the current endianness of the machine the library was compiled on. An example of this might be:

Using #define

if ( wxBYTE_ORDER == wxLITTLE_ENDIAN ) { // Do stuff for little endian machine... } else { // Do stuff for big endian machine... }

File handling

It's always difficult to write for different platforms that have different concepts of file storage. To overcome this issue, wxWindows has functions for multi-platform file handling. First let's look at some functions for the basic file operations like copy, remove, and rename.

Basic file operations

wxString old_report = "smithers_00.doc" wxString new_report = "my_smithers.doc"; if ( wxCopyFile( old_report, "smithers_00.bak" ) == true ) { if ( wxRemoveFile( old_report ) == true ) { if ( wxRenameFile( new_report, old_report ) == false ) { // Doh! } } }

Another serious problem posed by writing for different platforms is the directory separation character, which you can avoid altogether with the help of the wxPathList class. wxPathList contains a list of directories to search for files. If you want to look for a file, you just pass the filename to the wxPathList class and it'll search the predefined directories.

The wxPathList class

wxPathList path_list; // Add current working directory path_list.Add( "." ); // Add one directory above current working directory path_list.Add( ".." ); // Add directories from environment variable PATH to the list path_list.AddEnvList( "PATH" ); wxString path = path_list.FindValidPath( "homer.bmp" );

Two other useful functions in wxWindows are the wxFileNameFromPath(), which strips a filename from a full path and wxPathOnly(), which strips the path from a full path.

HTML

Vaclav Slavik's wxHTML library parses and renders basic HTML. It doesn't fully implement the HTML standard, but its functionality is good enough to handle online help and can be extended using tag handlers. To display HTML you create an object of class wxHtmlWindow. You then call its methods to set the related frame to actually display the HTML and the related status bar to display messages generated by the HTML parser.

wxHTML

wxHtmlWindow html_window = new wxHtmlWindow( this ); html_window->SetRelatedFrame( this, "HTML : %%s" ); html_window->SetRelatedStatusBar( 0 );

After this, you can load an HTML page by using:

Loading HTML

html_window->LoadPage( "burns.htm" );

Or you can display HTML code by using

Displaying HTML

html_window->SetPage( "<html><body>Hello, Monty!</body></html>" );

Images

The wxImage class loads various image file formats using image format handlers. You can extend wxImage to load a new image format by implementing your own image format handler. The existing image format handlers use well known libraries like Sam Leffler's libTIFF library or the Independent JPEG Group's JPEG library. There are handlers for BMP, PNG, JPEG, GIF, PCX, PNM, and TIFF. You can activate each image format handler by using the wxImage class' static method AddHandler() in your application startup code.

wxImages

bool MyApp::OnInit() { wxImage::AddHandler( new wxPNGHandler ); // more ... }

To use all existing image format handlers, simply call function wxInitAllImageHandlers() instead of the AddHandler() method shown above.

Take extra care when using toolbar bitmaps in your application on different platforms. On Windows you'll use the Windows bitmap format, but Linux bitmaps are usually pixmaps, in which case conditional compilation cannot be completely avoided. Let's have a look at some sample code.

#if defined(__WXGTK__) || defined(__WXMOTIF__) #include "maggie.xpm" #endif // more ... void MyApp::UseBitmap() { wxBitmap bitmap( wxBITMAP( maggie )); // more ... }

Get the idea? All the magic is done by the wxBITMAP() macro. For Windows and OS/2 it will create a wxBitmap object using a bitmap called 'maggie' from the application resources. For all other platforms it will create a wxBitmap object using a pixmap called 'maggie_xpm'.

While a bitmap can be drawn in a device context using wxDC::DrawBitmap(), you have to use a wxImage object for image manipulations, as shown below.

Image manipulation

wxImage* p_image = new wxImage( bitmap ); // Have some fun if ( p_image->Ok() ) { if ( p_image->GetHeight() > 50 && p_image->GetWidth() > 50 ) { unsigned char red = p_image->GetRed( 50, 50 ); unsigned char green = p_image->GetGreen( 50, 50 ); unsigned char blue = p_image->GetBlue( 50, 50 ); // Secure but might be slow p_image->SetRGB( 50, 50, red, green, blue ); // If you want fast action use a pointer... unsigned char* data = p_image->GetData(); // Manipulate the data... } }

Unicode and internationalization (i18n)

When developing software for the international market, you can't expect everybody to be able to read your application messages in English. But the widely used ANSI code can't handle all language symbols. (It does not, for example, cover Chinese.) And writing for both ANSI and Unicode can get a bit complex, as you can see from the example below.

ANSI and Unicode

#ifdef USE__UNICODE wchar_t wide_char = L'h'; wchar_t const* wide_string = L"Hello, World!"; int length = wcslen( wide_string ); #else char ansi_char = 'h'; char const* ansi_string = "Hello, World!"; int length = strlen( ansi_string ); #endif

So, using wxWindows Unicode capabilities you'll simply write the following:

The wxT() macro and the wxChar and wxString

wxChar wx_char = wxT( '*' ); wxString wx_string = wxT( "Hello, World!" ); int length = wx_string.Len();

The wxT() macro and the wxChar and wxString classes will take care of everything. The library uses this approach internally for all messages. There are currently translations for Czech, Danish, German, French, Italian, and Russian available, but you can compile a localized version of the library and use it with some help from the wxLocale class.

Debugging

The library offers various classes, functions, and macros to help you debug your application. If you compile the library in debug mode, the new and delete operators for class wxObject (the base class for most classes in wxWindows) have been redefined to store additional information about objects allocated on the heap. Using this information, the class wxDebugContext can be used to get detailed information about object allocation, memory leaks, overwrites, and underwrites.

// Start logging for Dump() call wxDebugContext::SetCheckpoint(); wxString *thing = new wxString; wxDate* date = new wxDate; // non-object allocation char *ordinaryNonObject = new char[1000]; // more ... // Print number of object and non-object allocations wxDebugContext::Dump(); // Print allocation statistics wxDebugContext::PrintStatistics();

A call to wxDebugContext::Dump() will pop up a window containing a list of allocations. Below you'll find such a list I've created using the memcheck sample provided with wxWindows.

Calling wxDebugContext::Dump()

13:32:45: ----- Memory dump of memcheck at Tue Dec 26 13:32:45 2000 ----- 13:32:45: ..\..\..\samples\memcheck\memcheck.cpp(88): non-object data at $DD3DC0, size 4 13:32:45: ..\..\..\samples\memcheck\memcheck.cpp(89): wxDate at $DD40D0, size 24 13:32:45: ..\..\..\samples\memcheck\memcheck.cpp(92): non-object data at $DD4118, size 1000

A call to wxDebugContext::PrintStatistics() will give you the statistics you see below.

Calling wxDebugContext::PrintStatistics()

13:32:45: ----- Memory statistics of memcheck at Tue Dec 26 13:32:45 2000 ----- 13:32:45: 1 objects of class wxDate, total size 24 13:32:45: 5 objects of class nonobject, total size 4256 13:32:45: 13:32:45: Number of object items: 1 13:32:45: Number of non-object items: 5 13:32:45: Total allocated size: 4280

Porting MFC apps to Linux

The wxWindows library can also port MFC applications to Linux and other operating systems. As you can see in the following code snippet, the wxWindows string class wxString has some similarities to the MFC string class CString.

wxString

wxString s1 = "Hello, World!"; wxString s2 = "Hello"; if ( s1.IsEmpty() == false ) { s2.Empty(); s2 = s1.Left( 5 ); int pos = s1.Find( ',' ); s2 += s1.Mid( pos, 2 ); s2 += s1.Right( 6 ); }

wxWindows' event system also closely resembles MFC, in which message maps map event handler methods to the event system. In wxWindows, these are called event tables. The event tables macros differ very slightly from MFC's message mapping. The main differences are illustrated in the source code below.

The MFC code for the header file

class CButtonCtrl : public COleControl { // Implementation protected: LRESULT OnOcmCommand( WPARAM wParam, LPARAM lParam ); DECLARE_MESSAGE_MAP() };

The MFC code for the implementation file

BEGIN_MESSAGE_MAP( CButtonCtrl, COleControl ) //{{AFX_MSG_MAP( CButtonCtrl ) ON_MESSAGE( OCM_COMMAND, OnOcmCommand ) //}}AFX_MSG_MAP END_MESSAGE_MAP()

The wxWindows code for the header file

class MyButton : public wxButton { void OnButton( wxMouseEvent& event ) private: DECLARE_EVENT_TABLE() };

The wxWindows code for the implementation file

BEGIN_EVENT_TABLE( MyButton, wxButton ) EVT_BUTTON( -1, MyButton::OnButton ) END_EVENT_TABLE()

As you can see, it's not that different. There are already quite a few people on the mailing list who have successfully ported their existing MFC applications to wxWindows (and are more than happy to help with yours :).

Standard C++

wxWindows doesn't yet use Standard C++ techniques (like std::string, STL, or namespaces) because it would drastically reduce the number of platforms on which wxWindows compiles. (Very few compilers fully support the latest features of Standard C++.) But as Standard C++ becomes more universally supported, the wxWindows development team will be integrating support for Standard C++ in its library. Let's have a look at some examples of the work they're doing.

Secure down-cast in Standard C++

class A { virtual void foo() {}; }; class B : public A {}; A* p_A = new B(); B* p_B = dynamic_cast<B*>( p_A );

If things go wrong here, p_B will contain a zero-pointer. In wxWindows, you'll find a macro-based system for run-time type information, but for the Standard C++ code above you have to use the wxDynamicCast() macro like this:

The wxDynamicCast() macro for Standard C++

B* p_B = wxDynamicCast( p_A, B );

If the cast fails, p_B contains a zero-pointer. For implementations that support the new cast syntax, the macro expands to dynamic_cast<>. But unfortunately the macro only works for pointers and not for references. Besides the wxDynamicCast() macro, there are also the wxStaticCast() and wxConstCast() macros.

The wxWindows string class wxString provides 90% of the methods of the standard string class. Here are some examples:

wxWindows' wxString

wxString s1 = "Hello, World!"; wxString s2 = "Hello"; s2.erase(); for ( size_t i = 0; i < s1.length(); ++i ) s2 += s1[i]; if ( s1.compare( s2 ) == 0 ) { // Strings are equal }

Note that if you typedef wxString as std::string here, you can write for both wxWindows and Standard C++.

Documentation

The wxWindows documentation is by no means perfect at this point. While "old" classes like wxString are well documented, the description for recently implemented classes like wxGrid or more obscure classes like wxGLCanvas could be better. The main documentation, which offers a quick introduction to the library and its concepts, an alphabetical class reference, programming strategies, topic overviews, and some notes for wxHTML and wxPython, is available as HTML, WinHelp, MS HTML Help, and PDF (see Resources).

If you're new to wxWindows you should start with the topic overviews. They provide a lot of basic information and code samples about common topics like debugging, event handling, printing, etc. The main documentation also contains a few technical notes and tutorials, which provide information on topics ranging from the more common questions about learning wxWindows to compiler specific questions. You'll also get a bunch of plain text files containing installation and release notes for supported platforms. The wxWindows distribution also provides about 50 sample applications to go along with the documentation.

Support

What if you have any problems using the library and you can't find an answer in the documentation? Don't worry. In general you have two support options: free support using the mailing list, or commercial support. Unfortunately, if you need a quick answer you're probably better off using the commercial support. While the core developers always have one eye on the mailing list, they're often too busy to respond immediately. But you can usually expect an answer within a day or so. (If you're asking something unusual or complex, you should wait at least two days before reposting.) You can access the latest source from the CVS source code database at Sourceforge (see Resources).

Wrapup

You should now have a good idea of what wxWindows is all about and what it offers for multi-platform development. But of course there's always more. Take for example the current wxStudio project, which is developing a Microsoft Visual Studio like IDE using wxWindows. Or the wxCVS project, which will be a multi-platform graphical interface for the CVS system. Or wxDesigner, an RAD tool developed by Robert Roebling to build wxWindows dialogs. As you can see, the wxWindows community is growing, so take a look at it the next time you have a multi-platform project in the wings.

All product names mentioned are the trademarks or registered trademarks of their respective owners.

Resources

About the author

M. Neifer first programmed with the help of the LOGO turtle and used various flavors of BASIC after that. During his studies of geoinformatics he learned some C but moved quickly to C++ and Java because of their object-oriented nature. He has worked in R&D where he has published articles on object-oriented development of scientific software. Currently he's working as a software engineer in the field of geographic information systems. He can be reached at howlingmad@users.sf.net.

[Top of Page] [MN Home Page]