Skip to content

HowTo : Drawing on a Panel Canvas

Martin Corino edited this page Mar 27, 2024 · 1 revision
     About      FAQ      User Guide      Reference documentation

Drawing on a Panel Canvas

One of the simplest ways to implement some image painting functionality for an application is using a Wx::Panel as to paint on. The following example code shows a basic drawing pane derived from Wx::Panel:

require 'wx'

class SimpleDrawingPane < Wx::Panel
  
  def initialize(parent, id=Wx::ID_ANY)
    super(parent, id)
    
    # connecting this event handler is essential
    evt_paint :on_paint
    
    # connecting the following event handlers might be useful
    # evt_erase_background :on_erase_background
    # evt_motion :on_motion
    # evt_left_down :on_left_down
    # evt_left_up :on_left_up
    # evt_left_up :on_right_down
    # evt_left_up :on_right_up
    # evt_left_dclick :on_left_double_click
    # evt_right_dclick :on_right_double_click
    # evt_left_up :on_leave_window
    # evt_left_up :on_enter_window
    # evt_left_up :on_key_down
    # evt_left_up :on_key_up
    # evt_left_up :on_mousewheel
  end
  
  def on_paint(evt)
    # Drawing in the EVT_PAINT handler requires a Wx::PaintDC which we could create ourselves
    # but luckily Wx::Window offers the convenience methods `#paint` and `#paint_buffered` which
    # nicely handle creating **and** cleaning up the DC for us 
    self.paint { |dc| do_paint(dc) }
  end
  
  protected
  
  # Separating the actual drawing operations this way will allow (later) addition of drawing functionality
  # on other DCs than just a Paint DC (like for example a Memory DC for painting a bitmap image).
  def do_paint(dc)
    # draw some text
    dc.draw_text('Testing', 40, 60)

    # draw a circle
    dc.set_brush(Wx::GREEN_BRUSH) # green filling
    dc.set_pen(Wx::Pen.new(Wx::Colour.new(255,0,0), 5)) # 5-pixels-thick red outline
    dc.draw_circle(Wx::Point.new(200,100),  # centre 
                   25)                      # radius 
    
    # draw a rectangle
    dc.set_brush(Wx::BLUE_BRUSH) # blue filling
    dc.set_pen(Wx::Pen.new(Wx::Colour.new(255,175,175), 10)) # 10-pixels-thick pink outline
    dc.draw_rectangle([300, 100],   # topleft 
                      [400, 200])   # size

    # draw a line
    dc.set_pen(Wx::Pen.new(Wx::Colour.new(0,0,0), 3)) # black line, 3 pixels thick
    dc.draw_line(300, 100, 700, 300) # draw line across the rectangle
  end
  
end

This drawing pane could than be used in an application frame like this:

Wx::App.run do
  frame = Wx::Frame.new(nil, title: 'Hello SimpleDrawingPane', pos: [50,50], size: [800,600])
  sizer = Wx::HBoxSizer.new
  sizer.add(SimpleDrawingPane.new(frame), 1, Wx::EXPAND)
  frame.sizer = sizer
  frame.set_auto_layout(true)
  frame.show
end

Important to note

  • You must handle the paint event and the connected handler 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; i.e. the actual drawing routine reads variables describing the current state and draws according to this state. When something needs to change, don't draw it straight away; instead update state variables and call for a repaint (Wx::Window#refresh).
  • Use a Wx::PaintDC (or Wx::BufferedPaintDC but see below) in paint events, and a Wx::ClientDC outside paint events.
  • If you do handle the paint event, you must create a Wx::PaintDC in it, even if you don't use it. If a Wx::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

Clone this wiki locally