?
This document uses PHP Chinese website manual Release
Ruby Application Archive (簡稱RAA)包括幾個支持你使用Ruby來創(chuàng)建GUI(Graphical User Interface)程序的擴展(extensions ),包括Tcl/Tk擴展,GTK擴展,OpenGL擴展和其它的一些。
TK擴展是隨著Ruby軟件一起發(fā)布的,可以在Unix和windows上使用。為了使用它,你需要在你的系統(tǒng)上安裝Tk。Tk是一個龐大的系統(tǒng),關(guān)于它的書已經(jīng)有很多了,所以我們不會在這里浪費太多的時間和資源去深入的研究Tk本身,而是著重于如何在Ruby中使用Tk。要想很好的在Ruby中使用Tk,你需要下面的一些參考書中的一本。因為Tk和Ruby的綁定和Tk到Perl的綁定類似,所以你可以用Learning Perl/Tk? 或者 Perl/Tk Pocket Reference
Tk以一種組合的方式工作,就是說你首先創(chuàng)建一個容器(container )部件(widget ),比如TkFrame
或者TkRoot
,然后在它上面繼續(xù)創(chuàng)建其它部件,比如按鈕或者標簽(labels)。當你打算開始啟動GUI的時候,調(diào)用Tk.mainloop
,然后Tk引擎將接替對程序的控制,顯示各個窗口部件,響應(yīng)各種GUI事件。
Ruby中一個簡單的Tk應(yīng)用程序像下面一樣:
require?'tk' root?=?TkRoot.new?{?title?"Ex1"?} TkLabel.new(root)?{ ??text??'Hello,?World!' ??pack??{?padx?15?;?pady?15;?side?'left'?} } Tk.mainloop |
界面如圖:
讓我們更近距離的看看這些代碼。在裝載完tk擴展模塊之后,我們用TkRoot.new
建立了一個最頂級(root-level )的框架(frame)。然后,我們建立了一個標簽部件作為這個父級窗口的子部件,并設(shè)置了這個標簽的一些屬性,最后,最后,我們讓父窗口顯示(pack?)并進入GUI主事件循環(huán)。
顯示的建立一個頂級窗口是一個很好的習慣,盡管你不建立這個窗口程序也能工作。例如上面的代碼即使變?yōu)橐韵氯幸材芄ぷ鳎?/p>
require?'tk' TkLabel.new?{?text?'Hello,?World!'?} Tk.mainloop |
這樣就可以工作了,再參考一下上面我們提到的Perl/Tk的書,我們就能寫出非常好的GUI程序了。但是,下面我們還是會更詳細的講述一下Tk在Ruby中的使用。
創(chuàng)建一個窗口部件很容易,在Ruby中這些部件名字都在原來的基礎(chǔ)上加上了Tk前綴,比如,Label,Button和Entry變成了TkLabel
,TkButton
和TkEntry
。你可以通過使用new方法創(chuàng)建一個部件,就像創(chuàng)建其它對象一樣。如果你創(chuàng)建組件的時候沒有指定父部件的名字,那么將把頂級窗口作為默認父部件。我們有時候也會在創(chuàng)建部件的時候設(shè)置一些部件屬性(選項),比如顏色,大小等等;有時候,我們也希望從這些部件得到程序返回信息,我們可以設(shè)置回調(diào)(callbacks )方法和共享數(shù)據(jù)(sharing data)。
如果你看一下Tk的參考手冊(比如關(guān)于Perl/Tk),你會發(fā)現(xiàn)這些窗口部件選項可以用一個連字符列出(listed with a hyphen ),就像命令行參數(shù)那樣。在Perl/Tk中,你可以將這些參數(shù)放入一個Hash傳給這個widget,在Ruby中你也可以這么做,但是,你也可以通過一個代碼block來設(shè)置這些參數(shù):選項的名稱作為方法名,選項的值作為這個方法的參數(shù)。一個widget創(chuàng)建時候的第一個參數(shù)為父部件,然后后面跟一個哈希結(jié)構(gòu)的選項列表,或者以代碼塊格式來設(shè)置這些選項。所以,下面這兩項實際上是相等地。
TkLabel.new(parent_widget)?{ ??text????'Hello,?World!' ??pack('padx'??=>?5, ???????'pady'??=>?5, ???????'side'??=>?'left') } #?or TkLabel.new(parent_widget,?text?=>?'Hello,?World!').pack(...) |
不過有一個需要注意的小地方:有時候變量的作用域可能出乎你的想象。block是在widget的環(huán)境上下文中執(zhí)行(evaluated ),而不是調(diào)用者的(caller's)。這就意味著調(diào)用者的實例變量在這個block中不可用,但是在包含它的范圍內(nèi)的局部變量和全局變量(不是你曾經(jīng)用過的)。我們將在后面的例子中使用這兩種方法設(shè)置widget的選項。
不確定的英文原文:The block is actually in the context of the widget's object, not the . This means that the caller's instance variables will not be available in the block, but local variables from the enclosing scope and globals (not that you ever use those) will be.
距離(比如上面例子中的padx和pady)默認單位為象素,但是你也可以指定為其它單位,比如以c為后綴表示單位為厘米,i表示英寸,m表示毫米,p表示點(point)等。
我們可以通過方法回調(diào)和綁定變量(binding variables)從窗口部件得到信息。
回調(diào)(Callbacks )很容易使用。command選項(比如下面例子中的TkButton)接收一個Proc對象,這個Proc對象會在這個回調(diào)觸發(fā)的時候被調(diào)用。這里,我們可以用 Kernel::proc
將block {exit}
轉(zhuǎn)換為一個Proc
對象。
TkButton.new(bottom)?{ ??text?"Ok" ??command?proc?{?p?mycheck.value;?exit?} ??pack('side'=>'left',?'padx'=>10,?'pady'=>10) } |
我們也可以使用TkVariable
代理將Ruby的變量綁定到 Tk widget的值。注意下面的TkCheckButton
是如何創(chuàng)建的,variable選項接受一個TkVariable類型的參數(shù),這個例子里我們用 TkVariable.new
首先創(chuàng)建了這個變量。訪問變量mycheck.value將根據(jù)這個選擇框是否被選中而返回0或1。你可以在各種支持variable選項的部件中使用這種方法,比如radio,button和文本框。
mycheck?=?TkVariable.new TkCheckButton.new(top)?{ ??variable?mycheck ??pack('padx'=>5,?'pady'=>5,?'side'?=>?'left') } |
除了在創(chuàng)建一個部件的時候設(shè)定選項,我們也可以在這個部件運行的時候重新配置它。每個widget都支持configure方法,這個方法接收一個Hash或者代碼塊作為參數(shù),和new方法類似,比如我們可以修改一下第一個例子,使它的按鈕在按下的時候能給修改標簽的文本。
lbl?=?TkLabel.new(top)?{?justify?'center' ??text????'Hello,?World!'; ??pack('padx'=>5,?'pady'=>5,?'side'?=>?'top')?} TkButton.new(top)?{ ??text?"Cancel" ??command?proc?{?lbl.configure('text'=>"Goodbye,?Cruel?World!")?} ??pack('side'=>'right',?'padx'=>10,?'pady'=>10) } |
所以,當你按Cancel按鈕的時候,標簽的文本將由``Hello, World!
'' 變?yōu)閌`Goodbye, Cruel World!
''。
你也可以用cget方法取得某個widget的某個選項的值。
require?'tk' | ||
b?=?TkButton.new?{ | ||
??text?????"OK" | ||
??justify??'left' | ||
??border???5 | ||
} | ||
b.cget('text') |
? |
"OK" |
b.cget('justify') |
? |
"left" |
b.cget('border') |
? |
5 |
下面來看一個稍微長點例子,一個真正的應(yīng)用程序-``pig Latin''生成器。輸入一個短語,比如 ``Ruby rules'' ,然后按下按鈕``Pig It'',程序?qū)⒘⒓窗堰@個短語翻譯為
require?'tk' class?PigBox ??def?pig(word) ????leadingCap?=?word?=~?/^A-Z/ ????word.downcase! ????res?=?case?word ??????when?/^aeiouy/ ????????word+"way" ??????when?/^([^aeiouy]+)(.*)/ ????????$2+$1+"ay" ??????else ????????word ????end ????leadingCap???res.capitalize?:?res ??end ??def?showPig ????@text.value?=?@text.value.split.collect{|w|?pig(w)}.join("?") ??end ??def?initialize ????ph?=?{?'padx'?=>?10,?'pady'?=>?10?}?????#?common?options ????p?=?proc?{showPig} ????@text?=?TkVariable.new ????root?=?TkRoot.new?{?title?"Pig"?} ????top?=?TkFrame.new(root) ????TkLabel.new(top)?{text????'Enter?Text:'?;?pack(ph)?} ????@entry?=?TkEntry.new(top,?'textvariable'?=>?@text) ????@entry.pack(ph) ????TkButton.new(top)?{text?'Pig?It';?command?p;?pack?ph} ????TkButton.new(top)?{text?'Exit';?command?{proc?exit};?pack?ph} ????top.pack('fill'=>'both',?'side'?=>'top') ??end end PigBox.new Tk.mainloop |
界面如圖:
Sidebar: 布局管理器?(Geometry Management) | |||||||||||||||||
在本章的例子中,我們已經(jīng)看到了pack方法,這是一個非常重要的方法,如果你不調(diào)用這個方法,那么你的widget將永遠不會出現(xiàn),pack命令將告訴geometry manager將這個部件按照設(shè)定的參數(shù)放到合適的位置上。支持三種命令:
因為pack可能是最常用的方法了,所以我們的例子里都使用pack方法。 譯者注:如果一個方法(method)沒有參數(shù),有時候也叫做命令(command) |
我們的窗口部件是暴露給現(xiàn)實世界的,人們可以在它上面點擊,移動鼠標,進入這個組件等。所有的這些事情,都會產(chǎn)生一個事件,我們也可以捕獲這些事件。你可以通過窗口部件的bind方法,給它的某一事件綁定一個事件處理,通常,這個時間處理是一個代碼塊(block of code)。
比如,我們創(chuàng)建了一個用來顯示一個圖像的按鈕,我們希望鼠標在這個按鈕上移動的時候圖像會變化,則可以用以下代碼:
image1?=?TkPhotoImage.new?{?file?"img1.gif"?} image2?=?TkPhotoImage.new?{?file?"img2.gif"?} b?=?TkButton.new(@root)?{ ??image????image1 ??command??proc?{?doit?} } b.bind("Enter")?{?b.configure('image'=>image2)?} b.bind("Leave")?{?b.configure('image'=>image1)?} |
首先,我們用TkPhotoImage
創(chuàng)建了2個GIF圖像,然后,創(chuàng)建了一個按鈕,名字為b,它顯示的圖像為image1,然后我們將它的Enter方法綁定一個將它的圖像變?yōu)閕mage2的代碼塊,Leave事件幫定到恢復image1圖像的代碼塊。
Button1
, Control
, Alt
, Shift
等等,type是事件的名字(遵循X11的命名習慣),包括ButtonPress,KeyPress和Expose等。Detail或者是一個用于表示button的從1到5的數(shù)字,或者用來表示鍵盤輸入的keysym。比如,如果我們想處理事件:在control鍵被按下的時候釋放鼠標的button1,可以如下表示:
Control-Button1-ButtonRelease
Control-ButtonRelease-1
事件本身也會包含一些特定的字段(fields ),比如事件的發(fā)成的時間,坐標x,y的值等。bind可以將這些屬性傳給一個回調(diào)方法,這叫做event field codes 。這種方法使用起來類似printf的規(guī)則,比如,為了得到一個鼠標移動時候的xy坐標值,我們可以給bind方法傳3個參數(shù),第二個參數(shù)是一個用于回調(diào)的Proc對象,第三個參數(shù)是包括事件屬性的字符串(event field string)
canvas.bind("Motion",?proc{|x,?y|?do_motion?(x,?y)},?"%x?%y") |
Tk提供了一個畫布(canvas)部件,你可以在上面進行繪畫,然后輸出為PostScript 格式。這里是一個簡單的例子,來自Ruby發(fā)布。按下鼠標按鍵1,然后拖動,當松開按鍵1的時候,將會在兩個點之間劃一條直線。按下按鍵2將會把這個畫布轉(zhuǎn)出為適合打印的PostScript 格式。
require?'tk' class?Draw ??def?do_press(x,?y) ????@start_x?=?x ????@start_y?=?y ????@current_line?=?TkcLine.new(@canvas,?x,?y,?x,?y) ??end ??def?do_motion(x,?y) ????if?@current_line ??????@current_line.coords?@start_x,?@start_y,?x,?y ????end ??end ??def?do_release(x,?y) ????if?@current_line ??????@current_line.coords?@start_x,?@start_y,?x,?y ??????@current_line.fill?'black' ??????@current_line?=?nil ????end ??end ??def?initialize(parent) ????@canvas?=?TkCanvas.new(parent) ????@canvas.pack ????@start_x?=?@start_y?=?0 ????@canvas.bind("1",?proc{|e|?do_press(e.x,?e.y)}) ????@canvas.bind("2",?proc{?puts?@canvas.postscript({})?}) ????@canvas.bind("B1-Motion",?proc{|x,?y|?do_motion(x,?y)},?"%x?%y") ????@canvas.bind("ButtonRelease-1", ?????????????????proc{|x,?y|?do_release?(x,?y)},?"%x?%y") ??end end root?=?TkRoot.new{?title?'Canvas'?} Draw.new(root) Tk.mainloop |
只需要用鼠標點幾下,就能創(chuàng)作出你的杰作了。
界面如下:
除非你只想畫一個非常小的圖畫,否則上面的程序肯定不會很適合的。TkCanvas
, TkListbox
和TkText
都可以支持滾動條,所以你能處理一個“大圖象”的一部分
我們以前還沒有怎么介紹列表框(listbox),我們下面的滾動條的例子將使用一個列表框。下面的例子中,我們先創(chuàng)建一個普通的TkListbox
,然后,又創(chuàng)建一個TkScrollbar
,通過command選項為這個滾動條增加了一個回調(diào),這個回調(diào)將調(diào)用列表的yview方法,這個方法用來在縱坐標方向改變list的可見部分。
在回調(diào)建立之后,我們反過來也需要在list在移動之后,滾動條的設(shè)置也要改變,可以用TkScrollbar#set
方法。下面的例子只是下一節(jié)例子中的一部分代碼。
list_w?=?TkListbox.new(frame,?'selectmode'?=>?'single') scroll_bar?=?TkScrollbar.new(frame, ??????????????????'command'?=>?proc?{?|*args|?list_w.yview?*args?}) scroll_bar.pack('side'?=>?'left',?'fill'?=>?'y') list_w.yscrollcommand(proc?{?|first,last| ?????????????????????????????scroll_bar.set(first,last)?}) |
我們可以用幾百頁來繼續(xù)討論Tk,但是那是其它的書本的內(nèi)容了。下面的程序是我們最后的Tk例子,一個簡單的GIF圖像查看器。你可以從一個列表框選擇一個GIF文件名,然后顯示一個適應(yīng)窗口大小的圖像。這里有一些內(nèi)容需要指出。
你是否看見過一個應(yīng)用程序?qū)⒐鈽嗽O(shè)置為忙狀態(tài)(沙漏)而忘了改回來嗎?在Ruby中則不必擔心這種情況的發(fā)生。還記得 File.new
方法能接收一個block而確保文件最后被關(guān)閉嗎,我們可以同樣的在busy方法中使用這種機制,來確保最后光標被恢復,如下面的例子。
這個例子也顯示了TkListbox
的一些其它常用方法,向它增加元素,建立鼠標鍵松開時候的事件回調(diào)[你也許需要鼠標松開事件,而不是按下事件,鼠標按下的時候表示選擇了一個widget],并且得到選擇的那一個元素等。
到目前位置我們用TkPhotoImage
直接來顯示圖片,我們也可以對它進行縮放,抽樣(subsample),或者只顯示它的一部分。這里,我們用到了subsample 來使圖片適合預覽。
require?'tk' def?busy ??begin ????$root.cursor?"watch"?#?Set?a?watch?cursor ????$root.update?#?Make?sure?it?updates??the?screen ????yield?#?Call?the?associated?block ??ensure ????$root.cursor?""?#?Back?to?original ????$root.update ??end end $root?=?TkRoot.new?{title?'Scroll?List'} frame?=?TkFrame.new($root) list_w?=?TkListbox.new(frame,?'selectmode'?=>?'single') scroll_bar?=?TkScrollbar.new(frame, ??????????????????'command'?=>?proc?{?|*args|?list_w.yview?*args?}) scroll_bar.pack('side'?=>?'left',?'fill'?=>?'y') list_w.yscrollcommand(proc?{?|first,last| ?????????????????????????????scroll_bar.set(first,last)?}) list_w.pack('side'=>'left') image_w?=?TkPhotoImage.new TkLabel.new(frame,?'image'?=>?image_w).pack('side'=>'left') frame.pack list_contents?=?Dir["screenshots/gifs/*.gif"] list_contents.each?{|x| ??list_w.insert('end',x)?#?Insert?each?file?name?into?the?list } list_w.bind("ButtonRelease-1")?{ ??index?=?list_w.curselection[0] ??busy?{ ????tmp_img?=?TkPhotoImage.new('file'=>?list_contents[index]) ????scale???=?tmp_img.height?/?100 ????scale???=?1?if?scale?<?1 ????image_w.copy(tmp_img,?'subsample'?=>?[scale,scale]) ????tmp_img?=?nil?#?Be?sure?to?remove?it,?the ????GC.start??????#?image?may?have?been?large ??} } Tk.mainloop |
譯者注:上面代碼倒數(shù)第七行,即copy圖像的那一行,在我的系統(tǒng)(2000+ruby 1.8.2)上運行出錯,改為如下即可通過:image_w.copy(tmp_img, '-subsample 2 2')?
界面如下:
最后再來看看垃圾收集,當我們有一些很大的圖片時,我們不想讓這些圖片在不需要的時候占用內(nèi)存,所以我們把對這個對象的引用設(shè)為nil,這就告訴了垃圾收集器立即把這些變量清理。
就說道這里了,你已經(jīng)初步了解了在Ruby中使用Tk。在很大程度上,你可以輕松的在Ruby中借鑒適用于Perl/Tk的文檔,但是這里有一些要注意,不是每個在Perl中實現(xiàn)的方法在Ruby中也被實現(xiàn),而且也可能存在沒有被文檔化的功能。在關(guān)于Ruby/Tk的書出來之前,你最好還是有什么問題到新聞組上去討論,或者閱讀更多的源代碼。
但是通常來說,你會直到如何去做。記住部件的選項可以通過一個哈?;蛘叽ablock傳給widget,而且TkWidget
中代碼塊方法的變量作用域在這個widget內(nèi),而不是在類的實例中。
Perl/Tk:??$widget?=?$parent->Widget(?[?option?=>?value?]?) Ruby:?????widget?=?TkWidget.new(parent,?option-hash) ??????????widget?=?TkWidget.new(parent)?{?code?block?} |
你也許不需要保存新創(chuàng)建widget的返回值,盡管這個值可以得到。不要忘了調(diào)用一個widget 的pack方法,否則你將不會看到這個部件。
Perl/Tk:??-background?=>?color Ruby:?????'background'?=>?color ??????????{?background?color?} |
記住代碼塊的作用域是不同的。
Perl/Tk:??-textvariable?=>?\$variable ??????????-textvariable?=>?varRef Ruby:?????ref?=?TkVariable.new ??????????'textvariable'?=>?ref ??????????{?textvariable?ref?} |
使用將一個Ruby變量綁定到widget的值,然后,你就可以用TkVariable
里的value的訪問方法(TkVariable#value
和TkVariable#value=
)來直接處理widget的內(nèi)容了。