Sizers

Introduction

This tutorial explains how to use sizers. It also describes the code that implements the sizers so that you get a better understanding of the wxWidgets framework. We expect that you already know how to write a simple wxWidgets application that uses windows and controls. There are beginner tutorials on the wxWidgets website that cover these topics already so we don't cover them here.

In this tutorial we will explain:

  • what sizers are used for,
  • how to set the window size automatically using the wxSizer::Fit and the wxSizer::SetSizeHints functions,
  • how to get layout performed automatically when the window is resized using the wxWindowBase::SetSizer function,
  • and we will also describe some of the functions used internally to do these things.

The purpose of sizers

Sizers are there to assist the programmer with the layout of windows on the screen. Sizers can help with this in two ways. They can:

  1. compute the minimum size for a window from the size of its children, and
  2. perform the layout of a window i.e. set the size of each child based on the size of the parent window.

The first task is linked to the use of the wxSizer::Fit function. The function is typically called when a window needs to be displayed for the first time and its initial size needs to be computed. This is covered next on this page.

The second task is linked to the use of the wxWindowBase::SetSizer function to attach a sizer to a window. Each time the window is resized the event handler will use the sizer attached to the window to update the size of the children. This assumes that the default window event handler or an event handler with similar behaviour is in use and that this functionality is enabled.

A basic example

Let's start with a simple example of how to use sizers to automatically set the size of a window so that it fits its contents. The example is a simple modification of the MinimalApp2 example we presented in the minimal application tutorial. The full source for this example is available from our GitHub repository: wxWidgetsTutorials/Sizers/SimpleFit1.

 File: Sizers/SimpleFit1/src/SimpleFit1Frame.cpp
#include "SimpleFit1Frame.h"
#include "wx/sizer.h"
#include "wx/stattext.h"

SimpleFit1Frame::SimpleFit1Frame(const wxString& title)
    : wxFrame(NULL, wxID_ANY, title)
{
    wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
    wxStaticText* text = new wxStaticText(this, wxID_STATIC, wxT("A static text box"));
    topSizer->Add(text);
    SetSizer(topSizer);
    topSizer->Fit(this);
}

The code highlighted in blue is the code for the sizer. First let's show how the application would look like if this code was NOT present. As you can see the window is way too big for the static text control it contains.

Figure 1: Screenshot of SimpleFit1 when sizer code is commented out

And here is the result with the sizer code in place. The top level window is now just large enough to hold the static text control.

Figure 2: Screenshot of SimpleFit1 when using sizer

Let's see how this code works. We start by giving a high level view of the mechanisms involved. We will refine this description later. You may also want to have a look at the sequence diagram further down on this page while reading this.

First of all we create the sizer topSizer (in this case a wxBoxSizer but this is not important for this simple example). This sizer will be responsible for setting the size of the top level window. It does so when we call Fit and pass in a pointer to the window we want to resize. To set the size of the window it needs to know what the contents of the window are. This is why we make the topSizer->Add(text) call. Each sizer has children and computes the total size required to display the children based on each child's required size. The sizer can query the required size by calling wxWindowBase::GetEffectiveMinSize() for each child.

As you see the developer has to manually add the children to the sizer. Another possible behaviour could be for the sizer to query the window for its children and automatically take them into account. But doing so would be far less flexible than sizers. For instance with the current implementation it is possible to have more than one sizer handle the layout of one window. This gives the developer a lot of control on the final layout of the controls within the window.

This application has still a small issue though. It is possible to resize the window beyond the minimal size so that the text will not be entirely visible. How to fix this issue is explained later on this page (see SetSizeHints).

The rest of the files don't have any significant changes but are shown here for completeness.

 File: Sizers/SimpleFit1/src/SimpleFit1Frame.h
#ifndef _TUTORIALS_WXWIDGETS_SIMPLEFIT1FRAME_H_
#define _TUTORIALS_WXWIDGETS_SIMPLEFIT1FRAME_H_

#include <wx/frame.h>

class SimpleFit1Frame : public wxFrame
{
public:
    SimpleFit1Frame(const wxString& title);
};

#endif
 File: Sizers/SimpleFit1/src/SimpleFit1App.h
#ifndef _TUTORIALS_WXWIDGETS_SIMPLEFIT1APP_H_
#define _TUTORIALS_WXWIDGETS_SIMPLEFIT1APP_H_

#include 

class SimpleFit1App : public wxApp
{
public:
    virtual bool OnInit();
};

#endif
 File: Sizers/SimpleFit1/src/SimpleFit1App.cpp
#include "SimpleFit1App.h"
#include "SimpleFit1Frame.h"

wxIMPLEMENT_APP(SimpleFit1App);

bool SimpleFit1App::OnInit()
{
    SimpleFit1Frame* frame = new SimpleFit1Frame("SimpleFit1");
    frame->Show(true);
    return true;
}

