程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 用IronRuby創建WPF應用程序

用IronRuby創建WPF應用程序

編輯:關於.NET

我曾在早期的博文中介紹過IronRuby。在文章中,我擴展了IronRuby的基礎 知識,來解釋需要在Rail應用程序所做的額外工作,好讓大家繼續深入.NET所實 現Ruby語言,但這方面的內容並不夠。所以現在我想深入地談談IronRuby與項目 的兼容性,以便開發全新的應用程序來說明IronRuby和.NET之間的互操作性。實 際上,我們會使用WPF(Windows Presentation Foundation),它是.NET Framework的組件,我們可以用它創建富媒體和圖形界面。

WPF基礎

再次申明,WPF是.NET Framework組件之一,負責呈現富用戶界面和其他媒體 。它不是.NET Framework中唯一可完成該功能的函數庫集,Window Form也可以 完成類似工作,在我們需要創建炫目效果的時候,WPF會顯得十分有用。無論是 演示文檔、視頻、數據錄入表格、某些類型的數據可視化(這是我最希望做的, 尤其用IronRuby完成,後面的故事更精彩)抑或用動畫把以上的都串聯起來,你 很可能會發現在給Windows開發這些應用程序的時候 WPF可以滿足你的需求。

舉例說明。某一天午飯時間,我創建了基於WPF的類似於時鐘的應用程序—— 我喜歡參考WPF的“Hello,Wold”應用程序——於是決定使用IronRuby。

注:學習本示例的過程中,需要參考WPF文檔。

示例程序

require 'WindowsBase'
require 'PresentationFramework'
require 'PresentationCore'
require 'System.Core, Version=3.5.0.0, Culture=neutral,  PublicKeyToken=b77a5c561934e089'

