Showing posts with label wxWidgets. Show all posts
Showing posts with label wxWidgets. Show all posts

Wednesday, April 10, 2013

Step By Step wxWidgets tutorial

wxWidgets tutorial

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.

wxWidgets

wxWidgets is a GUI (Graphical User Interface) toolkit for creating C++ applications. It is an opensource, mature and cross-platform toolkit. wxWidgets applications run on all major OS platforms, Windows, Unix and Mac.

The Tetris game in wxWidgets

The Tetris game in wxWidgets

The Tetris game is one of the most popular computer games ever created. The original game was designed and programmed by a Russian programmer Alexey Pajitnov in 1985. Since then, tetris is available on almost every computer platform in lots of variations. Even my mobile phone has a modified version of the Tetris game.
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.
Tetrominoes
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 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.
  • We use wxTimer to create a game cycle
  • The tetrominoes are drawn
  • The shapes move on a square by square basis (not pixel by pixel)
  • Mathematically a board is a simple list of numbers

Shape.h
#ifndef SHAPE_H
#define SHAPE_H

enum Tetrominoes { NoShape, ZShape, SShape, LineShape,
TShape, SquareShape, LShape, MirroredLShape };

class Shape
{
public:
Shape() { SetShape(NoShape); }
void SetShape(Tetrominoes shape);
void SetRandomShape();

Tetrominoes GetShape() const { return pieceShape; }
int x(int index) const { return coords[index][0]; }
int y(int index) const { return coords[index][1]; }

int MinX() const;
int MaxX() const;
int MinY() const;
int MaxY() const;

Shape RotateLeft() const;
Shape RotateRight() const;

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"

using namespace std;

void Shape::SetShape(Tetrominoes shape)
{
static const int coordsTable[8][4][2] = {
{ { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
{ { 0, -1 }, { 0, 0 }, { -1, 0 }, { -1, 1 } },
{ { 0, -1 }, { 0, 0 }, { 1, 0 }, { 1, 1 } },
{ { 0, -1 }, { 0, 0 }, { 0, 1 }, { 0, 2 } },
{ { -1, 0 }, { 0, 0 }, { 1, 0 }, { 0, 1 } },
{ { 0, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 } },
{ { -1, -1 }, { 0, -1 }, { 0, 0 }, { 0, 1 } },
{ { 1, -1 }, { 0, -1 }, { 0, 0 }, { 0, 1 } }
};

for (int i = 0; i < 4 ; i++) {
for (int j = 0; j < 2; ++j)
coords[i][j] = coordsTable[shape][i][j];
}
pieceShape = shape;
}



void Shape::SetRandomShape()
{
int x = rand() % 7 + 1;
SetShape(Tetrominoes(x));
}

int Shape::MinX() const
{
int m = coords[0][0];
for (int i=0; i<4; i++) {
m = min(m, coords[i][0]);
}
return m;
}

int Shape::MaxX() const
{
int m = coords[0][0];
for (int i=0; i<4; i++) {
m = max(m, coords[i][0]);
}
return m;
}

int Shape::MinY() const
{
int m = coords[0][1];
for (int i=0; i<4; i++) {
m = min(m, coords[i][1]);
}
return m;
}

int Shape::MaxY() const
{
int m = coords[0][1];
for (int i=0; i<4; i++) {
m = max(m, coords[i][1]);
}
return m;
}

Shape Shape::RotateLeft() const
{
if (pieceShape == SquareShape)
return *this;

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 Shape::RotateRight() const
{
if (pieceShape == SquareShape)
return *this;

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>

class Board : public wxPanel
{

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 };

Tetrominoes & ShapeAt(int x, int y) { return board[(y * BoardWidth) + x]; }

int SquareWidth() { return GetClientSize().GetWidth() / BoardWidth; }
int SquareHeight() { return GetClientSize().GetHeight() / BoardHeight; }
void ClearBoard();
void DropDown();
void OneLineDown();
void PieceDropped();
void RemoveFullLines();
void NewPiece();
bool TryMove(const Shape& newPiece, int newX, int newY);
void DrawSquare(wxPaintDC &dc, int x, int y, Tetrominoes shape);

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();
}

void Board::OnPaint(wxPaintEvent& event)
{
wxPaintDC dc(this);

wxSize size = GetClientSize();
int boardTop = size.GetHeight() - BoardHeight * SquareHeight();


for (int i = 0; i < BoardHeight; ++i) {
for (int j = 0; j < BoardWidth; ++j) {
Tetrominoes shape = ShapeAt(j, BoardHeight - i - 1);
if (shape != NoShape)
DrawSquare(dc, 0 + j * SquareWidth(),
boardTop + i * SquareHeight(), shape);
}
}

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());
}
}
}

void Board::OnKeyDown(wxKeyEvent& event)
{
if (!isStarted || curPiece.GetShape() == NoShape) {
event.Skip();
return;
}

int keycode = event.GetKeyCode();

if (keycode == 'p' || keycode == 'P') {
Pause();
return;
}

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::OnTimer(wxCommandEvent& event)
{
if (isFallingFinished) {
isFallingFinished = false;
NewPiece();
} else {
OneLineDown();
}
}

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;

for (int i = BoardHeight - 1; i >= 0; --i) {
bool lineIsFull = true;

for (int j = 0; j < BoardWidth; ++j) {
if (ShapeAt(j, i) == NoShape) {
lineIsFull = false;
break;
}
}

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();

if (!TryMove(curPiece, curX, curY)) {
curPiece.SetShape(NoShape);
timer->Stop();
isStarted = false;
m_stsbar->SetStatusText(wxT("game over"));
}
}

bool Board::TryMove(const Shape& newPiece, int newX, int newY)
{
for (int i = 0; i < 4; ++i) {
int x = newX + newPiece.x(i);
int y = newY - newPiece.y(i);
if (x < 0 || x >= BoardWidth || y < 0 || y >= BoardHeight)
return false;
if (ShapeAt(x, y) != NoShape)
return false;
}

curPiece = newPiece;
curX = newX;
curY = newY;
Refresh();
return true;
}

void Board::DrawSquare(wxPaintDC& dc, int x, int y, Tetrominoes shape)
{
static wxColour colors[] = { wxColour(0, 0, 0), wxColour(204, 102, 102),
wxColour(102, 204, 102), wxColour(102, 102, 204),
wxColour(204, 204, 102), wxColour(204, 102, 204),
wxColour(102, 204, 204), wxColour(218, 170, 0) };

static wxColour light[] = { wxColour(0, 0, 0), wxColour(248, 159, 171),
wxColour(121, 252, 121), wxColour(121, 121, 252),
wxColour(252, 252, 121), wxColour(252, 121, 252),
wxColour(121, 252, 252), wxColour(252, 198, 0) };

static wxColour dark[] = { wxColour(0, 0, 0), wxColour(128, 59, 59),
wxColour(59, 128, 59), wxColour(59, 59, 128),
wxColour(128, 128, 59), wxColour(128, 59, 128),
wxColour(59, 128, 128), wxColour(128, 98, 0) };


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>

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

};
Tetris.cpp
#include "Tetris.h"
#include "Board.h"

Tetris::Tetris(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(180, 380))
{
wxStatusBar *sb = CreateStatusBar();
sb->SetStatusText(wxT("0"));
Board *board = new Board(this);
board->SetFocus();
board->Start();
}
main.h
#include <wx/wx.h>

class MyApp : public wxApp
{
public:
virtual bool OnInit();

};
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 determine the actual position of the falling tetris shape.
for (int i = 0; i < BoardHeight; ++i) {
for (int j = 0; j < BoardWidth; ++j) {
Tetrominoes shape = ShapeAt(j, BoardHeight - i - 1);
if (shape != NoShape)
DrawSquare(dc, 0 + j * SquareWidth(),
boardTop + i * SquareHeight(), shape);
}
}
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.
void Board::OnTimer(wxCommandEvent& event)
{
if (isFallingFinished) {
isFallingFinished = false;
NewPiece();
} else {
OneLineDown();
}
}
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 it's 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 wasn't 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();

if (!TryMove(curPiece, curX, curY)) {
curPiece.SetShape(NoShape);
timer->Stop();
isStarted = false;
m_stsbar->SetStatusText(wxT("game over"));
}
}
The Board::NewPiece() method creates randomly a new tetris piece. If the piece cannot go into it's initial position, the game is over.
bool Board::TryMove(const Shape& newPiece, int newX, int newY)
{
for (int i = 0; i < 4; ++i) {
int x = newX + newPiece.x(i);
int y = newY - newPiece.y(i);
if (x < 0 || x >= BoardWidth || y < 0 || y >= BoardHeight)
return false;
if (ShapeAt(x, y) != NoShape)
return false;
}

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 Shape class saves information about the tetris piece.
for (int i = 0; i < 4 ; i++) {
for (int j = 0; j < 2; ++j)
coords[i][j] = coordsTable[shape][i][j];
}
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.
Coordinates
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.
Tetris
Figure: Tetris
This was a Tetris game in wxWidgets.

Custom widgets in wxWidgets

Custom widgets

Have you ever looked at an application and wondered, how a particular GUI item was created? Probably every wannabe programmer has. Then you were looking at a list of widgets provided by your favourite gui library. But you couldn't find it. Toolkits usually provide only the most common widgets like buttons, text widgets, sliders etc. No toolkit can provide all possible widgets.
There are actually two kinds of toolkits. Spartan toolkits and heavy weight toolkits. The FLTK toolkit is a kind of a spartan toolkit. It provides only the very basic widgets and assumes, that the programemer will create the more complicated ones himself. wxWidgets is a heavy weight one. It has lots of widgets. Yet it does not provide the more specialized widgets. For example a speed meter widget, a widget that measures the capacity of a CD to be burned (found e.g. in nero). Toolkits also don't have usually charts.
Programmers must create such widgets by themselves. They do it 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.
Here I assume, you have read the chapter on the Device Contexts.

The Burning Widget

This is an example of a widget, that we create from scratch. This widget can be found in various media burning applications, like Nero Burning ROM.
widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <wx/wx.h>

class Widget : public wxPanel
{
public:
Widget(wxPanel *parent, int id );

wxPanel *m_parent;


void OnSize(wxSizeEvent& event);
void OnPaint(wxPaintEvent& event);

};

#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]);

Widget::Widget(wxPanel *parent, int id)
: wxPanel(parent, id, wxDefaultPosition, wxSize(-1, 30), wxSUNKEN_BORDER)
{

m_parent = parent;

Connect(wxEVT_PAINT, wxPaintEventHandler(Widget::OnPaint));
Connect(wxEVT_SIZE, wxSizeEventHandler(Widget::OnSize));

}

void Widget::OnPaint(wxPaintEvent& event)
{
wxFont font(9, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL,
wxFONTWEIGHT_NORMAL, false, wxT("Courier 10 Pitch"));

wxPaintDC dc(this);
dc.SetFont(font);
wxSize size = GetSize();
int width = size.GetWidth();

Burning *burn = (Burning *) m_parent->GetParent();

int cur_width = burn->GetCurWidth();

int step = (int) round(width / 10.0);


int till = (int) ((width / 750.0) * cur_width);
int full = (int) ((width / 750.0) * 700);


if (cur_width >= 700) {

dc.SetPen(wxPen(wxColour(255, 255, 184)));
dc.SetBrush(wxBrush(wxColour(255, 255, 184)));
dc.DrawRectangle(0, 0, full, 30);
dc.SetPen(wxPen(wxColour(255, 175, 175)));
dc.SetBrush(wxBrush(wxColour(255, 175, 175)));
dc.DrawRectangle(full, 0, till-full, 30);

} else {

dc.SetPen(wxPen(wxColour(255, 255, 184)));
dc.SetBrush(wxBrush(wxColour(255, 255, 184)));
dc.DrawRectangle(0, 0, till, 30);

}

dc.SetPen(wxPen(wxColour(90, 80, 60)));
for ( int i=1; i <= asize; i++ ) {

dc.DrawLine(i*step, 0, i*step, 6);
wxSize size = dc.GetTextExtent(wxString::Format(wxT("%d"), num[i-1]));
dc.DrawText(wxString::Format(wxT("%d"), num[i-1]),
i*step-size.GetWidth()/2, 8);
}
}

void Widget::OnSize(wxSizeEvent& event)
{
Refresh();
}
burning.h
#ifndef BURNING_H
#define BURNING_H

#include <wx/wx.h>
#include "widget.h"

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

void OnScroll(wxScrollEvent& event);
int GetCurWidth();


wxSlider *m_slider;
Widget *m_wid;

int cur_width;

};

#endif
burning.cpp
#include "burning.h"
#include "widget.h"

int ID_SLIDER = 1;

Burning::Burning(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(350, 200))
{

cur_width = 75;

wxPanel *panel = new wxPanel(this, wxID_ANY);
wxPanel *centerPanel = new wxPanel(panel, wxID_ANY);

m_slider = new wxSlider(centerPanel, ID_SLIDER, 75, 0, 750, wxPoint(-1, -1),
wxSize(150, -1), wxSL_LABELS);

wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL);
wxBoxSizer *hbox = new wxBoxSizer(wxHORIZONTAL);
wxBoxSizer *hbox2 = new wxBoxSizer(wxHORIZONTAL);
wxBoxSizer *hbox3 = new wxBoxSizer(wxHORIZONTAL);

m_wid = new Widget(panel, wxID_ANY);
hbox->Add(m_wid, 1, wxEXPAND);

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();

}