Sizers implementation

It is time to have a look at how sizers work. The following figure shows the classes that are part of the sizer mechanism.

Figure 3: Class diagram

The class diagram shows that the wxSizer class holds a list of its children. Each child is wrapped in a wxSizerItem instance. This is necessary as a child can either be a wxWindow, another wxSizer or a wxSizerSpacer. In our basic example above the sizer's only child is the wxStaticText* text control.

On this diagram you will notice an oddity. wxWindowBase can own a wxSizer and this owned wxSizer has a link back to his owner but that link is of type wxWindow, not wxWindowBase. The reason for this isn't clear but it creates the need for some casts as can be seen in void wxWindowBase::SetSizer(wxSizer *sizer, bool deleteOld) that indeed uses a cast from wxWindowBase to wxWindow.

The diagram also shows the functions involved in computing the sizes of the various windows. The way this works is as follows. The top level wxSizer instance will use wxSizer::CalcMin() to compute the minimum window size needed to display all the items contained in the sizer. wxSizer::CalcMin() is a pure virtual function that gets implemented by the specific sizer classes (wxBoxSizer for instance). wxSizer::CalcMin() will call wxSizerItem::CalcMin() on each of the sizer's children to get their minimum size.

The code for wxSizerItem::CalcMin() is reproduced below. The function calls the appropriate function depending on the type of the child. In the case of a wxWindow this will be wxWindowBase::GetEffectiveMinSize(). A sizer can have a border so the function will take the size of the border into account as well.

 Code snippet: wxSizerItem::CalcMin() function
wxSize wxSizerItem::CalcMin()
{
    if (IsSizer())
    {
        m_minSize = m_sizer->GetMinSize();

        // if we have to preserve aspect ratio _AND_ this is
        // the first-time calculation, consider ret to be initial size
        if ( (m_flag & wxSHAPED) && wxIsNullDouble(m_ratio) )
            SetRatio(m_minSize);
    }
    else if ( IsWindow() )
    {
        // Since the size of the window may change during runtime, we
        // should use the current minimal/best size.
        m_minSize = m_window->GetEffectiveMinSize();
    }

    return GetMinSizeWithBorder();
}

The effective size of a window

We have seen in the code snippet above that wxSize wxWindowBase::GetEffectiveMinSize() is used by the sizer items to get the size of the windows. The code for this function is reproduced below.

 Code snippet: wxWindowBase::GetEffectiveMinSize() function
wxSize wxWindowBase::GetEffectiveMinSize() const
{
    // merge the best size with the min size, giving priority to the min size
    wxSize min = GetMinSize();
    if (min.x == wxDefaultCoord || min.y == wxDefaultCoord)
    {
        wxSize best = GetBestSize();
        if (min.x == wxDefaultCoord) min.x =  best.x;
        if (min.y == wxDefaultCoord) min.y =  best.y;
    }
    return min;
}

From this code it is obvious that the effective minimum size is defined as

  • the minimum size of the window if it has been set explicitly or,
  • the size returned by wxWindowBase::GetBestSize() if the minimum size of the window has not been set explicitly.

The GetBestSize function is thus a way for a control to compute its size automatically but is not used if the minimum size is explictly set by the developer.

There are several ways to set the minimum size. The most obvious is to call the void wxWindowBase::SetMinSize(const wxSize& minSize) function but specifying an initial size for a window will also set the minimum size so that it is the same as the initial size. For instance if we rewrite the code from the first example to specify an initial size for the text control as shown below, the GetBestSize function will not be used and the size of the top level window will be based on the initial size of the text control.

We call this modified application SimpleFit2. The full source for this example is available from our GitHub repository: wxWidgetsTutorials/Sizers/SimpleFit2.

 File: Sizers/SimpleFit2/src/SimpleFit2Frame.cpp
#include "SimpleFit2Frame.h"
#include <wx/sizer.h>
#include <wx/stattext.h>

SimpleFit2Frame::SimpleFit2Frame(const wxString& title)
    : wxFrame(NULL, wxID_ANY, title)
{
    wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
    wxStaticText* text = new wxStaticText(this, wxID_STATIC, wxT("A static text box"),
        wxDefaultPosition, wxSize(100, 200));
    topSizer->Add(text);
    SetSizer(topSizer);
    topSizer->Fit(this);
}

And here is what it looks like at runtime.

Figure 4: Screenshot of SimpleFit2

Note that there is still some undesirable behaviour with the code as it has been written above. Although the initial size of the main frame takes into account the minimum size of the wxTextCtrl that constraint is not respected when the user resizes the main frame. If we want to prevent the user from changing the size of the main frame to a size that is less than the minimum size we need to use the SetSizeHints function as shown in the SetSizeHints section further down on this page.

