HowTo : Creating a custom drawn widget - mcorino/wxRuby3 GitHub Wiki

     About      FAQ      User Guide      Reference documentation

Creating a custom drawn Widget

This tutorial demonstrates how to start to create a custom widget (that you paint yourself) by deriving from Wx::Window. It shows code to create a simple basic button that does nothing useful but can be used as a starting point.

Note that although this example draws a (rounded) rectangular component, nothing prevents you from changing the paint method to draw any shape or combination of shapes you would like.

Some pointers

  • Derive from Wx::Window for simple (i.e., not containing children) controls, not from Wx::Control, which is the base class for the common (and mostly native) controls.
  • Do not use Wx::PaintDC outside paint events (use Wx::ClientDC if you absolutely have to but better limit all your drawing to the Wx::EVT_PAINT handler).
  • Do not draw something on a Wx::ClientDC and then expect it will stay there forever (wrong because your window manager may throw away your drawing at any time, like when 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 visible. The best way to deal with this is to separate state/data from view. In the example the render routine reads variables describing current state and draws according to these variables. When something needs to change, don't render it straight away; instead, update the variables and call for a repaint. If well-coded, your paint routine should catch the change.

Example

Below is an example wxRuby program demonstrating a custom drawn widget.

require 'wx'

class MyCustomButton < Wx::Window
  
  def initialize(parent, text, id=Wx::ID_ANY)
    super(parent, id)
    @text = text
    @pressed = false
    @hover = false
    set_min_size(get_button_size)
    
    # set up event handlers
    evt_paint :on_paint
    evt_motion :on_mouse_moved
    evt_left_down :on_mouse_down
    evt_left_up :on_mouse_released
    evt_right_down :on_right_click
    evt_enter_window :on_enter_window
    evt_leave_window :on_leave_window
    evt_key_down :on_key_pressed
    evt_key_up  :on_key_released
    evt_mousewheel :on_mouse_wheel
  end
  
  protected
  
  # helper method to calculate the button's size
  # based on the current text and font using a temporary DC
  def get_button_size
    paint do |dc|
      sz = dc.get_text_size(@text)
      sz.inc_by([31, 21])
      sz
    end
  end
  
  # The actual rendering is put it in a separate
  # method so that it can be used from either the 
  # paint event handler or directly.
  # It use the Wx::Window#paint method to construct
  # an appropriate temporary DC (Wx::PaintDC when 
  # called from the paint event handler and Wx::ClientDC 
  # otherwise).
  def render
    sz = get_button_size
    sz.dec_by([1,1])
    paint do |dc|
      if @pressed
        dc.with_pen(Wx::MEDIUM_GREY_PEN) do
          dc.with_brush(Wx::MEDIUM_GREY_BRUSH) do
            dc.draw_rounded_rectangle([0, 0], sz, -0.2)
          end
          dc.with_brush(Wx::GREEN_BRUSH) do
            dc.draw_rounded_rectangle([1, 1], sz, -0.2)
            dc.with_text_foreground(:GREY) do
              dc.draw_text(@text, 16, 11)
            end
          end
        end
      else
        dc.with_pen(@hover ? Wx::GREEN_PEN : Wx::MEDIUM_GREY_PEN) do
            dc.with_brush(Wx::MEDIUM_GREY_BRUSH) do
              dc.draw_rounded_rectangle([1, 1], sz, -0.2)
            end
            dc.with_brush(Wx::LIGHT_GREY_BRUSH) do
              dc.draw_rounded_rectangle([0, 0], sz, -0.2)
              dc.with_text_foreground(:GREEN) do
                dc.draw_text(@text, 15, 10)
              end
            end
        end
      end
    end
  end

  def on_paint(_evt)
    render
  end

  def on_mouse_down(_evt)
    @pressed = true
    render
  end
  
  def on_mouse_released(_evt)
    @pressed = false
    render
    
    #wxMessageBox( wxT("You pressed a custom button") );
  end

  def on_enter_window(_evt)
    @hover = true
    render
  end
  
  def on_leave_window(_evt)
    @hover = false
    @pressed = false
    render
  end
  
  # currently unused events
  def on_mouse_moved(_evt); end
  def on_mouse_wheel(_evt); end
  def on_right_click(_evt); end
  def on_key_pressed(_evt); end
  def on_key_released(_evt); end
  
end

class MyWindow < Wx::Frame
  def initialize(title)
    super(nil, title: title)

    panel = Wx::Panel.new(self)
    
    # the sizer will take care of determining the needed scroll size
    # (if you don't use sizers you will need to manually set the viewport size)
    sizer = Wx::BoxSizer.new(Wx::VERTICAL) # or Wx::VBoxSizer.new

    btn_1 = MyCustomButton.new(panel, 'My Custom Button 1')
    sizer.add(btn_1, 0, Wx::ALL, 5)
    btn_2 = MyCustomButton.new(panel, 'Hello World!')
    sizer.add(btn_2, 0, Wx::ALL, 5)
    btn_3 = MyCustomButton.new(panel, 'Foo Bar')
    sizer.add(btn_3, 0, Wx::ALL, 5)
        
    panel.set_sizer(sizer)

    centre
  end
end

Wx::App.run do
  window = MyWindow.new("wxRuby Custom Widget Guide")
  window.show
end

The output:

screenshot1

Flickering

If you have flickering problems, see To avoid flickering in Drawing on a Panel Canvas

⚠️ **GitHub.com Fallback** ⚠️