From
http://www.newsmth.net/nForum/#!article/Apple/136327窗口
----
其实 Mac OS X 的老用户们都该熟悉了,和 Windows 不一样,这个系统里“窗口”并非
最重要的概念,一个程序的逻辑结构是:
+---------------------------------------------+
| Application |
| +-------------------------+ +-----------+ |
| | Window | | Menu | |
| | +----------------+ | +-----------+ |
| | | Control | | |
| | | +---------+ | | |
| | | | Control | | | |
| | | +---------+ | | |
| | +----------------+ | |
| +-------------------------+ |
+---------------------------------------------+
也就是说,菜单是独立于窗口的存在,有窗口和控件的区别。这和 Windows 中一切的
本质都是窗口有很大的区别。
虽然 Mac OS X 中区分 Window, Control 和 Menu 这几种概念,但并不代表其设计上
没有考虑到它们之间的一致性。在 Carbon 中,这些实体都是用 FooRef 的形式来表示
的,Ref 就有指针的意思,比如你创建了一个窗口之后,就会得到对应的
WindowRef,其实这就是一个用来操纵这个窗口的指针,而你创建控件之后,对应的
是 ControlRef,创建菜单对应的自然是 MenuRef 了,还是很好理解的吧。
我们这里先只谈窗口。很显然,要创建窗口,还得有些其他的属性,让我们看看
Carbon 的 CreateNewWindow 这个函数的原形是怎么要求的:
OSStatus CreateNewWindow (
WindowClass windowClass,
WindowAttributes attributes,
const Rect *contentBounds,
WindowRef *outWindow
);
WindowClass 是一个常量,我们最常见的一种是 kDocumentWindowClass (也是下面
打算要用的),还有 kDrawerWindowClass,这也很好理解:那种可以伸缩的 Drawer
嘛,kAlertWindowClass 呢?就是我们常见的提示框了。
WindowAttributes 则是针对具体 WindowClass 再作更仔细的属性定制了,这也是一个
32 位的无符号整数,但和 WindowClass 只能 n 选 1 不同,你可以把属性用位或 (|) 组
合起来使用。反正一时也记不住那么多,就先设置为
kWindowStandardDocumentAttributes | kWindowStandardHandlerAttribute 好了。前
者保证我们的窗口具有其他标准的文档窗口相同的特性,而后者给窗口加上系统提供的
标准 event handler,以自动处理一般的 event。下面是用于设置的代码:
WindowAttributes windowAttrs;
windowAttrs = kWindowStandardDocumentAttributes |
kWindowStandardHandlerAttribute;
直到这里,“event”都还是一个很模糊的概念,虽然我们前面多次提到了它,但为了避
免过多的讲理论,我拖到现在才来介绍它。
Event (事件) 其实是 Carbon 编程的基础。鼠标点击、键盘输入、菜单命令都是以
event 的形式发出的。窗口需要重绘、移动和放缩时,也会告知你的应用程序一个
event。当你的程序切换到前端或者后端时,你也会收到 event 告知你这个信息。
Carbon 程序的工作就是通过回应 event 来实现与用户和系统交互。
Carbon 的 event 处理是基于回调 (callback) 机制的。你可以定义针对不同 event 类型
的 event handler,然后在 Carbon Event Manager 中注册 (Install) 之。然后每当
event 发生时,Carbon Event Manager 就会调用你注册的 handler 函数。每个 event
handler 都必须与一个具体的 event target 对象关联起来,比如 target 是菜单、窗口或
整个程序。
应用程序包含窗口和菜单,窗口包含控件,控件还能进一步包含控件。一旦 event 出
现,首先得到通知的是最里层的 target,比如点击 button 的 event 首先发到 button 控
件上。如果最里面的 target 没有相关的 handler,就把 event 传播到更外层的包含它的
target 上。Carbon 给窗口和应用程序的 event target 提供了标准的 handler。标准
handler 可以负责处理类似窗口针对鼠标的操作,比如拖拽,伸缩等等。这样一来,你
就只需要关心自己的程序里针对拖拽或伸缩的特殊反映,而不比费神于那些所有程序都
通用的部分了。
当然,如果你愿意,也可以覆盖标准的 handler,比如有人可能会写个针对拉伸窗口的
handler,给窗口的伸缩增加音效。我们这里没那么复杂,用标准的就好啦。
第三个参数就更好理解了,是一个指向 Rect 这个结构体的指针,说明了窗口在屏幕坐
标系 [1] 中的位置和大小。这个东西其实还是 QuickDraw 中的概念,所以在程序中我
们也调用 QuickDraw 的 API 来完成设置:
#define kWindowTop 100
#define kWindowLeft 50
#define kWindowRight 800
#define kWindowBottom 600
Rect contentRect;
SetRect(&contentRect, kWindowLeft, kWindowTop,
kWindowRight, kWindowBottom);
设置的正是这个矩形四个点的坐标。
[1]: 注意屏幕坐标系中左上角是 (0, 0)。
最后一个参数是一个输出,也就是我们最终创建出来的那个新窗口的指针了。所以,我
们一般是这样创建窗口的:
WindowRef theWindow;
CreateNewWindow(kDocumentWindowClass, windowAttrs,
&contentRect, &theWindow);
等等,窗口是创建好了,存在 theWindow 指针里,可窗口的标题呢?我们这样设置:
SetWindowTitleWithCFString(theWindow, CFSTR("Hello Carbon"));
注意这里的 CFSTR 是一个宏,用于把 C 的 const char * 字符串转换为 Core
Foundation 定义的 CFStringRef 字符串,对于 CFString 的详细介绍可以看 Strings
Programming Guide for Core Foundation [2],不过其实现在我们知道它包括的是一个
数组和数组的长度,数组的元素都是 Unicode 字符 (UniChar),就行了,具体的转换细
节暂时不必考虑。
[2]:
http://developer.apple.com/documentation/CoreFoundation/Conceptual/ CFStrings/CFStrings.html
一切完毕之后,我们就可以显示这个窗口了:
ShowWindow(theWindow);
下面把完整的代码列出 (你也可以看附件里面的):
/* hello.c: testing Carbon basics */
#include <Carbon/Carbon.h>
#define kWindowTop 100
#define kWindowLeft 50
#define kWindowRight 800
#define kWindowBottom 600
int main(int argc, char *argv[])
{
WindowRef theWindow;
WindowAttributes windowAttrs;
Rect contentRect;
windowAttrs = kWindowStandardDocumentAttributes |
kWindowStandardHandlerAttribute;
SetRect(&contentRect, kWindowLeft, kWindowTop,
kWindowRight, kWindowBottom);
CreateNewWindow(kDocumentWindowClass, windowAttrs,
&contentRect, &theWindow);
SetWindowTitleWithCFString(theWindow,
CFSTR("Hello Carbon"));
ShowWindow(theWindow);
RunApplicationEventLoop();
return 0;
}
这一节的内容,呃,还是超出了我的预计,你要是有兴趣不妨再看看 Carbon Event
Manager Programming Guide,event 还是一个比较 tricky 的概念,而我们要到后面
用到的时候才会深谈。下一节讲菜单的创建。