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

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.
- 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.
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:
If you have flickering problems, see To avoid flickering in Drawing on a Panel Canvas