zoukankan      html  css  js  c++  java
  • NSWindow,一些有的沒的

    如果你有其他的平台的经验,来写 Cocoa 应用程式,可能会发现 NSWindow 的行为跟你想得似乎不太一样。

    ※ NSWindow 属于 View

    虽然现在的 GUI 应用程式的架构大都遵循 MVC 的设计典范,但是不同的 Framework 之间,那个部分属于 View,那个部分又属于 Contoller,规划却又不尽相同。在许多 Framework 的设计中,Window 被当成是 Controller 使用,但是在 Cocoa 的架构中,一个 NSWindow 物件,却是单纯扮演 View 的角色。

    当你打开 Visual Studio,打算写个 WinForm 程式,你大概会先用视觉化工具拉出一个 Window(这个 Window 的物件型别其实叫做 Form 就是了),在上面再拉一个按钮,在这个按钮上面点两下,就可以开始撰写使用者点到这个按钮时会触发的程式。-这里,在 Window 里头产生的按钮,被当成是这个 Window 物件的成员变数,点击这个按钮所触发的行为,也是这个 Window 物件的 method。总之,在这个 Window 中发生的种种,都是由 Window 物件处理,Window 物件是其他放在 Window 里头其他 UI 元件的 Controller。

    但是在 Cocoa 中却不一样。在 Cocoa 中产生了一个 Window 之后,这个 Window 并不扮演 Controller 的角色,而是另外有一个 Controller 物件,这个物件有一个成员变数(通常是个 IBOutlet)连结到这个 Window 上。你的 Window 物件与上面的其他 UI 元件的关系,就只是 NSWindow 有个 content view,content view 里头摆放这些元件,在 MVC 的界定上,Window 本身与其他 UI 元件,都是另外一个 Controller 的成员变数。

    所以,如果你想要做的事情是:让使用者在某个 Window 中点了某个按钮后,跳出另外一个 Window,就很容易发现两者之间的差别。在 WinForm 里头,要产生另外一个 Window,你会先另外制作一个继承 System.Windows.Forms.Form 的型别,然后在原本的 Form 里头产生这个 Form 的 instance,最后呼叫 Show() 显示出来。

    在 Cocoa 里头,因为 NSWindow 是被当成 View,所以要产生一个有两个以上 Window 的应用程式,就不是从某个主要 Window 去产生另外一个 Window,而是你可能会有一个继承自 NSObject 的 Controller 物件,这个物件可以同时管理两个 Window,只要设定两个 NSWindow 成员变数,再去 Interface Builder 拉出两个 Window,并且连结起来,就可以了。

    ※ NSWindowController

    现在有些人可能是因为 iPhone 接触 Objective-C 语言与苹果的开发工具,再来接触 Mac 上面的应用程式开发。-在 iPhone 应用程式中,我们会产生许多的 UIViewController 物件,然后把 UIViewController 放进 UINavigationController 或 UITabBarController 的浏览路径中。既然 iPhone 的 UI 实作是以 UIViewController 为主,因为 iPhone 的 UI 的重点是 View,而在 Mac 上的 UI 的重点应该就是 Window,所以在写 Mac 应用程式的时候,是不是可以比照 iPhone,产生许多的 NSWindowCongtroller 呢?

    这样想好像很直观,不过,状况并不是这样。NSWindowController,主要是给 NSDocument 用的,而使用 NSDocument 的应用程式,叫做 document-based application,甚至在 NSWindowController 的文件中,都可以看到参阅文件是 Document-Based Applications Overview。

    打开 Xcode,可以看到 project template 中,Cocoa Application 分类中有一个 checkbox,让你选择要不要产生 document-based application。什么是 document-based application?

    我们可以这样简单区分:有一种类型的应用程式,在程式中的 Window 数量是固定的,所有的工作都可以在这些有限的 Window 中完成,例如计算机-计算机程式只有一个 Window,上面是数字与计算用的按钮,各种计算工作都只在这个 Window 中完成,拉下 File 选单只会看到 Close,而没有像是 New 或 Save 这些指令;这是一般的 Cocoa Application。

    在这种应用程式中,可能还有其他的 Window,例如在计算机中,我们可能想要多一个Window 帮我们做单位转换,像摄氏转华氏之类。在这种状况下,其实只要像之前提到的,用一个物件产生两个 IBOutlet,分别连到这两个 Window 就好。

    另外有一类应用程式,则是可以用 New 产生新的文件,分别会有一个 Window 代表这个文件,比方说文字编辑器-你可以产生一份新档案,或是从 Finder 里头打开一份档案,每开一个新档案,就会多一个 Window,里头是档案的文字内容,你可以继续开启更多的档案,就会产生更多的 Window,所以应用程式中的 Window 数量会随着开启文件的数量而变动,没办法用有限的 Window 完成所有的工作。这种是 document-based application。

    在这两种类型的应用程式中,Window 会有不同的行为:

    1. 关闭 Window 的行为。在一般的 Cocoa 应用程式中,使用者按下计算机的关闭按钮,只是想要把计算机 Window 隐藏起来;但是在文书编辑器中,关闭 Window 就代表我已经不想要编辑这个档案了,不只是隐藏起来而已,而是除非要求应用程式重新开启档案,这个 Window 就不应该存在,在关闭的同时,如果档案被改过而没有储存,应用程式也应该要提醒使用者要不要存档。

    2. Window 是否要有一些代表档案的元件。在一般 Cocoa 应用程式中,Window 的标题列就只是单纯显示一个标题,计算机的 Window 标题就是「计算机」三个字;而文书编辑软体的标题呢,则会是目前正在编辑的档案的档名,同时显示一个档案图示的小 icon,在档名上面点选右键,会有一个选单告诉你这个档案的完整路径,拖拉这个 icon 到 Finder 里头,还可以搬移或是复制档案。

    Screen shot 2010-08-12 at 2.53.14 AM

    一个一般的 Cocoa 应用程式,可以直接用 IBOutlet 建立 Controller 与 Window 之间的简单关系;在 document-based application 中,Cocoa Framework 则是用 NSDocument、NSWindowController 与 NSWindow 实作。

    ※ NSDocument、NSWindowController 与 NSWindow

    NSDocument 所处理的是与档案之间的关系,最主要的是开启、修改与储存档案。以文书编辑软体来说,应用程式打开档案的时候,就会有一个 NSDocument 物件负责读入档案,把文字内容放在某个成员变数中,同时记得这个档案的路径(不过,最近几版的 Mac OS X,慢慢地都用 URL 取代本机路径)。另外,NSDocument 也负责档案列印与相关设定。

    在应用程式中,会有一个 Singleton 地 NSDocumentController,产生了一个新的 NSDocument 时,我们就要把这个 NSDocument 物件加到 NSDocumentController 中,这样,如果我们打开一个已经开过的档案,就可以透过比对 NSDocumentController 中是否有路径相同的档案,确认是否开过,如果已经开过,就不要产生新的 NSDocument 物件,直接用现有的物件,顶多把隐藏起来的文件 Window 显示出来。

    NSWindow 就是在萤幕上面看到的 UI,而 NSWindowController 的功能,就是介于 NSDocument 与 NSWindow 之间,与两者互动。

    从选单按下 New,产生 NSDocument,到一个 Window 出现在萤幕上,如果你直接用 project template 产生一个 document-based application,会发现 Cocoa 已经帮你做好很多预设实作,而光读程式码似乎看不懂 Cocoa 到底做了什么。官方文件里头 Message Flow in the Document Architecture 就在讲这几个 Class 是怎么串起来的,流程大抵是-

    1. NSDocument 首先免不了的要 alloc、init,设定档案 URL(如果是新文件,就是 nil),把 NSDocument 加入 NSDocumentController 的管理中。

    2. 接着,呼叫 NSDocument 的 makeWindowControllers。NSDocument 基本的实作是产生一个 NSWindowController,这个 NSWindowController 透过 NSDocument 的 windowNibName 决定要载入哪一个 nib。产生了 NSWindowController 后,NSDocument 会用 addWindowController:,把这个 NSWIndowController 物件加到自己的 windowControllers 中。每个 WindowController 负责管理一个 Window。

    3. 对 NSWindowController 呼叫 showWindow:,把从 nib 载入的 Window 显示出来。如果 NSWindowController 还没有载入 window,就会自动用 loadWindow 载入。

    NSWindowController 在 NSDocument 与 NSWindow 之间,最主要会用到的就是 setShouldCascadeWindows: 与 setShouldCloseDocument:,这部份大概都会在 makeWindowControllers 的时候设定。

    虽然 makeWindowControllers 的时候预设的实作是产生一个单一的 NSWindowController,但是一个 NSDocument 可能会用到很多不同的 Window,每个 Window 都可能有不同的行为。如果我们会用到很多 Window,就可以在 makeWindowControllers 时产生对应的 NSWindowController,用 addWindowController: 加入。

    以文字编辑器来说,一个档案的内容可能出现在一个主 Window 中,但是有可能有许多工具 Window,比方说主 Window 旁边有一个可以展开或收起来的 Drawer,里头有字数统计等相关资讯,在关闭主 Window 时,行为应该是关闭文件,这个 NSWindowController 就应该在 setShouldCloseDocument: 设成 YES,至于抽屉呢,关闭时就只是收起来而已,就反之,应该设成 NO。

    甚至,在同一个应用程式中,关闭不同 Window 的行为也都不一样。在 Safari 里头,关闭一个浏览器 Window,与关闭档案下载列表的 Window,前者代表的是我不要看这些网页了,但后者只是把档案下载列表隐藏起来而已。

    如果 setShouldCloseDocument: 设成 YES,在关闭 Window 的时候,Window 首先会用 delegate 的方式通知 NSWindowController,NSWindowController 再通知 NSDocument,确认档案是不是已经被改过,是不是应该要先存档才关闭;从 NSDocument 透过 NSWindowController 产生 Window 的过程中,NSDocument 也同时把自己的 Undo Manager 指派到 NSWindow 上,让 Window 上的 UI 元件-像是 NSTextField 或 NSTextView 等做 Undo 的时候,让 NSDocument 知道对档案的修改已经被 Undo 了。

    setShouldCascadeWindows: 则是决定我们要不要在新视窗产生的时候,让视窗位置稍微出现在前一个产生出来的文件的视窗的附近,但是视窗位置的上缘与左侧比前一个视窗多一点(打开「文字编辑」然后一直开新文件就看到这种效果)。同样的,只有编辑档案的主视窗需要这种行为,但 Drawer 则不用。

    Screen shot 2010-08-12 at 2.52.29 AM

    ※ 几个可以想到的问题

    问:我们要在应用程式中开一个视窗,一定要用 NSDocument 吗?假如我们要写一个聊天软体,开启新的聊天视窗的时候,这个聊天视窗根本与档案路径无关,我们是不是可以直接产生一个新的 NSWindowController 物件,让他来从 nib 载入 Window?

    答:这么做,在记忆体管理上,可能会有一些麻烦的地方。在关闭 Window 的时候,如果我们也要把管理 Window 的 NSWindowController 放掉,那么可能的作法就会是让 NSWindowController 成为 Window 的 delegate,在 Window 关闭时,NSWindowController 对自己呼叫 [self release],光想就觉得顶危险。而对 NSDocument 呼叫 close 的话,Cocoa 则已经有一套对 NSDocument 的记忆体管理机制。

    这边需要特别注意:NSDocument 的 close 代表的是要关闭文件,NSWindowController 如果没有一个关连的 NSDocument 的时候,单纯呼叫 close,只代表把 Window 隐藏起来而已,如果有关连的 NSDocument,就会去呼叫 NSDocument 的 close。

    而且,就算是聊天软体,我们大概也不会想点选相同的联络人,结果却是开不同的 Window 聊天。所以,每个聊天 Window 要知道是属于跟谁聊天的,大概也是透过一套 URL 管理,这样还是用 NSDocument 比较好,可能某个 NSDocument 代表 msn://zonble 之类,只是这类的 URL,通常就需要改写一下 Window 设定,让 Window 的标题列不要出现与档名相关的东西。

    问:那么,为什么 NSDocument 与 NSWindow 之间,需要经过 NSWindowController 这一层?而不是 NSDocument 直接连结到 NSWindow 上?一定要用 makeWindowControllers 产生 Window 吗?

    而且,事实上,NSDocument 自己也有 window 这个 method,我们也经常把 NSDocument 的 IBOutlet 直接连到 NSWindowController 负责载入的 Window 上,对吧?

    答:呃,说起来,如果你的 NSDocument 物件只有一个视窗的话,只要实作 windowNibName,回传一个要载入的 nib 的档名字串就好,其他事情预设实作都帮你做好了,就当做 NSWindowController 不存在就好。这个 API 也不是我规划的,有些事情你还真不知道为什么。

  • 相关阅读:
    Js获取当前日期时间及其它操作
    OpenResty
    Nginx开发从入门到精通
    TengineWeb服务器项目
    VS2012的SVN插件VISUALSVN
    VS项目如何运用svn的忽略列表
    SVN 中trunk、branches、tags都什么意思?
    SVN服务器搭建和使用(一)
    逗号分隔字符串转换为一张表--解决查询in(逗号分隔字符串)出错问题
    判断函数是否存在、判断函数是否存在并执行
  • 原文地址:https://www.cnblogs.com/watchdatalearn2012620/p/3101027.html
Copyright © 2011-2022 走看看