void Burning::OnScroll(wxScrollEvent& WXUNUSED(event))
{
cur_width = m_slider->GetValue();
m_wid->Refresh();
}


int Burning::GetCurWidth()
{
return cur_width;
}
main.h
#include <wx/wx.h>

class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "burning.h"

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{

Burning *burning = new Burning(wxT("The Burning Widget"));
burning->Show(true);

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 widget. 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 normally indicates overburning.
wxSize size = GetSize();
int width = size.GetWidth();
...
int till = (int) ((width / 750.0) * cur_width);
int full = (int) ((width / 750.0) * 700);
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 color. 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.
void Widget::OnSize(wxSizeEvent& event)
{
Refresh();
}
Every time the window is resized, we refresh the widget. This causes the widget to repaint itself.
void Burning::OnScroll(wxScrollEvent& WXUNUSED(event))
{
cur_width = m_slider->GetValue();
m_wid->Refresh();
}
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.
Burning Widget
Figure: Burning Widget
In this part of the wxWidgets tutorial, we have created a custom widget.

Device Contexts in wxWidgets

Device Contexts

The GDI (Graphics Device Interface) is an interface for working with graphics. It is used to interact with graphic devices such as monitor, printer or a file. The GDI allows programmers to display data on a screen or printer without having to be concerned about the details of a particular device. The GDI insulates the programmer from the hardware.
The GDI
Figure: The GDI structure
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.
Derived wxDC classes
  • wxBufferedDC
  • wxBufferedPaintDC
  • wxPostScriptDC
  • wxMemoryDC
  • wxPrinterDC
  • wxScreenDC
  • wxClientDC
  • wxPaintDC
  • wxWindowDC
The wxScreenDC is used to draw anywhere on the screen. The wx.WindowDC 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 it's 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 shoud 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

We begin with drawing a line.
line.h
#include <wx/wx.h>

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

void OnPaint(wxPaintEvent& event);

};
line.cpp
#include "line.h"


