Difference between revisions of "Drawing on a panel with a DC"

From WxWiki
Jump to navigation Jump to search
m (Text replacement - "<source>" to "<syntaxhighlight lang="cpp">")
 
(27 intermediate revisions by 12 users not shown)
Line 1: Line 1:
If you want to do drawing using software rendering on a panel, this code snippet should get you started :
+
__TOC__
 +
 
 +
== Snippet ==
 +
 
 +
If you want to do drawing using software rendering on a wxPanel, this code snippet should get you started :
 +
 
 +
<syntaxhighlight lang="cpp">
  
<source>
 
 
#include "wx/wx.h"
 
#include "wx/wx.h"
 
#include "wx/sizer.h"
 
#include "wx/sizer.h"
Line 11: Line 16:
 
     BasicDrawPane(wxFrame* parent);
 
     BasicDrawPane(wxFrame* parent);
 
      
 
      
     void paintEvent(wxPaintEvent& evt);
+
     void paintEvent(wxPaintEvent & evt);
 
     void paintNow();
 
     void paintNow();
 
      
 
      
Line 18: Line 23:
 
     // some useful events
 
     // some useful events
 
     /*
 
     /*
void mouseMoved(wxMouseEvent& event);
+
    void mouseMoved(wxMouseEvent& event);
void mouseDown(wxMouseEvent& event);
+
    void mouseDown(wxMouseEvent& event);
void mouseWheelMoved(wxMouseEvent& event);
+
    void mouseWheelMoved(wxMouseEvent& event);
void mouseReleased(wxMouseEvent& event);
+
    void mouseReleased(wxMouseEvent& event);
void rightClick(wxMouseEvent& event);
+
    void rightClick(wxMouseEvent& event);
void mouseLeftWindow(wxMouseEvent& event);
+
    void mouseLeftWindow(wxMouseEvent& event);
void keyPressed(wxKeyEvent& event);
+
    void keyPressed(wxKeyEvent& event);
void keyReleased(wxKeyEvent& event);
+
    void keyReleased(wxKeyEvent& event);
    */
+
    */
 
      
 
      
 
     DECLARE_EVENT_TABLE()
 
     DECLARE_EVENT_TABLE()
Line 35: Line 40:
 
{
 
{
 
     bool OnInit();
 
     bool OnInit();
 
+
   
 
     wxFrame *frame;
 
     wxFrame *frame;
 
     BasicDrawPane * drawPane;
 
     BasicDrawPane * drawPane;
 
public:
 
public:
     
+
       
 
};
 
};
  
Line 48: Line 53:
 
{
 
{
 
     wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
 
     wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
     frame = new wxFrame((wxFrame *)NULL, -1,  wxT("Hello wxDC"), wxPoint(50,50), wxSize(400,200));
+
     frame = new wxFrame((wxFrame *)NULL, -1,  wxT("Hello wxDC"), wxPoint(50,50), wxSize(800,600));
 
 
 
     drawPane = new BasicDrawPane( (wxFrame*) frame );
 
     drawPane = new BasicDrawPane( (wxFrame*) frame );
Line 61: Line 66:
  
 
BEGIN_EVENT_TABLE(BasicDrawPane, wxPanel)
 
BEGIN_EVENT_TABLE(BasicDrawPane, wxPanel)
 +
// some useful events
 
/*
 
/*
EVT_MOTION(BasicDrawPane::mouseMoved)
+
EVT_MOTION(BasicDrawPane::mouseMoved)
EVT_LEFT_DOWN(BasicDrawPane::mouseDown)
+
EVT_LEFT_DOWN(BasicDrawPane::mouseDown)
EVT_LEFT_UP(BasicDrawPane::mouseReleased)
+
EVT_LEFT_UP(BasicDrawPane::mouseReleased)
EVT_RIGHT_DOWN(BasicDrawPane::rightClick)
+
EVT_RIGHT_DOWN(BasicDrawPane::rightClick)
EVT_LEAVE_WINDOW(BasicDrawPane::mouseLeftWindow)
+
EVT_LEAVE_WINDOW(BasicDrawPane::mouseLeftWindow)
EVT_KEY_DOWN(BasicDrawPane::keyPressed)
+
EVT_KEY_DOWN(BasicDrawPane::keyPressed)
EVT_KEY_UP(BasicDrawPane::keyReleased)
+
EVT_KEY_UP(BasicDrawPane::keyReleased)
EVT_MOUSEWHEEL(BasicDrawPane::mouseWheelMoved)
+
EVT_MOUSEWHEEL(BasicDrawPane::mouseWheelMoved)
*/
+
*/
 +
 
 +
// catch paint events
 
EVT_PAINT(BasicDrawPane::paintEvent)
 
EVT_PAINT(BasicDrawPane::paintEvent)
 +
 
END_EVENT_TABLE()
 
END_EVENT_TABLE()
  
Line 77: Line 86:
 
// some useful events
 
// some useful events
 
/*
 
/*
void BasicDrawPane::mouseMoved(wxMouseEvent& event) {}
+
void BasicDrawPane::mouseMoved(wxMouseEvent& event) {}
void BasicDrawPane::mouseDown(wxMouseEvent& event) {}
+
void BasicDrawPane::mouseDown(wxMouseEvent& event) {}
void BasicDrawPane::mouseWheelMoved(wxMouseEvent& event) {}
+
void BasicDrawPane::mouseWheelMoved(wxMouseEvent& event) {}
void BasicDrawPane::mouseReleased(wxMouseEvent& event) {}
+
void BasicDrawPane::mouseReleased(wxMouseEvent& event) {}
void BasicDrawPane::rightClick(wxMouseEvent& event) {}
+
void BasicDrawPane::rightClick(wxMouseEvent& event) {}
void BasicDrawPane::mouseLeftWindow(wxMouseEvent& event) {}
+
void BasicDrawPane::mouseLeftWindow(wxMouseEvent& event) {}
void BasicDrawPane::keyPressed(wxKeyEvent& event) {}
+
void BasicDrawPane::keyPressed(wxKeyEvent& event) {}
void BasicDrawPane::keyReleased(wxKeyEvent& event) {}
+
void BasicDrawPane::keyReleased(wxKeyEvent& event) {}
*/
+
*/
  
 
BasicDrawPane::BasicDrawPane(wxFrame* parent) :
 
BasicDrawPane::BasicDrawPane(wxFrame* parent) :
Line 92: Line 101:
 
}
 
}
  
