非理性繁荣!

史诗人物,第四部分:拖放,多个上司

8月26日,2008.根据蟒蛇OS-X可可pyobjc

欢迎来到Epic PyobJC教程系列的第四部分。在教程的第三部分我们专注于三个较小的任务:处理双击,缓存到磁盘,并添加进度指示器。

这次我们将重点放在两个稍微棘手的问题上,但可可应用程序开发中极其重要的模式。我们将开始实现一些拖放功能,最后,使用第二个NIB文件在应用程序运行时创建新窗口。

在第三篇文章中,我说我还将包括这一部分的资源列表,但在我想到用第二个笔尖(理解它打开了你的可可地平线)来覆盖之后,我觉得这是一个真正有利于报道的话题,以及覆盖阻力和落差,附加笔尖在一篇文章中,这些参考文献可能有点重要。

如果你到目前为止还没有跟上,你可以下载项目的当前zip文件.

因为这是我们最后一次共舞(在这个系列中,至少)让我们玩一玩。

拖放:一种哲学,一个样式

我想首先讨论一下我对可可的长期贡献,但那是个谎言。当我刚开始使用PyObjC和Cocoa时——大约一年半以前(不要告诉招聘人员)——我编写了我的第一个相当复杂的应用程序,我对此感到非常自豪。我印象最深的两个批评是:不要使用拉丝金属(我还没有从反刷金属阴谋中恢复过来)和“在两个列表之间进行拖放操作比双击效果更好吗?”

我想我咕哝了一个不做这件事的蹩脚借口,也就是说,这比我容易理解的更令人困惑,但该死的,谁还需要拖放呢?

好吧,以前的行为恰恰相反,我需要拖放,你也是。拖放是为数不多的几个可以尝试用户界面的地方之一,它可以在不打破许多惯例的情况下真正做一些有趣的事情,从而让用户诅咒您的出生。拖放是可可编程的西部。

那么多的阻力和落差的潜力还没有被挖掘出来,仅仅因为它不需要。大多数应用程序都可以在没有或最小的拖放的情况下实现,因此,它们是以这种方式实现的。但是,我说的,不再!

有无数的直觉,有趣和有用的更多使用拖放的方法。从某种意义上说,这不是一个完整的清单,只是一个起点:

  1. 允许文本和内容放到停靠图标上。这是促进应用程序间通信的一种很好的方式。在元窗口我们可能会让它自动搜索掉在停靠图标上的字符串。(我写了一篇在此处简要输入有关实现的详细信息

  2. 使导出数据像拖放一样简单。假设你想用电子邮件发送一份报告,如果你能把桌子拖到mail.app里,这不是很好吗?它是否自动转换为PDF?但是如果你把它拖到纯文本的电子邮件中呢?好吧,然后它可能会将数据导出为文本。如果导出数据是如此简单,并且应用程序真正协作,这不是很好吗?

  3. 注意上下文中的拖放数据。如果用户试图输入电话地址,如果您可以使用regex从段落中解析电话号码,那就太好了。更好的是,如果你删除了一个电话号码列表,它识别并添加了所有的电话号码。我们习惯于对我们提供的应用程序输入严格的标准,但是用户真正有可能爱上那些允许他们不加选择地输入数据并为他们处理细节的应用程序。当然,我们必须让我们的用户知道什么时候他们可以不加选择,因为他们接受过担任电脑秘书的培训,除非你的应用程序释放它们,否则不会有其他的期望。

总而言之,拖放是Unix管道可可编程。这是应用程序协作最简单、最无缝的方式,一旦我们开始有创意,它将打开一个意想不到的组合和效用的世界。

拖放:通过拖放进行搜索

元窗口我们将实现两种类型的拖放,首先,通过将文本拖到搜索窗口中,使搜索成为可能。不仅仅是搜索文本字段,但是包含它的整个窗口。这创造了一个更大的甜蜜点,而且使用起来更快。

我们要做的第一件事就是敞开心扉mw控制器.py并创建一个助手函数,该函数将接受搜索字符串作为参数,然后执行搜索(以及使用搜索字符串更新文本字段以进行可视化确认)。我们需要这样做,因为我们将子类化窗口听一下下降操作,但是如果在这个子类中处理搜索,我们就必须在那里重新实现搜索逻辑。我们避免重复自己,通过给予窗口子类IBoutlet并将其连接到我们的MWController和它的搜索_方法。

因此,我们添加了拖动搜索方法MWController.

DEF拖动搜索自己,searchString):自己.文本字段.设置字符串值_searchString自己.搜索_自己

