永井@知能.九工大です.

[ruby-talk:169227] のサンプルですが,割と有用になりうるケースと
思いましたので,サンプル兼ライブラリとして少し改善してみました.
添付のものがそうですが,1.8.4 の ext/tk/sample ディレクトリに
ほおり込んでもかまわないでしょうか.
それとも,時期が悪い上にただでさえファイルが多いんだから
ダメでしょうか.
--
                                       永井 秀利 (九工大 知能情報)
                                           nagai / ai.kyutech.ac.jp
=====================================================================
#
#  Tk::ScrollFrame class
#
#    This widget class is a frame widget with scrollbars.
#    The ScrollFrame doesn't propagate the size of embedded widgets.
#    When it is configured, scrollregion of the container is changed.
#
#    Scrollbars can be toggled by Tk::ScrollFrame#vscroll & hscroll.
#    If horizontal or virtical scrollbar is turned off, the horizontal 
#    or virtical size of embedded widgets is propagated.
#
#                         Hidetoshi NAGAI (nagai / ai.kyutech.ac.jp)
#
require 'tk'

class Tk::ScrollFrame < TkFrame
  include TkComposite

  DEFAULT_WIDTH  = 200
  DEFAULT_HEIGHT = 200

  def initialize_composite(keys={})
    @frame.configure(:width=>DEFAULT_WIDTH, :height=>DEFAULT_HEIGHT)

    # create scrollbars
    @h_scroll = TkScrollbar.new(@frame, 'orient'=>'horizontal')
    @v_scroll = TkScrollbar.new(@frame, 'orient'=>'vertical')

    # create a canvas widget
    @canvas = TkCanvas.new(@frame, 
                           :borderwidth=>0, :selectborderwidth=>0, 
                           :highlightthickness=>0)

    # allignment
    TkGrid.rowconfigure(@frame, 0, 'weight'=>1, 'minsize'=>0)
    TkGrid.columnconfigure(@frame, 0, 'weight'=>1, 'minsize'=>0)
    @canvas.grid('row'=>0, 'column'=>0, 'sticky'=>'news')
    @frame.grid_propagate(false)

    # assign scrollbars
    @canvas.xscrollbar(@h_scroll)
    @canvas.yscrollbar(@v_scroll)

    # convert hash keys
    keys = _symbolkey2str(keys)

    # check options for the frame
    framekeys = {}
    if keys.key?('classname')
       keys['class'] = keys.delete('classname')
    end
    if @classname = keys.delete('class')
      framekeys['class'] = @classname
    end
    if @colormap  = keys.delete('colormap')
      framekeys['colormap'] = @colormap
    end
    if @container = keys.delete('container')
      framekeys['container'] = @container
    end
    if @visual    = keys.delete('visual')
      framekeys['visual'] = @visual
    end
    if @classname.kind_of? TkBindTag
      @db_class = @classname
      @classname = @classname.id
    elsif @classname
      @db_class = TkDatabaseClass.new(@classname)
    else
      @db_class = self.class
      @classname = @db_class::WidgetClassName
    end

    # create base frame
    @base = TkFrame.new(@canvas, framekeys)

    # embed base frame
    @cwin = TkcWindow.new(@canvas, [0, 0], :window=>@base, :anchor=>'nw')
    @canvas.scrollregion(@cwin.bbox)

    # binding to reset scrollregion
    @base.bind('Configure'){ _reset_scrollregion(nil, nil) }

    # set default receiver of method calls
    @path = @base.path

    # scrollbars ON
    vscroll(keys.delete('vscroll'){true})
    hscroll(keys.delete('hscroll'){true})

    # please check the differences of the following definitions
    option_methods(
      :scrollbarwidth
    )

    # set receiver widgets for configure methods (with alias)
    delegate_alias('scrollbarrelief', 'relief', @h_scroll, @v_scroll)

    # set receiver widgets for configure methods
    delegate('DEFAULT', @base)
    delegate('background', @frame, @base, @canvas, @h_scroll, @v_scroll)
    delegate('width', @frame)
    delegate('height', @frame)
    delegate('activebackground', @h_scroll, @v_scroll)
    delegate('troughcolor', @h_scroll, @v_scroll)
    delegate('repeatdelay', @h_scroll, @v_scroll)
    delegate('repeatinterval', @h_scroll, @v_scroll)
    delegate('borderwidth', @frame)
    delegate('relief', @frame)

    # do configure
    configure keys unless keys.empty?
  end

  # callback for Configure event
  def _reset_scrollregion(h_mod=nil, v_mod=nil)
    cx1, cy1, cx2, cy2 = @canvas.scrollregion
    x1, y1, x2, y2 = @cwin.bbox
    @canvas.scrollregion([x1, y1, x2, y2])

    if h_mod.nil? && v_mod.nil?
      if x2 != cx2 && TkGrid.info(@h_scroll).size == 0
        @frame.grid_propagate(true)
        @canvas.width  = x2
        Tk.update_idletasks
        @frame.grid_propagate(false)
      end
      if y2 != cy2 && TkGrid.info(@v_scroll).size == 0
        @frame.grid_propagate(true)
        @canvas.height = y2
        Tk.update_idletasks
        @frame.grid_propagate(false)
      end
    else
      @h_scroll.ungrid if h_mod == false
      @v_scroll.ungrid if v_mod == false

      h_flag = (TkGrid.info(@h_scroll).size == 0)
      v_flag = (TkGrid.info(@v_scroll).size == 0)

      @frame.grid_propagate(true)

      @canvas.width  = (h_flag)? x2: @canvas.winfo_width
      @canvas.height = (v_flag)? y2: @canvas.winfo_height

      @h_scroll.grid('row'=>1, 'column'=>0, 'sticky'=>'ew') if h_mod
      @v_scroll.grid('row'=>0, 'column'=>1, 'sticky'=>'ns') if v_mod

      Tk.update_idletasks

      @frame.grid_propagate(false)
    end
  end
  private :_reset_scrollregion

  # forbid to change binding of @base frame
  def bind(*args)
    @frame.bind(*args)
  end
  def bind_append(*args)
    @frame.bind_append(*args)
  end
  def bind_remove(*args)
    @frame.bind_remove(*args)
  end
  def bindinfo(*args)
    @frame.bindinfo(*args)
  end

  # set width of scrollbar
  def scrollbarwidth(width = nil)
    if width
      @h_scroll.width(width)
      @v_scroll.width(width)
    else
      @h_scroll.width
    end
  end

  # vertical scrollbar : ON/OFF
  def vscroll(mode)
    Tk.update_idletasks
    st = TkGrid.info(@v_scroll)
    if mode && st.size == 0 then
      @v_scroll.grid('row'=>0, 'column'=>1, 'sticky'=>'ns')
      _reset_scrollregion(nil, true)
    elsif !mode && st.size != 0 then
      _reset_scrollregion(nil, false)
    else
      _reset_scrollregion(nil, nil)
    end
    self
  end

  # horizontal scrollbar : ON/OFF
  def hscroll(mode)
    Tk.update_idletasks
    st = TkGrid.info(@h_scroll)
    if mode && st.size == 0 then
      _reset_scrollregion(true, nil)
    elsif !mode && st.size != 0 then
      _reset_scrollregion(false, nil)
    else
      _reset_scrollregion(nil, nil)
    end
    self
  end
end

# test
if __FILE__ == $0
  f = Tk::ScrollFrame.new(:scrollbarwidth=>10, :width=>300, :height=>200)
  f.pack(:expand=>true, :fill=>:both)

  TkButton.new(f, :text=>'foo button', :command=>proc{puts 'foo'}).pack
  TkButton.new(f, :text=>'baaar button', :command=>proc{puts 'baaar'}).pack
  TkButton.new(f, :text=>'baz button', :command=>proc{puts 'baz'}).pack
  TkButton.new(f, :text=>'hoge hoge button', 
               :command=>proc{puts 'hoge hoge'}).pack(:side=>:bottom)

  # f.hscroll(false)

  Tk.after(3000){
    t = TkText.new(f).pack(:expand=>true, :fill=>:both)
    t.insert(:end, 'Here is a text widget')
  }

  Tk.after(6000){ f.vscroll(false) }

  Tk.after(9000){ f.vscroll(true) }

  Tk.after(12000){ f.hscroll(false) }

  Tk.after(15000){ f.hscroll(true) }

  Tk.mainloop
end