Painting your custom control
Jump to navigation
Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
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