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