Line::Line(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(280, 180))
{
this->Connect(wxEVT_PAINT, wxPaintEventHandler(Line::OnPaint));
this->Centre();
}

void Line::OnPaint(wxPaintEvent& event)
{
wxPaintDC dc(this);

wxCoord x1 = 50, y1 = 60;
wxCoord x2 = 190, y2 = 60;

dc.DrawLine(x1, y1, x2, y2);
}
main.h
#include <wx/wx.h>

class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "line.h"

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{

Line *line = new Line(wxT("Line"));
line->Show(true);

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.
void OnPaint(wxPaintEvent& event);
Here we declare a OnPaint() event handler function.
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
wxCoord x1 = 50, y1 = 60;
wxCoord x2 = 190, y2 = 60;
We define four coordinates.
  dc.DrawLine(x1, y1, x2, y2);
We draw a simple line calling the DrawLine() method.
Line
Figure: A simple line

Drawing text

Drawing some text on the window is easy.
text.h
#include <wx/wx.h>

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

void OnPaint(wxPaintEvent & event);

};
text.cpp
#include "text.h"


Text::Text(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 150))
{
Connect(wxEVT_PAINT, wxPaintEventHandler(Text::OnPaint));
Centre();
}

void Text::OnPaint(wxPaintEvent& event)
{
wxPaintDC dc(this);

dc.DrawText(wxT("Лев Николaевич Толстoй"), 40, 60);
dc.DrawText(wxT("Анна Каренина"), 70, 80);
}
main.h
#include <wx/wx.h>

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

main.cpp
#include "main.h"
#include "text.h"

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{

Text *text = new Text(wxT("Text"));
text->Show(true);

return true;
}
In our example, we draw text Lev Nikolayevich Tolstoy, Anna Karenina in Russian azbuka onto the window.
  dc.DrawText(wxT("Лев Николaевич Толстoй"), 40, 60);
dc.DrawText(wxT("Анна Каренина"), 70, 80);
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.
Drawing text
Figure: Drawing text

Point

The simplest geometrical object is a point. It is a plain dot on the window.
DrawPoint(int x, int y)
This method draws a point at x, y coordinates.
point.h
#include <wx/wx.h>

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

void OnPaint(wxPaintEvent & event);

};
points.cpp
#include "points.h"
#include <stdlib.h>
#include <time.h>


Points::Points(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(280, 180))
{

this->Connect(wxEVT_PAINT, wxPaintEventHandler(Points::OnPaint));
srand(time(NULL));
this->Centre();
}

void Points::OnPaint(wxPaintEvent & event)
{
wxPaintDC dc(this);

wxCoord x = 0;
wxCoord y = 0;

wxSize size = this->GetSize();

for (int i = 0; i<1000; i++) {
x = rand() % size.x + 1;
y = rand() % size.y + 1;
dc.DrawPoint(x, y);
}
}
main.h
#include <wx/wx.h>

