在Dashboard Widget中嵌入Cocoa
Author: Michael Marmarou
译者:欧嘉蔚
对牛弹吉他翻译练习 Ver 1.0
l 导言
你是否有需要在Dashboard widget中嵌入Cocoa对象呢?如果答案是有的话,那么你就读对了文章了。而且这篇文章还会让你了解到Dashboard的内部是怎么工作的。通常来说,Widget是以 HTML,CSS和JavaScript的结合的形式展现在用户眼前的,你所看到的只是这些技术联合起来所显示的一些图像。 你可以使用JavaScript建立一些控件,把他们的消息传回一个可以使用系统功能的Cocoa束(Cocoa Bundle)。而这个教程会为你展示一种直接访问Dashboard背后的Cocoa内核的方法。
尽管我们是可以把Cocoa对象添加到Widget里面,但是它们的行为和普通的Cocoa程序会有差异。作为免责声明,这个我写这个文档是为了纯粹的教学目的,如果出了什么问题,我不会负任何责任。Dashboard不是一个安全的(被保护的)环境,我也曾经让它崩溃过很多次。我个人并不认为这些方法是“Hacks”,因为Dashboard的设计者似乎是有意开放了这些能力,而且用的是十分直接的方式。
因为所有的层次的程序员都有可能编写Dashboard Widget,所以这里会提供一点基础知识。我假设阅读这个文档的程序员都懂得Objective-C并且可以熟练地使用XCode。
l Dashboard是怎么工作的:
对于用户和程序员来说,Dashboard进程是被隐藏的。真实的应用程序是位于Dock.app包里面(/System/Library/CoreServices),它是Dock的子进程。对于程序员来说,如果你的Dashboard崩溃了,你必须重新启动Dock来重置它。在后面会有一些关于调试的章节。
最好的方式看待Dashboard是把它看成一个巨大的浏览器。或者,很好的描述是:在一个灰色背景上的一堆浏览器。每个widget是一个被封装在一个WebFrameView(DBFrameView)里面的WebView(实际上是一个叫做DashboardWebView的子类)。这些类的实际名字并不重要,因为他们是私有类。因为我们没有那些头文件,我们不能知道有什么方法可调用。
当然,用Cocoa来编写一个Widget并不就是修改DashboardView那么简单的。还有很多东西你是不能做,随着我们一步一步地研究在一个Widget里面我们有什么能够做有什么不能做,我们会讨论到很多的限制。
镜像TextView的例子
l 目标:
这个例子会展示一些基本原理,包括如何加入一个子视图,放置一个子视图,访问它的代理。我们会把两个NSTextView会加进一个空白的画布(canvas),并且设置一个代理,其中的一个Textview会通过代理来更新。这个例子只是为了展示如何加入Cocoa对象,并且让你了解从它们那里获取事件是十分简单的。如果你不知道怎么通过代码(译者:不是使用Interface Builder)直接创建Cocoa对象,我强烈建议你先花点时间在上面,因为这在所有的Cocoa编程中都将会非常有用。
l 基础:
在Widget能够显示之前,有几样东西是必须的。缺少其中一个或者是用了一个错误的值都会让你的Widget不能显示,或者显示不正常(还会让你郁闷几个小时)。所以,在开始构建插件(plugin)之前,在Widget的目录下有一个符合标准的基础结构,是非常重要的。在这个例子里面,我们会从零开始建立一个新的Widget。尽管在开发者文档的例子里(/Developer/Examples/Dashboard/Sample Code)有一个空白的Widget。但是为了更好的理解一个Widget的每一部分,并且确保没有忽略任何东西,我们还是从零开始建立一个Widget。
进入你的工作文件夹(就是你要把最终会变成Widget的文件夹放置的地方)。建立一个新的文件夹,命名为“embeddedCocoa.widget”。你可能会想:“那个扩展名不是‘.wdgt’吗?”是的,没错。但是为了避免经常要使用终端、Authenticating或者按着control点击再选显示包内容。我们还是让扩展名继续为‘.widget’。直到我们要开始使用我们的Widget为止。
每个Widget都需要两个plist:info.plist 和 version.plist。打开‘Property List Editor.app’ (/Developer/Applications/Utilities/),建立一个新的文件。建立一个新的根(Root),建立下面的子节点,和下面的相应的值。
BundleVersion | String | 219 |
CFBundleShortVersionString | String | 1.0 |
CFBundleVersion | String | 1.0 |
ProjectName | String | DashboardSDK |
SourceVersion | String | 20000 |
现在,并不要在意这些值的意义,但是他们是必须存在的。把文件保存为version.plist并且放置在embeddedCocoa.widget里面。建立一个新文件,建立一个根,再加入下面的子节点:
AllowMultipleInstances | Boolean | No |
CFBundleIdentifier | String | com.apple.widget.embeddedcocoa |
CFBundleName | String | embeddedCocoa |
CFBundleShortVersionString | String | 1.0 |
CFBundleVersion | String | 1.0 |
DefaultImage | String | Default |
Height | Number | 205 |
MainHTML | String | embeddedCocoa.html |
Plugin | String | embeddedCocoa.widgetplugin |
Width | Number | 300 |
AllowMultipleInstances 的意义简单直接(译者:可不可以同时运行多个相同的Widget)。CFBundleIdentifier 对我们来说并不重要,但是要确保它的值是正确的。如果多于一个Widget有相同的值就会有问题了。DefaultImage 很重要,如果没有默认图片Widget就不会被加载了。他不会一直显示(译者:实际上,它只在主HTML没有加载的时候显示),我希望在最终版本中不再需要这个了,不过现在你还是需要一个默认图片(PNG格式的)。MainHTML 是就是WebView会频繁地加载的页面,所以也是非常重要的。Plugin 标识了最终会被放在同一个文件夹里面的插件捆束(plugin bundle)。
和文档同时发布的,还包括一些范例代码。但是,这不是必需的。如果你确实有这些范例,在随便在其中的一个范例里面找一个Default.png 文件。如果你没有这些范例,没关系,就随便建立一个图像文件,并且重命名它。任何的PNG图像都可以用,不过我会避免让它大于4GB,就算这样的文件真的有可能存在。
现在,我们必须要建立一个HTML文件。用你手头上任意一个文本编辑器建立一个新的文件。有趣的是,这个文件可以是完全空白的。不过当前你可能会要加入一个简单的HTML和一个body标签来显示背景图片。
<html> <body> <img src=”Default.png”> </body> </html> |
现在我们需要测试这Widget并确保所有东西都正常的工作。把文件夹重命名为‘embeddedCocoa.wdgt’。这个文件夹会马上把自己的图标变为Dashboard Widget的图标。双击这个图标看看Widget是否加载。如果没有加载,可能是你打错了那些plist中的一个值,缺少了一个文件或者命名不正确,又或者就是Dashboard不喜欢你了。
l 创建捆束
打开Xcode (/Developer/Applications/)。创建一个新的Cocoa捆束,在菜单下选择“New Project…”
在模版列表中,选择“Cocoa Bundle”。把捆束命名为“embeddedCocoa”。这个新建的捆束会连最基本的类也没有,所以我们需要自己创建一个。选择类文件夹(Classes folder),双击它,然后选择‘Add’再选择‘New File…’。
从显示出来的表单中,选择‘Objective-C class’。这是一个从NSObject类派生的Objective-C类。
请检查是否创建了两个文件(embeddedCocoa.h 和 embeddedCocoa.m),并且他们被放置在‘Classes’文件夹。如果不是这样的,请把它们拖到那儿。有两个方法是必须被声明的,但是有一个现在还不会用到。尽管不被用到,他们必须在头文件和实现文件中出现。头文件应该像这样子的:
#import <Cocoa/Cocoa.h> #import <WebKit/WebKit.h> @interface embeddedCocoa : NSObject { WebView *webView; NSTextView *textView;
NSTextView *textViewMirror; } - (id) initWithWebView:(WebView*)webView; - (void) windowScriptObjectAvailable:(WebScriptObject* )windowScriptObject; @end
|
在这里,我们导入了Cocoa和WebKit两个框架。因为WebView是在WebKit框架中声明的,所以它必须被导入。webView是一个WebView的引用,它通过initWithWebView传入我们创建的对象。 我们还声明了两个NSTextView。我们会保留对这些对象的引用,这样我们就可以在进程的生命周期中的任何时间点修改他们的内容。为了使插件工作,那两个方法是必须声明。
在实现文件,有一个init方法。用户可以通过这个函数来初始化捆束、实例变量和做任何需要做的工作。尽管这看来不是设计者的初衷,但是我们还是窃取它所接受的WebView,并且在上面耍些小把戏。那个init函数应该像这样子的:
#import "embeddedCocoa.h" @implementation embeddedCocoa -(id) initWithWebView:(WebView*)wview { /* Top TextView */ textView = [[NSTextView alloc] initWithFrame:NSMakeRect(0,105,300,100)]; NSClipView *clipView = [[NSClipView alloc] init]; //Initialize ClipView [clipView setDocumentView:textView]; //Add TextView as subview [clipView setFrame:NSMakeRect(0,105,300,100)]; //Set frame NSScrollView *scrollView = [[NSScrollView alloc] init];//Initialize ScrollView [scrollView setDocumentView:clipView]; //Add ClipView as subview [scrollView setFrame:NSMakeRect(0,105,300,100)]; //Set frame /* Mirror TextView */ textViewMirror = [[NSTextView alloc] initWithFrame:NSMakeRect(0,0,300,100)]; NSClipView *clipViewMirror = [[NSClipView alloc] init]; [clipViewMirror setDocumentView:textViewMirror]; [clipViewMirror setFrame:NSMakeRect(0,0,300,100)]; NSScrollView *scrollViewMirror = [[NSScrollView alloc] init]; [scrollViewMirror setDocumentView:clipViewMirror]; [scrollViewMirror setFrame:NSMakeRect(0,0,300,100)]; [wview addSubview:scrollView]; //Adds the subview to the main WebView [wview addSubview:scrollViewMirror]; //Adds the subview to the main Webview [textView setString:@"Type here, and it will be mirrored..."]; [textViewMirror setString:@"...down here."]; [textView setDelegate:self]; //set self as delegate to receive notifications webView = wview; //save the webView, in case we need to use it later return self; } -(void) windowScriptObjectAvailable:(WebScriptObject *)windowScriptObject { } -( |