The rest of the files for the SimpleFit2 application do not differ significantly from the SimpleFit1 application but are shown here for completeness.

 File: Sizers/SimpleFit2/src/SimpleFit2Frame.h
#ifndef _TUTORIALS_WXWIDGETS_SIMPLEFIT2FRAME_H_
#define _TUTORIALS_WXWIDGETS_SIMPLEFIT2FRAME_H_

#include <wx/frame.h>

class SimpleFit2Frame : public wxFrame
{
public:
    SimpleFit2Frame(const wxString& title);
};

#endif
 File: Sizers/SimpleFit2/src/SimpleFit2App.h
#ifndef _TUTORIALS_WXWIDGETS_SIMPLEFIT2APP_H_
#define _TUTORIALS_WXWIDGETS_SIMPLEFIT2APP_H_

#include <wx/app.h>

class SimpleFit2App : public wxApp
{
public:
    virtual bool OnInit();
};

#endif
 File: Sizers/SimpleFit2/src/SimpleFit2App.cpp
#include "SimpleFit2App.h"
#include "SimpleFit2Frame.h"

wxIMPLEMENT_APP(SimpleFit2App);

bool SimpleFit2App::OnInit()
{
    SimpleFit2Frame* frame = new SimpleFit2Frame("SimpleFit2");
    frame->Show(true);
    return true;
}

Analysis of a call to Fit

To summarize all what we have said above here is a sequence diagram representing the execution of call to the Fit function in the basic example. If you have a debugger and the wxWidgets source code it should be possible to follow this sequence diagram as you step trough the code. Stepping through the code should help you get a better feeling of what is happening as we can't reproduce all the details in a diagram.

Figure 5: Basic example sequence diagram

As we mentioned previously the GetEffectiveMinSize function will only call GetBestSize if the minimum size hasn't been set explicitly. In our basic examples this means it will be called for SimpleFit1 but not SimpleFit2.

SetSizeHints

Our SimpleFit2 example has still a small issue. It is possible to resize the window to a size that is smaller than the minimum size as set by the Fit function. The figure below illustrates the problem.

Figure 6: Screenshot of SimpleFit1 issue

This is simple to fix by calling SetSizeHints instead of Fit as has been done in the code below (blue line). This example is SimpleFit3. The full source for this example is available from our GitHub repository: wxWidgetsTutorials/Sizers/SimpleFit3.

 File: Sizers/SimpleFit3/src/SimpleFit3Frame.cpp
#include "SimpleFit3Frame.h"
#include <wx/sizer.h>
#include <wx/stattext.h>

SimpleFit3Frame::SimpleFit3Frame(const wxString& title)
    : wxFrame(NULL, wxID_ANY, title)
{
    wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
    wxStaticText* text = new wxStaticText(this, wxID_STATIC, wxT("A static text box"),
        wxDefaultPosition, wxSize(100, 200));
    topSizer->Add(text);
    SetSizer(topSizer);
    topSizer->SetSizeHints(this);
}

You may wonder why we don't have to call Fit if we use SetSizeHints. This is because SetSizeHints also performs the equivalent of Fit automatically so calling it before calling SetSizeHints is unnecessary.

Another function you may find useful is wxWindow::SetSizerAndFit that combines the call to SetSizer and SetSizeHints. So instead of writing SetSizer(topSizer); topSizer->SetSizeHints(this); we could simply have written SetSizerAndFit(topSizer);. Note that the name of this function is slightly misleading as it is SetSizeHints that is called and not Fit.

The rest of the files do not differ significantly from the SimpleFit2 application but are shown here for completeness.

 File: Sizers/SimpleFit3/src/SimpleFit3Frame.h
#ifndef _TUTORIALS_WXWIDGETS_SIMPLEFIT3FRAME_H_
#define _TUTORIALS_WXWIDGETS_SIMPLEFIT3FRAME_H_

#include <wx/frame.h>

class SimpleFit3Frame : public wxFrame
{
public:
    SimpleFit3Frame(const wxString& title);
};

#endif
 File: Sizers/SimpleFit3/src/SimpleFit3App.h
#ifndef _TUTORIALS_WXWIDGETS_SIMPLEFIT3APP_H_
#define _TUTORIALS_WXWIDGETS_SIMPLEFIT3APP_H_

#include <wx/app.h>

class SimpleFit3App : public wxApp
{
public:
    virtual bool OnInit();
};

#endif
 File: Sizers/SimpleFit3/src/SimpleFit3App.cpp
#include "SimpleFit3App.h"
#include "SimpleFit3Frame.h"

wxIMPLEMENT_APP(SimpleFit3App);

bool SimpleFit3App::OnInit()
{
    SimpleFit3Frame* frame = new SimpleFit3Frame("SimpleFit3");
    frame->Show(true);
    return true;
}