原创 在Dashboard Widget中嵌入Cocoa(图片贴不上去T_T)收藏

新一篇: QQ协议 | 旧一篇: 今天好开心哦,我要翻译一片关于Dashboard的文章了。

Dashboard Widget中嵌入Cocoa

Author: Michael Marmarou

译者:欧嘉蔚

对牛弹吉他翻译练习 Ver 1.0

 

l         导言

你是否有需要在Dashboard widget中嵌入Cocoa对象呢?如果答案是有的话,那么你就读对了文章了。而且这篇文章还会让你了解到Dashboard的内部是怎么工作的。通常来说,Widget是以 HTMLCSSJavaScript的结合的形式展现在用户眼前的,你所看到的只是这些技术联合起来所显示的一些图像。 你可以使用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都需要两个plistinfo.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

 

      

       在这里,我们导入了CocoaWebKit两个框架。因为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

{

}

-(