User Guide : wxRuby Extensions - mcorino/wxRuby3 GitHub Wiki

     About      HowTo      FAQ      Reference documentation

Keyword Constructors

The Keyword Constructors extension allows the use of Ruby hash-style keyword arguments in constructors of common wxRuby Windows, Frame, Dialog and Control classes.

Introduction

Building a GUI in wxRuby involves lots of calls to new, but these methods often have long (optional) argument lists. Often the default values for many of these arguments are correct. For example, if you're using a sizer-based layout, you usually don't want to specify a size for widgets, but you still have to type

Wx::TreeCtrl.new( parent, -1, Wx::DEFAULT_POSITION, Wx::DEFAULT_SIZE, Wx::NO_BUTTONS )

just to create a standard TreeCtrl with the 'no buttons' style. If you want to specify the 'NO BUTTONS' style, you can't avoid all the typing of DEFAULT_POSITION etc.

Basic Keyword Constructors

With keyword_constructors, you could write the above as

TreeCtrl.new(parent, :style => Wx::NO_BUTTONS)

And it will assume you want the default id (-1), and the default size and position. If you want to specify an explicit size, you can do so:

TreeCtrl.new(parent, :size => Wx::Size.new(100, 300))

For brevity, this module also allows you to specify positions and sizes using a a two-element array:

TreeCtrl.new(parent, :size => [100, 300])

Similarly with position:

TreeCtrl.new(parent, :pos => Wx::Point.new(5, 25))

TreeCtrl.new(parent, :pos => [5, 25])

You can have multiple keyword arguments:

TreeCtrl.new(parent, :pos => [5, 25], :size => [100, 300] )

No ID required

As with position and size, you usually don't want to deal with assigning unique ids to every widget and frame you create - it's a C++ hangover that often seems clunky in Ruby. The Event Handling extensions allows you to set up event handling without having to use ids, and if no :id argument is supplied to a constructor, the default (-1) will be passed. This will allocate a unique ID for the window automatically (all such automatically-assigned IDs are negative and so will not conflict with any user-defined IDs as long as they are positive).

There are occasions when a specific ID does need to be used - for example, to tell wxRuby that a button is a 'stock' item, so that it can be displayed using platform-standard text and icon. To do this, simply pass an :id argument to the constructor - here, the system's standard 'preview' button

Wx::Button.new(parent, :id => Wx::ID_PREVIEW)

Class-specific arguments

The arguments :size, :pos and :style are common to many wxRuby window classes. The new methods of these classes also have parameters that are specific to those classes; for example, the text label on a button, or the initial value of a text control.

Wx::Button.new(parent, :label => 'press me')
Wx::TextCtrl.new(parent, :value => 'type some text here')

The keyword names of these arguments can be found by looking at the WxRuby documentation, in the relevant class's new method. You can also get a string description of the class's new method parameters within Ruby by doing:

puts Wx::TextCtrl.describe_constructor()

This will print a list of the argument names expected by the class's new method, and the correct type for them.

Mixing positional and keyword arguments

To support existing code, and to avoid forcing the use of more verbose keyword-style arguments where they're not desired, you can mix positional and keyword arguments, omitting or including ids as desired.

Wx::Button.new(parent, 'press me', :style => Wx::BU_RIGHT)

Handling complex defaults or version differences

To support complex (context-dependent) defaults and/or auto conversion of arguments for backwards compatibility, the keyword constructors extension allows the definition of lambdas or procs to be associated with a parameter specification.

Ruby-style accessors

The wxWidgets API, in typical C++ style, has lots of accessor methods like

  • GetPosition()
  • SetSize(a_size)
  • IsChecked()
  • CanUndo()
  • HasStyle(a_style)

which in wxRuby are mapped to Ruby methods like get_position, set_size etc.

In Ruby however these kind of methods that set, get or query attributes or state are normally simply called by the attribute name or, in other cases, by a predicate method like:

pos = frame.position
frame.size = a_size
item.checked?
control.can_undo?
window.has_style?(a_style)

With wxRuby3 (most of) the API methods that begin with get_, set_, is_, can_ and has_ are identified and the wxRuby API will have Ruby-style accessor aliases defined for those (appropriately decorated as needed). Note that if you are calling a 'setter' method on self, you must explicitly send the message to self like:

