Cocoa 用户界面组件使用指导(教程翻译)
=== 原文地址: 本地: ~/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-18
===
目录
0 概述
这篇 HOWTO 文档示范了如何通过 Lisp 调用来实例化和初始化 Objective-C 对象,进而创建 Cocoa 用户界面组件。
Cocoa 程序员通常使用苹果的 InterfaceBuilder 应用程序来创建的 UI 组件,然后从一个 nibfile 文件中加载这些组件,但是 Cocoa 支持在 Objective-C 方法调用中创建所有的相同的 UI 组件。事实上,调用 nibfiles 就是这是怎么一回事:通过方法调用来实例化那些在 nibflie 中描述的对象。
Lisp 程序员习惯于在工作中采取增量和交互的方式,所以通过方法调用来交互式创建用户界面组件,比起在 InterfaceBuilder 中构建一整个用户界面组件, 有时可能更有感。这份 HOWTO 将会示范如何通过使用 Objective-C 方法调用来创建和显示窗口和其他用户界面组件。
想了解更多关于如何从 Lisp 加载 nibfiles 的信息,请参阅文档 “nib-loading” 中的示例。 想了解完整的关于如何使用通过 InterfaceBuilder 创建的 nibfile 文件来构建一个 Cocoa 应用程序的讨论,请参阅 “currency-converter” 示例。
1 创建一个窗口
Mac OS X 中的每一个用户界面组件要么出现在一个窗口中,要么出现在一个菜单中。我们将探讨如何创建和显示窗口。
首先,切换到 CCL 包,根据约定。 Clozure CL 的大部分 Objective-C 应用都在 CCL 包:
? (in-package :ccl)
#<Package "CCL">
创建一个 Cocoa 窗口, 按照 Objective-C 模式:分配一个对象然后用一些起始数据来初始化它. 要分配一个窗口, 只要调用 NSWindow 类的 alloc 方法就行了:
? (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:。
通常在 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
?
显示窗口如下:
一个窗口,虽然是空的,但是带着我们指定的形状和特性,出现在屏幕的左下角。
2 增加一个按钮
一旦我们有一个窗口在屏幕上,我们可能会希望把一些东西置于其内。让我们开始添加按钮。
创建一个按钮对象如同创建一个窗口对象一样简单,我们简单地分配一个:
(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
?
按钮出现在窗口伴随着相当平庸的标题“按钮”。点击它会高亮按钮不过其他什么也没做, 因为我们并没有给它任何动作去执行。
3 增加按钮相关的事件处理函数
我们可以给按钮一个更有趣的标题,或许更重要的是,一个当做去执行, 通过传导一个字符串和一个动作给它。首先,让我们来设置按钮标题:
? (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:"))
(全文完)
来源:oschina
链接:https://my.oschina.net/u/219279/blog/150374