void BasicDrawPane::paintEvent(wxPaintEvent& evt)
+
/*
 +
* 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 BasicDrawPane::paintEvent(wxPaintEvent & evt)
 
{
 
{
 
     wxPaintDC dc(this);
 
     wxPaintDC dc(this);
 
     render(dc);
 
     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).
 +
*
 +
* In most cases, this will not be needed at all; simply handling
 +
* paint events and calling Refresh() when a refresh is needed
 +
* will do the job.
 +
*/
 
void BasicDrawPane::paintNow()
 
void BasicDrawPane::paintNow()
 
{
 
{
Line 103: Line 131:
 
}
 
}
  
void BasicDrawPane::render(wxDC& 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 BasicDrawPane::render(wxDC& dc)
 
{
 
{
 +
    // draw some text
 
     dc.DrawText(wxT("Testing"), 40, 60);  
 
     dc.DrawText(wxT("Testing"), 40, 60);  
 +
   
 +
    // draw a circle
 +
    dc.SetBrush(*wxGREEN_BRUSH); // green filling
 +
    dc.SetPen( wxPen( wxColor(255,0,0), 5 ) ); // 5-pixels-thick red outline
 +
    dc.DrawCircle( wxPoint(200,100), 25 /* radius */ );
 +
   
 +
    // draw a rectangle
 +
    dc.SetBrush(*wxBLUE_BRUSH); // blue filling
 +
    dc.SetPen( wxPen( wxColor(255,175,175), 10 ) ); // 10-pixels-thick pink outline
 +
    dc.DrawRectangle( 300, 100, 400, 200 );
 +
   
 +
    // draw a line
 +
    dc.SetPen( wxPen( wxColor(0,0,0), 3 ) ); // black line, 3 pixels thick
 +
    dc.DrawLine( 300, 100, 700, 300 ); // draw line across the rectangle
 +
   
 +
    // Look at the wxDC docs to learn how to draw other stuff
 
}
 
}
</source>
+
 
 +
 
 +
</syntaxhighlight>
 +
 
 +
== Important Notes ==
 +
 
 +
 
 +
* You ''need'' to handle the paint event, and it must be able to redraw the whole drawing at any time. This is because your window manager may throw away your drawing at any time and ask you to draw it again later through a paint event.
 +
** 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).
 +
* Use a wx[Buffered]PaintDC in paint events, and a wxClientDC outside paint events.
 +
* If you do catch the paint event, you ''must'' create a wx[Buffered]PaintDC in it, even if you don't use it.  If a wx[Buffered]PaintDC object isn't created, the event isn't correctly handled which can cause weird behavior.
 +
* Do not store the created DC or keep it for later in any way.
 +
 
 +
== To avoid flickering ==
 +
 
 +
* Try bufferred DCs, they are necessary on some platforms. wxBufferedDC and wxAutoBufferedPaintDC might help you.
 +
* Call ->SetDoubleBuffered(true); on the panel (On Windows only?)
 +