class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "points.h"

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{

Points *points = new Points(wxT("Points"));
points->Show(true);

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.
wxSize size = this->GetSize();
Here we get the size of the window.
x = rand() % size.x + 1;
Here we get a random number in the range of 1 to size.x.
Points
Figure: Points

Pen

Pen is an elementary graphics object. It is used to draw lines, curves and outlines of rectangles, ellipses, polygons or other shapes.
 wxPen(const wxColour& colour, int width = 1, int style = wxSOLID)
The wxPen constructor has three parameters. Colour, width and style. Follows a list of possible pen styles. Pen styles
  • wxSOLID
  • wxDOT
  • wxLONG_DASH
  • wxSHORT_DASH
  • wxDOT_DASH
  • wxTRANSPARENT
pen.h
#include <wx/wx.h>

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

void OnPaint(wxPaintEvent& event);

};
pen.cpp
#include "pen.h"


Pen::Pen(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(360, 180))
{
this->Connect(wxEVT_PAINT, wxPaintEventHandler(Pen::OnPaint));
this->Centre();
}

void Pen::OnPaint(wxPaintEvent& event)
{
wxPaintDC dc(this);

wxColour col1, col2;

col1.Set(wxT("#0c0c0c"));
col2.Set(wxT("#000000"));

wxBrush brush(wxColour(255, 255, 255), wxTRANSPARENT);
dc.SetBrush(brush);

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>

class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
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.
Pen
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>

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

void OnPaint(wxPaintEvent & event);

};
Regions.cpp
#include "Regions.h"


Regions::Regions(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(270, 220))
{
this->Connect(wxEVT_PAINT, wxPaintEventHandler(Regions::OnPaint));
this->Centre();
}

void Regions::OnPaint(wxPaintEvent & event)
{
wxPaintDC dc(this);
wxColour gray, white, red, blue;
wxColour orange, green, brown;

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.DrawRectangle(20, 20, 50, 50);
dc.DrawRectangle(30, 40, 50, 50);

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>

class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
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;
}

Regions
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)
  void GradientFillLinear(const wxRect& rect, const wxColour& initialColour, 
const wxColour& destColour, wxDirection nDirection = wxEAST)
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>

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

void OnPaint(wxPaintEvent& event);

};
gradient.cpp
#include "gradient.h"


Gradient::Gradient(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(220, 260))
{
this->Connect(wxEVT_PAINT, wxPaintEventHandler(Gradient::OnPaint));
this->Centre();
}

void Gradient::OnPaint(wxPaintEvent& event)
{
wxPaintDC dc(this);


wxColour col1, col2;

col1.Set(wxT("#e12223"));
col2.Set(wxT("#000000"));

dc.GradientFillLinear(wxRect(20, 20, 180, 40), col1, col2, wxNORTH);
dc.GradientFillLinear(wxRect(20, 80, 180, 40), col1, col2, wxSOUTH);
dc.GradientFillLinear(wxRect(20, 140, 180, 40), col1, col2, wxEAST);
dc.GradientFillLinear(wxRect(20, 200, 180, 40), col1, col2, wxWEST);
}
main.h
#include <wx/wx.h>

class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
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;
}
Gradient
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>

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

void OnPaint(wxPaintEvent & event);

};
shapes.cpp
#include "shapes.h"


Shapes::Shapes(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(350, 300))
{
this->Connect(wxEVT_PAINT, wxPaintEventHandler(Shapes::OnPaint));
this->Centre();
}

void Shapes::OnPaint(wxPaintEvent& event)
{
wxPaintDC dc(this);

wxPoint lines[] = { wxPoint(20, 260), wxPoint(100, 260),
wxPoint(20, 210), wxPoint(100, 210) };
wxPoint polygon[] = { wxPoint(130, 140), wxPoint(180, 170),
wxPoint(180, 140), wxPoint(220, 110), wxPoint(140, 100) };
wxPoint splines[] = { wxPoint(240, 170), wxPoint(280, 170),
wxPoint(285, 110), wxPoint(325, 110) };

dc.DrawEllipse(20, 20, 90, 60);
dc.DrawRoundedRectangle(130, 20, 90, 60, 10);
dc.DrawArc(240, 40, 340, 40, 290, 20);

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>

class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "shapes.h"

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{

Shapes *shapes = new Shapes(wxT("Shapes"));
shapes->Show(true);

return true;
}
Shapes
Figure: Shapes
In this chapter, we covered GDI in wxWidgets.

Drag and Drop in wxWidgets

Drag and Drop

Wikipedia: In computer graphical user interfaces, drag-and-drop is the action of (or support for the action of) clicking on a virtual object and dragging it to a different location or onto another virtual object. In general, it can be used to invoke many kinds of actions, or create various types of associations between two abstract objects.
Drag and drop functionality is one of the most visible aspects of the graphical user interface. Drag and drop operation enables you to do complex things intuitively.
In drag and drop we basically drag some data from a data source to a data target. So we must have:
  • 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>

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

void OnSelect(wxCommandEvent& event);
void OnDragInit(wxListEvent& event);

wxGenericDirCtrl *m_gdir;
wxListCtrl *m_lc1;
wxListCtrl *m_lc2;

};


class MyTextDropTarget : public wxTextDropTarget
{
public:
MyTextDropTarget(wxListCtrl *owner);

virtual bool OnDropText(wxCoord x, wxCoord y,
const wxString& data);

wxListCtrl *m_owner;

};
textdrop.cpp
#include "textdrop.h"
#include <wx/treectrl.h>
#include <wx/dirctrl.h>
#include <wx/dir.h>
#include <wx/splitter.h>


TextDrop::TextDrop(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(300, 200))
{

wxSplitterWindow *spl1 = new wxSplitterWindow(this, -1);
wxSplitterWindow *spl2 = new wxSplitterWindow(spl1, -1);
m_gdir = new wxGenericDirCtrl(spl1, -1, wxT("/home/"),
wxPoint(-1, -1), wxSize(-1, -1), wxDIRCTRL_DIR_ONLY);

m_lc1 = new wxListCtrl(spl2, -1, wxPoint(-1, -1),
wxSize(-1, -1), wxLC_LIST);
m_lc2 = new wxListCtrl(spl2, -1, wxPoint(-1, -1),
wxSize(-1, -1), wxLC_LIST);

MyTextDropTarget *mdt = new MyTextDropTarget(m_lc2);
m_lc2->SetDropTarget(mdt);

Connect(m_lc1->GetId(), wxEVT_COMMAND_LIST_BEGIN_DRAG,
wxListEventHandler(TextDrop::OnDragInit));

wxTreeCtrl *tree = m_gdir->GetTreeCtrl();

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;
}