# set's self size to be 100px by 100px
self.size = Wx::Size.new(100, 100)

since this will not work as you expect it to:

# only sets the value of a local variable 'size'
size = Wx::Size.new

Initializer blocks

All wxRuby Windows, Frame, Dialog, Control and Sizer classes support construction initialization blocks. When constructing instances of these classes, a Ruby block can be provided that will be executed after the construction of the instance passing the instance as the single argument for the block.

When, for example, creating a frame window with a panel with some widgets, this could be coded like this:

class MyWindow < Wx::Frame
  def initialize(title)
    super(nil, title: title)
    Wx::Panel.new(self) do |panel|
      
      Wx::HBoxSizer.new do |wrapper|
    
        Wx::FlexGridSizer.new(3, 2, 5, 5) do |sizer|
        
          [[Wx::StaticText.new(panel, label: 'Username')],
           [Wx::TextCtrl.new(panel), 0, Wx::EXPAND],
           [Wx::StaticText.new(panel, label: 'Password')],
           [Wx::TextCtrl.new(panel), 0, Wx::EXPAND],
           [Wx::StaticText.new(panel, label: 'Address')],
           [Wx::TextCtrl.new(panel, style: Wx::TE_MULTILINE|Wx::TE_NO_VSCROLL), 0, Wx::EXPAND]
          ].each { |args| sizer.add(*args) }
        
          sizer.add_growable_row(2, 1)
          sizer.add_growable_col(1, 1)
        end
        
        wrapper.add(sizer, Wx::SizerFlags.new(1).expand.border(Wx::ALL, 15))
        
        panel.sizer = wrapper
      end
    end
    
    centre
  end
end

This can help structure the code and make it more comprehensible.

As the return value constructor+block combination still is the newly constructed instance, the above example could even be coded like this:

class MyWindow < Wx::Frame
  def initialize(title)
    super(nil, title: title)
    Wx::Panel.new(self) do |panel|

      panel.sizer = Wx::HBoxSizer.new do |wrapper|

        wrapper.add(Wx::FlexGridSizer.new(3, 2, 5, 5) do |sizer|
        
          [[Wx::StaticText.new(panel, label: 'Username')],
           [Wx::TextCtrl.new(panel), 0, Wx::EXPAND],
           [Wx::StaticText.new(panel, label: 'Password')],
           [Wx::TextCtrl.new(panel), 0, Wx::EXPAND],
           [Wx::StaticText.new(panel, label: 'Address')],
           [Wx::TextCtrl.new(panel, style: Wx::TE_MULTILINE|Wx::TE_NO_VSCROLL), 0, Wx::EXPAND]
          ].each { |args| sizer.add(*args) }
        
          sizer.add_growable_row(2, 1)
          sizer.add_growable_col(1, 1)
        end, Wx::SizerFlags.new(1).expand.border(Wx::ALL, 15))
      end
    end
    
    centre
  end
end

Ruby Enumerators

Many wxRuby classes (Windows or others) provide methods with which lists of contained elements can be retrieved or contained ("child") elements can be iterated.
Examples of this are Wx::Window#get_children, Wx::TreeCtrl#get_first_child/Wx::TreeCtrl#get_next_child and Wx::Sizer#get_children.

In order to provide better Ruby style enumeration options (most) classes providing these kind of methods have been enhanced with enumerator methods like Wx::Window#each_child, Wx::TreeCtrl#each_item_child and Wx::Sizer#each_child. These enumerator methods can either be called with a block to be called for each element enumerated for classic style Ruby enumeration or called without a block to return a more modern Ruby Enumerator instance.

To enumerate the child elements placed in a Sizer the following is possible:

# classic enumeration
sizer.each_child do |szr_item|
  
  if szr_item.spacer?
    puts 'Spacer item'
  elsif szr_item.window?
    puts 'Window item'
  elsif szr_item.sizer?
    puts 'Sizer item'
  else
    puts 'Oops! Unknown item'
  end

end

# or modern enumeration
puts "Sizer contains #{sizer.each_child.select { |szr_item| szr_item.window? }.size} Window items"

In some the containment and iteration are such essential class features that a standard #each method is provided and the Enumerable module has been mixed in. See Wx::TreeCtrl for example.

Check the reference documentation for a class in case iteration of child elements is needed to see if enumerator methods are available.

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