=== 原文地址: 本地: ~/ccl-1.8-darwinx86/examples/cocoa/ui-elements/HOWTO.html 網絡: http://trac.clozure.com/ccl/browser/trunk/source/examples/cocoa/ui-elements/HOWTO.html 原文標題: UI Elements HOWTO 翻譯者: FreeBlues 2013-07-18html
===程序員
這篇 HOWTO 文檔示範瞭如何經過 Lisp 調用來實例化和初始化 Objective-C 對象,進而建立 Cocoa 用戶界面組件。網絡
Cocoa 程序員一般使用蘋果的 InterfaceBuilder 應用程序來建立的 UI 組件,而後從一個 nibfile 文件中加載這些組件,可是 Cocoa 支持在 Objective-C 方法調用中建立全部的相同的 UI 組件。事實上,調用 nibfiles 就是這是怎麼一回事:經過方法調用來實例化那些在 nibflie 中描述的對象。app
Lisp 程序員習慣於在工做中採起增量和交互的方式,因此經過方法調用來交互式建立用戶界面組件,比起在 InterfaceBuilder 中構建一整個用戶界面組件, 有時可能更有感。這份 HOWTO 將會示範如何經過使用 Objective-C 方法調用來建立和顯示窗口和其餘用戶界面組件。函數
想了解更多關於如何從 Lisp 加載 nibfiles 的信息,請參閱文檔 「nib-loading」 中的示例。 想了解完整的關於如何使用經過 InterfaceBuilder 建立的 nibfile 文件來構建一個 Cocoa 應用程序的討論,請參閱 「currency-converter」 示例。ui
Mac OS X 中的每個用戶界面組件要麼出如今一個窗口中,要麼出如今一個菜單中。咱們將探討如何建立和顯示窗口。翻譯
首先,切換到 CCL 包,根據約定。 Clozure CL 的大部分 Objective-C 應用都在 CCL 包:code
? (in-package :ccl) #<Package "CCL">
建立一個 Cocoa 窗口, 按照 Objective-C 模式:分配一個對象而後用一些起始數據來初始化它. 要分配一個窗口, 只要調用 NSWindow 類的 alloc 方法就好了:orm
? (setf my-window (#/alloc (@class ns-window))) #<NS-WINDOW <NSWindow: 0x13b68580> (#x13B68580)> 譯者注,在個人環境下執行結果以下: ? (setf my-window (#/alloc (@class ns-window))) #<NS-WINDOW [uninitialized] (#x1447550)>
上面的表達式建立了一個新窗口,不過沒有顯示它。在它顯示在屏幕上以前,咱們必須用一些適當的值來對其進行初始化。 爲了這個目的,咱們將使用方法 initWithContentRect:styleMask:backing:defer:。htm
一般在 Objective-C 中, 方法的名字揭示了它所期待的一些參數. 咱們傳遞給 initWithContentRect: 的 NSRect , 方法名的這一段描述了窗口的形狀. styleMask: 的 mask 是一個位序列, 用來指示那個窗口特性會被開啓. backing: 參數是一個 NSBackingStoreType 類型的常量, 用來指示 Cocoa 如何繪製窗口的 contents. 最後的 defer: 參數是一個布爾值, 它決定是否一個窗口一被建立好就顯示出來.
接下來,咱們將建立數據值來傳遞給這些參數,這樣咱們就能夠把咱們的新窗口顯示在屏幕上。咱們會一點一點地創建適當的初始化形式。
第一個參數,固然是被初始化的窗口對象。咱們把以前建立好的窗口對象傳遞過去:
(#/initWithContentRect:styleMask:backing:defer: my-window ...)
下一個參數,NSRect,是一個咱們只是臨時須要的結構。 由於 NSRect 的值在 Cocoa 代碼中出現得很是頻繁, 因此 Clozure CL 提供了一種方便的方法來臨時分配,自動處理它們。with-ns-rect 宏(在 NS 包中)新建一個 NSRect 值, 而後當控制流離開宏的區域時處理它; 例如:
(ns:with-ns-rect (r 100 100 400 300) ...) 譯者注: Lisp 中 with- 形式的宏會自動釋放資源
咱們能夠用這個矩形來初始化咱們新窗口的形狀:
(ns:with-ns-rect (r 100 100 400 300) (#/initWithContentRect:styleMask:backing:defer: my-window r ...))
爲了指定咱們想要的窗口特性,咱們必須結合幾個標誌來造成適當的風格掩碼(style mask)。 Cocoa 爲每個窗口特性都提供了命名好的常量。爲了建立描繪了一個新窗口的風格掩碼,可使用 inclusive-or 來把這些命名標識結合在一個風格掩碼中:
(logior #$NSTitledWindowMask #$NSClosableWindowMask #$NSMiniaturizableWindowMask #$NSResizableWindowMask)
你能夠在蘋果開發者文檔的 NSWindow Constants 一節中找到全部窗口掩碼的定義。
把窗口掩碼當作下一個參數傳遞過去, 就是下面這個表達式
(ns:with-ns-rect (r 100 100 400 300) (#/initWithContentRect:styleMask:backing:defer: my-window r (logior #$NSTitledWindowMask #$NSClosableWindowMask #$NSMiniaturizableWindowMask #$NSResizableWindowMask) ...))
和風格掩碼相似,NSBackingStoreType 的值是一個被命名的常量, 它描述了 Cocoa 將會爲窗口的 content 採用什麼樣的繪圖策略. 它的值能夠是 NSBackingStoreRetained, NSBackingStoreNonretained, 或者是 NSBackingStoreBuffered. 在這個例子中,咱們將使用NSBackingStoreBuffered:
(ns:with-ns-rect (r 100 100 400 300) (#/initWithContentRect:styleMask:backing:defer: my-window r (logior #$NSTitledWindowMask #$NSClosableWindowMask #$NSMiniaturizableWindowMask #$NSResizableWindowMask) #$NSBackingStoreBuffered ...))
最後,defer 參數只是一個布爾值。若是咱們傳遞一個 true 值,Cocoa 將會推遲顯示窗口,直到咱們明確告訴它。若是咱們傳遞 false 值,它會當即顯示該窗口。咱們能夠傳遞 Lisp 值 Ť 或 NIL, Objective-C 橋將爲咱們自動轉換它們, 可是基於爲 Objective-C 操做使用 Objective-C 值的精神, 仍是讓咱們使用 Objective-C 的常量 #$YES 和 #$NO 吧:
(ns:with-ns-rect (r 100 100 400 300) (#/initWithContentRect:styleMask:backing:defer: my-window r (logior #$NSTitledWindowMask #$NSClosableWindowMask #$NSMiniaturizableWindowMask #$NSResizableWindowMask) #$NSBackingStoreBuffered #$NO))
這裏; 用來初始化咱們窗口對象的表達式終於完成了。咱們能夠經過在一個 Lisp 的 Listener 中求值這個表達式來初始化咱們的窗口:
? (ns:with-ns-rect (r 100 100 400 300) (#/initWithContentRect:styleMask:backing:defer: my-window r (logior #$NSTitledWindowMask #$NSClosableWindowMask #$NSMiniaturizableWindowMask #$NSResizableWindowMask) #$NSBackingStoreBuffered #$NO)) ;Compiler warnings : ; In an anonymous lambda form at position 91: Undeclared free variable MY-WINDOW #<NS-WINDOW <NSWindow: 0x1447550> (#x1447550)> ?
而後咱們就能夠調用 makeKeyAndOrderFront: 來顯示這個窗口:
? (#/makeKeyAndOrderFront: my-window nil) NIL ?
顯示窗口以下:
一個窗口,雖然是空的,可是帶着咱們指定的形狀和特性,出如今屏幕的左下角。
一旦咱們有一個窗口在屏幕上,咱們可能會但願把一些東西置於其內。讓咱們開始添加按鈕。
建立一個按鈕對象如同建立一個窗口對象同樣簡單,咱們簡單地分配一個:
(setf my-button (#/alloc ns:ns-button)) #<NS-BUTTON <NSButton: 0x13b7bec0> (#x13B7BEC0)> 譯者注,在個人環境下執行結果以下: ? (setf my-button (#/alloc ns:ns-button)) #<NS-BUTTON [uninitialized] (#x14B4480)> ?
做爲窗口,最有趣的工做是設置那些被分配的按鈕在它們被分配以後.
實例 NSButton 包括 pushbutton 帶有文本或圖像標籤(或二者兼有),複選框,單選按鈕。爲了生成一個文本 pushbutton, 咱們須要告訴咱們的按鈕使用一個 NSMomentaryPushInButton 的 button-type, 一個 NSNoImage 的圖像位置, 以及一個 NSRoundedBezelStyle 的邊框風格. 這些風格選項所有由 Cocoa 常量來表示.
咱們還須要給一個按鈕一個矩形幀, 用來定義它的尺寸和位置. 咱們能夠基於初始化咱們按鈕的目的, 再一次使用 ns:with-ns-rect 來指定一個臨時矩形.
(ns:with-ns-rect (frame 10 10 72 32) (#/initWithFrame: my-button frame) (#/setButtonType: my-button #$NSMomentaryPushInButton) (#/setImagePosition: my-button #$NSNoImage) (#/setBezelStyle: my-button #$NSRoundedBezelStyle)) ;Compiler warnings : ; Undeclared free variable MY-BUTTON (4 references), in an anonymous lambda form NIL
如今,咱們只是把按鈕添加到窗口。爲此,咱們要求窗口的 content 視圖,並要求這個視圖添加按鈕, 做爲一個子視圖:
? (#/addSubview: (#/contentView my-window) my-button) NIL ?
按鈕出如今窗口伴隨着至關平庸的標題「按鈕」。點擊它會高亮按鈕不過其餘什麼也沒作, 由於咱們並無給它任何動做去執行。
咱們能夠給按鈕一個更有趣的標題,或許更重要的是,一個當作去執行, 經過傳導一個字符串和一個動做給它。首先,讓咱們來設置按鈕標題:
? (let ((label (%make-nsstring "Hello!"))) (#/setTitle: my-button label) (#/release label)) ;Compiler warnings : ; In an anonymous lambda form at position 56: Undeclared free variable MY-BUTTON NIL ? 譯者注:這裏能夠直接使用中文標題: (ccl::%make-nsstring "你好")
按鈕變化爲顯示文字 「hello!」 . 請注意,咱們當心地保存一個對按鈕文字的引用而且在修改完按鈕標題後釋放了它。在 Cocoa 中正常的內存管理政策是這樣的: 若是咱們分配一個對象(如 NSString 的 「hello!」)咱們負責釋放它。不像 Lisp 中,Cocoa 默認不會對全部分配的對象自動進行垃圾收集。
給按鈕增長一個動做稍微複雜一些。點擊按鈕會致使該按鈕對象發送一條消息到一個目標對象。咱們既沒有給咱們的按鈕一條消息來發送,也沒有一個目標對象來發,因此它不作任何事情。爲了讓它執行一些行動, 咱們須要給它一個目標對象和一個消息來發送. 而後, 只要咱們按下按鈕, 它將會發送咱們指定的消息到咱們提供的對象. 固然,目標對象最好能對消息作出響應,不然,咱們只會看到一個運行時錯誤。
讓咱們定義一個類, 它知道如何響應一個問候的消息,而後建立一個該類的對象爲咱們按鈕的目標來服務.
咱們能夠定義一個 NSObject 類的子類來處理咱們按鈕的消息:
? (defclass greeter (ns:ns-object) () (:metaclass ns:+ns-object)) #<OBJC:OBJC-CLASS GREETER (#x66F2C0)> ?
咱們須要定義一個方法來對那個按鈕的消息作出響應. 行動方法接受有給i參數(加入到接收者-receiver): 一個發送者(sender). 一般狀況下,Cocoa 把按鈕對象自身做爲發送者參數傳遞,; 方法能夠對發送者作任何它喜歡(或者一點也不喜歡)的事情.
這裏有一個方法,它顯示一個警告對話框
? (objc:defmethod #/greet: ((self greeter) (sender :id)) (declare (ignore sender)) (let ((title (%make-nsstring "Hello!")) (msg (%make-nsstring "Hello, World!")) (default-button (%make-nsstring "Hi!")) (alt-button (%make-nsstring "Hello!")) (other-button (%make-nsstring "Go Away"))) (#_NSRunAlertPanel title msg default-button alt-button other-button) (#/release title) (#/release msg) (#/release default-button) (#/release other-button))) |-[Greeter greet:]| ?
如今咱們能夠建立Greeter類的實例,並用它做爲按鈕的目標:
? (setf my-greeter (#/init (#/alloc greeter))) #<GREETER [uninitialized] (#x104CE80)> ? ? (#/setTarget: my-button my-greeter) NIL ? ? (#/setAction: my-button (@SELECTOR "greet:")) NIL ?
如今,點擊一下按鈕,一個告警面板將會彈出.
完整代碼以下:
(in-package :ccl) ;;; 建立窗口 (setf my-window (#/alloc (@class ns-window))) (ns:with-ns-rect (r 100 100 400 300) (#/initWithContentRect:styleMask:backing:defer: my-window r (logior #$NSTitledWindowMask #$NSClosableWindowMask #$NSMiniaturizableWindowMask #$NSResizableWindowMask) #$NSBackingStoreBuffered #$NO)) (#/makeKeyAndOrderFront: my-window nil) ;;; 建立按鈕 (setf my-button (#/alloc ns:ns-button)) (ns:with-ns-rect (frame 10 10 72 32) (#/initWithFrame: my-button frame) (#/setButtonType: my-button #$NSMomentaryPushInButton) (#/setImagePosition: my-button #$NSNoImage) (#/setBezelStyle: my-button #$NSRoundedBezelStyle)) (#/addSubview: (#/contentView my-window) my-button) ;;; 設置按鈕標籤上的顯示文字 (let ((label (%make-nsstring "你好!"))) (#/setTitle: my-button label) (#/release label)) ;;; 定義消息接收對象類和方法 (defclass greeter (ns:ns-object) () (:metaclass ns:+ns-object)) (objc:defmethod #/greet: ((self greeter) (sender :id)) (declare (ignore sender)) (let ((title (%make-nsstring "好啊!")) (msg (%make-nsstring "Hello, world 世界!")) (default-button (%make-nsstring "Hi 啊!")) (alt-button (%make-nsstring "Hello 啊!")) (other-button (%make-nsstring "Go Away 走起"))) (#_NSRunAlertPanel title msg default-button alt-button other-button) (#/release title) (#/release msg) (#/release default-button) (#/release other-button))) |-[Greeter greet:]| (setf my-greeter (#/init (#/alloc greeter))) (#/setTarget: my-button my-greeter) (#/setAction: my-button (@SELECTOR "greet:"))
(全文完)