« RubyのWindowsバイナリ選び Main Postfixでのエイリアス状況の確認 »

Ruby/Tk:キャンバス上のアイテム作成

Ruby/Tkでキャンバス上へのアイテム作成で、以下のように書く例がよく紹介されている:

#!ruby
require "tk"
c = TkCanvas.new {pack}
TkcLine.new(c, 0, 0, 50, 50)
Tk.mainloop

だが、これだとアイテム生成毎にTkcLine.new(…やら、TkcRectangle.new(…やらと常にTkcを付けて、またアイテム種をキャピタライズして書かなければならず面倒で、アイテムを生成するキャンバス情報もいちいち持ち回る必要がある。

他の言語の呼び出しはもっと簡単(気分的に?)になっていて、Tclでのサブコマンドの利用とほぼ同様なのでそれほど違和感がない:

  • TkDocs – Tk Tutorial – Canvas

    • Tcl: .canvas create line 10 10 200 50
    • Ruby: TkcLine.new( canvas, 10, 10, 200, 50)
    • Perl: $canvas->create_line(10,10,200,50);
    • Python: canvas.create_line(10, 10, 200, 50)

そこで、これを他の言語(PerlやPython)と同様にできないのかと、以下のようなTkCanvasクラスへのアイテム生成のためのメソッド追加を考えてみた:

class TkCanvas
  def create_arc(*args); TkcArc.new(self, args); end
  def create_bitmap(*args); TkcBitmap.new(self, args); end
  def create_image(*args); TkcImage.new(self, args); end
  def create_line(*args); TkcLine.new(self, args); end
  def create_oval(*args); TkcOval.new(self, args); end
  def create_polygon(*args); TkcPolygon.new(self, args); end
  def create_rectangle(*args); TkcRectangle.new(self, args); end
  def create_text(*args); TkcText.new(self, args); end
  def create_window(*args); TkcWindow.new(self, args); end
end

(当初は、メソッド名をcreate_lineなどでなくlineだけ、とか、create_polygonは短くpolyとすることも考えたのだが、他の言語の状況にも合わせてみた)

これで呼び出しをすると記述が簡単になって便利だが、改めてRubyのライブラリファイルのtk/canvas.rb【注1】

【注1】

ファイルの場所はWindowsだと「C:/Ruby21-x64/lib/ruby/2.1.0/」など。また、このファイルは上位フォルダのtkcanvas.rbでrequireされている)

を見てみると、内部にcreateメソッドがあり、TkCanvasインスタンスへの以下のような指示で作成できることがわかった:

c = TkCanvas
c.create("line", 10, 10, 200, 50)

アイテム個別のTkcXxxxクラスを使ってアイテムを生成するより、こちらのほうが把握しやすいのでは。

さらに見ていると、このTkCanvas::createメソッドでは、以下のようにTkcItemクラスのtype2classメソッドを使って、アイテム文字列からクラスへのマッピングを行なっていた:

class Tk::Canvas<TkWindow
  include TkCanvasItemConfig
  ...
  def create(type, *args)
    if type.kind_of?(Class) && type <TkcItem
      # do nothing
    elsif TkcItem.type2class(type.to_s)
      type = TkcItem.type2class(type.to_s)
    else
      fail ArgumentError, "type must a subclass of TkcItem class, or a string in CItemTypeToClass"
    end
    type.create(self, *args)
  end
  ...
end

同ファイルの下部にあるTkcItemクラスを見ていくと、type2classメソッドではアイテム名からクラスへの対応表をCItemTypeToClassハッシュに保持している:

class TkcItem<TkObject
  ...
  def TkcItem.type2class(type)
    CItemTypeToClass[type]
  end
  ...
end

個々のアイテムクラスの情報は、さらに下部にあるクラス定義で行われていた:

class TkcLine<TkcItem
  CItemTypeName = 'line'.freeze
  CItemTypeToClass[CItemTypeName] = self
end
...

このCItemTypeToClassハッシュの情報を使うと、TkCanvasへのcreate_xxxxメソッドの登録は以下のようにひとまとめにできる(class_evalを使った):

class TkCanvas
  TkcItem::CItemTypeToClass.each {|k, v| class_eval("def create_#{k}(*args); #{v}.create(self, *args); end")}
end

上記を使うと以下のように記述できる:

c = TkCanvas
c.create_line(10, 10, 200, 50)

create_の入力は増えるわけだが、いちいちTkcXxxx.new(c, …)とせずこちらでいいんじゃないかなぁ。

リンク

class_eval

Ruby/Tk紹介

Leave a comment

Your comment