Painting your custom control

From WxWiki
Revision as of 08:45, 21 January 2014 by VZ (talk | contribs) (Correct wrong advice about deriving from wxPanel)
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.
  • Using wxPaintDC outside paint events (use wxClientDC if you absolutely have to but better limit all your drawing to wxEVT_PAINT handler).
  • Draw something on a wxClientDC and expect it will stay there until you draw something else (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