class Clock

   CLOCK_WIDTH   = 150
   CLOCK_HEIGHT  = 150
   LABEL_HEIGHT  = CLOCK_HEIGHT / 7
   LABEL_WIDTH   = CLOCK_WIDTH / 7
   RADIUS     = CLOCK_WIDTH / 2
   RADS      = Math::PI / 180
   MIN_LOCATIONS  = {}
   HOUR_LOCATIONS = {}

   def run!
     plot_locations

     # build our window
     @window = System::Windows::Window.new
     @window.background =  System::Windows::Media::Brushes.LightGray
     @window.width = CLOCK_WIDTH * 2
     @window.height = CLOCK_HEIGHT * 2
     @window.resize_mode =  System::Windows::ResizeMode.NoResize

     @canvas = System::Windows::Controls::Canvas.new
     @canvas.width = CLOCK_WIDTH
     @canvas.height = CLOCK_HEIGHT

     # create shapes to represent clock hands
     @minute_hand = System::Windows::Shapes::Line.new
     @minute_hand.stroke =  System::Windows::Media::Brushes.Black
     @minute_hand.stroke_thickness = 1 
     @minute_hand.x1 = CLOCK_WIDTH / 2
     @minute_hand.y1 = CLOCK_HEIGHT / 2

     @hour_hand = System::Windows::Shapes::Line.new
     @hour_hand.stroke =  System::Windows::Media::Brushes.Black
     @hour_hand.stroke_thickness = 3
     @hour_hand.x1 = CLOCK_WIDTH / 2
     @hour_hand.y1 = CLOCK_HEIGHT / 2

     # .. and stick them to our canvas
     @canvas.children.add(@minute_hand)
     @canvas.children.add(@hour_hand)

     plot_face # draw a clock face
     plot_labels # draw clock numbers
     plot_hands # draw minute / hour hands

     @window.content = @canvas
     app = System::Windows::Application.new
     app.run(@window)
     # the Application object handles the lifecycle of  our app
     # including the execution loop
   end

   # determine 2 sets of equidistant points around the  circumference of a circle
   # of CLOCK_WIDTH and CLOCK_HEIGHT dimensions.
   def plot_locations
     for i in (0..60) # 60 minutes, and 12 hours
       a = i * 6
       x = (RADIUS * Math.sin(a * RADS)).to_i +  (CLOCK_WIDTH / 2)
       y = (CLOCK_HEIGHT / 2) - (RADIUS * Math.cos(a  * RADS)).to_i
       coords = [x, y]
       HOUR_LOCATIONS[i / 5] = coords if i % 5 == 0  # is this also an 'hour' location (ie. every 5 minutes)? 
       MIN_LOCATIONS[i] = coords
     end
   end

   # draws a circle to represent the clock's face
   def plot_face
     extra_x = (CLOCK_WIDTH * 0.15) # pad our circle a  little
     extra_y = (CLOCK_HEIGHT * 0.15)
     face = System::Windows::Shapes::Ellipse.new
     face.fill = System::Windows::Media::Brushes.White
     face.width = CLOCK_WIDTH + extra_x
     face.height = CLOCK_HEIGHT + extra_y
     face.margin = System::Windows::Thickness.new(0 -  (extra_x/2), 0 - (extra_y/2), 0, 0)
     face.stroke = System::Windows::Media::Brushes.Gray #  give it a slight border
     face.stroke_thickness = 1
     System::Windows::Controls::Canvas.set_z_index(face, -1) #  send our circle to the back
     @canvas.children.add(face) # add the clock face to  our canvas
   end

   # at each point along the hour locations, put a  number
   def plot_labels
     HOUR_LOCATIONS.each_pair do |p, coords|
       unless p == 0
         lbl = System::Windows::Controls::Label.new
         lbl.horizontal_content_alignment =  System::Windows::HorizontalAlignment.Center
         lbl.width = LABEL_WIDTH
         lbl.height = LABEL_HEIGHT
         lbl.content = p.to_s
         lbl.margin = System::Windows::Thickness.new (coords[0] - (LABEL_WIDTH / 2), coords[1] - (LABEL_HEIGHT /  2), 0, 0)
         lbl.padding = System::Windows::Thickness.new(0,  0, 0, 0)
         @canvas.children.add(lbl)
       end
     end
   end

   def plot_hands
     time = Time.now
     hours = time.hour
     minutes = time.min

     if !@minutes || minutes != @minutes
       @hours = hours >= 12 ? hours - 12 :  hours
       @minutes = minutes == 0 ? 60 : minutes
       # Dispatcher.BeginInvoke() is asynchronous, though  it probably doesn't matter too much here
       @minute_hand.dispatcher.begin_invoke (System::Windows::Threading::DispatcherPriority.Render,  System::Action.new {
         @minute_hand.x2 = MIN_LOCATIONS[@minutes][0]
         @minute_hand.y2 = MIN_LOCATIONS[@minutes][1]
         @hour_hand.x2 = HOUR_LOCATIONS[@hours][0]
         @hour_hand.y2 = HOUR_LOCATIONS[@hours][1]
       })
     end
   end

end

clock = Clock.new

timer = System::Timers::Timer.new
timer.interval = 1000
timer.elapsed { clock.plot_hands }
timer.enabled = true

clock.run!

查看GitHub站點的實例效果

世上沒有完美的事物,但我認為本實例使用數據可視化來說明IronRuby與WPF 間的互操作。我相信你會細心研究以上代碼,但我仍要逐步解析它的關鍵之處。 (順便提一下,通過ir來運行本實例可第一時間看到效果)。

現在,我們使用的是IronRuby,並非我之前提到的那樣純使用Ruby代碼並用 ir(IronRuby解析器)運行代碼來以證明它的兼容性。本文的主旨在於說明.NET 命名空間和Ruby模塊,.NET類和Ruby類之間的明顯相似性。在這方面我覺得無需 多說,你也許已經能夠熟練地應用Ruby 的繪圖函數。

以上例子中,我們實例化.NET對象,但使用的是標准的Ruby對象的.new方法 ,即Object#new。我們調用這些對象(和類)的方法(例如,對 System.Windows.Controls.Canvas.SetZIndex()調用)可為Ruby語言建立相應的 小寫規則。無縫集成讓我們可在.NET CLR之上運行動態語言(公共語言運行時需 要動態語言運行時來支持動態語言)。這對於我們來說是完全抽象的,僅用於創 建軟件。---www.bianceng.cn

