Painting your custom control
Jump to navigation
Jump to search
The following code demonstrates how to start to create a custom widget (that you paint yourself) by deriving from wxWindow. It creates a simple basic button that does nothing useful but can be used as starting point.
Not that although this example draws a rectangular component, nothing prevents you from changing the paint method to draw any shape you fancy.
Some common mistakes
- Derive from wxWindow for simple (i.e. not containing children) controls, not from wxControl, which is the base class for the native controls.
- Do not use wxPaintDC outside paint events (use wxClientDC if you absolutely have to but better limit all your drawing to wxEVT_PAINT handler).
- Do not draw something on a wxClientDC and then expect it will stay there forever (wrong because your window manager may throw away your drawing at any time - e.g. if the window is minimized or hidden behind something else - and will expect you can draw it back later when receiving a paint event) i.e. always enable the paint event to draw everything. The best way to deal with this is to separate state/data from view. (The render routine reads variables describing current state and draws according to these variables. When something needs to change, don't render it straight ahead; instead, update the variables and call for a repaint - if well-coded, your paint routine should catch the change)
Example
#include <wx/wx.h>
#include <wx/sizer.h>
class wxCustomButton : public wxWindow
{
bool pressedDown;
wxString text;
static const int buttonWidth = 200;
static const int buttonHeight = 50;
public:
wxCustomButton(wxFrame* parent, wxString text);
void paintEvent(wxPaintEvent & evt);
void paintNow();
void render(wxDC& dc);
// some useful events
void mouseMoved(wxMouseEvent& event);
void mouseDown(wxMouseEvent& event);
void mouseWheelMoved(wxMouseEvent& event);
void mouseReleased(wxMouseEvent& event);
void rightClick(wxMouseEvent& event);
void mouseLeftWindow(wxMouseEvent& event);
void keyPressed(wxKeyEvent& event);
void keyReleased(wxKeyEvent& event);
DECLARE_EVENT_TABLE()
};
BEGIN_EVENT_TABLE(wxCustomButton, wxPanel)
EVT_MOTION(wxCustomButton::mouseMoved)
EVT_LEFT_DOWN(wxCustomButton::mouseDown)
EVT_LEFT_UP(wxCustomButton::mouseReleased)
EVT_RIGHT_DOWN(wxCustomButton::rightClick)
EVT_LEAVE_WINDOW(wxCustomButton::mouseLeftWindow)
EVT_KEY_DOWN(wxCustomButton::keyPressed)
EVT_KEY_UP(wxCustomButton::keyReleased)
EVT_MOUSEWHEEL(wxCustomButton::mouseWheelMoved)
// catch paint events
EVT_PAINT(wxCustomButton::paintEvent)
END_EVENT_TABLE()
wxCustomButton::wxCustomButton(wxFrame* parent, wxString text) :
wxWindow(parent, wxID_ANY)
{
SetMinSize( wxSize(buttonWidth, buttonHeight) );
this->text = text;
pressedDown = false;
}
/*
* Called by the system of by wxWidgets when the panel needs
* to be redrawn. You can also trigger this call by
* calling Refresh()/Update().
*/
void wxCustomButton::paintEvent(wxPaintEvent & evt)
{
// depending on your system you may need to look at double-buffered dcs
wxPaintDC dc(this);
render(dc);
}
/*
* Alternatively, you can use a clientDC to paint on the panel
* at any time. Using this generally does not free you from
* catching paint events, since it is possible that e.g. the window
* manager throws away your drawing when the window comes to the
* background, and expects you will redraw it when the window comes
* back (by sending a paint event).
*/
void wxCustomButton::paintNow()
{
// depending on your system you may need to look at double-buffered dcs
wxClientDC dc(this);
render(dc);
}
/*
* Here we do the actual rendering. I put it in a separate
* method so that it can work no matter what type of DC
* (e.g. wxPaintDC or wxClientDC) is used.
*/
void wxCustomButton::render(wxDC& dc)
{
if (pressedDown)
dc.SetBrush( *wxRED_BRUSH );
else
dc.SetBrush( *wxGREY_BRUSH );
dc.DrawRectangle( 0, 0, buttonWidth, buttonHeight );
dc.DrawText( text, 20, 15 );
}
void wxCustomButton::mouseDown(wxMouseEvent& event)
{
pressedDown = true;
paintNow();
}
void wxCustomButton::mouseReleased(wxMouseEvent& event)
{
pressedDown = false;
paintNow();
wxMessageBox( wxT("You pressed a custom button") );
}
void wxCustomButton::mouseLeftWindow(wxMouseEvent& event)
{
if (pressedDown)
{
pressedDown = false;
paintNow();
}
}
// currently unused events
void wxCustomButton::mouseMoved(wxMouseEvent& event) {}
void wxCustomButton::mouseWheelMoved(wxMouseEvent& event) {}
void wxCustomButton::rightClick(wxMouseEvent& event) {}
void wxCustomButton::keyPressed(wxKeyEvent& event) {}
void wxCustomButton::keyReleased(wxKeyEvent& event) {}
// ----------------------------------------
// how-to-use example
class MyApp: public wxApp
{
wxFrame *frame;
wxCustomButton* m_btn_1;
wxCustomButton* m_btn_2;
wxCustomButton* m_btn_3;
public:
bool OnInit()
{
// make sure to call this first
wxInitAllImageHandlers();
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
frame = new wxFrame(NULL, wxID_ANY, wxT("Hello wxDC"), wxPoint(50,50), wxSize(800,600));
// then simply create like this
m_btn_1 = new wxCustomButton( frame, wxT("My Custom Button 11") );
sizer->Add(m_btn_1, 0, wxALL, 5);
m_btn_2 = new wxCustomButton( frame, wxT("Hello World!") );
sizer->Add(m_btn_2, 0, wxALL, 5);
m_btn_3 = new wxCustomButton( frame, wxT("Foo Bar") );
sizer->Add(m_btn_3, 0, wxALL, 5);
frame->SetSizer(sizer);
frame->Show();
return true;
}
};
IMPLEMENT_APP(MyApp)
Flickering
If you have flickering problems, see Drawing_on_a_panel_with_a_DC#To_avoid_flickering