IronRuby Pre Alpha 1 で WPF を試すの巻☆その2

前回の最後で「 WPF がちょっと楽しくなってしまって、IronPython に浮気中。こっちは少し頑張れば結構おもしろいものができそう〜」と書いたが、実は LL魂のライトニングトーク用のプレゼン資料がそれだった(動画がこちら)。
おかげで IronPythonWPF をぺけぺけ叩くノウハウはそこそこたまったので、またざっくりまとめて出してみたい。


とりあえず今回は「IronRuby で電卓」の完結編。
あらかじめ念を押しておくが、本当に出来る限りの事はやったのである。
最後まで読んだあとに石を投げたりしないでね……


さて、コードがこちら。

# wpf.rb
# Reference the WPF assemblies
require 'PresentationFramework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
require 'PresentationCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
require 'WindowsBase, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'

# Initialization Constants
Window      = System::Windows::Window
Application = System::Windows::Application
FontStyles  = System::Windows::FontStyles
FontWeights = System::Windows::FontWeights
Point       = System::Windows::Point
Thickness   = System::Windows::Thickness

Button      = System::Windows::Controls::Button
Canvas      = System::Windows::Controls::Canvas
StackPanel  = System::Windows::Controls::StackPanel
Label       = System::Windows::Controls::Label
TextBox     = System::Windows::Controls::TextBox
TextBlock   = System::Windows::Controls::TextBlock
RichTextBox = System::Windows::Controls::RichTextBox

Brushes         = System::Windows::Media::Brushes
Color           = System::Windows::Media::Color
ColorConverter  = System::Windows::Media::ColorConverter
FontFamily      = System::Windows::Media::FontFamily
GradientStop    = System::Windows::Media::GradientStop
LinearGradientBrush = System::Windows::Media::LinearGradientBrush
SolidColorBrush = System::Windows::Media::SolidColorBrush

DropShadowBitmapEffect = System::Windows::Media::Effects::DropShadowBitmapEffect

Rectangle = System::Windows::Shapes::Rectangle

FlowDocument = System::Windows::Documents::FlowDocument
Paragraph    = System::Windows::Documents::Paragraph
Run          = System::Windows::Documents::Run

これは以下のページを参考にさせていただき、さらに必要な require や定数定義を加えたものである。

First Look at IronRuby
http://weblogs.asp.net/scottgu/archive/2007/07/23/first-look-at-ironruby.aspx


続いて電卓本体。

require 'wpf'

def getColor(c)
  ColorConverter.new.convert_from_string(c)
end
def setPosi(view, x, y)
  Canvas.set_left view, x
  Canvas.set_top  view, y
end

w = Window.new
w.title = "calc"
#w.size_to_content = System::Windows::SizeToContent::WidthAndHeight
w.width = 238
w.height = 340

w.content = cv = Canvas.new

cv.children.add( cv2 = Canvas.new )
rect1 = Rectangle.new
rect1.stroke=Brushes.Black
rect1.radius_x = 0
rect1.radius_y = 0
rect1.stroke_thickness = 5
rect1.stroke_miter_limit=2
#rect1.stroke_end_line_cap=System::Windows::Media::PenLineCap::Flat
#rect1.stroke_start_line_cap=System::Windows::Media::PenLineCap::Flat
#rect1.stroke_line_join=System::Windows::Media::PenLineJoin::Miter
rect1.width = 231
rect1.height = 311
cv2.children.add rect1
setPosi rect1, -1, 0

rect1.fill = b = LinearGradientBrush.new
b.start_point = Point.new(0, 0.5)
b.end_point = Point.new(1, 0.5)
gs = GradientStop.new(getColor("sc#1.000000, 0.157502, 0.408674, 0.808030"), 0)
b.gradient_stops.add(gs)
b.gradient_stops.add(GradientStop.new(getColor("#ffffff"), 1))

cv.children.add( cv1 = Canvas.new )
cv1.children.add( tbox = TextBox.new )
tbox.name = "Result"
tbox.width = 135
tbox.height = 33.55
setPosi tbox, 20, 65
cv1.children.add( rbox = RichTextBox.new )
rbox.name = "Title"
rbox.is_read_only = true
rbox.width = 191
rbox.height = 48
setPosi rbox, 18, 11
rbox.border_brush = SolidColorBrush.new(getColor("sc#0.000000, 0.510939, 0.510939, 0.510930"))
rbox.background = SolidColorBrush.new(getColor("sc#0.000000, 1.000000, 1.000000, 1.000000"))
rbox.foreground = SolidColorBrush.new(getColor("#FF000000"))
rbox.document = doc = FlowDocument.new
doc.blocks.add( p1 = Paragraph.new )
p1.inlines.add( r1 = Run.new("IronCalc2007") )
r1.font_family = FontFamily.new("Perpetua Titling MT")
r1.font_style = FontStyles.italic
r1.font_weight = FontWeights.bold
r1.font_size = 20
r1.foreground = SolidColorBrush.new(getColor("sc#1.000000, 0.172911, 0.172911, 0.172910"))

buttonlist = [
  ["One", "1", 40, 40, 20, 110],
  ["Nine", "9", 40, 40, 120, 210],
  ["Eight", "8", 40, 40, 70, 210],
  ["Five", "5", 40, 40, 70, 162],
  ["Four", "4", 40, 40, 20, 162],
  ["Two", "2", 40, 40, 70, 110],
  ["Three", "3", 40, 40, 120, 110],
  ["Six", "6", 40, 40, 120, 162],
  ["Multiply", "*", 40, 40, 170, 162],
  ["Seven", "7", 40, 40, 20, 210],
  ["Subtract", "-", 40, 40, 170, 210],
  ["Zero", "0", 40, 40, 20, 260],
  ["DecimalPoint", ".", 40, 40, 70, 260],
  ["Equals", "=", 40, 40, 120, 260],
  ["Plus", "+", 40, 40, 170, 260],
  ["Divide", "/", 40, 40, 170, 110],
  ["Clear", "C", 40, 40, 169, 62],
]

expression = ""
buttonlist.each do |prop|
  cv1.children.add( b = Button.new )
  b.name = prop[0]
  b.content = prop[1]
  b.width = prop[2]
  b.height = prop[3]
  setPosi b, prop[4], prop[5]
  b.click do |sender, args|
    value = sender.content
    if value=="="
      #expression = eval(expression).to_s
      expression = "12345"
      tbox.text = expression
    else
      if value=="C"
        expression = ""
        tbox.text = expression
      else
        expression += value
        tbox.text = expression
      end
    end
  end
end

app = Application.new
app.run w

前回にも書いたとおり、Enumerable がまだ実装されていないため扱えない定数があるので、それは再現できていない(コード内にコメントとして残してある)。
また、元の XAML はツールで作っていると思われるので、Transform 系で明らかにゴミがいくつかあり、これらも省いている。


これを実行すると、見事! IronRuby による WPF電卓 が表示された。

ちゃんとイベントも実装しているので、ボタンを押すと画面に数字が表示される。
では試しに「1+2」を計算してみると……

12345

ありゃ。どこかのなにかみたいに計算を間違うバグでもあるんかいな?


実は、最後の最後になってようやく「 IronRuby に eval が実装されていない」ことに気づきました orz *1
さすがに「計算機そのもの」を実装する気にはなれず、しかたなく何を入力しても計算結果は「12345」に……イテテ。

*1:我ながらもっと早く気づけよと思う