1.2 创建MonoTouch应用程序
在本节中,将编写一个带有标签和按钮的简单应用程序,并通过单击按钮来改变标签的文本显示。完成后的应用程序将如图1-10所示。
1.2.1 创建用户界面
首先要做的是创建用户界面。返回刚才在MonoDevelop创建的包含一个空白窗口的应用程序,如果IB还没打开,双击MainWindow.xib文件打开IB。前面提及过,IB是苹果的用户界面设计工具,可以用它来创建应用程序的界面。如果曾经进行过Windows或ASP.NET开发,就会觉得它的作用有点类似于Visual Studio中的设计工具,但是功能不同。IB管理的是xib文件,它是一个XML格式的文件,在文件里封装了所有定义应用程序用户界面的对象。当生成应用程序时,xib文件会转换为以.nib为扩展名的二进制格式文件(这就是常说的“nib文件”)。基本上,xib文件就是UI层的序列化对象图。现在,可根据以下步骤创建一个用户界面:
1)在IB中,如图1-11所示那样在Library窗口中选择Objects(对象)标签页。
2)在Library窗口内滚动中间区域的滚动条,直到看到类型为UILabel的标签控件(Label)。然后将它拖动到窗口的设计界面上,并放置在靠窗口顶部的位置。
3)现在,添加一个按钮用来改变标签的显示。在Library窗口中找到类型为UIButton的Round Rect Button对象,将它拖到窗口里,并放在标签下面。
4)将按钮加入设计界面后,双击按钮进入编辑模式,将按钮的文本改为“Say Hello”。
5)保存xib文件后,返回MonoDevelop并运行应用程序。
当应用程序在模拟器中运行时,会看到添加的按钮和标签。触碰(实际只能单击,因为是在模拟器上运行的)按钮,会看到按钮的背景颜色变成了蓝色,但什么也没有发生。要在触碰按钮时改变标签的文本,还要为按钮编写相应的代码。准备好,继续!
1.2.2 添加outlet
IB不像常用的微软开发工具那样支持类代码生成。要在IB中定义的视图与代码之间实现交互,需要使用outlet,它可在代码中引用在xib文件中定义的对象。虽然需要一些手动步骤来完成该工作,但很快就会熟悉。而且,通过C#语言(尤其是局部类)与MonoTouch设计工具相结合,实际接触的代码比在使用objective-C和Xcode时要少很多。现在,回到IB。
在IB中,需要为示例创建两个outlet,一个用于按钮,以便订阅其事件;另一个用于标签,以便通过程序来修改它的文本。下面先创建标签的outlet,在Library窗口中选择Classes标签页,然后在列表中选择AppDelegate。最后,如图1-12所示,选择AppDelegate之后,在显示的Library窗口下半部分区域选择Outlets。
AppDelegate是一个类,其作用是接收由UIApplication操作所触发的调用,例如应用程序启动完成(有点类似回调接口,不过并不完全相同)。这里,“委托”(Delegate)并不是.NET中所熟悉和喜爱的委托,而是整个IOS SDK中常用的Objective-C技术。MonoTouch支持Objective-C 的Delegates(委托),将该委托抽象化,让C#和C# Delegates(委托)都可以对它进行访问。如果暂时无法理解这个概念也不要紧,第2章会详细讲述它。现在,只需要知道在应用程序中可通过AppDelegate类访问按钮和标签就可以了,因此需要在AppDelegate中为它们创建outlet。
要将UILabel的outlet添加到AppDelegate中,单击Library窗口左下角的+按钮添加一个outlet,将它的默认的名字myOutlet1修改为HelloLabel。新outlet的类型(Type)的默认值是id,它的值也可以是任何的NSObject,和C#的对象有点类似,将该值改为UILabel,这样就可将标签连接到outlet了(下面要做的事),而且只能连接到UILabel,不能是其他对象。完成后,将会看到如图1-13所示的Library窗口。
回顾一下刚才所做的,会给有微软开发经验的用户带来非常不同的用户体验。现在,有了保存用户界面显示所需要的所有序列化对象的xib文件,有了实现用户界面对象之间交互的代码,而要将xib中的对象与代码连接起来,需要在IB的AppDelegate中为UILabel添加outlet。剩下的工作就是将UILabel连接到刚刚定义的outlet,而这将会在C#中创建一个指向UILabel的属性。
要连接窗口中的UILabel与AppDelegate中的UILabel outlet,在MainWindow.xib窗口中选择AppDelegate,然后在主菜单中打开Connections Inspector (Tools→Connections Inspector)窗口。在Outlet区域中,会看到刚才创建的HelloLable outlet。单击Connections Inspector中outlet右边的圆形图标,并按住鼠标键(如果是多按钮鼠标,按住鼠标左键),将它拖到窗口的标签上。按住鼠标键将鼠标指针移动标签上面,会看到标签的颜色变成了蓝色,这说明它的属性类型没有错,是outlet定义的UILabel类型。如果拖动到按钮上,就不会有提示表明该控件可以与outlet连接。这是因为之前将outlet的类型由id改为UILabel了,如果不修改,类型还是id,outlet就可以连接任何控件。这就留下了一个隐患,有可能做出错误的连接。在标签上松开鼠标按钮即可建立连接。下面讲述在MonoDevelop中会触发什么事件,不过,要先完成按钮的连接。重复之前的步骤将按钮连接到outlet。这次,outlet的名称为SayHellButton,类型为UIButton。重复之前的拖放操作,这次的目标是窗口中的UIButton。请注意,也可以将outlet拖动到MainWindow.xib窗口的元素上进行连接(这也是为什么要保留树形视图的原因),因为它们都代表着相同的对象。如果元素在窗口的初始状态是隐藏的,就会相当方便。将IB中完成的操作保存。此时,应用程序所需要的东西已经准备完毕,可通过代码实现对象串之间的交互了(如图1-14所示)。
现在开始把注意力集中到代码上。首先,先看一下在IB中保存所有东西后,MonoDevelop如何响应所有这些outlet。在MonoDevelop中,展开Solution Explorer的MainWindow.xib,打开MainWindow.xib.designer.cs文件,将看到代码清单1-1所示的代码。在代码中定义了一个局部类,包含了所有在IB中创建的outlet连接。这里看到的是AppDelegate类的一部分类,包括为IB中每一个连接所创建的多个属性。此外,要注意使用Connect特性(attributes)来声明这些属性(properties)。这些都是MonoTouch为代码连接到IB中的outlet所要做的。最后要注意的是AppDelegate类的Register特性,MonoTouch会使用它在Objective-C运行时注册一个类。
注意 严格地说,MonoTouch会自动注册NSObject的子类。默认情况下,它使用Full.Namespace.Typemape这样的格式来注册子类。特性的作用就是将注册类名映射成“简写名称”(ShortName);否则,MonoTouch.UIKit.UIButton就连接不到UIButton。
代码清单1-1 MainWindow.xib.designer.cs
//
// <autogenerated>
// This code was generated by a tool.
// Mono Runtime Version: ...
//
// Changes to this file may cause incorrect behavior and
// will be lost if the code is regenerated.
// </autogenerated>
//
namespace LMT12
{
// Base type probably should be MonoTouch.Foundation.NSObject or
// subclass
[MonoTouch.Foundation.Register("AppDelegate")]
public partial class AppDelegate
{
private MonoTouch.UIKit.UIWindow __mt_window;
private MonoTouch.UIKit.UIButton __mt_SayHelloButton;
private MonoTouch.UIKit.UILabel __mt_HelloLabel;
#pragma warning disable 0169
[MonoTouch.Foundation.Connect("window")]
private MonoTouch.UIKit.UIWindow window {
get {
this.__mt_window = ((MonoTouch.UIKit.UIWindow)
(this.GetNativeField ("window")));
return this.__mt_window;
}
set {
this.__mt_window = value;
this.SetNativeField ("window", value);
}
}
[MonoTouch.Foundation.Connect("SayHelloButton")]
private MonoTouch.UIKit.UIButton SayHelloButton {
get {
this.__mt_SayHelloButton = ((MonoTouch.UIKit.UIButton)
(this.GetNativeField ("SayHelloButton")));
return this.__mt_SayHelloButton;
}
set {
this.__mt_SayHelloButton = value;
this.SetNativeField ("SayHelloButton", value);
}
}
[MonoTouch.Foundation.Connect("HelloLabel")]
private MonoTouch.UIKit.UILabel HelloLabel {
get {
this.__mt_HelloLabel = ((MonoTouch.UIKit.UILabel)
(this.GetNativeField ("HelloLabel")));
return this.__mt_HelloLabel;
}
set {
this.__mt_HelloLabel = value;
this.SetNativeField ("HelloLabel", value);
}
}
}
}
打开Main.cs文件,会看到局部类AppDelegate的另一部分代码。这里可以添加响应按钮的TouchUpInside事件的代码,并改变标签的文本,详细代码请看代码清单1-2。
保存后,运行应用程序。当应用程序在模拟器中运行后,单击按钮,并注意标签上文本的变化。可以看到,标签尺寸太小,容纳不了全部文本,因而在文本的末尾使用了省略号代替(其实,这是故意的,目的是为了讲述将要在IB中做的事)。现在,返回IB,修正这个问题。
注意 在IB和MonoDevelop之间来回切换是很常见的事,因而尽早熟悉有好处。
返回IB,在窗口中选择UILabel并通过拉伸增加它的宽度。如图1-15所示,IB会显示Guide Geometry来协助调整大小和布局,以符合苹果人机界面指南(Human Interface Guideline,HIG)。
然后,设置标签文本居中显示。选择标签,打开Attributes Inspector(Tools→ Attribures Inspector),在Attribures Inspector布局区域,设置文本的排列(alignment)为居中(centered)。保存修改并返回MonoDevelop运行应用程序。单击按钮,这次就可在标签内看到全部文本了。
如果回头再看MonoDevelop,也许希望它能生成一些代码反映刚才对标签所做的改动。然而,IB不会生成代码,它会将它内部的改动直接序列化到xib文件中。在整个过程中,只有钩上(hook)outlet的属性才会生成代码,而且是在MonoDevelop观察到xib文件发生改动时创建的。代码清单1-3列出了xib文件中反映标签变化的代码,节点表示增大后标签的尺寸。
返回MonoDevelop并运行应用程序,单击按钮,这次会看到“Hello MonoTouch”已经全部显示出来了。值得庆贺,终于完成了这个简单的Hello World示例。现在已经可以在模拟器中看到应用程序的运行情况了,下一步要做的是将它移植到设备上进行开发。