窗口是应用 UI 界面视图的容器,它负责接收用户的鼠标键盛等系统事件,转发消息到相关的接收者对象。Appkit 提供的一些子类化的窗口还可以实现一些辅助交互功能,比如文件打开保存的对话框,字体颜色选择器等等。
每个应用启动后至少会打开一个窗口。当你运行多个应用时,屏幕上会有多个窗口界面。我们把当前用户正在工作的应用的窗口称为活动或激活的窗口,其他应用的商口相应的称为非活动的窗口。活动窗口顶部的title bar 部分的颜色是高亮灰色选中状态。。
可以接收输入事件(键盘,鼠标,触控板等外设)的窗口对象称为 keyWindow (keyboard window);当前的活动窗口也称为 mainWvindow。一个时刻只能有一个keyWindow 和一个mainWindow。如下 图。
keywindow 和mainWvindow可以是同一个窗口,也可以是不同的窗口。当mainWindow 可以接收 输入事件时,它同时也是 keyWindow。
面板是一种特殊的 window,执行一些辅助功能。常用来做一些警告确认框,用户输入信息等对话 框。
NSPanel 类型的window 不能做为 mainWindow,只能做为 key Window。
一些常用的子类有 NSColorPanel(颜色选择)、NSFontPanel(字体选择)、NSSavePanel (保存打开文件),这些子类化的window 都只能做为 keyWindow,它们将在面板和警告框章节做使用说明。
窗口界面的组成
窗口对象包括 title Bar, contentview 内容视图,contentBorder 底部边框区。titleBar 上面包括控制按钮、标题,如下图
可以在Window 的属性面板中配置 U界面。 Title Bar:去掉勾选后 window 就没有顶部title 栏了 Close、Minimize、Resize 分别表示顶部左边三个控制按钮是否有效 Restorable:是否允许 window 状态可保存等下次运行时恢复之前状态。比如记忆历史位置 frame 的功能。
Content Border 配置:默认是none 不显示出来,需要显示的话可以选择 SmallLarge Bottom Border 其他选项,如图所示
模态窗口
当有多个window在屏幕上时,用户可以点击切换到任何一个窗口上。有些特殊场景需要限制用户必须处理完当前的窗口的任务,完成任务后关闭它才能继续操作其它的window,这种窗口称为模态窗口。
有2种创建模态窗口的方法:
- Modal window 这种中window比较霸道,当它启动后,此时只有这个window可以接收响应用户操作,无法切换到其他窗口操作,其他窗口也不能接收处理系统内部的各种事件。
使用NSApplication的runModalForVVindow方法来创建Modal window
- (IBAction)showModelWindowAction:(id)sender {
[[NSApplication sharedApplication]runModalForWindow:self.myWindow];
}
使用stopModal方法来结束模态。如果用户直接点击了窗口顶部最左侧的关闭按钮,窗口关闭了,但是整个应用仍然处于模态,任何操作都无法得到响应。正确的做法是监听窗口关闭事件,增加结束模态的方法调用。
NotificationCenter.default.addObserver(self, selector:#selector(self.windowClose(_:)),
name: NSWindow.willCloseNotification,
object: nil)
@objc func windowClose(_ aNotification: Notification){
if let sessionCode = sessionCode {
NSApplication.shared.endModalSession(sessionCode)
self.sessionCode = nil
}
if let window = aNotification.object as? NSWindow {
if self.modalWindow == window {
NSApplication.shared.stopModal()
}
if window == self.window! {
NSApp.terminate(self)
}
}
}
2. Modal sessions
比起Modal windows, Modal sessions方式创建的window稍微温和一些,允许响应快捷键和系统菜单,比如字体颜色选择这些panel面板. 启动Modal sessions 窗口
NSModalSession sessionCode = [[NSApplication sharedApplication]beginModalSessionF
orWindow:window];
结束Modal sessions窗口,使用sessionCode 做为参数来关闭Modal sessions窗口。跟上面的模态处理一样需要注册窗口关闭事件来正常执行模态状态关闭。
- (void)windowWillClose: (NSNotification *)notification {
if (sessionCode != 0) {
[ [NSApplication sharedApplication]endModalSession:sessionCode];
}
}
总结一下有两点要注意:
- 任何窗口的关闭要么通过点击左上角的系统关闭按钮,要么通过代码执行 window的 close方
法关闭。 2. 对于任何一种模态窗口,关闭后还必须额外调用结束模态的方法去结束状态。如果点击了 window 左上角的关闭按钮,而没有执行结束模态的方法,整个系统仍然处于模态,其他窗口 无法正常工作。
窗口对象控制
创建窗口
使用NSWindow类创建窗口对象,除了frame 参数,还需要制定 style Mask来确定窗口样式风格。
NSRect frame = CGRectMake (0,0, 200, 200);
NSUInteger style = NSTitledWindowMask | NSClosableWindowMask |NSMiniaturizableWi
ndowMask | NSResizableWindowMask;
NSWindow *window = [ [NSwindow alloc]initwithContentRect:frame styleMask:style bac
king:NSBackingStoreBuffered defer:YES];
window.title = @"New Create Window";
//窗口显示
[window makekeyndorderFront : self];
//窗口居中
[window center];
- styleMask:按位表示的窗口风格参数
enum {
NSBorderlessWindowMask= 日, /1没有顶部titlebar边框
NSTitledwindowMask = 1 <<日, //有顶部titlebar边框
NSClosablewindowask = 1 << 1, 1/带有关闭按钮
NSMiniaturizablewindowMask = 1 << 2,1/带有最小化按钮
NSResizablewindowMask = 1 << 3,1/恢复按钮
NSTexturedBackgroundwindowMask = 1s< 8 //带纹理背景的window
};
2. backing:窗口绘制的缓存模式
enum {
NSBackingStoreRetained
=0,1/ 兼容老系统参数,基本很少用到
NSBackingstoreNonretained =1,1/不缓存直接绘制
NSBackingStoreBuffered
=21/缓存绘制
};
3. defer:表示延迟创建还是立即创建
将窗口定义为一个厲性变量
使用懒加载方法实现初始化如下代码。
@property(nonatomic, strong)NSWindow *myWindow;
- (NSWindow*)myWindow {
if(!_myWindow){
NSRect frame = CGRectMake (0, 0, 200, 200);
NSUInteger style = NSTitledWindowMask NSClosableWindowMask NSMiniatur
izableWindowMask | NSResizableWindowMask;
myWindow = [ [NSWindow alloc]initWithContentRect:frame styleMask:style ba
king:NSBackingStoreBuffered defer:YES];
_myWindow.title = @'New Create Window";
}
return _mywindow;
}
通过按钮方法来控制显示。
- (IBAction) showwindowAction: (id)sender {
//窗口显示
[self.myWindow makekeyAndOrderFront: self];
1/窗口居中
[self.myWindow center];
}
上述使用代码创建的 window 只能品示关闭一次,关闭后再次调用showwindowAction:显示会crash。
我们在MyWindow的实现中增加dealloc方法
-(void)dealloc {
NSLog(@'MyWindow dealloc");
}
我们重现运行,点击button,显示示window。关闭window后发现控制台输出打印:MyWindow dealloc 这说明虽然我们声明了window为strong引用,但是对于window的释放,系统是特殊处理,只要关闭就 释放了。
窗口通知
当窗口状态变化时候,系统会发出相关通知消息。
NSWindowDidBecomekeyNotification:窗口成为keywindow
NSWindowDidBecomeMainNotification:窗口成为mainwindow
NSWindowDidMoveNotification:窗口移动
NSWindowDidResignkeyNotification:窗口不再是keywindow
NSWindowDidResignMainNotification:窗口不再是mainwindow
NSWindowDidResizeNotification:窗口大小改变
NSWindowwi11C1oseNotification:关闭窗口
NSwindowDidMiniaturizeNotification:窗口最小化
比如说有多个窗口来回切换操作,2个窗口之问会有业务影响,一个窗口的界面数据修改会影响另外一个窗口的界面数据。可以注册NSWindowDidBecomeMainNotification,NSWindowDidBecomekeyNotification消息,当窗口每次接收到这个消息可以重新获取数据刷新界面。
注册关闭窗口的消息通知,可以在窗口关闭前完成一些资源释放,提醒用户有变化的数据是否需要保存等。
NSWindow 中的contentView
查看NSWindow.h头文件,有2个重要的属性说明一下
@property (strong) id /* NSView * */ contentview;
@property (strong) NSViewController *contentViewController NS_AVAILABLE_MAC (10_10)
通过xib设计window元素布局的话,直接从控件 库拖上去就行了。在运行过程中要动态增加view元素到NSWindow的话,使用window的contentView,它代表了window的根视图。 有2种方法改变NSWindow的contentview 11.可以使用自定义的NSView或NSViewController的view,增加到contentView
[self.window.contentView addSubview:view]
2.macos 10.10系统及以后,创建一个NSViewController子类,实例化后赋值给NSWindow的 contentViewController.
NSViewController *mVC = [NSViewController alloc]init];
self.window.contentViewController = myVC;
设置 Window 的image 和title
- (void)setWindowIcon {
[self.window setRepresentedURL: [NSURL URLWithString:@"WindowTitle"]];
[self.window setTitle:@"SQLiteApp"];
NSImage *image = [NSImage imageNamed:@"AppIcon"];
[[self.window standardwindowButton:NSWindowDocumentIconButton]setImage:image
];
}
设置窗口的背景颜色
[self.window setOpaque:NO];
[self.window setBackgroundColor: [NSColor greenColor]];
关闭 window 时终止应用
AppDelegate中 增加下面的代码可以保证当你关闭最后一个window或者关闭应用唯一的一个 window时应用自动退出。
- (BOOL) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)applica
tion {
return YES;
}
另外一个方法是监听窗口关闭的NSWindow WillCloseNotification消息,判断关闭的消息通知中的window对象为 主window时执行关闭应用。
- (void)recvWindowWillCloseMsg: (NSNotification*)notification {
NSWindow *window = notification.object;
if(window==self.window){
[NSApp terminate:self;
}
}
window title 区域增加视图
通过获取contentview的父视图,然后可以将提示信息或操作按钮(比如注册按钮)增加到 window的顶部区域提醒用户操作。
- (void)addButtonToTitleBar {
NSView *titleView = [self.window standardWindowButton:NSWindowCloseButton].su
perview;
NSButton *button = [[NSButton alloc]init];
button.title = @"Register";
float × = self.window.contentView.frame.size.width - 100;
button.frame = NSMakeRect (x, 0, 80, 24) ;
button.bezelStyle = NSBezelStyleRounded;
[titleView addSubview:button];
}
NSWindow 如何正确的保证居中显示
调用 NSWindow 的 center 方法实现窗口居中。由于 Window 有历史记忆功能,会记住上次应用运行时退出前的 frame 位置,因此需要先在 xib 界面中 或 通过代码设置 isRestorable 属性为 NO
@implementation WindowController
- (void)windowDidLoad {
[super windowDidLoad];
[self.window setRestorable:NO];
[self.window center];
}
@end
NSWindow 显示位置控制
调用window 的 setFrame 方法控制窗口的位置。同样的也必须关闭 isRestorable 属性。
[self.window setRestorable:NO];
NSRect frame = NSMakeRect (0, 0, 100, 100);
[self.window setFrame:frame display:YES];
应用关闭后 点击 Dock 菜单再次打开应用
AppDelegate中实现下面代理协议方法,重新打开 Window。
- (BOOL)applicationShouldHandleReopen: (NSApplication *)sender hasVisibleWindows:
BOOL)flag {
[self.window makekeyAndOrderFront: self];
return YES;
}
窗口创建和管理
一般情况下很少需要单独创建和管理窗口NSWindow对象。NSWindow的创建都是基于项目场景 模版创建,或者通过NSWindowController创建管理的。
新建一个项目,工程中会自动生成的MainMenu.xib中会包含一个Window对象,这个Window是由 AppDelegate管理的。
新建一个项目,勾选Create Document-Based Application, 自动生成的Document.xib会包含一个 Window对象,这个Window是由NSDocument文档类来管理的。
新建一个NSWindowController的子 类windowController,勾选使用xib,自动生成的 WindowController.xib会包含一个Window对象。