HowTo : Scrolling - mcorino/wxRuby3 GitHub Wiki

     About      FAQ      User Guide      Reference documentation

Scrolling

In many cases, the functionality offered by the available widgets in combination with available sizers provides everything needed to create great looking window panes and dialogs perfectly fitting available display areas.

However, it is also not uncommon to encounter situations where the available space on the display is just not enough to get every bit of information crammed into widgets organized into the window available.
Luckily, wxRuby provides multiple options to work around such boundary situations. Widgets can be distributed over several pages of a wizard dialog or multiple pages of a notebook control.
Another option is to use a scrolling container window which can have a (virtual) content size larger than the actually visible viewport.

wxRuby provides three major scrolling widget base classes:

  1. Wx::ScrolledWindow provides a Wx::Panel like container window into which other widgets can be placed. Like Wx::Panel it handles TAB traversal and focus changes for child widgets.
  2. Wx::ScrolledControl is a base for scrolling widgets (typically not containing child widgets although aggregation to combine into new functionality is possible) that need themselves to process user input and/or display one or more items of data.
  3. Wx::ScrolledCanvas is a base for scrolling widgets without special consideration for child widgets of input processing and best suited for something like a drawing pane.

Note: A good (and extensive) example of the use of Wx::ScrolledCanvas can be found in the wxRuby-Shapes framework.

Scrolling widgets

Below is a simple example of using Wx::ScrolledWindow to create a scrolling window pane containing (way) more widgets than visible and providing a view into the content that can be scrolled.

require 'wx'

class MyWindow < Wx::Frame
  def initialize(title)
    super(nil, title: title)
    
    @scrolling_pane = Wx::ScrolledWindow.new(self, Wx::ID_ANY)

    # 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

    # add a (large) series of widgets
    120.times do |ix|
      button = Wx::Button.new(@scrolling_pane, label: "Button #{ix+1}")
      sizer.add(button, flag: Wx::ALL, border: 3)
    end

    @scrolling_pane.sizer = sizer

    # this part makes the scrollbars show up
    @scrolling_pane.fit_inside # ask the sizer about the needed size
    @scrolling_pane.set_scroll_rate(5, 5)
    
    centre
  end
end

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

The output:

screenshot1

Scrolling image widget

Below is a simple example of a simple scrolling widget displaying an image larger than the viewing area using Wx::ScrolledCanvas.

This simple widget could just as easily be build based on Wx::ScrolledControl. Just try replacing Wx::ScrolledCanvas for Wx::ScrolledControl. For most (if not all) custom widgets however it is advised to derive from ScrolledCanvas instead of ScrolledControl. See the documentation on Wx::Control (the base for ScrolledControl) for more information.

require 'wx'

class ScrollingImage < Wx::ScrolledCanvas
    def initialize(parent, image_path, id=Wx::ID_ANY)
      super(parent, id)
      image = Wx::Image.new(image_path)

      unless image.ok?
        Wx.message_box('there was an error loading the image')
        return
      end

      w = image.width
      h = image.height

      # init scrolled area size, scrolling speed, etc. 
      set_scrollbars(1, 1, w, h, 0, 0)

      @bitmap = Wx::Bitmap.new(image)

      # set up paint handler
      evt_paint :on_paint
    end

    def on_paint(_evt)
      paint_buffered do |dc|
          prepare_dc(dc)
          
          # render the image - in a real app, if your scrolled area
          # is somewhat big, you will want to draw only visible parts,
          # not everything like below 
          dc.draw_bitmap(@bitmap, 0, 0, false)
      end
    end
end

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

    panel = Wx::Panel.new(self)
    panel.set_extra_style(Wx::WS_EX_BLOCK_EVENTS)
    
    sizer = Wx::HBoxSizer.new

    scroll_image = ScrollingImage.new(panel, 'motyl.png')

    sizer.add(scroll_image, 1, flag: Wx::ALL|Wx::EXPAND, border: 20)
    
    panel.sizer = sizer
    panel.layout
    sizer.fit(panel)

    centre
  end
end

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

The output:

screenshot2

Note: To run this example you will need the image file too. Download this here.

Synchronize two scrolling widgets

In some cases, it can be necessary to have two scrolling widgets displayed of which the view needs to be synchronized when scrolling.

Below is a simple example of two synchronized scrolling image widgets.

require 'wx'

class ScrollingImage < Wx::ScrolledCanvas
    def initialize(parent, image_path, id=Wx::ID_ANY)
      super(parent, id)
      image = Wx::Image.new(image_path)

      unless image.ok?
        Wx.message_box('there was an error loading the image')
        return
      end

      w = image.width
      h = image.height

      # init scrolled area size, scrolling speed, etc. 
      set_scrollbars(1, 1, w, h, 0, 0)

      @bitmap = Wx::Bitmap.new(image)
      @peer = nil

      # set up paint handler
      evt_paint :on_paint
      # set up scroll interception
      evt_scrollwin_thumbtrack :on_scroll
      evt_scrollwin_thumbrelease :on_scroll
      evt_scrollwin_lineup :on_scroll
      evt_scrollwin_linedown :on_scroll
    end

    attr_accessor :peer
    
    def on_paint(_evt)
      paint_buffered do |dc|
          prepare_dc(dc)
          
          # render the image - in a real app, if your scrolled area
          # is somewhat big, you will want to draw only visible parts,
          # not everything like below 
          dc.draw_bitmap(@bitmap, 0, 0, false)
      end
    end
  
    def on_scroll(evt)
      if @peer
        @peer.scroll(scroll_pos(Wx::HORIZONTAL), scroll_pos(Wx::VERTICAL))
      end
      evt.skip # let the event go
    end
end

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

    panel = Wx::Panel.new(self)
    panel.set_extra_style(Wx::WS_EX_BLOCK_EVENTS)
    
    sizer = Wx::HBoxSizer.new

    scroll_image1 = ScrollingImage.new(panel, 'motyl.png')
    scroll_image2 = ScrollingImage.new(panel, 'motyl.png')

    scroll_image2.peer = scroll_image1
    scroll_image1.peer = scroll_image2
    
    sizer.add(scroll_image1, 1, flag: Wx::ALL|Wx::EXPAND, border: 10)
    sizer.add(scroll_image2, 1, flag: Wx::ALL|Wx::EXPAND, border: 10)
    
    panel.sizer = sizer
    panel.layout
    sizer.fit(panel)

    centre
  end
end

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

The output:

screenshot3

Note: To run this example you will need the image file too. Download this here.

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