bool MyTextDropTarget::OnDropText(wxCoord x, wxCoord y,
const wxString& data)
{

m_owner->InsertItem(0, data);
return true;

}

void TextDrop::OnSelect(wxCommandEvent& event)
{
wxString filename;
wxString path = m_gdir->GetPath();
wxDir dir(path);

bool cont = dir.GetFirst(&filename, wxEmptyString, wxDIR_FILES);

int i = 0;

m_lc1->ClearAll();
m_lc2->ClearAll();

while ( cont )
{
m_lc1->InsertItem(i, filename);
cont = dir.GetNext(&filename);
i++;
}
}

void TextDrop::OnDragInit(wxListEvent& event)
{

wxString text = m_lc1->GetItemText(event.GetIndex());

wxTextDataObject tdo(text);
wxDropSource tds(tdo, m_lc1);
tds.DoDragDrop(wxDrag_CopyOnly);

}
main.h
#include <wx/wx.h>

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

main.cpp
#include "main.h"
#include "textdrop.h"

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{

TextDrop *td = new TextDrop(wxT("TextDrop"));
td->Show(true);

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.
MyTextDropTarget *mdt = new MyTextDropTarget(m_lc2);
m_lc2->SetDropTarget(mdt);
Here we define a text drop target.
wxString text = m_lc1->GetItemText(event.GetIndex());

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.
bool MyTextDropTarget::OnDropText(wxCoord x, wxCoord y, 
const wxString& data)
{
m_owner->InsertItem(0, data);
return true;
}
During the dropping operation, we insert the text data into the list control.
Drag & Drop
Figure: Drag & Drop
In this chapter, we covered drag and drop operations in wxWidgets.

Introducing various Widgets in wxWidgets

Widgets

In this chapter, we will continue introducing various other widgets. We will mention wxListBox, wxNotebook and wxScrolledWindow.

wxListBox

A wxListBox widget is used for displaying and working with a list of items. As it's 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 wxListBox. The second one is the wxEVT_COMMAND_LISTBOX_DOUBLE_CLICKED event. It is generated 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>

class MyPanel : public wxPanel
{
public:
MyPanel(wxPanel *parent);

void OnNew(wxCommandEvent& event);
void OnRename(wxCommandEvent& event);
void OnClear(wxCommandEvent& event);
void OnDelete(wxCommandEvent& event);

wxListBox *m_lb;

wxButton *m_newb;
wxButton *m_renameb;
wxButton *m_clearb;
wxButton *m_deleteb;

};

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

void OnDblClick(wxCommandEvent& event);

wxListBox *listbox;
MyPanel *btnPanel;

};

const int ID_RENAME = 1;
const int ID_LISTBOX = 5;
Listbox.cpp
#include "listbox.h"
#include <wx/textdlg.h>


Listbox::Listbox(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(270, 200))
{

wxPanel * panel = new wxPanel(this, -1);

wxBoxSizer *hbox = new wxBoxSizer(wxHORIZONTAL);

listbox = new wxListBox(panel, ID_LISTBOX,
wxPoint(-1, -1), wxSize(-1, -1));

hbox->Add(listbox, 3, wxEXPAND | wxALL, 20);

btnPanel = new MyPanel(panel);
hbox->Add(btnPanel, 2, wxEXPAND | wxRIGHT, 10);

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);

Listbox *lb = (Listbox *) parent->GetParent();
m_lb = lb->listbox;

m_newb = new wxButton(this, wxID_NEW, wxT("New"));
m_renameb = new wxButton(this, ID_RENAME, wxT("Rename"));
m_deleteb = new wxButton(this, wxID_DELETE, wxT("Delete"));
m_clearb = new wxButton(this, wxID_CLEAR, wxT("Clear"));

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);

}


void MyPanel::OnNew(wxCommandEvent& event)
{
wxString str = wxGetTextFromUser(wxT("Add new item"));
if (str.Len() > 0)
m_lb->Append(str);
}

void MyPanel::OnClear(wxCommandEvent& event)
{
m_lb->Clear();
}

void MyPanel::OnRename(wxCommandEvent& event)
{
wxString text;
wxString renamed;

int sel = m_lb->GetSelection();
if (sel != -1) {
text = m_lb->GetString(sel);
renamed = wxGetTextFromUser(wxT("Rename item"),
wxT("Rename dialog"), text);
}

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);
}
}


void Listbox::OnDblClick(wxCommandEvent& event)
{
wxString text;
wxString renamed;

int sel = listbox->GetSelection();
if (sel != -1) {
text = listbox->GetString(sel);
renamed = wxGetTextFromUser(wxT("Rename item"),
wxT("Rename dialog"), text);
}

if (!renamed.IsEmpty()) {
listbox->Delete(sel);
listbox->Insert(renamed, sel);
}
}
main.h
#include <wx/wx.h>

class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "Listbox.h"

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{

Listbox *listbox = new Listbox(wxT("Listbox"));
listbox->Show(true);

return true;
}
listbox = new wxListBox(panel, ID_LISTBOX, 
wxPoint(-1, -1), wxSize(-1, -1));
This is the constructor of the listbox widget.
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.
wxString str = wxGetTextFromUser(wxT("Add new item"));
if (str.Len() > 0)
m_lb->Append(str);
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.
int sel = m_lb->GetSelection();
if (sel != -1) {
m_lb->Delete(sel);
}
To delete an item, we figure out the selected item. Then we call the Delete() method.
Renaming an item requires several steps.
wxString text;
wxString renamed;
We define two local variables.
int sel = listbox->GetSelection();
if (sel != -1) {
text = listbox->GetString(sel);
renamed = wxGetTextFromUser(wxT("Rename item"),
wxT("Rename dialog"), text);
}
We get the selected string and save it to the renamed variable.
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.
Listbox
Figure: Listbox