拖动搜索把子弹装进去搜索_然后把它点燃。

现在,我们需要创建一个新文件。转到文件菜单,然后新文件(苹果-N)。我们真的想要子类化窗口,但是没有一个默认的模板可以做到这一点,我们只需要创建一个python nsObject子类将其归档并根据我们的需要进行调整。名字的文件mwdragwindow.py版,然后把它拖到在Xcode的大纲视图中分组(严格用于组织目的)。

然后——容易忘记但至关重要的一步——打开主.py并为添加导入mDragWindow窗口所以它看起来是这样的:

包含启动应用程序和加载MainMenu.nib所需的类的导入模块进口metaweb进口MetaWindowAppDelegate进口MWController进口mDragWindow窗口

现在打开mwdragwindow.py版归档,我们就可以开始工作了。

在顶部,您需要导入*AppKit和导入Objc,所以现在进口产品是:

进口Objc基金会进口*AppKit进口*

接下来我们要改变mDragWindow窗口从子类化N对象到子类化窗口.我们还想给它一个名为控制器.

mDragWindow窗口窗口):控制器=Objc.伊博特莱特()

在继续,我们开始设置它来支持drop操作。第一步是为它将响应的粘贴板类型注册一个类。在这种情况下,我们只想听NSStringPboardType,这是文本的纸板类型。

要注册对象的拖放类型,请调用对象的拖放类型registerForDraggedTypes_方法,其中包含要侦听的类型列表。通常在awakeFromNib方法,在初始化对象时调用。

为了mDragWindow窗口代码是这样简单:

DEFawakeFromNib自己):自己.registerForDraggedTypes_([NSStringPboardType])

小心通过registerForDraggedTypes_类型列表,不仅仅是传球NSStringPboardType直接。


关于用户创建的粘贴板类型的简短旁注。

可可已经支持许多不同类型的纸板。我们使用NSStringPboardType,但还有许多其他现有的粘贴板可供选择:nsurlpboardType(nsurlpboardType),nsColorBoardType(nsColorBoardType),NSFilenamesPboardType,nsFileContentsBoardType文件等等。

有时候,然而,您试图拖放一些不属于现有粘贴板类型范围的内容。在这些情况下,您可以创建自己的粘贴板类型。粘贴板类型实际上只是一个字符串,因此,创建粘贴板类型与创建包含字符串的模块级变量并将其导入使用相同粘贴板的任何其他模块一样简单。

一些模块AmwRowpboardType(M轮盘类型)=u“MWRowPboardType”我的窗口窗口):DEFawakeFromNib自己):自己.registerForDraggedTypes_([mwRowpboardType(M轮盘类型)])

在另一个模块中使用它会是这样的:

模块进口mwRowpboardType(M轮盘类型)另一个窗口窗口):DEFawakeFromNib自己):自己.registerForDraggedTypes_([mwRowpboardType(M轮盘类型)])

通常,您只能在声明它的应用程序中使用用户创建的粘贴板类型,只是因为其他应用程序不知道。然而,如果开发人员积极宣传他们的纸板的字符串,但事实未必如此。


既然mDragWindow窗口已注册NSStringPboardType,我们需要实现另外两个方法来告诉它如何处理传入的拖动。

第一个是牵引输入_,它用于确定用于表示传入拖动的图像的类型。例如,如果将文本拖到文本字段中,你可能想用NSDragOperationAdd,但是如果你把物体连接在一起,你会更喜欢nsdrag操作链接.也许最重要的手术,然而,是nsdrag操作无当您拒绝传入的拖动事件时返回。

当你回来的时候nsdrag操作无然后就看不到阻力了,对象拒绝处理拖动类型。这为用户提供了关于应用程序/窗口/字段可以接受哪些类型的数据的可视化提示。

mDragWindow窗口我们将实现牵引输入_如下:

DEF牵引输入_自己,发送方):pboard=发送方.拉筋粘贴板()类型=pboard.类型()OpType=nsdrag操作无如果NSStringPboardType在里面类型:OpType=nsdrag操作复制返回OpType

如果粘贴板包含NSStringPboardType我们会处理的,否则我们不会。我们使用nsdrag操作复制因为这是文本字段用来响应NSStringPboardType,在拖动图像之间的变化可能有点令人不安(除非你试图表明,如果你在不同的位置下落,会发生不同的行为,在这种情况下,这是完全合适的)。

牵引输入_处理接受或拒绝拖动,以及显示阻力的视觉图像,但到目前为止,我们还没有处理传入拖动的内容。这是通过实现执行随机操作_.

DEF执行随机操作_自己,发送方):pboard=发送方.拉筋粘贴板()成功的=如果NSStringPboardType在里面pboard.类型()TXT=pboard.字符串类型_NSStringPboardType自己.控制器.拖动搜索TXT成功的=返回成功的

如果我们返回执行随机操作_然后,应用程序将直观地指示丢弃被拒绝——如果我们返回然后它将在视觉上显示drop已经成功—但是实际上处理传入的数据取决于我们。

这里我们简单地调用MWController方法拖动搜索将字符串存储为类型NSStringPboardType在纸板上,和有MWController为我们处理事情。

我们需要做的最后一件事是打开InterfaceBuilder并修改一些东西,所以,敞开心扉吧主菜单.xib.

打开检查,并选择窗口,转到第6个选项卡。检查员的职务将改为窗口的身份当你在正确的标签。

在名为的段的检查器的顶部类标识,在名为的字段中等级取代窗口具有mDragWindow窗口.然后转到包含所有对象的窗口,压具控制,和阻力窗口控制器和释放。选择控制器从弹出菜单。

重新分类InterfaceBuilder中笔尖中的对象。

现在拖动一些文本到窗口,释放它,它将开始搜索,就好像我们在文本字段中键入了文本,然后点击搜索。

拖放:通过拖动导出数据

现在我们已经在窗口中实现了拖放,我们将看看另一个常见的拖放场景:在NSTableView.

传统的拖放操作是在NSTableView的数据源,但如果记得回到第二段,我们不指定的数据源,所以我们知道事情会有所不同。事实上,我们将通过子类化NSarray控制器分类和重新分类控制器中的对象主菜单.xib我们的定制课程。

我们先在项目中创建一个新文件,从子类化N对象,命名MwDragarayController.py(MwDragarayController.py),并将导入添加到主.py以便进口主.py如下所示:

包含启动应用程序和加载MainMenu.nib所需的类的导入模块进口metaweb进口MetaWindowAppDelegate进口MWController进口mDragWindow窗口进口MwDragarayController(多功能显示器控制器)

现在,早在MwDragarayController.py(MwDragarayController.py),让我们改变MwDragarayController(多功能显示器控制器)从子类化N对象从子类化NSarray控制器,以及进口NSStringPboardTypeAppKit.

AppKit进口NSStringPboardType基金会进口*MwDragarayController(多功能显示器控制器)NSarray控制器):通过

让我们绕个小弯子,敞开心扉吧主菜单.xib在InterfaceBuilder中。选择阵列控制器实例,打开检查员,选择Identity选项卡(第六个)并将类从NSarray控制器MwDragarayController(多功能显示器控制器).

然后,这一步很容易忘记,但很重要,您需要选择TableView(而不是包含它的滚动视图,所以点击它两次)并且连接它到把数组控制器对于代理和数据源出口。

保存,关闭接口生成器,回到编辑mwdroparray控制器.py.

令人惊讶的是,我们快结束了!我们只需要将此方法添加到MWDropArrayController:

DEFTableView_WriteRows_TopasteBoard_自己,电视,,):安排=自己.arrangedObjects()数据=”、“.加入([安排(X]“名字”]对于X在里面]).声明类型所有者_([NSStringPboardType自己.设置字符串类型_数据,NSStringPboardType返回

保存并构建应用程序,搜索并将其中一行从表中拖出来。很干净,是的,但有些事情不太对劲:你不能拖入其他应用程序。这很不方便,并且真正减少了拖动的效用。

来解决这个问题MWController,去awakeFromNib方法,并添加此行:

自己.表视图.setDraggingSourceOperationMask_forLocal_nsdrag操作复制,

所有这些意味着'MWControllerawakeFromNib方法如下:

DEFawakeFromNib自己):如果自己.表视图:自己.表视图.设置目标_自己自己.表视图.设置双精度_“打开时间:”自己.表视图.setDraggingSourceOperationMask_forLocal_nsdrag操作复制,

现在保存并尝试构建元窗口一次。您还应该能够将内容从行拖到其他应用程序中。

总结了我们对拖放的看法元窗口.我们不会考虑将数据放到TableView中,主要是因为我无法想象一个单一的方法来使用它,因为我们显示来自metaweb的结果,就好像它们是不可变的一样。如果元窗口扩展到修改数据,这样的操作可能有一些值(一种导入数据的方法,例如)。

使用第二个笔尖

是时候开始看我们史诗PyObjC系列教程的最后一个主题了,一个重要的主题是:在一个项目中使用多个笔尖。对于许多应用程序,可以避免使用多个笔尖。例如,如果你看看iTunes的播放列表方法,默认情况下,单击播放列表时,它只显示在主窗口的列表中。

你可以用一个笔尖。没问题。但是如果你双击播放列表,它将打开一个新窗口。,另一方面,您不能使用一个NIB(除非您通过编程创建和填充窗口,这是可能的,但通常会有更多的工作)。

一旦你从琐碎的申请中毕业,你将不可避免地需要学习在一个项目中使用多个笔尖,就像我们在本教程中看到的一样,一旦你决定坐下来做这件事,其实并不难。

现在,当您双击一行时,它会在Web浏览器中打开该条目的页面,不过,如果它打开一个新窗口,向我们展示更多有关该条目的详细信息,那就太好了。到目前为止,我们只是在抓取metaweb.py,我们可以用一个完整的窗口来展示更多的结果。

我们将采取三个步骤将额外的NIB集成到我们的项目中:

  1. 创建自定义Python类的子类NSWindowController窗口控制器,并在初始化时给定特定行的字典。

  2. 创建新的笔尖,我们会打电话给你的行窗口.xib,我们把文件的所有者类作为我们的自定义子类NSWindowController窗口控制器.

  3. 更新OpenX方法MWController使用创建新窗口行窗口.xib而不是在web浏览器中打开行条目。

子类别化NSWindowController窗口控制器

第一,让我们创建一个名为mwrowwindowcontroller.py.最初它将被子类化N对象,但是让我们把它的父类改为NSWindowController窗口控制器,同时给它一个名为rowDict.

M车窗控制器NSWindowController窗口控制器):rowDict=没有一个

然后导入到主.py像往常一样:

包含启动应用程序和加载MainMenu.nib所需的类的导入模块进口metaweb进口MetaWindowAppDelegate进口MWController进口mDragWindow窗口进口MwDragarayController(多功能显示器控制器)进口M车窗控制器

还有…这是它。

创建行窗口.xib.

创建新文件,这次选择窗口XIB模板,和命名它行窗口.xib.然后在InterfaceBuilder中打开它。

第一,打开Inspector并选择文件的所有者,然后转到Identity选项卡并更改它的类fromN对象RowWindowController.下一步创建nstextfield(nstextfield)在笔尖的窗口,选择它,并转到绑定选项卡。

价值结合,将其绑定到文件的所有者,以及模型密钥路径键入rowdict.name.名称.

正在为nstextfield设置绑定。

这是使用Cocoa绑定避免重复代码的另一个例子。他们是你的朋友。

最后,我们需要点击文件的所有者并连接它窗口出口至窗口.

更新OpenX方法

最后一步是更新OpenX方法MWController.为此,我们将从为M车窗控制器类到mw控制器.py.

进口Objc,metaweb,控件,泡菜,datetime,MD5,穿线M车窗控制器进口M车窗控制器AppKit进口*基金会进口*

然后我们转向OpenX.现在看起来是这样的:

DEFOpenX自己,发送方):选定对象=自己.阵列控制器.selectedObjects()如果len选定对象=:国家统计局你没有选定行!”返回=选定对象(]国家统计局u”行:%s%如果.有\键“id”(“id”]=没有一个:国家统计局“Row没有id!”返回网址=u“http://www.freebase.com/view/查看%s%(“id”]控件.开放网址

除了最后两行以外,一切都已经完美了。继续删除最后两个,并替换为:

RWC=M车窗控制器.同种异体().initWithWindowNibName_U“行窗口”RWC.rowDict=RWC.显示窗口_自己RWC.保持()

现在继续构建并运行应用程序。搜索某个内容,然后双击该行,您将看到它打开一个新窗口,并显示其名称。当然我们想要填满那个窗口中显示的内容,但现在你已经知道了,可以自己处理了。

所以我们结束了。

除了两个大问题

好。。。done可能是一个强烈的词。我们当前的实现有两个明显的问题。第一个是我们打电话来的保持RWC,但我们从不打电话释放,所以我们不允许垃圾收集器释放那个窗口。

第二个问题是如果你点击同一行两次,然后为这一行打开两个窗口。如果它只是将现有的行窗口放到前面,而不是创建第二个窗口,那么它将更加灵活。

好消息是,这两个问题的解决方案重叠,而且不会花太多时间去处理。

首先将字段添加到MWController命名行缓存用空dict初始化。

MWControllerN对象):表视图=Objc.伊博特莱特()文本字段=Objc.伊博特莱特()阵列控制器=Objc.伊博特莱特()指示器=Objc.伊博特莱特()结果=[]行缓存={}超高速缓存=没有一个

我们要用行缓存跟踪窗口控制器以允许我们重用它们。现在我们要重做OpenX最后一次:

DEFOpenX自己,发送方):选定对象=自己.阵列控制器.selectedObjects()如果len选定对象=:国家统计局你没有选定行!”返回=选定对象(]国家统计局u”行:%s%如果.有\键“id”(“id”]=没有一个:国家统计局“Row没有id!”返回如果自己.行缓存.有\键):RWC=自己.行缓存(]RWC.显示窗口_自己其他的:RWC=M车窗控制器.同种异体().initWithWindowNibName_U“行窗口”RWC.rowDict=RWC.显示窗口_自己RWC.保持()自己.行缓存(]=RWC

变化相当简单:在我们创建新的M车窗控制器我们正在检查是否已经有一个存储在行缓存.

你可以构建并运行应用程序并正确地重用窗口。哈利路亚。

现在来解决“不来电”释放'问题。

会议释放内存

到目前为止,在我们进入Pyobjc和Cocoa的旅程中,我们已经涉及了很多Cocoa主题和概念:释放,保持,可可绑定,NSarray控制器,拖放。我要增加你武器装备的最后一个工具是释放内存.释放内存对象子类化时调用的方法是N对象的内存将由垃圾收集释放。这是您最后一次在保留的引用变成内存泄漏之前解除它们的标记的机会。

每一步都有两个步骤(顺序很重要)释放内存方法:

  1. 释放任何保留的对象。
  2. 叫超级班'释放内存方法。

很容易忘记第二步,但这是导致内存泄漏的另一种常见途径。

释放内存方法MWController看起来像这样:

DEF释放内存自己):对于钥匙在里面自己.行缓存:价值=自己.行缓存(钥匙]价值.释放()超级MWController,自己.释放内存()

在某个时候你会想要阅读Cocoa内存管理指南,这是对深入研究内存管理时出现的各种细微差别的全面介绍。

结局四个部分

你可以下载项目的当前zip文件,或从在GitHub库.

我们最后完成了本教程的编程方面。已经很久了,也许对某些人来说是地狱,旅行。第五个和最后一个条目将包含各种资源,这些资源可以帮助您继续掌握pyobjc和cocoa。

我要讨论的最后一个主题是元窗口,我们在本教程中共同构建的应用程序。这确实是一个有点奇怪的,没有经过充分考虑的项目,这表明,特别是当我们考虑到当前迭代有多么无用时。

仍然,我认为下面的某个地方有一个有用的应用程序。Github存储库修复得很糟糕,为此我深表歉意。在你同时尝试辅导的时候,插入一些关于编写应用程序固有的尴尬的模糊的借口。我打算清理仓库,并乐于看到其他人尝试把它变成有用和有趣的东西。我很乐意回答问题或帮助解决出现的问题。

我想把更多的时间花在元窗口,但是在写这个系列的时候有点消极,而且在不久的将来不会有太多的时间来开发兆瓦。

可以继续本教程的第五部分吗.