本文是Apple Documentation Archive Window Programming Guide的学习笔记。

窗口控制器是如何工作的

How Window Controllers Work

NSWindow本身无法决定窗口的所有行为,有一些行为需要controller object来管理。对于NSWindow,常见的controller boject就是NSWindowController。如果使用interfacebuilder来构建窗口的话,NSWindowController通常是存在在的nib文件中,其管理对象是主窗口,并且通常是nib文件的所有者。(大概是说,在nib加载的时候,其owner会是这个NSWindowController的实例)。对于只有一个窗口的nib文件,AppKit会自动为其实例化一个NSWindowController。如果有多个窗口,则每个窗口都需要自己的window controller。

window controller决定NSWindow的下列行为:

  • 加载并显示窗口
  • 允许的情况下关闭窗口
  • 自定义窗口的标题栏
  • 保存窗口帧框的位置和大小
  • 决定当前窗口和其他窗口的叠放关系

如果是文档类型的窗口,其控制器有一些特定的行为,参考下面的文档:

Document-Based App Programming Guide for Mac

窗口的打开和关闭

Opening and Closing Windows

打开一个窗口,意味着把该窗口加入应用的窗口列表。可以通过NSWindow的makeKeyAndOrderFront:orderFront:方法操作。

关闭一个窗口,可以使用close方法直接将窗口从屏幕上移除,也可以使用performClose:来模拟用户点击关闭按键的操作。如果想在移除窗口之后里面释放内存,可以setReleasedWhenClosed:。窗口的委派(delegate)会收到窗口关闭的通知。

如果只是想隐藏窗口而不是关闭它,可以使用orderOut:。前面提到的setHidesOnDeactivate:可以让应用不活跃的时候自动隐藏窗口。isVisible方法可以获知窗口是否在屏幕上。

窗口分层以及窗口类型

Window Layering and Types of Windows

在屏幕上的窗口受窗口服务器(Window Server)管理。屏幕是一个有限的共享区域,所以当窗口一多,就必须以某种有秩序的方式层叠在一起。这种秩序就是由窗口服务器负责维护的。窗口与窗口之间存在先后关系,就像两个窗口对半平分屏幕,在窗口服务器眼里眼里,也是有先后顺序的。

同时,窗口服务器还通过等级(level)的方式来管理窗口。像菜单、工具面板这些窗口通常比普通窗口有更高的等级,能够帮助它们从普通窗口中脱颖而出,方便用户交互。

总之,等级高的窗口、并且层次高的窗口会优先获得显示。应用的主窗口或者文档窗口在同一层次,所以不同应用的这些窗口可以穿插显示。一个窗口在层次中的深度由访问次序决定,越久没有访问的窗口深度越深。如果一个应用有多个窗口,只有被访问的那个窗口的层次深度才会改变。如果想把一个应用的所有层次的窗口都提到最前,可以点击Dock上该应用的图标,或者是在Window菜单中选择Bring All to Front。

一个程序可以有若干个窗口,但是其中只能有一个主窗口。主窗口同时可以是键窗口,表示它可以获取用户输入。但是键窗口也可以是其他面板,比如字体面板。当主窗口不是键窗口是,主窗口和键窗口可以同为活动窗口。

谈一谈键窗口。用户在用鼠标或者键盘做交互时,不仅得让用户知道是跟哪个应用交互,还得让用户知道是跟应用的哪个窗口交互。所以窗口服务器和AppKit有责任把用户的输入跟一个窗口绑定,这个窗口就是键窗口。如果用鼠标操作,用户很容易知道当前操作的是哪个窗口,对于键盘操作,AppKit会把当前输入焦点的窗口以区别于其他窗口的方式显示,甚至窗口中具有输入焦点的控件也会高亮显示。需要注意的是,窗口管理器只会允许一个窗口成为键窗口,就算你有多个屏幕。

对于键盘快捷键而言,非键窗口也可以响应,只要当前应用是活跃应用。

再来谈一谈主窗口和键窗口的关系。首先主窗口也可以是键窗口,但是在一些情况下,需要其他窗口来辅助交互,比如需要设置主窗口的字体,这时候会打开字体面板。这时候主窗口依然是主窗口,但是键窗口却是字体面板。在主窗口和键窗口分离的情况下,它们要共同响应菜单事件,菜单事件会先发给键窗口,如果无法响应,则会发给主窗口。在编程方面,makeKeyWindow可以把一个窗口设置为键窗口,makeMainWindow可以把一个窗口设置为主窗口。如果想同时把一个窗口设为键窗口,并让它冒出来,可以使用makeKeyAndOrderFront:

不是所有的窗口都适合成为主窗口或者键窗口的。在NSWindow的派生类中,可以覆盖canBecomeKeyWindowcanBecomeMainWindow来告知AppKit该类型窗口是否适合成为主窗口或者键窗口。

窗口的层次和等级

Window Layers and Levels

如果你把每个窗口想象成一张纸,然后把屏幕想象成一张桌子。把窗口放置在屏幕上的时候,不仅需要决定窗口在屏幕的位置,还要决定窗口与窗口之间如何叠加。这种叠加秩序是由两个因素决定的,层次(layer)和等级(level)。

文档中有一句话很好解释了什么是窗口等级:

A window’s level serves as a high-order bit to determine its position with regard to other windows.

窗口得等级其实是对窗口得一种分类,具有相同目的得窗口会在同一等级。比如应用程序得主窗口和通知面板这两种窗口就在不同的等级。通知面板的等级要高,当它出现的时候会优先于主窗口显示。有一些窗口等级是在NSWindow预定义好的:

  • NSNormalWindowLevel
  • NSFloatingWindowLevel
  • NSScreenSaverWindowLevel
  • NSStatusWindowLevel
  • NSModalPanelWindowLevel
  • NSPopUpMenuWindowLevel.

下面两个是系统保留的等级:

  • NSTornOffMenuWindowLevel
  • NSMainMenuWindowLevel

通常情况下,Cocoa会自行决定窗口的等级,不需要太多用户干预。不过你还是可以通过setLevel:来主动设置窗口等级。

在同一窗口等级内,可以通过orderWindow:relativeTo:makeKeyAndOrderFront:orderFront:orderBack:orderOut:来调整窗口的层次。isVisible可以告知窗口是否在屏幕上显示,setHidesOnDeactivate:可以自动隐藏窗口。

目前NSScreenSaverWindowLevel是最高的窗口等级,如果需要超越它,可以设置:

[aWindow setLevel:NSScreenSaverWindowLevel + 1];

(本篇完)