wxNotebook

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
The default position is wxNB_TOP.
Notebook.h
#include <wx/wx.h>
#include <wx/notebook.h>
#include <wx/grid.h>

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

void OnQuit(wxCommandEvent& event);

};


class MyGrid : public wxGrid
{
public:
MyGrid(wxNotebook *parent);

};
Notebook.cpp
#include "Notebook.h"

Notebook::Notebook(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(400, 350))
{

wxNotebook *nb = new wxNotebook(this, -1, wxPoint(-1, -1),
wxSize(-1, -1), wxNB_BOTTOM);

wxMenuBar *menubar = new wxMenuBar;
wxMenu *file = new wxMenu;
file->Append(wxID_EXIT, wxT("Quit"), wxT(""));
menubar->Append(file, wxT("&File"));
SetMenuBar(menubar);

Connect(wxEVT_COMMAND_MENU_SELECTED,
wxCommandEventHandler(Notebook::OnQuit));

MyGrid *grid1 = new MyGrid(nb);
MyGrid *grid2 = new MyGrid(nb);
MyGrid *grid3 = new MyGrid(nb);

nb->AddPage(grid1, wxT("Sheet1"));
nb->AddPage(grid2, wxT("Sheet2"));
nb->AddPage(grid3, wxT("Sheet3"));

CreateStatusBar();
Center();
}


void Notebook::OnQuit(wxCommandEvent& event)
{
Close(true);
}

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));

for (int i = 0; i < 30 ; i++) {
this->SetRowSize(i, 25);
}
}
main.h
#include <wx/wx.h>

class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "Notebook.h"

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{

Notebook *notebook = new Notebook(wxT("Notebook"));
notebook->Show(true);

return true;
}
In this example, we have created a notebook widget with three grids. The notebook widget is positioned at the bottom.
wxNotebook *nb = new wxNotebook(this, -1, wxPoint(-1, -1), 
wxSize(-1, -1), wxNB_BOTTOM);
Here we create the notebook widget.
nb->AddPage(grid1, wxT("Sheet1"));
nb->AddPage(grid2, wxT("Sheet2"));
nb->AddPage(grid3, wxT("Sheet3"));
We add three grid objects into the notebook widget.
Notebook
Figure: Notebook widget

wxScrolledWindow

This is one of the container widgets. It can be useful, when we have a larger area than a window can display. In our example, we demonstrate such a case. We place a large image into our window. When the window is smaller than our image, Scrollbars are displayed automatically.
scrolledwindow.h
#include <wx/wx.h>

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

};
scrolledwindow.cpp
#include "scrolledwindow.h"

ScrWindow::ScrWindow(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(300, 200))
{
wxImage::AddHandler(new wxJPEGHandler);
wxScrolledWindow *sw = new wxScrolledWindow(this);

wxBitmap bmp(wxT("castle.jpg"), wxBITMAP_TYPE_JPEG);
wxStaticBitmap *sb = new wxStaticBitmap(sw, -1, bmp);

int width = bmp.GetWidth();
int height = bmp.GetHeight();

sw->SetScrollbars(10, 10, width/10, height/10);
sw->Scroll(50,10);

Center();
}
main.h
#include <wx/wx.h>

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

main.cpp
#include "main.h"
#include "scrolledwindow.h"

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{

ScrWindow *sw = new ScrWindow(wxT("ScrolledWindow"));
sw->Show(true);

return true;
}
In our example, we display a picture of a Spis castle.
wxImage::AddHandler(new wxJPEGHandler);
To handle jpg images, we must initiate the wxJPEGHandler.
wxScrolledWindow *sw = new wxScrolledWindow(this);

wxBitmap bmp(wxT("castle.jpg"), wxBITMAP_TYPE_JPEG);
wxStaticBitmap *sb = new wxStaticBitmap(sw, -1, bmp);
We create a scroll window and put a static bitmap into it.
sw->SetScrollbars(10, 10, width/10, height/10);
We set the scrollbars.
sw->Scroll(50,10);
We scroll the window a bit.
In this chapter, we continued covering widgets in wxWidgets library.

Available Widgets for wxWidgets

Widgets

In this chapter, we will show small examples of several widgets, available in wxWidgets. Widgets are builing blocks of our applications. wxWidgets consists of a large amout of useful widgets. Widget is a basic GUI object by definition. A widget gave wxWidgets toolkit a name. This term is used on UNIX systems. On windows, a widget is usually called a control.

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/hide splashscreen at startup, toggle visibility of a toolbar etc.
checkbox.h
#include <wx/wx.h>

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

void OnToggle(wxCommandEvent& event);

wxCheckBox *m_cb;

};

const int ID_CHECKBOX = 100;
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);

m_cb = new wxCheckBox(panel, ID_CHECKBOX, wxT("Show title"),
wxPoint(20, 20));
m_cb->SetValue(true);
Connect(ID_CHECKBOX, wxEVT_COMMAND_CHECKBOX_CLICKED,
wxCommandEventHandler(CheckBox::OnToggle));
Centre();
}

void CheckBox::OnToggle(wxCommandEvent& WXUNUSED(event))
{

if (m_cb->GetValue()) {
this->SetTitle(wxT("CheckBox"));
} else {
this->SetTitle(wxT(" "));
}
}
main.h
#include <wx/wx.h>

class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "checkbox.h"

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{

CheckBox *cb = new CheckBox(wxT("CheckBox"));
cb->Show(true);

return true;
}
In our example, we display one checkbox on the window. We toggle the title of the window by clicking on the checkbox.
m_cb = new wxCheckBox(panel, ID_CHECKBOX, wxT("Show title"), 
wxPoint(20, 20));
m_cb->SetValue(true);
We create a checkbox. By default, the title is visible. So we check the checkbox by calling the method SetValue().
Connect(ID_CHECKBOX, wxEVT_COMMAND_CHECKBOX_CLICKED, 
wxCommandEventHandler(CheckBox::OnToggle));
If we click on the checkbox, a wxEVT_COMMAND_CHECKBOX_CLICKED event is generated. We connect this event to the user defined OnToggle() method.
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.
wxCheckBox
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>

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

wxSlider *slider;
wxBitmapButton *button;
int pos;

void OnScroll(wxScrollEvent& event);

};

