Monday, January 9, 2012

Easily adding lines to a wxGrid

wxGrid is a good control, but users expect a behaviour more similar to a spreadsheet, so I derived my own control from wxGrid, with a number of customizations.

The first is the ability to easily add a new row at the end of the grid. With some simple code it is possible to always have an empty row at the end of the grid, so users can simply go to that line and start typing data: a new row will pop up at the end of the grid and so on...

All we need to do is handling the EVT_GRID_SELECT_CELL event:

 void fsGridBase::OnSelectCell( wxGridEvent& event )  
 {  
     int row = event.GetRow();  
     int lastRow = GetTable()->GetNumberRows() - 1;  
     if( row == lastRow ) {  
         AppendRows( 1 );  
     }  
   
   event.Skip();  
 }  
   

as soon as a cell is selected in the last row a new row is appended. This makes the grid behaviour more similar to the one of a spreadsheet.

Sunday, November 6, 2011

Hiding and showing controls in a dialog

Often a single dialog can be used for different purposes, with slight variations. Sometimes a control is not needed if the uses chooses a certain option, but it is needed if he chooses a different option.

If disabling the control is enough the simplest solution is to use UPDATE_UI events, but those events cannot be used to hide and show controls.

Dynamically hiding a control requires a few lines of code, but this feature is not very well documented.
Suppose that m_Control is a pointer to the control to be hidden, and that m_Sizer is a pointer to the sizer that contains that control. The following code, in a method of a wxDialog-derived window, hides the control at run time, rearranging the other controls and the window to reuse the resulting empty space:

    m_Sizer->Hide( m_Control );
    m_Sizer->Layout();
    GetSizer()->SetSizeHints(this);

Monday, September 26, 2011

Directly printing to a certain printer

When users have more than one printer available, sometimes a requirement is being able to print many documents to a certain printer. That printer is not necessarily the default printer.
Think of printing a set of invoices, for example. This is not a trivial task in wxWidgets.

The standard code to print a document is the following (see also this post):

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


The code above shows a printer selection dialog, then it prints to the printer selected by the user.

After printing you can get the name of the selected printer with this code:

wxString pName = printer.GetPrintDialogData().GetPrintData().GetPrinterName();


Then you can use the printer name to print again directly to that printer:


    m_PrintData->SetPrinterName( pName );
    wxPrintDialogData printDialogData(* m_PrintData);

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


 The code above sets the printer name and prints without showing a printer selection dialog.
As you can see the solution a a simple one, but I had problems finding it because nobody seems to have this problem and I had to look hard in the documentation.

Friday, September 9, 2011

Printing images with wxWidgets

Printing an image, mixed with text, is a common requirement. Just think of printing a customer's logo on an invoice.

Printing an image at a given size in wxWidgets has been a rather difficult task, mainly for the lack of documentation or examples. After some trial and error I found a working solution and I will describe it here. This solution is based on what I described for printing text. You will need to read that post too.

1 - Load the image in a wxImage object, resize it, convert it to a wxBitmap. You need to resize the image so that you can print it pixel by pixel and obtain a printed image of the desired size. To define the image size you need to know the desired printed size or the image DPI. You can know the printer's resolution using wxDC::GetPPI().
Starting from the original image size and the printer's resolution you can compute the new image size and rescale it.
If you know the image's DPI you can compute the new image size in pixels using this formula:
newWidth_pixels = printerPPI / imageDPI * originalWidth_pixels

If you know the printed image's width (in mm) that you want to obtain you can compute the new image size in pixels using this formula:
newWidth_pixels =  printedWidth_mm * PPI / 25.4

2 - Print the bitmap, making sure that wxWidgets does not resize it. This is the difficult part, since I ended up with a much larger image than what I wanted. The problem is that I set up a user scale with wxDC::SetUserScale() and that scale was also applied to the image, enlarging it. The solution is, obviously, to set the user scale to 1 before drawing the bitmap. Here is a function that I use for the purpose (x and y are the print coordinates in mm):

void PrintBitmap( wxDC *dc, const float logUnitsFactor, wxBitmap &bitmap, const float x, const float y )
{
        double xScale, yScale;
        dc->GetUserScale( &xScale, &yScale );
        dc->SetUserScale( 1, 1 );
        dc->DrawBitmap( bitmap, x*logUnitsFactor*xScale, y*logUnitsFactor*yScale, true );
        dc->SetUserScale( xScale, yScale );
}

Thursday, July 21, 2011

Tab order in dialogs

Accounting programs often show dialogs that contain many controls for data entry.
The mouse is an easy way to move from one control to the other while entering data, but this is a very slow solution. Users that need to input a lot of data quickly learn to use the TAB key to move from one control to the other.

This is a much faster solution, but pressing TAB moves among controls in a fixed sequence (usually called tab order), so this sequence must be the best possible one from a user's viewpoint. To optimize space in the dialog sometimes the tab order must be different from the usual left-to-right, top-to-bottom layout.

Visual Basic and other RAD tools controls have a specific property to set the tab order of each control, but wxWidgets does not have anything similar. In wxWidgets the default tab order is the order of creation of the controls: this is usually leads to the left-right, top-bottom order.

The functions wxWindow::MoveAfterInTabOrder ()  and wxWindow::MoveBeforeInTabOrder()can be used to change the tab order of a control. They work but they have some drawbacks.
  • You must write this code by hand. DialogBlocks, for example, does not do it.
  • If you want to jump to a control and then move to the ten controls following it, you need to write this code for ALL the controls. Suppose that you have the following controls: A, B, C, D, E, F, G. Moving D after A will cause the following tab order: A, D, B, C, E, F, G. SO, to obtain something like A, D, E, F, G, B, C you will need to call the function a lot of times.
Using wxGridBagSizer can be a simpler solution to set tab order. Using this sizer you can obtain the same layout even if you create controls in a different order. Since the default tab order is the creation order you can change the creation order to get the desired tab order without changing the visual layout of the dialog.
Using DialogBloks this can be obtained moving controls up or down in the left pane.

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"