注:使用IronRuby的時候,.NET堆棧確實在各級別上集成。有一個地方要注 意的是所有的IronRuby對象並非真正意義上的Object而是System.Object。

事件

事件是開發.NET客戶端應用程序的重要一環,在其它開發環境下也同樣如此 。萬一你沒有注意到這一點,事件驅動編程實質上也需要在不可預知的情況下調 用方法或者其它代碼塊(比如:委托)。你永遠無法預測用戶什麼時候點擊按鈕 ,敲擊按鍵或者執行任何輸入,所以事件驅動編程必須處理GUI事件。

我最喜歡Ruby語言的原因之一就在於它的“blocks”確實能夠幫助我們。例 如在傳統的C#語言中,你需要通過以下一種或兩種方式來訂閱事件(即在事件發 生時執行所分配的代碼塊):把引用傳遞給指定的方法,或者提供匿名代碼塊。 你正好可以看到Ruby中的類似概念“block”“Proc”和 “lambda”。最後在相 對簡單的代碼中說明這些概念,我們會使用.NET的System.Timers.Timer來嘗試 每秒鐘更新該時鐘(我知道這並非最佳做法,僅用於示范)

注:和我之前說的稍有不同,時鐘的運行是可預期的,然而我們仍使用Timer 事件進行更新,這是在主線程之外完成任務的眾多方式的一種。

接下來,你會看到為處理事件所需編寫的代碼僅是向CLR提供處理事件的函數 名。這種方式的缺點在於它對每個事件僅允許委托一個代碼塊。我們需要使用 add方法讓該事件訂閱多個處理程序,即把處理函數放到隊列的末端。如下所示 :

def tick
   puts "tick tock"
end

timer.elapsed.add method(:tick)
timer.elapsed.add proc { puts "tick tock" }
tick_handler = lambda { puts "tick tock" }
timer.elapsed.add(tick_handler)

創建代碼塊作為事件處理程序的能力使得IronRuby向優秀的動態語言又邁進 了一步。小寫規范減少了樣板代碼的數量。當然,匿名方法在其它傳統的.NET語 言——像C#和VB——中也可用,但是在IronRuby則讓人感覺更加優雅和自然。

注:無論方法是已命名還是匿名,處理事件的委托代碼都可以接收參數,一 般來說,參數會包括一個sender 對象和一些args。

XAML和IronRuby

XAML是微軟用於定義CLR對象及其屬性的類XML語言,主要在WPF和 Silverlight應用程序中使用。有了它,我們可以用描述的方式來創建整個UI, 在程序性代碼中關聯事件並在運行時綁定數據、創建圖形、甚至為那些圖形創建 具有故事情節的動畫。我不准備深入探討XAML的架構,如果你有任何使用基於 XML語言的經驗的話,你就會了解其中發生的事情。

<Window  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
   <StackPanel>
     <Rectangle x:Name="mySquare" Width="50" Height="50">
       <Rectangle.Fill>
         <SolidColorBrush Color="Green" />
       </Rectangle.Fill>

     </Rectangle>
     <TextBlock Text="Hello, world">
       <TextBlock.Foreground>
         <SolidColorBrush Color="Red" />
       </TextBlock.Foreground>
     </TextBlock>

   </StackPanel>
</Window>

注:Window、StackPanel、TextBlock、SolidColorBrush和Rectangle 都是 WPF類。XAML代碼可以輕松地用C#、VB或者IronRuby編程實現。

以上代碼會顯示一個中等尺寸的獨立窗體。該窗體中有StackPanel對象,它 是WPF控件,用於定義其子控件采取流布局樣式。在 StackPanel中有兩個不同對 象:一個文本框和一個矩形。在XAML定義的對象皆可被命名以供後續引用亦可匿 名(我們的Rectangle對象就命名為mySquare,盡管TextBlock未被命名)。這些 對象的屬性可以通過兩種方式進行賦值:利用XML元素屬性(例如:Width="50" ),或者所期望的值非初級類型它們的子元素(例如:預期 <Rectangle.Fill> 為Brush或者派生自Brush)。