const int ID_SLIDER = 100;
bitmapbutton.cpp
#include "bitmapbutton.h"

BitmapButton::BitmapButton(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 130))
{
wxImage::AddHandler( new wxPNGHandler );
wxPanel *panel = new wxPanel(this);
slider = new wxSlider(panel, ID_SLIDER, 0, 0, 100,
wxPoint(10, 30), wxSize(140, -1));

button = new wxBitmapButton(panel, wxID_ANY, wxBitmap(wxT("mute.png"),
wxBITMAP_TYPE_PNG), wxPoint(180, 20));

Connect(ID_SLIDER, wxEVT_COMMAND_SLIDER_UPDATED,
wxScrollEventHandler(BitmapButton::OnScroll));
Center();
}


void BitmapButton::OnScroll(wxScrollEvent& event)
{
pos = slider->GetValue();

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>

class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "bitmapbutton.h"

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{

BitmapButton *bb = new BitmapButton(wxT("BitmapButton"));
bb->Show(true);

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.
wxImage::AddHandler( new wxPNGHandler );
We will use PNG images, so we must initialize a PNG image handler.
button = new wxBitmapButton(panel, wxID_ANY, wxBitmap(wxT("mute.png"), 
wxBITMAP_TYPE_PNG), wxPoint(180, 20));
We create a bitmap button. We specify a bitmap type, in our case wxBITMAP_TYPE_PNG
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.
wxBitmapButton
Figure: wxBitmapButton

wxToggleButton

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>

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

void OnToggleRed(wxCommandEvent& event);
void OnToggleGreen(wxCommandEvent& event);
void OnToggleBlue(wxCommandEvent& event);

protected:
wxToggleButton *m_tgbutton1;
wxToggleButton *m_tgbutton2;
wxToggleButton *m_tgbutton3;

wxPanel *m_panel;
wxColour *colour;

};

const int ID_TGBUTTON1 = 101;
const int ID_TGBUTTON2 = 102;
const int ID_TGBUTTON3 = 103;
togglebutton.cpp
#include "togglebutton.h"


ToggleButton::ToggleButton(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(280, 180))
{
wxPanel *panel = new wxPanel(this, wxID_ANY);

colour = new wxColour(0, 0, 0);

m_tgbutton1 = new wxToggleButton(panel, ID_TGBUTTON1,
wxT("Red"), wxPoint(20, 20));
m_tgbutton2 = new wxToggleButton(panel, ID_TGBUTTON2,
wxT("Green"), wxPoint(20, 70));
m_tgbutton3 = new wxToggleButton(panel, ID_TGBUTTON3,
wxT("Blue"), wxPoint(20, 120));

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));

m_panel = new wxPanel(panel, wxID_NEW, wxPoint(150, 20),
wxSize(110, 110), wxSUNKEN_BORDER);
m_panel->SetBackgroundColour(colour->GetAsString());

}

void ToggleButton::OnToggleRed(wxCommandEvent& WXUNUSED(event))
{
unsigned char green = colour->Green();
unsigned char blue = colour->Blue();


if ( colour->Red() ) {
colour->Set(0, green, blue);

} else {
colour->Set(255, green, blue);
}

m_panel->SetBackgroundColour(colour->GetAsString());

}

void ToggleButton::OnToggleGreen(wxCommandEvent& WXUNUSED(event))
{
unsigned char red = colour->Red();
unsigned char blue = colour->Blue();


if ( colour->Green() ) {
colour->Set(red, 0, blue);

} else {
colour->Set(red, 255, blue);
}

m_panel->SetBackgroundColour(colour->GetAsString());

}

