Saturday, March 19, 2011

Complex dialogs with wxWidgets

Most RAD tools, like the venerable Visual Basic, let users put controls in a dialog using a fixed layout: just drag each control where you want to see it. This is a very simple approach, but it has some shortcomings, especially for applications that will run on different operating systems.
The problem is that the layout is fixed and it does not adapt to the size of the contained text. Widows uses a default font that is smaller than the one used in Linux or OS X, and even in Windows users can set larger fonts.
This means that if a dialog has been designed in Windows with default fonts then the text will no be completely visible if the used font is larger.
The same happens with translations: if the translated text is longer it will not be completely visible.

To solve these problems wxWidgets (like other frameworks) uses sizers for dialogs layout. Sizers change dynamically the size of controls so that their content will always be visible. This is very useful but it look rather complicated at first: you need some time to become used to sizers if you are used to fixed layouts.

Accounting programs often need to show many controls in a dialog, so it is important to use the available space in the best possible way. This sometimes means arranging controls in an irregular way to avoid wasting space.
Sizers can cause troubles in this situation: one of the more powerful sizer ix wxFlexGridSizer: it arranges controls in a regular grid, with rows and columns of different sizes to accommodate controls. This works reasonably well, but each column (for example) has the width of the widest control so if it contains a very wide and a very narrow control a lot of space will be wasted, and the dialog will not look very well.

A few days ago I had a similar situation, so I tried wxGridBagSizer: there is not much information about it but it looked promising. This is very complicated to use at the source code level, but I discovered that DialogBlocks has great support for this sizer. wxGridBagSizer arranges controls in a layout that is similar to HTML tables: controls lie in a grid, but a grid cell can span over more than one row or column. This solves most of the problems and the resulting dialog looks much better and uses the available space very well.

Here is an example of a dialog using wxGridBagSizer in DialogBlocks: you can see how some controls span over more than one row or column:

Tuesday, March 8, 2011

Printing text with wxWidgets II

Printing to the default printer

A common requirement is printing directly to the default printer, without showing any printer selection dialog.
Referring to the code of the previous post this can be accomplished by changing one line of code from

bool success = printer.Print( NULL, &printout, true );

to

bool success = printer.Print( NULL, &printout, false );

According to the documentation this should work, but the code (at the moment of writing this post) does not print anything.
Some debugging shows that there is a bug in the wxWidgets code: the bug will be fixed, but at the moment the problem can be solved adding one line of code.
So working code looks like the this:

wxPrintDialogData printDialogData(* m_PrintData);
printDialogData.SetToPage( printDialogData.GetMaxPage() );
wxPrinter printer( &printDialogData );
bool success = printer.Print( NULL, &printout, true );


Setting the To page to the Max page fixes the problem.

Centering text (and similar)

Another common requirement is comparing the width of some text to the width of the page, for example to center the text, but it is not very clear how to obtain the desired sizes in the same units, so that they can be compared in a correct way.

The previous post shows how to compute a variable (logUnitsFactor) to convert from millimeters to logical units.
The page size in millimeters can be obtained by calling wxPrintout::GetPageSizeMM(), then the size in millimeters can be converted to logical units multiplying it by logUnitsFactor.

The width of a text can be obtained calling wxDC::GetTextExtent(): the result is in logical units that can be compared to the previously computed page width.
For example (code inside a wxPrintout-derived object):

    // compute the available width in logical units
    int pageWidthMM, pageHeightMM;
    GetPageSizeMM(&pageWidthMM, &pageHeightMM);
    int availableWidth = (pageWidthMM - m_MarginLeft - m_MarginRight)*logUnitsFactor;
    wxCoord ew, eh;
    dc->GetTextExtent( "Some text", &ew, &eh, NULL, NULL );
    // now we can compare "ew" and "availableWidth"