Wxwidgets Tutorial 2014 - Cross-Platform Toolkit For Creating C++ GUI Applications
Wxwidgets Tutorial 2014 - Cross-Platform Toolkit For Creating C++ GUI Applications
This is wxWidgets tutorial for the C++ programming language. wxWidgets is a cross platform
toolkit or framework for creating C++ GUI applications. After reading this tutorial, you will be able
to program non trivial wxWidgets applications.
Table of contents
Introduction
Helper classes
First programs
Menus and toolbars
Layout management
Events
Dialogs
Widgets I
Widgets II
Drag & drop
Device contexts
Custom widgets
Tetris
wxWidgets
wxWidgets is a GUI (Graphical User Interface) toolkit for creating C++ applications. It is an open
source, mature and cross-platform toolkit. wxWidgets applications run on all major OS platforms,
Windows, Unix, and Mac OS.
Tweet
Related tutorials
wxPython tutorial covers the binding of the Python language to the wxWidgets library. The Qt4
tutorial presents the Qt4 library.
Introduction to wxWidgets
This tutorial will introduce you to the programming with the wxWidgets toolkit.
wxWidgets
wxWidgets is a Graphical User Interface (GUI) toolkit for creating C++ applications. It is an open
source, mature, and cross-platform toolkit. wxWidgets applications run on all major OS platforms,
including Windows, Unix, and Mac OS. The project was started by Julian Smart in 1992.
wxWidgets is much more than a toolkit. It provides a large variety of classes for handling streams,
databases, threads, online help, or application settings. wxWidgets consists of a large group of
widgets. The community around wxWidgets is grouped around their website.
Programming languages
There are currently several widely used programming languages. The following list is based on the
TIOBE Programming Community Index. The numbers are from November 2010. As we can see,
C++ still belongs to the most popular programming languages in the world.
2 C 16.7%
3 C++ 9.5%
4 PHP 7.8%
5 C# 5.7%
6 Python 5.7%
8 Objective-C 3.2%
9 Perl 2.4%
10 Ruby 1.9%
Java is the most widely used programming language. Java excels in creating portable mobile
applications, programming various appliances and in creating enterprise applications. Every fourth
application is programmed in C/C++. They are standard for creating operating systems and various
desktop applications. C/C++ are the most widely used system programming languages.
PHP dominates over the Web. While Java is used mainly by large organisations, PHP is used by
smaller companies and individuals. PHP is used to create dynamic web applications.
C# is the main programming language of the Microsoft .NET platform. C# is followed in .NET by
Visual Basic. It represents of the popularity of the RAD. (Rapid Application Development.)
Perl, Python, and Ruby are the most widely used scripting languages. They share many similarities.
They are close competitors.
Multiplatform programming
Today, multiplatform programming is a buzzword. Most languages and libraries want to be
multiplatform. wxWidgets was created as a multiplatform tool from the beginning. Most developers
choose among these options. If it is possible, they go to the web. Or they can use Qt, wxWidgets,
Swing, or SWT. The Qt library is the closest competitor to wxWidgets.
wxWidgets library can be used to create console and GUI applications. In this chapter, we will
illustrate some of the helper classes in console based applications.
Console
This is a simple console application. The application puts some text into the console window.
console.cpp
#include <wx/string.h>
wxString
wxString is a class representing a character string.
In the following example, we define three wxStrings. We create one string of these strings using
addition operation.
addition.cpp
#include <wx/string.h>
wxPuts(str);
}
formatted.cpp
#include <wx/string.h>
wxString str;
str.Printf(wxT("There are %d red roses."), flowers);
wxPuts(str);
}
The following example checks, whether a string contains another string. For this we have a
Contains() method.
contains.cpp
#include <wx/string.h>
if (str.Contains(wxT("history"))) {
wxPuts(wxT("Contains!"));
}
if (!str.Contains(wxT("plain"))) {
wxPuts(wxT("Does not contain!"));
}
Contains!
Does not contain!
length.cpp
#include <wx/string.h>
The MakeLower() and MakeUpper() methods make characters lower case and upper case.
cases.cpp
#include <wx/string.h>
wxPuts(str.MakeLower());
wxPuts(str.MakeUpper());
}
Utility functions
wxWidgets has several handy utility functions for executing a process, getting a home user
directory or getting the OS name.
In the following example, we execute the ls command. For this, we have the wxShell() function
(Unix only).
shell.cpp
#include <wx/string.h>
#include <wx/utils.h>
wxShell(wxT("ls -l"));
total 40
-rwxr-xr-x 1 vronskij vronskij 9028 2007-09-06 22:10 basic
-rw-r--r-- 1 vronskij vronskij 95 2007-09-06 22:09 basic.cpp
-rw-r--r-- 1 vronskij vronskij 430 2007-09-06 00:07 basic.cpp~
-rwxr-xr-x 1 vronskij vronskij 11080 2007-09-05 23:17 console
-rw-r--r-- 1 vronskij vronskij 500 2007-09-05 23:17 console.cpp
-rw-r--r-- 1 vronskij vronskij 485 2007-09-05 23:16 console.cpp~
Next we will we will get the home user directory, os name, user name, host name, and total free
memory.
system.cpp
#include <wx/string.h>
#include <wx/utils.h>
/home/vronskij
Linux 2.6.20-16-generic i686
jan bodnar
spartan
Memory: 741244928
This is the output.
datetime.cpp
#include <wx/datetime.h>
wxPuts(date1);
wxPuts(date2);
wxPuts(date3);
}
datetime2.cpp
#include <wx/datetime.h>
The following example shows, how we can add date spans to our date/time. We add one month to
the current time.
datespan.cpp
#include <wx/datetime.h>
September 07 2007
October 07 2007
Files
wxWidgets has several classes to facilitate working with files. This is low level access to files, as
opposed to working with streams.
In the following example, we use the wxFile class to create a new file and write data to it. We also
test, whether the file is opened. Note that when we create a file, it automatically stays as opened.
createfile.cpp
#include <wx/file.h>
wxFile file;
file.Create(wxT("quote"), true);
if (file.IsOpened())
wxPuts(wxT("the file is opened"));
file.Write(str);
file.Close();
if (!file.IsOpened())
wxPuts(wxT("the file is not opened"));
}
$ ls qoute
ls: qoute: No such file or directory
$ ./createfile
the file is opened
the file is not opened
$ cat quote
You make me want to be a better man.
The wxTextFile is a simple class which allows to work with text files on line by line basis. It is easier
to work with this class than with wxFile class.
In the next example, we will print the number of lines in a file, first and last lines and finally we will
read and show the contents of the file.
readfile.cpp
#include <wx/textfile.h>
wxTextFile file(wxT("test.c"));
file.Open();
wxPuts(wxT("-------------------------------------"));
wxString s;
file.Close();
}
Number of lines: 8
First line: #include <glib.h>
Last line: }
-------------------------------------
#include <glib.h>
#include <glib/gstdio.h>
int main() {
g_mkdir("/home/vronskij/test", S_IRWXU);
In the following example, we will print all files and directories available in the current working
directory.
dir.cpp
#include <wx/dir.h>
#include <wx/filefn.h>
wxDir dir(wxGetCwd());
wxString file;
while (cont) {
wxPuts(file);
cont = dir.GetNext(&file);
}
}
$ ./dir
dir
temp
console
basic.cpp
basic
quote
createfile
console.cpp
basic.cpp~
test.c
console.cpp~
A simple application
First we create the very basic wxWidgets program.
simple.h
#include <wx/wx.h>
};
simple.cpp
#include "simple.h"
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "simple.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Simple *simple = new Simple(wxT("Simple"));
simple->Show(true);
return true;
}
This very basic example shows a small window on the screen. The window is centered.
Centre();
This method centers the window on the screen, both horizontally and vertically.
IMPLEMENT_APP(MyApp)
The code that implements the application is hidden behind this macro. This is copy and paste code,
we usually do not have to care about.
Figure: Simple
Application icon
In this example, we provide an icon for our application. It became a standard to display a small icon
in the upper left corner of the window. The icon is a graphical identity of the program.
icon.h
#include <wx/wx.h>
};
icon.cpp
#include "icon.h"
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "icon.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Icon *icon = new Icon(wxT("Icon"));
icon->Show(true);
return true;
}
SetIcon(wxIcon(wxT("web.xpm")));
To display an application icon is a matter of one code line. XPM (X PixMap) is an ASCII image
format.
Figure: Icon
A simple button
In the following example, we create a button on the frame widget. We will show, how to create a
simple event handler.
button.h
#include <wx/wx.h>
button.cpp
#include "button.h"
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "button.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
We create a wxButton widget. It is placed on the panel. We use the predefined wxID_EXIT id for the
button. It will cause to display a small exit icon on the button. The label of the button is "Quit". The
button is positioned manually at x=20, y=20 coordinates. The beginning of the coordinate system is
at the upper left hand corner.
Connect(wxID_EXIT, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(Button::OnQuit));
button->SetFocus();
We set the keyboard focus to the button. So if we press the Enter key, the button is being clicked.
Close(true);
Inside the OnQuit() method, we call the Close() method. This will terminate our application.
Figure: Button
Widgets communicate
It is important to know, how widgets can communicate in application. Follow the next example.
Panels.h
#include <wx/wx.h>
#include <wx/panel.h>
wxButton *m_plus;
wxButton *m_minus;
wxPanel *m_parent;
int count;
};
wxStaticText *m_text;
};
Panels.cpp
#include <wx/stattext.h>
#include "Communicate.h"
LeftPanel::LeftPanel(wxPanel * parent)
: wxPanel(parent, -1, wxPoint(-1, -1), wxSize(-1, -1), wxBORDER_SUNKEN)
{
count = 0;
m_parent = parent;
m_plus = new wxButton(this, ID_PLUS, wxT("+"),
wxPoint(10, 10));
m_minus = new wxButton(this, ID_MINUS, wxT("-"),
wxPoint(10, 60));
Connect(ID_PLUS, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(LeftPanel::OnPlus));
Connect(ID_MINUS, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(LeftPanel::OnMinus));
}
RightPanel::RightPanel(wxPanel * parent)
: wxPanel(parent, wxID_ANY, wxDefaultPosition,
wxSize(270, 150), wxBORDER_SUNKEN)
{
m_text = new wxStaticText(this, -1, wxT("0"), wxPoint(40, 60));
}
Communicate.h
#include "Panels.h"
#include <wx/wxprec.h>
LeftPanel *m_lp;
RightPanel *m_rp;
wxPanel *m_parent;
};
Communicate.cpp
#include "Communicate.h"
m_parent->SetSizer(hbox);
this->Centre();
}
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "Communicate.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
In our example we have two panels. A left and right panel. The left panel has two buttons. The right
panel has one static text. The buttons change the number displayed in the static text. The question
is, how do we grab the pointer to the static text?
m_parent = parent;
Here we save the pointer to the parent widget of the LeftPanel. It is a wxPanel widget.
These two lines are the most important lines of the example. It is shown, how we get access to the
static text widget, which is placed on a different panel. First we get the parent of the both left and
right panels. This parent widget has a pointer to the right panel. And the right panel has a pointer
to the static text.
In this part of the wxWidgets tutorial, we have created some simple programs.
Menus and toolbars in wxWidgets
A menubar is one of the most visible parts of the GUI application. It is a group of commands
located in various menus. While in console applications you had to remember all those arcane
commands, here we have most of the commands grouped into logical parts. There are accepted
standards that further reduce the amount of time spending to learn a new application. To
implement a menubar in wxWidgets we need to have three classes: a wxMenuBar, a wxMenu, and a
wxMenuItem.
menu.h
#include <wx/wx.h>
#include <wx/menu.h>
wxMenuBar *menubar;
wxMenu *file;
};
menu.cpp
#include "menu.h"
SimpleMenu::SimpleMenu(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(280, 180))
{
Connect(wxID_EXIT, wxEVT_COMMAND_MENU_SELECTED,
wxCommandEventHandler(SimpleMenu::OnQuit));
Centre();
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "menu.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
We append a menu item into the menu object. The first parameter is the id of the menu item. The
second parameter is the name of the menu item. Here we did not create a wxMenuItem explicitly. It
was created by the Append() method behind the scenes. Later on, we will create a wxMenuItem
manually.
menubar->Append(file, wxT("&File"));
SetMenuBar(menubar);
After that, we append a menu into the menubar. The & character creates an accelerator key. The
character that follows the & is underlined. This way the menu is accessible via the Alt+F shortcut.
In the end, we call the SetMenuBar() method. This method belongs to the wxFrame widget. It sets up
the menubar.
Submenus
Each menu can also have a submenu. This way we can group similar commands into groups. For
example we can place commands that hide or show various toolbars like personal bar, address bar,
status bar, or navigation bar into a submenu called toolbars. Within a menu, we can separate
commands with a separator. It is a simple line. It is common practice to separate commands like
new, open, save from commands like print, print preview with a single separator. In our example
we will see, how we can create submenus and menu separators.
menu.h
#include <wx/wx.h>
#include <wx/menu.h>
};
menu.cpp
#include "menu.h"
file->Append(wxID_ANY, wxT("&New"));
file->Append(wxID_ANY, wxT("&Open"));
file->Append(wxID_ANY, wxT("&Save"));
file->AppendSeparator();
file->AppendSubMenu(imp, wxT("I&mport"));
menubar->Append(file, wxT("&File"));
SetMenuBar(menubar);
Connect(wxID_EXIT, wxEVT_COMMAND_MENU_SELECTED,
wxCommandEventHandler(SubMenu::OnQuit));
Centre();
main.h
#include <wx/wx.h>
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
We created one submenu in a file menu. It is an import submenu, which can be seen in Opera web
browser.
file->AppendSeparator();
file->AppendSubMenu(imp, wxT("I&mport"));
Figure: Submenu
Toolbars
Menus group all commands that we can use in an application. Toolbars provide a quick access to
the most frequently used commands.
virtual wxToolBar* CreateToolBar(long style = wxNO_BORDER | wxTB_HORIZONTAL,
wxWindowID id = -1, const wxString& name = "toolBar")
A simple toolbar
toolbar.h
#include <wx/wx.h>
};
toolbar.cpp
#include "toolbar.h"
Connect(wxID_EXIT, wxEVT_COMMAND_TOOL_CLICKED,
wxCommandEventHandler(Toolbar::OnQuit));
Centre();
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "toolbar.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
In our example, we create a toolbar and one tool button. Clicking on the toolbar button will
terminate the application.
We create a toolbar.
toolbar->Realize();
Figure: Toolbar
Toolbars
If we want to have more than one toolbar, we must create them in a different way, e.g. other than
calling the CreateToolbar() method.
toolbars.h
#include <wx/wx.h>
wxToolBar *toolbar1;
wxToolBar *toolbar2;
};
toolbars.cpp
#include "toolbars.h"
vbox->Add(toolbar1, 0, wxEXPAND);
vbox->Add(toolbar2, 0, wxEXPAND);
SetSizer(vbox);
Connect(wxID_EXIT, wxEVT_COMMAND_TOOL_CLICKED,
wxCommandEventHandler(Toolbar::OnQuit));
Centre();
}
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "toolbars.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Toolbar *toolbar = new Toolbar(wxT("Toolbar"));
toolbar->Show(true);
return true;
}
In our example, we create two horizontal toolbars. We place them in a vertical box sizer.
vbox->Add(toolbar1, 0, wxEXPAND);
vbox->Add(toolbar2, 0, wxEXPAND);
In this part of the wxWidgets tutorial, we have covered menus and toolbars.
Layout management in wxWidgets
A typical application consists of various widgets. Those widgets are placed inside container widgets.
A programmer must manage the layout of the application. This is not an easy task.
Absolute positioning.
Sizers.
Absolute Positioning
The programmer specifies the position and the size of each widget in pixels. When we use absolute
positioning, we have to understand several things:
The size and the position of a widget do not change if we resize a window.
Applications look different (often poorly) on various platforms.
Changing fonts in our application might spoil the layout.
If we decide to change our layout, we must completely redo your layout, which is tedious and
time consuming.
There might be situations, where we can possibly use absolute positioning, for example, in simple
tutorials. We do not want to make the examples too difficult, so we often use absolute positioning to
explain a certain topic. But mostly, in real world programs, programmers use sizers.
In our example we have a simple skeleton of a text editor. If we resize the window, the size of out
wxTextCtrl does not change as we would expect.
Figure: Before resizement Figure: After resizement
absolute.h
#include <wx/wx.h>
wxMenuBar *menubar;
wxMenu *file;
wxMenu *edit;
wxMenu *help;
wxTextCtrl *textctrl;
};
absolute.cpp
#include "absolute.h"
menubar->Append(file, wxT("&File"));
menubar->Append(edit, wxT("&Edit"));
menubar->Append(help, wxT("&Help"));
SetMenuBar(menubar);
Centre();
}
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "absolute.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
This is an example, where we use absolute positioning. We position a wxTextCtrl widget on a panel
widget.
We do the absolute positioning in the constructor of the wxTextCtrl widget. In our case, we provide
the default position for the widget. The width is 250px and the height 150px.
Using sizers
Sizers in wxWidgets do address all those issues, we mentioned by absolute positioning. We can
choose among these sizers.
wxBoxSizer
wxStaticBoxSizer
wxGridSizer
wxFlexGridSizer
wxGridBagSizer
Figure: Before resizement Figure: After resizement
sizer.h
#include <wx/wx.h>
wxMenuBar *menubar;
wxMenu *file;
wxMenu *edit;
wxMenu *help;
wxTextCtrl *textctrl;
};
sizer.cpp
#include "sizer.h"
menubar->Append(file, wxT("&File"));
menubar->Append(edit, wxT("&Edit"));
menubar->Append(help, wxT("&Help"));
SetMenuBar(menubar);
Centre();
}
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "sizer.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
The wxTextCtrl is placed inside the wxFrame widget. The wxFrame widget has a special built-in sizer.
We can put only one widget inside the wxFrame container. The child widget occupies all the space,
which is not given to the borders, menu, toolbar, and the statusbar.
wxBoxSizer
This sizer enables us to put several widgets into a row or a column. We can put another sizer into
an existing sizer. This way we can create very complex layouts.
wxBoxSizer(int orient)
wxSizerItem* Add(wxWindow* window, int proportion = 0, int flag = 0, int border = 0)
The orientation can be wxVERTICAL or wxHORIZONTAL. Adding widgets into the wxBoxSizer is done via
the Add() method. In order to understand it, we need to look at its parameters.
The proportion parameter defines the ratio of how will the widgets change in the defined
orientation. Let's assume we have tree buttons with the proportions 0, 1, and 2. They are added into
a horizontal wxBoxSizer. Button with proportion 0 will not change at all. Button with proportion 2
will change twice more than the one with proportion 1 in the horizontal dimension.
With the flag parameter you can further configure the behaviour of the widgets within a
wxBoxSizer. We can control the border between the widgets. We add some space between widgets
in pixels. In order to apply border we need to define sides, where the border will be used. We can
combine them with the | operator, e.g wxLEFT | wxBOTTOM. We can choose between these flags:
wxLEFT
wxRIGHT
wxBOTTOM
wxTOP
wxALL
};
border.cpp
#include "border.h"
Centre();
}
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "border.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
In this example, we create two panels. The second panel has some space around itself.
We have placed a 20 px border around a midPan panel. The wxALL flag applies the border size to all
four sides. If we use wxEXPAND flag, the widget will use all the space that has been allotted to it.
Lastly, we can also define the alignment of our widgets. We do it with the following flags:
wxALIGN_LEFT
wxALIGN_RIGHT
wxALIGN_TOP
wxALIGN_BOTTOM
wxALIGN_CENTER_VERTICAL
wxALIGN_CENTER_HORIZONTAL
wxALIGN_CENTER
Say we wanted to place two buttons into the right bottom of the window.
align.h
#include <wx/wx.h>
};
align.cpp
#include "align.h"
hbox2->Add(ok);
hbox2->Add(cancel);
Centre();
}
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "align.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
We create three sizers. One vertical sizer and two horizontal sizers. We put those two horizontal
sizers into the vertical one.
We put a wxPanel into the first horizontal sizer. We set the proportion to 1 and set a wxEXPAND flag.
This way the sizer will occupy all the space except the hbox2.
We have placed the buttons into the hbox2 sizer. The hbox2 is right aligned and we also put some
space to the bottom and to the right of the buttons.
Go To Class
gotoclass.h
#include <wx/wx.h>
};
gotoclass.cpp
#include "gotoclass.h"
vbox->Add(-1, 10);
hbox2->Add(st2, 0);
vbox->Add(hbox2, 0, wxLEFT | wxTOP, 10);
vbox->Add(-1, 10);
hbox3->Add(tc2, 1, wxEXPAND);
vbox->Add(hbox3, 1, wxLEFT | wxRIGHT | wxEXPAND, 10);
vbox->Add(-1, 25);
hbox4->Add(cb1);
wxCheckBox *cb2 = new wxCheckBox(panel, wxID_ANY,
wxT("Nested Classes"));
vbox->Add(-1, 25);
panel->SetSizer(vbox);
Centre();
}
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "gotoclass.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
GotoClass *gotoclass = new GotoClass(wxT("GotoClass"));
gotoclass->Show(true);
return true;
}
This is a complex example using wxBoxSizer. The layout is straitforward. We create one vertical
sizer. We put then five horizontal sizers into it.
vbox->Add(-1, 25);
We already know that we can control the distance among widgets by combining the flag parameter
with the border parameter. But there is one real constraint. In the Add() method we can specify
only one border for all given sides. In our example, we give 10 px to the right and to the left. But we
cannot give 25 px to the bottom. What we can do is to give 10 px to the bottom, or 0 px. If we omit
wxBOTTOM. So if we need different values, we can add some extra space. With the Add() method, we
We place the two buttons on the right side of the window. How do we do it? Three things are
important to achieve this: the proportion, the align flag, and the wxEXPAND flag. The proportion must
be zero. The buttons should not change their size, when we resize our window. We must not specify
wxEXPAND flag. The buttons occopy only the area that has been alotted to it. And finally, we must
specify the wxALIGN_RIGHT flag. The horizontal sizer spreads from the left side of the window to the
right side. So if we specify wxALIGN_RIGHT flag, the buttons are placed to the right side. Exactly, as
we wanted.
Figure: GotoClass
wxGridSizer
wxGridSizer lays out widgets in two dimensional table. Each cell within the table has the same size.
In the constructor we specify the number of rows and columns in the table. And the vertical and
horizontal space between our cells.
gridsizer.h
#include <wx/wx.h>
wxMenuBar *menubar;
wxMenu *file;
wxBoxSizer *sizer;
wxGridSizer *gs;
wxTextCtrl *display;
};
gridsizer.cpp
#include "gridsizer.h"
SetMenuBar(menubar);
sizer->Add(gs, 1, wxEXPAND);
SetSizer(sizer);
SetMinSize(wxSize(270, 220));
Centre();
}
main.h
#include <wx/wx.h>
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "gridsizer.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
In our example, we set a vertical sizer for a wxFrame. We put a static text and a grid sizer into the
vertical sizer.
Notice how we managed to put a space between the Bck and the Close buttons. We simply put an
empty wxStaticText there.
We call the Add() method multiple times. Widgets are placed inside the table in the order, they are
added. The first row is filled first, then the second row etc.
Figure: GridSizer
wxFlexGridSizer
This sizer is similar to wxGridSizer. It does also lay out its widgets in a two dimensional table. It
adds some flexibility to it. wxGridSizer cells are of the same size. All cells in wxFlexGridSizer have
the same height in a row. All cells have the same width in a column. But all rows and columns are
not necessarily the same height or width.
rows and cols specify the number of rows and columns in a sizer. vgap and hgap add some space
Many times developers have to develop dialogs for data input and modification. I find
wxFlexGridSizer suitable for such a task. A developer can easily set up a dialog window with this
sizer. It is also possible to accomplish this with wxGridSizer, but it would not look nice, because of
the constraint that each cell has the same size.
flexgridsizer.h
#include <wx/wx.h>
};
flexgridsizer.cpp
#include "flexgridsizer.h"
fgs->Add(thetitle);
fgs->Add(tc1, 1, wxEXPAND);
fgs->Add(author);
fgs->Add(tc2, 1, wxEXPAND);
fgs->Add(review, 1, wxEXPAND);
fgs->Add(tc3, 1, wxEXPAND);
fgs->AddGrowableRow(2, 1);
fgs->AddGrowableCol(1, 1);
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "flexgridsizer.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
In our example we create a simple dialog. It could be used to insert data into the database.
We create a horizontal box sizer in order to put some space (15px) around the table of widgets.
fgs->Add(thetitle);
fgs->AddGrowableRow(2, 1);
fgs->AddGrowableCol(1, 1);
We make the third row and the second column growable. This way we make the text controls grow,
when the window is resized. The first two text controls will grow in horizontal direction, the third
one will grow in both direction. We must not forget to make the widgets expandable (wxEXPAND) in
order to make it really work.
Figure: FlexGridSizer
Definitions
Event is a piece of application-level information from the underlying framework, typically the GUI
toolkit. Event loop is a programming construct that waits for and dispatches events or messages in a
program. The event loop repeatedly looks for events to process them. A dispatcher is a process
which maps events to event handlers. Event handlers are methods that react to events.
Event object is an object associated with the event. It is usually a window. Event type is a unique
event that has been generated.
Event table
In the next example, we show an example, where we use event tables.
button.h
#include <wx/wx.h>
private:
DECLARE_EVENT_TABLE()
};
button.cpp
#include "button.h"
Centre();
}
BEGIN_EVENT_TABLE(MyButton, wxFrame)
EVT_BUTTON(wxID_EXIT, MyButton::OnQuit)
END_EVENT_TABLE()
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "button.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
In our example we create a simple button. By clicking on the button, we close the application.
private:
DECLARE_EVENT_TABLE()
In the header file, we declare an event table with the DECLARE_EVENT_TABLE() macro.
BEGIN_EVENT_TABLE(MyButton, wxFrame)
EVT_BUTTON(wxID_EXIT, MyButton::OnQuit)
END_EVENT_TABLE()
We implement an event table by mapping each event to the appropriate member function.
move.h
#include <wx/wx.h>
wxStaticText *st1;
wxStaticText *st2;
};
move.cpp
#include "move.h"
Connect(wxEVT_MOVE, wxMoveEventHandler(Move::OnMove));
Centre();
}
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "move.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Move *move = new Move(wxT("Move event"));
move->Show(true);
return true;
}
Connect(wxEVT_MOVE, wxMoveEventHandler(Move::OnMove));
The event parameter in the OnMove() method is an object specific to a particular event. In our case
it is the instance of a wxMoveEvent class. This object holds information about the event. We can find
out the current position by calling the GetPosition() method of the event.
Figure: Move event
Event propagation
There are two types of events: basic events and command events. They differ in propagation. Event
propagation is travelling of events from child widgets to parent widgets and grand parent widgets
etc. Basic events do not propagate. Command events do propagate. For example wxCloseEvent is a
basic event. It does not make sense for this event to propagate to parent widgets.
By default, the event that is caught in a event handler stops propagating. To continue propagation,
we must call the Skip() method.
propagate.h
#include <wx/wx.h>
};
};
propagate.cpp
#include <iostream>
#include "propagate.h"
Connect(ID_BUTTON, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(Propagate::OnClick));
Centre();
}
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "propagate.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
In our example, we have a button on a panel. The panel is placed in a frame widget. We define a
handler for all widgets.
We get this, when we click on the button. The event travels from the button to panel and to frame.
Vetoing events
Sometimes we need to stop processing an event. To do this, we call the method Veto().
veto.h
#include <wx/wx.h>
};
veto.cpp
#include "veto.h"
Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(Veto::OnClose));
Centre();
}
if (ret == wxID_YES) {
Destroy();
} else {
event.Veto();
}
}
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "veto.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
In our example, we process a wxCloseEvent. This event is called, when we click the X button on the
titlebar, press Alt+F4 or select close from the system menu. In many applications, we want to
prevent from accidentally closing the window if we made some changes. To do this, we must
connect the wxEVT_CLOSE_WINDOW event type.
if (ret == wxID_YES) {
Destroy();
} else {
event.Veto();
}
Depending on the return value, we destroy the window, or veto the event. Notice that to close the
window, we must call the Destroy() method. By calling the Close() method, we would end up in an
endless cycle.
Window identifiers
Window identifiers are integers that uniquely determine the window identity in the event system.
There are three ways to create a window id:
Each widget has an id parameter. This is a unique number in the event system. If we work with
multiple widgets, we must differantiate among them.
wxButton(parent, -1)
wxButton(parent, wxID_ANY)
If we provide -1 or wxID_ANY for the ID parameter, we let wxWidgets automatically create an id for
us. The automatically created id's are always negative, whereas user specified IDs must always be
positive. We usually use this option when we do not need to change the widget state. For example a
static text that will never be changed during the life of the application. We can still get the ID if we
want. There is a method GetId(), which will determine the id for us.
Standard identifiers should be used whenever possible. The identifiers can provide some standard
graphics or behaviour on some platforms.
ident.h
#include <wx/wx.h>
};
ident.cpp
#include "ident.h"
panel->SetSizer(grid);
Centre();
}
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "ident.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
In our example we use standard identifiers on buttons. On Linux, the buttons have small icons.
Figure: Identifiers
There are essentially two types of dialogs: predefined dialogs and custom dialogs.
Predefined dialogs
Predefined dialogs are dialogs available in the wxWidgets toolkit. These are dialogs for common
programming tasks like showing text, receiving input, loading and saving files etc. They save
programmer's time and enhance using some standard behaviour.
Message dialogs
Message dialogs are used to show messages to the user. They are customisable. We can change
icons and buttons that will be shown in a dialog.
Messages.h
#include <wx/wx.h>
};
Messages.cpp
#include "Messages.h"
Connect(ID_INFO, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(Messages::ShowMessage1));
Connect(ID_ERROR, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(Messages::ShowMessage2));
Connect(ID_QUESTION, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(Messages::ShowMessage3));
Connect(ID_ALERT, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(Messages::ShowMessage4));
gs->Add(btn1, 1, wxEXPAND);
gs->Add(btn2, 1);
gs->Add(btn3, 1);
gs->Add(btn4, 1);
Center();
}
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "Messages.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
In our example, we have created four buttons and put them in a grid sizer. These buttons will show
four different dialog windows. We create them by specifying different style flags.
The creation of the message dialog is simple. We set the dialog to be a toplevel window by
providing NULL as a parent. The two strings provide the message text and the dialog title. We show
an OK button and an error icon by specifying the wxOK and wxICON_ERROR flags. To show the dialog
on screen, we call the ShowModal() method.
wxFileDialog
This is a common dialog for opening and saving files.
openfile.h
#include <wx/wx.h>
wxTextCtrl *tc;
};
openfile.cpp
#include "openfile.h"
Connect(wxID_OPEN, wxEVT_COMMAND_MENU_SELECTED,
wxCommandEventHandler(Openfile::OnOpen));
Center();
if (openFileDialog->ShowModal() == wxID_OK){
wxString fileName = openFileDialog->GetPath();
tc->LoadFile(fileName);
}
}
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "openfile.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
In our example, we display a open file menu item and a simple multiline text control. If we click on
the open file menu item a wxFileDialog is displayed. We can load some simple text files into the
text control.
Here we create a wxFileDialog. We use the default parameters. (The open file dialog is the default
dialog.)
if (openFileDialog->ShowModal() == wxID_OK){
wxString fileName = openFileDialog->GetPath();
tc->LoadFile(fileName);
}
Here we show the dialog. We get the selected file name and load the file into the text control.
wxFontDialog
fontdialog.h
#include <wx/wx.h>
wxStaticText *st;
};
fontdialog.cpp
#include <wx/fontdlg.h>
#include "fontdialog.h"
Connect(ID_FONTDIALOG, wxEVT_COMMAND_MENU_SELECTED,
wxCommandEventHandler(ChangeFont::OnOpen));
Center();
if (fontDialog->ShowModal() == wxID_OK) {
st->SetFont(fontDialog->GetFontData().GetChosenFont());
}
main.h
#include <wx/wx.h>
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
Here we display a static text on the panel. We will change its font using the wxFontDialog.
if (fontDialog->ShowModal() == wxID_OK) {
st->SetFont(fontDialog->GetFontData().GetChosenFont());
}
In these code lines, we show the font dialog. Then we get the choosen font. And finally, we change
the font of the static text, we created earlier.
customdialog.h
#include <wx/wx.h>
};
customdialog.cpp
#include "customdialog.h"
hbox->Add(okButton, 1);
hbox->Add(closeButton, 1, wxLEFT, 5);
vbox->Add(panel, 1);
vbox->Add(hbox, 0, wxALIGN_CENTER | wxTOP | wxBOTTOM, 10);
SetSizer(vbox);
Centre();
ShowModal();
Destroy();
}
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "customdialog.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
This example is a dialog based application. We illustrate, how to create a custom dialog.
Note that wxStaticBox widget must be created before the widgets that it contains, and that those
widgets should be siblings, not children, of the static box.
ShowModal();
Destroy();
To show the dialog on the screen, we call the ShowModal() method. To clear the dialog from the
memory, we call the Destroy() method.
Figure: Custom dialog
wxCheckBox
wxCheckBox is a widget that has two states: on and off. It is a box with a label. The label can be set to
the right or to the left of the box. If the checkbox is checked, it is represented by a tick in a box. A
checkbox can be used to show or hide a splashscreen at startup, toggle visibility of a toolbar etc.
checkbox.h
#include <wx/wx.h>
wxCheckBox *m_cb;
};
checkbox.cpp
#include "checkbox.h"
CheckBox::CheckBox(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(270, 150))
{
wxPanel *panel = new wxPanel(this, wxID_ANY);
if (m_cb->GetValue()) {
this->SetTitle(wxT("CheckBox"));
} else {
this->SetTitle(wxT(" "));
}
}
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "checkbox.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
In our example, we display one checkbox on the window. We toggle the title of the window by
clicking on the checkbox.
Connect(ID_CHECKBOX, wxEVT_COMMAND_CHECKBOX_CLICKED,
wxCommandEventHandler(CheckBox::OnToggle));
if (m_cb->GetValue()) {
this->SetTitle(wxT("CheckBox"));
} else {
this->SetTitle(wxT(" "));
}
Inside the OnToggle() method, we check the state of the checkbox. If it is checked, we display
"CheckBox" string in the titlebar, otherwise we clear the title.
Figure: wxCheckBox
wxBitmapButton
A bitmap button is a button that displays a bitmap. A bitmap button can have three other states.
Selected, focused and displayed. We can set a specific bitmap for those states.
bitmapbutton.h
#include <wx/wx.h>
#include <wx/slider.h>
wxSlider *slider;
wxBitmapButton *button;
int pos;
};
const int ID_SLIDER = 100;
bitmapbutton.cpp
#include "bitmapbutton.h"
Connect(ID_SLIDER, wxEVT_COMMAND_SLIDER_UPDATED,
wxScrollEventHandler(BitmapButton::OnScroll));
Center();
}
if (pos == 0) {
button->SetBitmapLabel(wxBitmap(wxT("mute.png"), wxBITMAP_TYPE_PNG));
} else if (pos > 0 && pos <= 30 ) {
button->SetBitmapLabel(wxBitmap(wxT("min.png"), wxBITMAP_TYPE_PNG));
} else if (pos > 30 && pos < 80 ) {
button->SetBitmapLabel(wxBitmap(wxT("med.png"), wxBITMAP_TYPE_PNG));
} else {
button->SetBitmapLabel(wxBitmap(wxT("max.png"), wxBITMAP_TYPE_PNG));
}
}
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "bitmapbutton.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
In our example, we have a slider and a bitmap button. We simulate a volume control. By dragging
the handle of a slider, we change a bitmap on the button.
pos = slider->GetValue();
We get the slider value. Depending on this value, we set a bitmap for our button. We have four
volume states: mute, minimum, medium, and maximum. To change a bitmap on the button, we call
the SetBitmapLabel() method.
Figure: wxBitmapButton
wxToggleButton
A wxToggleButton is a button that has two states: pressed and not pressed. You toggle between
these two states by clicking on it. There are situations where this functionality fits well.
togglebutton.h
#include <wx/wx.h>
#include <wx/tglbtn.h>
protected:
wxToggleButton *m_tgbutton1;
wxToggleButton *m_tgbutton2;
wxToggleButton *m_tgbutton3;
wxPanel *m_panel;
wxColour *colour;
};
togglebutton.cpp
#include "togglebutton.h"
Connect(ID_TGBUTTON1, wxEVT_COMMAND_TOGGLEBUTTON_CLICKED,
wxCommandEventHandler(ToggleButton::OnToggleRed));
Connect(ID_TGBUTTON2, wxEVT_COMMAND_TOGGLEBUTTON_CLICKED,
wxCommandEventHandler(ToggleButton::OnToggleGreen));
Connect(ID_TGBUTTON3, wxEVT_COMMAND_TOGGLEBUTTON_CLICKED,
wxCommandEventHandler(ToggleButton::OnToggleBlue));
} else {
colour->Set(255, green, blue);
}
m_panel->SetBackgroundColour(colour->GetAsString());
if ( colour->Green() ) {
colour->Set(red, 0, blue);
} else {
colour->Set(red, 255, blue);
}
m_panel->SetBackgroundColour(colour->GetAsString());
if ( colour->Blue() ) {
colour->Set(red, green, 0);
} else {
colour->Set(red, green, 255);
}
m_panel->SetBackgroundColour(colour->GetAsString());
}
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "togglebutton.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
button->Centre();
button->Show(true);
return true;
}
In our example, we show three toggle buttons and a panel. We set the background colour of the
panel to black. The togglebuttons will toggle the red, green, and blue parts of the colour value. The
background colour will depend on which togglebuttons we have pressed.
This is the initial colour value. No red, green and blue equals to black. Theoretically speaking, black
is not a color after all.
Connect(ID_TGBUTTON1, wxEVT_COMMAND_TOGGLEBUTTON_CLICKED,
wxCommandEventHandler(ToggleButton::OnToggleRed));
if ( colour->Blue() ) {
colour->Set(red, green, 0);
} else {
colour->Set(red, green, 255);
}
m_panel->SetBackgroundColour(colour->GetAsString());
We set the background of the panel.
Figure: wxToggleButton
wxStaticLine
This widget displays a simple line on the window. It can be horizontal or vertical.
staticline.h
#include <wx/wx.h>
};
staticline.cpp
#include "staticline.h"
#include <wx/stattext.h>
#include <wx/statline.h>
this->Centre();
}
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "staticline.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Staticline *sl = new Staticline(wxT("The Central Europe"));
sl->ShowModal();
sl->Destroy();
return true;
}
In the previous example, we show Centreal European countries and their populations. The use of
wxStaticLine makes it more visually attractive.
Here we create a horizontal static line. It is 300 px wide. The height is 1 px.
Figure: wxStaticLine
wxStaticText
A wxStaticText widget displays one or more lines of read-only text.
statictext.h
#include <wx/wx.h>
statictext.cpp
#include "statictext.h"
this->SetSize(600, 110);
this->Centre();
}
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "statictext.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
In our example, we display a part of the Eminem's Till I Collapse lyrics on the window.
wxStaticText *st = new wxStaticText(panel, wxID_ANY, text,
wxPoint(10, 10), wxDefaultSize, wxALIGN_CENTRE);
Here we create the wxStaticText widget. The static text is aligned to the cetre.
Figure: wxStaticText
wxSlider
A wxSlider is a widget that has a simple handle. This handle can be pulled back and forth. This way
we are choosing a value for a specific task. Sometimes using a slider is more natural than simply
providing a number or using a spin control.
Slider.h
#include <wx/wx.h>
#include <wx/slider.h>
wxSlider *slider;
int fill;
};
MyPanel *panel;
};
Slider.cpp
#include "Slider.h"
Slider::Slider(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition,
wxSize(270, 200))
{
panel = new MyPanel(this);
Center();
}
MyPanel::MyPanel(wxFrame * parent)
: wxPanel(parent, wxID_ANY)
{
fill = 0;
slider = new wxSlider(this, ID_SLIDER, 0, 0, 140, wxPoint(50, 30),
wxSize(-1, 140), wxSL_VERTICAL);
Connect(ID_SLIDER, wxEVT_COMMAND_SLIDER_UPDATED,
wxScrollEventHandler(MyPanel::OnScroll));
Connect(wxEVT_PAINT, wxPaintEventHandler(MyPanel::OnPaint));
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "Slider.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
In our example, we display a slider widget. By pulling the handle of the slider, we control the
background color of the panel. In such an example, using slider is more natural than using e.g. a
spin control.
We create a vertical slider. The initial value is 0, minimal value is 0 and maximal value is 140. We
display no ticks and no labels.
Connect(ID_SLIDER, wxEVT_COMMAND_SLIDER_UPDATED,
wxScrollEventHandler(MyPanel::OnScroll));
Connect(wxEVT_PAINT, wxPaintEventHandler(MyPanel::OnPaint));
We will also do some drawing, so we connect OnPaint() method to the wxEVT_PAINT event.
fill = slider->GetValue();
Refresh();
In the OnScroll() method, we will get the current slider value. We call the Refresh() method,
which will generate a wxEVT_PAINT event.
Inside the OnPaint() event handler, we draw two rectangles. The first method is draws a white
rectangle with a gray border. The second method draws the a rectangle with some brownish color.
The height of the rectangle is controled by the fill value, which is set by the slider widget.
Figure: wxSlider
wxListBox
A wxListBox widget is used for displaying and working with a list of items. As its name indicates, it
is a rectangle that has a list of strings inside. We could use it for displaying a list of MP3 files, book
names, module names of a larger project or names of our friends. A wxListBox can be created in
two different states. In a single selection state or a multiple selection state. The single selection state
is the default state. There are two significant events in wxListBox. The first one is the
wxEVT_COMMAND_LISTBOX_SELECTED event. This event is generated when we select a string in a
when we double click an item in a wxListBox. The number of elements inside a wxListBox is limited
on GTK platform. According to the documentation, it is currently around 2000 elements. The
elements are numbered from zero. Scrollbars are displayed automatically if needed.
Listbox.h
#include <wx/wx.h>
#include <wx/listbox.h>
wxListBox *m_lb;
wxButton *m_newb;
wxButton *m_renameb;
wxButton *m_clearb;
wxButton *m_deleteb;
};
wxListBox *listbox;
MyPanel *btnPanel;
};
Listbox.cpp
#include "listbox.h"
#include <wx/textdlg.h>
Connect(wxEVT_COMMAND_LISTBOX_DOUBLECLICKED,
wxCommandEventHandler(Listbox::OnDblClick));
panel->SetSizer(hbox);
Center();
}
MyPanel::MyPanel(wxPanel * parent)
: wxPanel(parent, wxID_ANY)
{
wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL);
Connect(wxID_NEW, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(MyPanel::OnNew) );
Connect(ID_RENAME, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(MyPanel::OnRename) );
Connect(wxID_CLEAR, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(MyPanel::OnClear) );
Connect(wxID_DELETE, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(MyPanel::OnDelete) );
vbox->Add(-1, 20);
vbox->Add(m_newb);
vbox->Add(m_renameb, 0, wxTOP, 5);
vbox->Add(m_deleteb, 0, wxTOP, 5);
vbox->Add(m_clearb, 0, wxTOP, 5);
SetSizer(vbox);
}
if (!renamed.IsEmpty()) {
m_lb->Delete(sel);
m_lb->Insert(renamed, sel);
}
}
void MyPanel::OnDelete(wxCommandEvent& event)
{
int sel = m_lb->GetSelection();
if (sel != -1) {
m_lb->Delete(sel);
}
}
if (!renamed.IsEmpty()) {
listbox->Delete(sel);
listbox->Insert(renamed, sel);
}
}
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "Listbox.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
In our example, we have a list box and four buttons. The buttons are used to add, rename, delete
and clear all items in the listbox.
To add a new string to the listbox, we display a wxGetTextFromUser dialog. We call the Append()
method to append string to the listbox.
m_lb->Clear();
To clear all items is the easiest action to do. We just call the Clear() method.
To delete an item, we figure out the selected item. Then we call the Delete() method.
wxString text;
wxString renamed;
if (!renamed.IsEmpty()) {
m_lb->Delete(sel);
m_lb->Insert(renamed, sel);
}
We check whether the renamed variable is empty. This is to avoid inserting empty strings. Then we
delete the old item and insert a new one.
Figure: Listbox
wxNotebook
The wxNotebook widget joins multiple windows with corresponding tabs. You can position the
Notebook widget using the following style flags:
wxNB_LEFT
wxNB_RIGHT
wxNB_TOP
wxNB_BOTTOM
Notebook.h
#include <wx/wx.h>
#include <wx/notebook.h>
#include <wx/grid.h>
};
};
Notebook.cpp
#include "Notebook.h"
Notebook::Notebook(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(400, 350))
{
Connect(wxEVT_COMMAND_MENU_SELECTED,
wxCommandEventHandler(Notebook::OnQuit));
nb->AddPage(grid1, wxT("Sheet1"));
nb->AddPage(grid2, wxT("Sheet2"));
nb->AddPage(grid3, wxT("Sheet3"));
CreateStatusBar();
Center();
}
MyGrid::MyGrid(wxNotebook * parent)
: wxGrid(parent, wxID_ANY)
{
CreateGrid(30, 30);
SetRowLabelSize(50);
SetColLabelSize(25);
SetRowLabelAlignment(wxALIGN_RIGHT, wxALIGN_CENTRE);
SetLabelFont(wxFont(9, wxFONTFAMILY_DEFAULT,
wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD));
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "Notebook.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
In this example, we have created a notebook widget with three grids. The notebook widget is
positioned at the bottom.
nb->AddPage(grid1, wxT("Sheet1"));
nb->AddPage(grid2, wxT("Sheet2"));
nb->AddPage(grid3, wxT("Sheet3"));
scrolledwindow.h
#include <wx/wx.h>
};
scrolledwindow.cpp
#include "scrolledwindow.h"
Center();
}
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "scrolledwindow.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
wxImage::AddHandler(new wxJPEGHandler);
sw->Scroll(50,10);
In drag and drop we basically drag some data from a data source to a data target. We deal with the
following objects:
Data object
Data source
Data target
For drag & drop of text, wxWidgets has a predefined wxTextDropTarget class.
In the following example, we drag and drop file names from the upper list control to the bottom
one.
textdrop.h
#include <wx/wx.h>
#include <wx/dnd.h>
};
wxListCtrl *m_owner;
};
textdrop.cpp
#include "textdrop.h"
#include <wx/treectrl.h>
#include <wx/dirctrl.h>
#include <wx/dir.h>
#include <wx/splitter.h>
Connect(m_lc1->GetId(), wxEVT_COMMAND_LIST_BEGIN_DRAG,
wxListEventHandler(TextDrop::OnDragInit));
spl2->SplitHorizontally(m_lc1, m_lc2);
spl1->SplitVertically(m_gdir, spl2);
Connect(tree->GetId(), wxEVT_COMMAND_TREE_SEL_CHANGED,
wxCommandEventHandler(TextDrop::OnSelect));
Center();
}
MyTextDropTarget::MyTextDropTarget(wxListCtrl *owner)
{
m_owner = owner;
}
m_owner->InsertItem(0, data);
return true;
int i = 0;
m_lc1->ClearAll();
m_lc2->ClearAll();
while ( cont )
{
m_lc1->InsertItem(i, filename);
cont = dir.GetNext(&filename);
i++;
}
}
wxTextDataObject tdo(text);
wxDropSource tds(tdo, m_lc1);
tds.DoDragDrop(wxDrag_CopyOnly);
main.h
#include <wx/wx.h>
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
In our example, we have a window separated into three parts. This is done by the wxSplitterWindow
widget. In the left part of the window, we have a generic directory control. We display all
directories available under our filesystem. In the right part there are two windows. The first
displays all files under a selected directory. The second one is used for dragging the files.
wxTextDataObject tdo(text);
wxDropSource tds(tdo, m_lc1);
tds.DoDragDrop(wxDrag_CopyOnly);
In the OnDragInit() method, we define a text data object and a drop source object. We call the
DoDragDrop() method. The wxDrag_CopyOnly constant allows only copying of data.
During the dropping operation, we insert the text data into the list control.
Figure: Drag & Drop
From the programmer's point of view, the GDI is a group of classes and methods for working with
graphics. The GDI consists of 2D Vector Graphics, Fonts, and Images.
To begin drawing graphics, we must create a device context (DC) object. In wxWidgets the device
context is called wxDC. The documentation defines wxDC as a device context onto which which
graphics and text can be drawn. It represents number of devices in a generic way. Same piece of
code can write to different kinds of devices. Be it a screen or a printer. The wxDC is not intended to
be used directly. Instead a programmer should choose one of the derived classes. Each derived
class is intended to be used under specific conditions.
wxBufferedPaintDC
wxPostScriptDC
wxMemoryDC
wxPrinterDC
wxScreenDC
wxClientDC
wxPaintDC
wxWindowDC
The wxScreenDC is used to draw anywhere on the screen. The wxWindowDC is used if we want to paint
on the whole window (Windows only). This includes window decorations. The wxClientDC is used
to draw on the client area of a window. The client area is the area of a window without its
decorations (title and border). The wxPaintDC is used to draw on the client area as well. But there is
one difference between the wxPaintDC and the wxClientDC. The wxPaintDC should be used only from
a wxPaintEvent. The wxClientDC should not be used from a wxPaintEvent. The wxMemoryDC is used
to draw graphics on the bitmap. The wxPostScriptDC is used to write to PostScript files on any
platform. The wxPrinterDC is used to access a printer (Windows only).
Simple line
line.h
#include <wx/wx.h>
};
line.cpp
#include "line.h"
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "line.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
In our example, we draw a simple line onto the client area of the window. If we resize the window,
it is redrawn. An wxPaintEvent is generated. And the line is drawn again.
this->Connect(wxEVT_PAINT, wxPaintEventHandler(Line::OnPaint));
We connect a paint event to the OnPaint() method. All the drawing happens inside the OnPaint()
event handler.
wxPaintDC dc(this);
We define a wxPaintDC device context. It is a device context, that is used to draw on the window
inside the wxPaintEvent
Drawing text
Drawing some text on the window is easy.
text.h
#include <wx/wx.h>
};
text.cpp
#include "text.h"
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "text.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
In our example, we draw text Lev Nikolayevich Tolstoy, Anna Karenina in Russian azbuka onto the
window.
The DrawText() method draws text on the window. It Draws a text string at the specified point,
using the current text font, and the current text foreground and background colours. Thanks to the
wxT() macro, we can use azbuka directly in the code. The wxT() macro is identical to _T() macro. It
wraps string literals for use with or without Unicode. When Unicode is not enabled, wxT() is an
empty macro. When Unicode is enabled, it adds the necessary L for the string literal to become a
wide character string constant.
Figure: Drawing text
Point
DrawPoint(int x, int y)
point.h
#include <wx/wx.h>
};
points.cpp
#include "points.h"
#include <stdlib.h>
#include <time.h>
this->Connect(wxEVT_PAINT, wxPaintEventHandler(Points::OnPaint));
srand(time(NULL));
this->Centre();
}
wxCoord x = 0;
wxCoord y = 0;
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "points.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
A single point might be difficult to see. So we create 1000 points. Each time the window is resized,
we draw the 1000 points over the client area of the window.
x = rand() % size.x + 1;
Pen
Pen is an elementary graphics object. It is used to draw lines, curves and outlines of rectangles,
ellipses, polygons, or other shapes.
The wxPen constructor has three parameters: the colour, width and style. The following is a list of
possible pen styles:
wxSOLID
wxDOT
wxLONG_DASH
wxSHORT_DASH
wxDOT_DASH
wxTRANSPARENT
pen.h
#include <wx/wx.h>
};
pen.cpp
#include "pen.h"
col1.Set(wxT("#0c0c0c"));
col2.Set(wxT("#000000"));
dc.SetPen(wxPen(col1, 1, wxSOLID));
dc.DrawRectangle(10, 15, 90, 60);
dc.SetPen(wxPen(col1, 1, wxDOT));
dc.DrawRectangle(130, 15, 90, 60);
dc.SetPen(wxPen(col1, 1, wxLONG_DASH));
dc.DrawRectangle(250, 15, 90, 60);
dc.SetPen(wxPen(col1, 1, wxSHORT_DASH));
dc.DrawRectangle(10, 105, 90, 60);
dc.SetPen(wxPen(col1, 1, wxDOT_DASH));
dc.DrawRectangle(130, 105, 90, 60);
dc.SetPen(wxPen(col1, 1, wxTRANSPARENT));
dc.DrawRectangle(250, 105, 90, 60);
}
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "pen.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Pen *pen = new Pen(wxT("Pen"));
pen->Show(true);
return true;
}
In our example, we draw 6 rectangles with different pen styles. The last one is transparent, not
visible.
dc.SetPen(wxPen(col1, 1, wxSOLID));
dc.DrawRectangle(10, 15, 90, 60);
Here we define a pen for our first rectangle. We set a pen with color col1 (#0c0c0c), 1 pixel wide,
solid. The DrawRectangle() method draws the rectangle.
Figure: Pen
Regions
Regions can be combined to create more complex shapes. We can use four set operations: Union(),
Intersect(), Substract() and Xor(). The following example shows all four operations in action.
Regions.h
#include <wx/wx.h>
};
Regions.cpp
#include "Regions.h"
gray.Set(wxT("#d4d4d4"));
white.Set(wxT("#ffffff"));
red.Set(wxT("#ff0000"));
orange.Set(wxT("#fa8e00"));
green.Set(wxT("#619e1b"));
brown.Set(wxT("#715b33"));
blue.Set(wxT("#0d0060"));
dc.SetPen(wxPen(gray));
dc.SetBrush(wxBrush(white));
dc.DrawRectangle(100, 20, 50, 50);
dc.DrawRectangle(110, 40, 50, 50);
wxRegion region1(100, 20, 50, 50);
wxRegion region2(110, 40, 50, 50);
region1.Intersect(region2);
wxRect rect1 = region1.GetBox();
dc.SetClippingRegion(region1);
dc.SetBrush(wxBrush(red));
dc.DrawRectangle(rect1);
dc.DestroyClippingRegion();
dc.SetBrush(wxBrush(white));
dc.DrawRectangle(180, 20, 50, 50);
dc.DrawRectangle(190, 40, 50, 50);
wxRegion region3(180, 20, 50, 50);
wxRegion region4(190, 40, 50, 50);
region3.Union(region4);
dc.SetClippingRegion(region3);
wxRect rect2 = region3.GetBox();
dc.SetBrush(wxBrush(orange));
dc.DrawRectangle(rect2);
dc.DestroyClippingRegion();
dc.SetBrush(wxBrush(white));
dc.DrawRectangle(20, 120, 50, 50);
dc.DrawRectangle(30, 140, 50, 50);
wxRegion region5(20, 120, 50, 50);
wxRegion region6(30, 140, 50, 50);
region5.Xor(region6);
wxRect rect3 = region5.GetBox();
dc.SetClippingRegion(region5);
dc.SetBrush(wxBrush(green));
dc.DrawRectangle(rect3);
dc.DestroyClippingRegion();
dc.SetBrush(wxBrush(white));
dc.DrawRectangle(100, 120, 50, 50);
dc.DrawRectangle(110, 140, 50, 50);
wxRegion region7(100, 120, 50, 50);
wxRegion region8(110, 140, 50, 50);
region7.Subtract(region8);
wxRect rect4 = region7.GetBox();
dc.SetClippingRegion(region7);
dc.SetBrush(wxBrush(brown));
dc.DrawRectangle(rect4);
dc.DestroyClippingRegion();
dc.SetBrush(white);
dc.DrawRectangle(180, 120, 50, 50);
dc.DrawRectangle(190, 140, 50, 50);
wxRegion region9(180, 120, 50, 50);
wxRegion region10(190, 140, 50, 50);
region10.Subtract(region9);
wxRect rect5 = region10.GetBox();
dc.SetClippingRegion(region10);
dc.SetBrush(wxBrush(blue));
dc.DrawRectangle(rect5);
dc.DestroyClippingRegion();
}
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "Regions.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Regions *regions = new Regions(wxT("Regions"));
regions->Show(true);
return true;
}
Figure: Regions
Gradient
In computer graphics, gradient is a smooth blending of shades from light to dark or from one color
to another. In 2D drawing programs and paint programs, gradients are used to create colorful
backgrounds and special effects as well as to simulate lights and shadows. (answers.com)
This method fills the area specified by a rect with a linear gradient, starting from initialColour
and eventually fading to destColour. The nDirection parameter specifies the direction of the
colour change, the default value is wxEAST.
gradient.h
#include <wx/wx.h>
};
gradient.cpp
#include "gradient.h"
col1.Set(wxT("#e12223"));
col2.Set(wxT("#000000"));
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "gradient.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
Gradient *grad = new Gradient(wxT("Gradient"));
grad->Show(true);
return true;
}
Figure: Gradient
Shapes
Shapes are more sophisticated geometrical objects. We will draw various geometrical shapes in the
following example.
shapes.h
#include <wx/wx.h>
};
shapes.cpp
#include "shapes.h"
dc.DrawPolygon(4, polygon);
dc.DrawRectangle(20, 120, 80, 50);
dc.DrawSpline(4, splines);
dc.DrawLines(4, lines);
dc.DrawCircle(170, 230, 35);
dc.DrawRectangle(250, 200, 60, 60);
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "shapes.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
Figure: Shapes
Custom widgets are created by using the drawing tools provided by the toolkit. There are two
possibilities: a programmer can modify or enhance an existing widget, or he can create a custom
widget from scratch.
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <wx/wx.h>
wxPanel *m_parent;
};
#endif
widget.cpp
#include <wx/wx.h>
#include "widget.h"
#include "burning.h"
int num[] = { 75, 150, 225, 300, 375, 450, 525, 600, 675 };
int asize = sizeof(num)/sizeof(num[1]);
m_parent = parent;
Connect(wxEVT_PAINT, wxPaintEventHandler(Widget::OnPaint));
Connect(wxEVT_SIZE, wxSizeEventHandler(Widget::OnSize));
wxPaintDC dc(this);
dc.SetFont(font);
wxSize size = GetSize();
int width = size.GetWidth();
} else {
burning.h
#ifndef BURNING_H
#define BURNING_H
#include <wx/wx.h>
#include "widget.h"
wxSlider *m_slider;
Widget *m_wid;
int cur_width;
};
#endif
burning.cpp
#include "burning.h"
#include "widget.h"
int ID_SLIDER = 1;
cur_width = 75;
wxPanel *panel = new wxPanel(this, wxID_ANY);
wxPanel *centerPanel = new wxPanel(panel, wxID_ANY);
hbox2->Add(centerPanel, 1, wxEXPAND);
hbox3->Add(m_slider, 0, wxTOP | wxLEFT, 35);
centerPanel->SetSizer(hbox3);
vbox->Add(hbox2, 1, wxEXPAND);
vbox->Add(hbox, 0, wxEXPAND);
panel->SetSizer(vbox);
m_slider->SetFocus();
Connect(ID_SLIDER, wxEVT_COMMAND_SLIDER_UPDATED,
wxScrollEventHandler(Burning::OnScroll));
Centre();
int Burning::GetCurWidth()
{
return cur_width;
}
main.h
#include <wx/wx.h>
main.cpp
#include "main.h"
#include "burning.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
return true;
}
We put a wxPanel on the bottom of the window and draw the entire widget manually. All the
important code resides in the OnPaint() method of the widget class. This widget shows graphically
the total capacity of a medium and the free space available to us. The widget is controlled by a
slider. The minimum value of our custom widget is 0, the maximum is 750. If we reach value 700,
we began drawing in red colour. This indicates overburning.
We draw the widget dynamically. The greater the window, the greater the burning widget. And vice
versa. That is why we must calculate the size of the wxPanel onto which we draw the custom widget.
The till parameter determines the total size to be drawn. This value comes from the slider widget.
It is a proportion of the whole area. The full parameter determines the point, where we begin to
draw in red colour. Notice the use of floating point arithmetics. This is to achieve greater precision.
The actual drawing consists of three steps. We draw the yellow or red and yellow rectangle. Then
we draw the vertical lines, which divide the widget into several parts. Finally, we draw the
numbers, which indicate the capacity of the medium.
Every time the window is resized, we refresh the widget. This causes the widget to repaint itself.
If we scroll the thumb of the slider, we get the actual value and save it into the cur_width variable.
This value is used, when the burning widget is drawn. Then we cause the widget to be redrawn.
Figure: Burning Widget
Tetris is called a falling block puzzle game. In this game, we have seven different shapes called
tetrominoes. S-shape, Z-shape, T-shape, L-shape, Line-shape, MirroredL-shape, and a Square-
shape. Each of these shapes is formed with four squares. The shapes are falling down the board.
The object of the Tetris game is to move and rotate the shapes, so that they fit as much as possible.
If we manage to form a row, the row is destroyed and we score. We play the Tetris game until we
top out.
Figure: Tetrominoes
wxWidgets is a toolkit designed to create applications. There are other libraries which are targeted
at creating computer games. Nevertheless, wxWidgets and other application toolkits can be used to
create simple games.
The development
We do not have images for our Tetris game, we draw the tetrominoes using the drawing API
available in the wxWidgets programming toolkit. Behind every computer game, there is a
mathematical model. So it is in Tetris.
Some ideas behind the game.
Shape.h
#ifndef SHAPE_H
#define SHAPE_H
class Shape
{
public:
Shape() { SetShape(NoShape); }
void SetShape(Tetrominoes shape);
void SetRandomShape();
private:
void SetX(int index, int x) { coords[index][0] = x; }
void SetY(int index, int y) { coords[index][1] = y; }
Tetrominoes pieceShape;
int coords[4][2];
};
#endif
Shape.cpp
#include <stdlib.h>
#include <algorithm>
#include "Shape.h"
void Shape::SetRandomShape()
{
int x = rand() % 7 + 1;
SetShape(Tetrominoes(x));
}
Shape result;
result.pieceShape = pieceShape;
for (int i = 0; i < 4; ++i) {
result.SetX(i, y(i));
result.SetY(i, -x(i));
}
return result;
}
Shape result;
result.pieceShape = pieceShape;
for (int i = 0; i < 4; ++i) {
result.SetX(i, -y(i));
result.SetY(i, x(i));
}
return result;
}
Board.h
#ifndef BOARD_H
#define BOARD_H
#include "Shape.h"
#include <wx/wx.h>
public:
Board(wxFrame *parent);
void Start();
void Pause();
void linesRemovedChanged(int numLines);
protected:
void OnPaint(wxPaintEvent& event);
void OnKeyDown(wxKeyEvent& event);
void OnTimer(wxCommandEvent& event);
private:
enum { BoardWidth = 10, BoardHeight = 22 };
wxTimer *timer;
bool isStarted;
bool isPaused;
bool isFallingFinished;
Shape curPiece;
int curX;
int curY;
int numLinesRemoved;
Tetrominoes board[BoardWidth * BoardHeight];
wxStatusBar *m_stsbar;
};
#endif
Board.cpp
#include "Board.h"
Board::Board(wxFrame *parent)
: wxPanel(parent, wxID_ANY, wxDefaultPosition,
wxDefaultSize, wxBORDER_NONE)
{
timer = new wxTimer(this, 1);
m_stsbar = parent->GetStatusBar();
isFallingFinished = false;
isStarted = false;
isPaused = false;
numLinesRemoved = 0;
curX = 0;
curY = 0;
ClearBoard();
Connect(wxEVT_PAINT, wxPaintEventHandler(Board::OnPaint));
Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(Board::OnKeyDown));
Connect(wxEVT_TIMER, wxCommandEventHandler(Board::OnTimer));
}
void Board::Start()
{
if (isPaused)
return;
isStarted = true;
isFallingFinished = false;
numLinesRemoved = 0;
ClearBoard();
NewPiece();
timer->Start(300);
}
void Board::Pause()
{
if (!isStarted)
return;
isPaused = !isPaused;
if (isPaused) {
timer->Stop();
m_stsbar->SetStatusText(wxT("paused"));
} else {
timer->Start(300);
wxString str;
str.Printf(wxT("%d"), numLinesRemoved);
m_stsbar->SetStatusText(str);
}
Refresh();
}
if (curPiece.GetShape() != NoShape) {
for (int i = 0; i < 4; ++i) {
int x = curX + curPiece.x(i);
int y = curY - curPiece.y(i);
DrawSquare(dc, 0 + x * SquareWidth(),
boardTop + (BoardHeight - y - 1) * SquareHeight(),
curPiece.GetShape());
}
}
}
if (isPaused)
return;
switch (keycode) {
case WXK_LEFT:
TryMove(curPiece, curX - 1, curY);
break;
case WXK_RIGHT:
TryMove(curPiece, curX + 1, curY);
break;
case WXK_DOWN:
TryMove(curPiece.RotateRight(), curX, curY);
break;
case WXK_UP:
TryMove(curPiece.RotateLeft(), curX, curY);
break;
case WXK_SPACE:
DropDown();
break;
case 'd':
OneLineDown();
break;
case 'D':
OneLineDown();
break;
default:
event.Skip();
}
void Board::ClearBoard()
{
for (int i = 0; i < BoardHeight * BoardWidth; ++i)
board[i] = NoShape;
}
void Board::DropDown()
{
int newY = curY;
while (newY > 0) {
if (!TryMove(curPiece, curX, newY - 1))
break;
--newY;
}
PieceDropped();
}
void Board::OneLineDown()
{
if (!TryMove(curPiece, curX, curY - 1))
PieceDropped();
}
void Board::PieceDropped()
{
for (int i = 0; i < 4; ++i) {
int x = curX + curPiece.x(i);
int y = curY - curPiece.y(i);
ShapeAt(x, y) = curPiece.GetShape();
}
RemoveFullLines();
if (!isFallingFinished)
NewPiece();
}
void Board::RemoveFullLines()
{
int numFullLines = 0;
if (lineIsFull) {
++numFullLines;
for (int k = i; k < BoardHeight - 1; ++k) {
for (int j = 0; j < BoardWidth; ++j)
ShapeAt(j, k) = ShapeAt(j, k + 1);
}
}
}
if (numFullLines > 0) {
numLinesRemoved += numFullLines;
wxString str;
str.Printf(wxT("%d"), numLinesRemoved);
m_stsbar->SetStatusText(str);
isFallingFinished = true;
curPiece.SetShape(NoShape);
Refresh();
}
}
void Board::NewPiece()
{
curPiece.SetRandomShape();
curX = BoardWidth / 2 + 1;
curY = BoardHeight - 1 + curPiece.MinY();
curPiece = newPiece;
curX = newX;
curY = newY;
Refresh();
return true;
}
wxPen pen(light[int(shape)]);
pen.SetCap(wxCAP_PROJECTING);
dc.SetPen(pen);
dc.DrawLine(x, y + SquareHeight() - 1, x, y);
dc.DrawLine(x, y, x + SquareWidth() - 1, y);
wxPen darkpen(dark[int(shape)]);
darkpen.SetCap(wxCAP_PROJECTING);
dc.SetPen(darkpen);
dc.DrawLine(x + 1, y + SquareHeight() - 1,
x + SquareWidth() - 1, y + SquareHeight() - 1);
dc.DrawLine(x + SquareWidth() - 1,
y + SquareHeight() - 1, x + SquareWidth() - 1, y + 1);
dc.SetPen(*wxTRANSPARENT_PEN);
dc.SetBrush(wxBrush(colors[int(shape)]));
dc.DrawRectangle(x + 1, y + 1, SquareWidth() - 2,
SquareHeight() - 2);
}
Tetris.h
#include <wx/wx.h>
};
Tetris.cpp
#include "Tetris.h"
#include "Board.h"
main.h
#include <wx/wx.h>
};
main.cpp
#include "main.h"
#include "Tetris.h"
IMPLEMENT_APP(MyApp)
bool MyApp::OnInit()
{
srand(time(NULL));
Tetris *tetris = new Tetris(wxT("Tetris"));
tetris->Centre();
tetris->Show(true);
return true;
}
I have simplified the game a bit, so that it is easier to understand. The game starts immediately,
after it is launched. We can pause the game by pressing the p key. The space key will drop the tetris
piece immediately to the bottom. The d key will drop the piece one line down. (It can be used to
speed up the falling a bit.) The game goes at constant speed, no acceleration is implemented. The
score is the number of lines, that we have removed.
...
isFallingFinished = false;
isStarted = false;
isPaused = false;
numLinesRemoved = 0;
curX = 0;
curY = 0;
...
Before we start the game, we initialize some important variables. The isFallingFinished variable
determines, it the tetris shape has finished falling and we then need to create a new shape. The
numLinesRemoved counts the number of lines, we have removed so far. The curX and curY variables
The painting of the game is divided into two steps. In the first step, we draw all the shapes, or
remains of the shapes that have been dropped to the bottom of the board. All the squares are
rememberd in the board array. We access it using the ShapeAt() method.
if (curPiece.GetShape() != NoShape) {
for (int i = 0; i < 4; ++i) {
int x = curX + curPiece.x(i);
int y = curY - curPiece.y(i);
DrawSquare(dc, 0 + x * SquareWidth(),
boardTop + (BoardHeight - y - 1) * SquareHeight(),
curPiece.GetShape());
}
}
The next step is drawing of the actual piece that is falling down.
...
switch (keycode) {
case WXK_LEFT:
TryMove(curPiece, curX - 1, curY);
break;
...
In the Board::OnKeyDown() method we check for pressed keys. If we press the left arrow key, we try
to move the piece to the left. We say try, because the piece might not be able to move.
In the Board::OnTimer() method we either create a new piece, after the previous one was dropped
to the bottom, or we move a falling piece one line down.
void Board::DropDown()
{
int newY = curY;
while (newY > 0) {
if (!TryMove(curPiece, curX, newY - 1))
break;
--newY;
}
PieceDropped();
}
The Board::DropDown() method drops the falling shape immediately to the bottom of the board. It
happens, when we press the space key.
void Board::PieceDropped()
{
for (int i = 0; i < 4; ++i) {
int x = curX + curPiece.x(i);
int y = curY - curPiece.y(i);
ShapeAt(x, y) = curPiece.GetShape();
}
RemoveFullLines();
if (!isFallingFinished)
NewPiece();
}
In the Board::PieceDropped() method we set the current shape at its final position. We call the
RemoveFullLines() method to check if we have at least one full line. And we create a new tetris
shape if it was not already created in the Board::PieceDropped() method in the meantime.
if (lineIsFull) {
++numFullLines;
for (int k = i; k < BoardHeight - 1; ++k) {
for (int j = 0; j < BoardWidth; ++j)
ShapeAt(j, k) = ShapeAt(j, k + 1);
}
}
This code removes the full lines. After finding a full line we increase the counter. We move all the
lines above the full row one line down. This way we destroy the full line. Notice that in our tetris
game, we use so called naive gravity. This means that the squares may be left floating above empty
gaps.
void Board::NewPiece()
{
curPiece.SetRandomShape();
curX = BoardWidth / 2 + 1;
curY = BoardHeight - 1 + curPiece.MinY();
The Board::NewPiece() method creates randomly a new tetris piece. If the piece cannot go into its
initial position, the game is over.
curPiece = newPiece;
curX = newX;
curY = newY;
Refresh();
return true;
}
In the Board::TryMove() method we try to move our shapes. If the shape is at the edge of the board
or is adjacent to some other shape, we return false. Otherwise we place the current falling shape to
a new position and return true.
The coords array saves the coordinates of the tetris piece. For example, numbers { 0, -1 }, { 0, 0 }, {
1, 0 }, { 1, 1 } , represent a rotated S-shape. The following diagram illustrates the shape.
Figure: Coordinates
When we draw the current falling piece, we draw it at curX, curY position. Then we look at the
coordinates table and draw all the four squares.
Figure: Tetris