void ToggleButton::OnToggleBlue(wxCommandEvent& WXUNUSED(event))
{
unsigned char red = colour->Red();
unsigned char green = colour->Green();


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>

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

main.cpp
#include "main.h"
#include "togglebutton.h"

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{

ToggleButton *button = new ToggleButton(wxT("ToggleButton"));

button->Centre();
button->Show(true);

return true;
}
In our example, we show three toggle buttons and a panel. We set the background color of the panel to black. The togglebuttons will toggle the red, green and blue parts of the color value. The background color will depend on which togglebuttons we have pressed.
colour = new wxColour(0, 0, 0);
This is the initial color value. No red, green and blue equals to black. Theoretically speaking, black is not a color after all.
m_tgbutton1 = new wxToggleButton(panel, ID_TGBUTTON1, 
wxT("Red"), wxPoint(20, 20));
Here we create a toggle button.
Connect(ID_TGBUTTON1, wxEVT_COMMAND_TOGGLEBUTTON_CLICKED, 
wxCommandEventHandler(ToggleButton::OnToggleRed));
If we click on the toggle button, a wxEVT_COMMAND_TOGGLEBUTTON_CLICKED event is generated. We connect the event handlers for this event. Notice, that we don't connect events to the button methods, but to the wxFrame. widget, which is a grand parent of the toggle buttons. It is possible to do this, because command events are propagated to their parents. In our case, button -> panel -> frame. If we wanted to connect the event to the button, we would have to create our derived button classe, which would mean more work.
 if ( colour->Blue() ) {
colour->Set(red, green, 0);

} else {
colour->Set(red, green, 255);
}
In the event handlers, we set the respective wxColour parameters.
m_panel->SetBackgroundColour(colour->GetAsString());
We set the background of the panel.
wxToggleButton
Figure: wxToggleButton

wxStaticLine

This widget displays a simple line on the window. It can be horizontal or vertical.
staticline.h
#include <wx/wx.h>

class Staticline : public wxDialog
{
public:
Staticline(const wxString& title);

};
staticline.cpp
#include "staticline.h"
#include <wx/stattext.h>
#include <wx/statline.h>

Staticline::Staticline(const wxString& title) : wxDialog(NULL, wxID_ANY, title,
wxDefaultPosition, wxSize(360, 350))
{

wxFont font(10, wxDEFAULT, wxNORMAL, wxBOLD);
wxStaticText *heading = new wxStaticText(this, wxID_ANY, wxT("The Central Europe"),
wxPoint(30, 15));
heading->SetFont(font);

wxStaticLine *sl1 = new wxStaticLine(this, wxID_ANY, wxPoint(25, 50),
wxSize(300,1));

wxStaticText *st1 = new wxStaticText(this, wxID_ANY, wxT("Slovakia"),
wxPoint(25, 80));
wxStaticText *st2 = new wxStaticText(this, wxID_ANY, wxT("Hungary"),
wxPoint(25, 100));
wxStaticText *st3 = new wxStaticText(this, wxID_ANY, wxT("Poland"),
wxPoint(25, 120));
wxStaticText *st4 = new wxStaticText(this, wxID_ANY, wxT("Czech Republic"),
wxPoint(25, 140));
wxStaticText *st5 = new wxStaticText(this, wxID_ANY, wxT("Germany"),
wxPoint(25, 160));
wxStaticText *st6 = new wxStaticText(this, wxID_ANY, wxT("Slovenia"),
wxPoint(25, 180));
wxStaticText *st7 = new wxStaticText(this, wxID_ANY, wxT("Austria"),
wxPoint(25, 200));
wxStaticText *st8 = new wxStaticText(this, wxID_ANY, wxT("Switzerland"),
wxPoint(25, 220));


wxStaticText *st9 = new wxStaticText(this, wxID_ANY, wxT("5 379 000"),
wxPoint(220, 80), wxSize(90, -1), wxALIGN_RIGHT);
wxStaticText *st10 = new wxStaticText(this, wxID_ANY, wxT("10 084 000"),
wxPoint(220, 100), wxSize(90, -1), wxALIGN_RIGHT);
wxStaticText *st11 = new wxStaticText(this, wxID_ANY, wxT("38 635 000"),
wxPoint(220, 120), wxSize(90, -1), wxALIGN_RIGHT);
wxStaticText *st12 = new wxStaticText(this, wxID_ANY, wxT("10 240 000"),
wxPoint(220, 140), wxSize(90, -1), wxALIGN_RIGHT);
wxStaticText *st13 = new wxStaticText(this, wxID_ANY, wxT("82 443 000"),
wxPoint(220, 160), wxSize(90, -1), wxALIGN_RIGHT);
wxStaticText *st14 = new wxStaticText(this, wxID_ANY, wxT("2 001 000"),
wxPoint(220, 180), wxSize(90, -1), wxALIGN_RIGHT);
wxStaticText *st15 = new wxStaticText(this, wxID_ANY, wxT("8 032 000"),
wxPoint(220, 200), wxSize(90, -1), wxALIGN_RIGHT);
wxStaticText *st16 = new wxStaticText(this, wxID_ANY, wxT("7 288 000"),
wxPoint(220, 220), wxSize(90, -1), wxALIGN_RIGHT);

wxStaticLine *sl2 = new wxStaticLine(this, wxID_ANY, wxPoint(25, 260),
wxSize(300, 1));

wxStaticText *sum = new wxStaticText(this, wxID_ANY, wxT("164 102 000"),
wxPoint(220, 280));
wxFont sum_font = sum->GetFont();
sum_font.SetWeight(wxBOLD);
sum->SetFont(sum_font);

this->Centre();
}
main.h
#include <wx/wx.h>

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

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 wxStaticLinemakes it more visually attractive.
wxStaticLine *sl1 = new wxStaticLine(this, wxID_ANY, wxPoint(25, 50), 
wxSize(300,1));
Here we create a horizontal static line. It is 300px wide. The height is 1px.
wxStaticLine
Figure: wxStaticLine

wxStaticText

A wxStaticText widget displays one or more lines of read-only text.
statictext.h
#include <wx/wx.h>

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

};
statictext.cpp
#include "statictext.h"

StaticText::StaticText(const wxString& title)
: wxFrame(NULL, wxID_ANY, title)
{

wxPanel *panel = new wxPanel(this, wxID_ANY);
wxString text = wxT("'Cause sometimes you feel tired,\n\
feel weak, and when you feel weak,\
you feel like you wanna just give up.\n\
But you gotta search within you,\
you gotta find that inner strength\n\
and just pull that shit out of you\
and get that motivation to not give up\n\
and not be a quitter,\
no matter how bad you wanna just fall flat on your face and collapse.");


wxStaticText *st = new wxStaticText(panel, wxID_ANY, text,
wxPoint(10, 10), wxDefaultSize, wxALIGN_CENTRE);

this->SetSize(600, 110);
this->Centre();
}
main.h
#include <wx/wx.h>

class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "statictext.h"

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{

StaticText *st = new StaticText(wxT("StaticText"));
st->Show(true);

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.
wxStaticText
Figure: wxStaticText

wxSlider

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>

class MyPanel : public wxPanel
{
public:
MyPanel(wxFrame *parent);

void OnPaint(wxPaintEvent& event);
void OnScroll(wxScrollEvent& event);

wxSlider *slider;
int fill;

};

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

MyPanel *panel;

};

const int ID_SLIDER = 100;
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));

}

void MyPanel::OnScroll(wxScrollEvent& event)
{
fill = slider->GetValue();
Refresh();
}

void MyPanel::OnPaint(wxPaintEvent& event)
{
wxPaintDC dc(this);

wxPen pen(wxColour(212, 212, 212));
dc.SetPen(pen);

dc.DrawRectangle(wxRect(140, 30, 80, 140));

wxBrush brush1(wxColour(197, 108, 0));
dc.SetBrush(brush1);

dc.DrawRectangle(wxRect(140, 30, 80, fill));
}
main.h
#include <wx/wx.h>

class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
#include "main.h"
#include "Slider.h"

IMPLEMENT_APP(MyApp)

bool MyApp::OnInit()
{

Slider *slider = new Slider(wxT("Slider"));
slider->Show(true);

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.
slider = new wxSlider(this, ID_SLIDER, 0, 0, 140, wxPoint(50, 30), 
wxSize(-1, 140), wxSL_VERTICAL);
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));
Here we connect a wxEVT_COMMAND_SLIDER_UPDATED event to the OnScroll() user defined method.
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.
dc.DrawRectangle(wxRect(140, 30, 80, 140)); 
...
dc.DrawRectangle(wxRect(140, 30, 80, fill));
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.
wxSlider
Figure: wxSlider
In this part of the wxWidgets tutorial, we covered various widgets.