不要陷入WPF和XAML的謎團當中,因為任何人都可以輕松地編寫大量代碼,讓 我們用IronRuby運行這些代碼。

require 'PresentationFramework'
require 'PresentationCore'

@window = System::Windows::Markup::XamlReader.parse(File.open ('my_xaml.xaml', 'r').read)
System::Windows::Application.new.run(@window)

WPF方法Application.Run需要Window作為其中一個參數。如果我們回頭看之 前的XAML代碼,就會發現根元素其實就是 Window,那也是語法分析後所返回的 對象。所有在XAML中定義的控件都會作為反射XAML文檔結構的控件樹返回, Window是根元素,StackPanel作為Window的唯一子元素,Rectangle和TextBlock 則作為StackPanel的子元素等等。我們可以通過以下方式添加控件:

@window.find_name("mySquare").class # =>  "System::Windows::Shapes::Rectangle"

關於CLR類繼承的解析

我們提到兼容性、互操作性卻忽略了可擴展性。我已經清楚解釋了IronRuby 與.NET間如何無縫繼承,甚至你可以用繼承來擴展CLR類。以下是一個示例,讓 我們再來看一看之前寫的文章中用C#創建的Person類。

namespace MyClassLibrary
{
   public class Person
   {
     public string Name { get; set; }

     public string Introduce()
     {
       return String.Format("Hi, I'm {0}", Name);
     }
   }
}

讓我們用Ruby來擴展它,並借此培養程序員的思維習慣。

require 'MyClassLibrary.dll'

class Programmer < MyClassLibrary::Person
   ACCEPTABLE_DRINKS = [:coffee, :tea, :cola, :red_bull]

   def drink(liquid)
     if ACCEPTABLE_DRINKS.include? liquid
       puts "Mmm... #{name} likes code juice!"
     else
       raise "Need caffeine!"
     end
   end
end

me = Programmer.new
me.name = "Edd"
puts me.introduce
me.drink(:coffee)

關於代碼冗長

老實說,我不介意使用繁瑣的代碼引用,只要它不影響程序的性能即可,就 像在之前的代碼顯示的那樣。我喜歡簡潔的代碼,冗長的尋址和對象描述的簡化 會產生某種安全感,盡管這和本句形成了鮮明的對比。然而,在使用IronRuby的 時候,我已厭倦輸入 System::Whatever::Something。不管使用何種語言,總有 一些開發人員喜歡設定命名空間並忘掉它們。不用擔心,IronRuby 也有這種人 。

由於.NET命名空間在IronRuby中是模塊,所以在調用include後,完全可以把 .NET命名空間引入IronRuby代碼,就像要引入一個Ruby組織模塊一樣。

 

class Clock
   include System::Windows::Shapes
   include System::Windows::Media 
   include System::Windows::Threading 
   # and so on...

這樣做可以減少調用 System::Windows::Shapes::Ellipse.new,代之以 Ellipse.new,或通過 System::Windows::Threading::DispatcherPriority.Render引用 DispatcherPriority.Render。

在.NET Framework中,另一個簡化IronRuby代碼以及處理這些冗長代碼的方 法就是通過給命名空間取別名來完成。

require 'System.Windows.Forms'

WinForms = System::Windows::Forms
WinForms::Form.new
WinForms::Label.new

結論

到此為止,我希望你能更好的了解IronRuby與.NET間的互操作,以及如何利 用.NET Framework的動態屬性和Ruby的優雅語法。

Ruby的風格和用法讓數據處理變成一種樂趣,當然,在IronRuby也一樣,它 結合了 WPF生成圖像的功能。我希望大家能具體看到使用這兩種技術進行數據可 視化的可能性。使用IronRuby來創建數據和信息圖的視覺描述是多麼的振奮人心 。盡管在這個項目中,我們僅展現了一些簡單的信息——時間——但潛在的可能 性是巨大的。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved