使用 Cocoa 为初学者编写 Mac OS X 程序/构建 GUI
上一页:一些 Cocoa 基本原则 | 下一页:容器 - 数组和字典
到目前为止,我们已经使用 Interface Builder 为我们的“Hello World”示例创建了一个非常简单的界面。现在,我们将更详细地了解它,以便我们了解如何构建更复杂、更有用的用户界面。
我们已经以非常笼统的方式讨论了目标和动作的概念;现在我们将了解 Interface Builder 如何广泛地利用这一点来将图形控件连接到您编写的代码段,这些代码段实现了您应用程序的有趣功能。
以“Hello World”为起点,让我们在代码中添加一个简单的动作,以便我们可以了解它是如何工作的。这个动作非常基本 - 它只是在移动滑块控件时设置文本的字体大小。在 Xcode 中,单击“GCHelloView.h”,以便它出现在编辑器中。现在将以下行添加到类定义中,位于其他方法下方,但在“@end”语句之前
- (IBAction) textSizeAction:(id) sender;
然后进行保存,以确保将此更改保存到文件。在这里,我们将方法的返回类型声明为 IBAction。事实上,这只是一个宏,它只是“void”,但任何标记为 IBAction 的东西都可以被 Interface Builder 自动检测为一个 *动作例程*,即一个可以连接到支持目标/动作机制的任何控件的例程。我们稍后会回到 Xcode 来实现此方法,但首先让我们在 IB 中将其连接起来。
如果 IB 未运行,请双击“MainMenu.nib”启动它。将窗口排列在 IB 中,以便您可以从 Xcode 拖动文件“GCHelloView.h”到 IB。这会触发 IB 读取文件,因此它将获取动作方法并将其添加到 GCHelloView 对象可用动作的列表中。接下来,切换回“实例”面板,并通过双击其图标将“窗口”置于最前面(或者如果您能看到它,只需单击窗口即可)。通过稍微拉大窗口,在窗口底部留出一些空间。在部件调色板中,选择“控件”面板(从左侧数第二个),然后将水平滑块控件从调色板拖动到窗口中。确保您将滑块放在您腾出的空间中,而不是放在 GCHelloView 中。
选择滑块控件,然后打开“检查器”(如果不可见,请使用“工具”->“显示检查器”)。确保弹出菜单设置为“属性”。将属性设置为以下值
- 最小值 - 9.0
- 最大值 - 72.0
- 当前值 - 48.0
此外,选中“滑动时持续发送动作”和“启用”复选框。其他设置应保持其默认值。
接下来,我们需要为滑块控件指定一个目标,即与应将动作发送到的对象建立连接。IB 以图形方式设置目标。我们从动作的发送方到目标进行控制拖动。一条线将连接这两个对象。现在就这样做 - 从滑块到 GCHelloView 进行控制拖动。检查器将切换到“连接”部分,并列出 GCHelloView 的所有可用动作方法。突出显示“textSizeAction:”并单击“连接”以建立连接。(注意 - 如果“textSizeAction:”没有出现在列表中,您可以手动添加它。在“类”列表中选择 GCHelloView。使用检查器属性切换到“动作”,单击“添加”,然后键入方法名称 - 不要忘记冒号!您可能需要这样做,因为之前将文件拖动到 IB 中的操作(应该为您处理此操作)似乎并不总是可靠地工作。添加完方法后,请再次尝试控制拖动步骤)。
保存更改,然后返回到 Xcode。现在我们需要实现动作方法。找到并选择 GCHelloView.m 文件。将以下方法添加到实现体的主体中
- (IBAction) textSizeAction:(id) sender { [self setText:[self text] withSize:[sender floatValue]]; }
现在构建并运行项目。拖动滑块... 当您拖动时,文本的大小应该会发生变化。
上面的代码实现了动作。它在滑块拖动到新位置时被调用,并向其自己的 setText:withSize: 方法发送消息,传递现有的文本([self text])和一个从滑块的值本身获得的大小。我们在 IB 中设置了 9 到 72 的值范围 - 这就变成了文本的字号。动作方法的“sender”参数始终是导致动作的对象 - 在这种情况下,是滑块。因此,我们可以简单地调用其“floatValue”方法来找出其当前值,并将其直接作为文本大小传递。更改在您拖动时立即可见,因为我们之前在 setText:withSize: 方法中添加了代码行 [self setNeedsDisplay:YES],这会导致 Cocoa 调用我们的 drawRect: 方法,该方法使用新大小重新绘制文本。
尽管很简单,但此示例非常典型地展示了所有控件如何与您应用程序中的代码段交互。您在某个合适的目标对象中编写一个动作方法(它始终具有 IBAction 返回类型,以及一个单独的“sender”对象参数),然后在 IB 中连接该动作和目标。
菜单命令的工作方式与滑块或按钮非常相似。它们具有一个目标和一个动作。当选择菜单项时,它会将动作发送到目标。您可以像我们对滑块所做的那样设置它们 - 从菜单项到目标进行控制拖动,选择动作并单击“连接”。
然而,有时我们不希望菜单命令转到特定的固定目标,而是希望它取决于上下文。例如,我们的应用程序可能有多个类似的窗口打开,例如文档。当用户选择“复制”或“粘贴”命令时,它应该始终将目标设置为当前活动文档。如果我们将此菜单命令绑定到特定目标,那么这些命令将无法按照用户通常期望的方式工作。
为了解决这个问题,Cocoa 保持一个“命令链”,它会根据上下文而改变。最前面的窗口将包含一个目标,该目标应该是第一个响应命令的对象。如果它可以响应,则会响应。否则,命令将传递到链中的下一个对象,这可能是窗口中的另一个视图,也可能是窗口本身。如果可以处理命令,则会处理。否则,它会再次向上传递,这一次传递到应用程序对象。如果应用程序无法处理命令,则会丢弃并忽略它。
在任何特定时间可以响应命令的链中的第一个对象称为 **第一个响应者**,在主 IB 窗口中将显示一个表示此对象的图标。因此,我们只需要将其设置为我们动作的目标,我们就可以解决菜单项的更改目标问题。您会发现您可以像任何其他可设置为目标的对象一样,对该目标进行控制拖动。“动作”列表中将列出第一个响应者的所有已知于 IB 的对象的所有动作。您可以链接到任何动作。如果在当前命令链中找到动作,则该动作将找到其目标并执行。如果在特定时间无法在命令链中找到特定的动作,将会发生什么?好吧,如果发送了动作,则不会发生任何事情,因为没有人响应它,但实际上,在这种情况下,Cocoa 会自动禁用菜单项!因此,找不到目标的动作会自动变灰,这为我们节省了大量管理菜单启用工作,因为上下文会发生变化。
“Hello World”没有多个窗口,因此在这个阶段我们无法令人信服地演示这一点,但很快我们将创建一个更复杂的应用程序,这种方法将大放异彩。
运行“Hello World”。尝试调整窗口大小。这可能不是您期望的行为 - 所有内容都保持与底部固定距离。让我们来看看如何使视图按照我们想要的方式调整大小。
在 Interface Builder 中,将窗口置于最前端并选中 GCHelloView。使用 Inspector,通过弹出菜单切换到“Size”面板。图示表明视图的哪些边缘应该与窗口的边缘严格绑定,哪些可以灵活调整。通过点击链接,可以将它们从一种状态切换到另一种状态。GCHelloView 应该随着窗口大小改变而改变大小,因此将内部链接设置为灵活,外部链接设置为固定。现在选中滑块。允许它与顶部和右侧边缘保持灵活链接,但与左侧和底部边缘保持固定链接。内部应固定。保存文件并使用 Xcode 构建并运行项目。现在,您将看到 GCHelloView(由蓝色边框显示)在调整窗口大小后会伸缩。
您可能会注意到,然而,文本似乎并没有按照我们的预期那样表现,它与视图底部的距离是固定的。我们习惯于文本保持与顶部边缘的固定距离。造成这种情况的原因是 Cocoa 的默认坐标遵循底层的 Quartz 图形系统,其中 y 值的增大是向上移动屏幕,而不是向下移动。虽然这是数学上的约定,但计算机程序员通常习惯于相反的方式。Cocoa 允许任何视图以这种方式进行“翻转”,使用一个简单的覆盖方法。所以我们现在就来改变它。
在 Xcode 中,选择 GCHelloView.m 并将以下方法添加到实现中
- (BOOL) isFlipped { return YES; }
这是一个对 NSView 方法的覆盖,因此我们不需要在我们的类中声明它。我们只需返回 YES 即可让 Cocoa 知道我们希望这个视图的 y 坐标从上到下。再次构建并运行,您将看到这带来的区别。由于在 drawRect: 中使用的 y 坐标的选择,文本将位于视图的相当靠下的位置 - 您可能想要将其更改为类似于 10 的值,这样它就会靠近顶部。