* Call SetBackgroundStyle(wxBG_STYLE_CUSTOM) ( See this : https://docs.wxwidgets.org/stable/wx_wxwindow.html#wxwindowsetbackgroundstyle )
 +
** On wx 2.9.1+, wxBG_STYLE_PAINT can help too and is clearer than "custom"
 +
** Or, alternatively, Catch the wxEVT_ERASE_BACKGROUND event
 +
 
 +
For all details, see article [[Flicker-Free Drawing]]. This generally does not happen on systems with composition (OS X, Windows Vista/7, linux with compositor installed)

Latest revision as of 19:36, 19 October 2018

Snippet

If you want to do drawing using software rendering on a wxPanel, this code snippet should get you started :

#include "wx/wx.h"
#include "wx/sizer.h"

class BasicDrawPane : public wxPanel
{
    
public:
    BasicDrawPane(wxFrame* parent);
    
    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()
};


class MyApp: public wxApp
{
    bool OnInit();
    
    wxFrame *frame;
    BasicDrawPane * drawPane;
public:
        
};

IMPLEMENT_APP(MyApp)


bool MyApp::OnInit()
{
    wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
    frame = new wxFrame((wxFrame *)NULL, -1,  wxT("Hello wxDC"), wxPoint(50,50), wxSize(800,600));
	
    drawPane = new BasicDrawPane( (wxFrame*) frame );
    sizer->Add(drawPane, 1, wxEXPAND);
	
    frame->SetSizer(sizer);
    frame->SetAutoLayout(true);
	
    frame->Show();
    return true;
} 

BEGIN_EVENT_TABLE(BasicDrawPane, wxPanel)
// some useful events
/*
 EVT_MOTION(BasicDrawPane::mouseMoved)
 EVT_LEFT_DOWN(BasicDrawPane::mouseDown)
 EVT_LEFT_UP(BasicDrawPane::mouseReleased)
 EVT_RIGHT_DOWN(BasicDrawPane::rightClick)
 EVT_LEAVE_WINDOW(BasicDrawPane::mouseLeftWindow)
 EVT_KEY_DOWN(BasicDrawPane::keyPressed)
 EVT_KEY_UP(BasicDrawPane::keyReleased)
 EVT_MOUSEWHEEL(BasicDrawPane::mouseWheelMoved)
 */

// catch paint events
EVT_PAINT(BasicDrawPane::paintEvent)

END_EVENT_TABLE()


// some useful events
/*
 void BasicDrawPane::mouseMoved(wxMouseEvent& event) {}
 void BasicDrawPane::mouseDown(wxMouseEvent& event) {}
 void BasicDrawPane::mouseWheelMoved(wxMouseEvent& event) {}
 void BasicDrawPane::mouseReleased(wxMouseEvent& event) {}
 void BasicDrawPane::rightClick(wxMouseEvent& event) {}
 void BasicDrawPane::mouseLeftWindow(wxMouseEvent& event) {}
 void BasicDrawPane::keyPressed(wxKeyEvent& event) {}
 void BasicDrawPane::keyReleased(wxKeyEvent& event) {}
 */

BasicDrawPane::BasicDrawPane(wxFrame* parent) :
wxPanel(parent)
{
}

/*
 * 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 BasicDrawPane::paintEvent(wxPaintEvent & evt)
{
    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).
 *
 * In most cases, this will not be needed at all; simply handling
 * paint events and calling Refresh() when a refresh is needed
 * will do the job.
 */
void BasicDrawPane::paintNow()
{
    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 BasicDrawPane::render(wxDC&  dc)
{
    // draw some text
    dc.DrawText(wxT("Testing"), 40, 60); 
    
    // draw a circle
    dc.SetBrush(*wxGREEN_BRUSH); // green filling
    dc.SetPen( wxPen( wxColor(255,0,0), 5 ) ); // 5-pixels-thick red outline
    dc.DrawCircle( wxPoint(200,100), 25 /* radius */ );
    
    // draw a rectangle
    dc.SetBrush(*wxBLUE_BRUSH); // blue filling
    dc.SetPen( wxPen( wxColor(255,175,175), 10 ) ); // 10-pixels-thick pink outline
    dc.DrawRectangle( 300, 100, 400, 200 );
    
    // draw a line
    dc.SetPen( wxPen( wxColor(0,0,0), 3 ) ); // black line, 3 pixels thick
    dc.DrawLine( 300, 100, 700, 300 ); // draw line across the rectangle
    
    // Look at the wxDC docs to learn how to draw other stuff
}

Important Notes

  • You need to handle the paint event, and it must be able to redraw the whole drawing at any time. This is because your window manager may throw away your drawing at any time and ask you to draw it again later through a paint event.
    • 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).
  • Use a wx[Buffered]PaintDC in paint events, and a wxClientDC outside paint events.
  • If you do catch the paint event, you must create a wx[Buffered]PaintDC in it, even if you don't use it. If a wx[Buffered]PaintDC object isn't created, the event isn't correctly handled which can cause weird behavior.
  • Do not store the created DC or keep it for later in any way.

To avoid flickering

  • Try bufferred DCs, they are necessary on some platforms. wxBufferedDC and wxAutoBufferedPaintDC might help you.
  • Call ->SetDoubleBuffered(true); on the panel (On Windows only?)
  • Call SetBackgroundStyle(wxBG_STYLE_CUSTOM) ( See this : https://docs.wxwidgets.org/stable/wx_wxwindow.html#wxwindowsetbackgroundstyle )
    • On wx 2.9.1+, wxBG_STYLE_PAINT can help too and is clearer than "custom"
    • Or, alternatively, Catch the wxEVT_ERASE_BACKGROUND event

For all details, see article Flicker-Free Drawing. This generally does not happen on systems with composition (OS X, Windows Vista/7, linux with compositor installed)