我们知道在Cocoa程序中, 如果你想处理一个窗口的事件或者应用程序的事件, 你可以使用Delegate的方法来实现响应的事件处理函数,但是如果你要处理一个button控件或者一个窗口里面很多的类似Button的这种有一种 缺省行为的控件的时候, 是不是要为他们每个控件都来实现这个Delegate呢?
答案是否定的,因为针对这些具有指定的缺省行为的控件,NSControl已经帮我们进行了一部 分的处理(这个control要求继承自NSActionCell, 比如你添加一个Button到窗口中,那么对Button的Click事件,NSControl是有自己的处理的,这就是Target-Action模 式,当Button的click事件被触发时,NSControl就会去检查这个Control中是否有对应的Target,这个Target针对这个事 件处理的Action是什么,如果Target和Action都存在,那么这个Click事件就会被NSControl直接处理,类似的处理代码如下(假设的实现)
-
- ( void ) mouseClicked ( )
-
{
-
if ( target != nil && action != nil && [ target respondsToSelector : action ] )
-
{
-
[ target performSelector : action withObject : self ];
-
}
-
}
下面我们通过具体的代码来展示一下这个技术。
-
int main ( int argc, char *argv [ ] )
-
{
-
NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
-
[ NSApplication sharedApplication ];
-
-
//Create main window
-
NSRect rc = NSMakeRect ( 0, 0, 800, 600 );
-
NSUInteger uiStyle = NSTitledWindowMask | NSResizableWindowMask | NSClosableWindowMask;
-
NSBackingStoreType backingStoreStyle = NSBackingStoreBuffered;
-
NSWindow * win = [ [ NSWindow alloc ] initWithContentRect :rc styleMask :uiStyle backing :backingStoreStyle defer : NO ];
-
[win setTitle :@ "HelloWin Test" ];
-
//Create a button and set it as content view
-
NSButton * button = [ [ NSButton alloc ] initWithFrame : NSMakeRect ( 200, 200, 60, 40 ) ];
-
[button setTitle :@ "Quit Application" ];
-
[win setContentView :button ];
-
//Set target and action
-
MyController * controller = [ [MyController alloc ] init ];
-
[button setTarget :controller ];
-
[button setAction : @selector (onButtonClicked : ) ];
-
[win center ]; //Center main Window
-
[win makeKeyAndOrderFront :win ];
-
[win makeMainWindow ];
-
-
[ NSApp run ];
-
[button release ];
-
[win release ];
-
[pool drain ];
-
-
return 0;
-
}
上面这段代码首先创建了一个窗口,然后在窗口内创建了一个Button,这个Button作为窗口的ContentView呈现,因为NSButton是从NSView继承来的,所以也是NSView的一个实例。在创建了Button对象之后,我们就为这个Button对象设置了用来处理缺省事件的 Target对象,然后又通过setAction来设置处理这个事件的方法, 这里要注意的是这个Action方法一定是可以被Target响应的,也就是说符合respondsToSelector:的调用。
这个Target对象的实现被放在一个叫做MyController的类里面,它实现了一个方法叫做onButtonClicked:,这个方法被作为一个 Action方法,所以它有一个参数(id)sender,这是因为Action方法被调用的时候,调用者会将触发这个Action的对象通过这个参数传 递给Action方法。这个例子的实现很简单,直接调用NSApp对象的Terminate:方法来退出应用程序。
这个例子结束的地方和 以前有所不同,我分别调用了Button和Window的release方法,这是因为Window和Button对象都是通过引用形式分配的,所以当不再需要使用的时 候需要将他们所占用的资源释放掉。Obj-C 2.0中提供了Garbage Colletion的功能,你分配的对象会在不被使用的时候自动释放,不过如果能了解Cocoa对象的分配和释放模式,对我们理解整个Cocoa框架的实 现还是大有益处的。
上面这个例子还有一个地方需要注意的是Button的大小,你可以看到在代码中我为button分配的Rect的大小 是60×40,但是运行的时候你会发现这个Button充满了整个Windows的Content区域,这是因为这个Button作为Window的ContentView,而Window的ContentView的Size Policy是随父窗口改变高度和宽度的。如果我们将
[win setContentView:button];
这句代码注释掉,换成下面的这句
[[win contentView] addSubview:button];
那么这个Button就在窗口中被正确定位了, 但是当你改变窗口大小的时候,这个Button的位置和大小都没有改变,这个是由它的Sizing Policy决定的。
============================================================================================================
上次我们的程序在Window中创建了一个Button,如果这个Button是作为Window的ContentView的时候, 当你改变窗口的时候, 这个Button的大小也跟着改变,而且总是充满了整个Window,但是如果你通过NSView addSubview:把这个Button添加到窗口中的时候, 当你改变窗口大小的时候, 这个Button的位置和大小始终不变,所以当你的窗口的宽度小于Button的Left时,Button就消失了。
熟悉Java或者Unix下面GUI编程的人可能会使用Layout机制来解决这个问题,熟悉Windows的人可能会想到使用OnSize事件来处理这个问题, Cocoa的确在窗口或者Parent View的大小改变的时候发送通知,但是在Cocoa中我们还有另外一种处理方法,这个方法可以称作Sizing Policy,意思是在窗口改变的时候, 对应的Child View或者Control应该如何响应这个改变。
NSView类有一个方法setAutoresizingMask:这个方法可以用来控制NSView对父窗口或者Parent View的Size Change事件做什么样的处理。这个方法有一个参数,可以是下面列出的常量的一个或者几个的组合。
-
NSViewNotSizable = 0,
-
NSViewMinXMargin = 1,
-
NSViewWidthSizable = 2,
-
NSViewMaxXMargin = 4,
-
NSViewMinYMargin = 8,
-
NSViewHeightSizable = 16,
-
NSViewMaxYMargin = 32
通过这个函数,我们就可以控制程序中的Button如何响应Window的Size Change事件了,下面我们看看修改后的main函数。
-
int main ( int argc, char *argv [ ] )
-
{
-
NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
-
[ NSApplication sharedApplication ];
-
-
//Create main window
-
NSRect rc = NSMakeRect ( 0, 0, 400, 200 );
-
NSUInteger uiStyle = NSTitledWindowMask | NSResizableWindowMask | NSClosableWindowMask;
-
NSBackingStoreType backingStoreStyle = NSBackingStoreBuffered;
-
NSWindow * win = [ [ NSWindow alloc ] initWithContentRect :rc styleMask :uiStyle backing :backingStoreStyle defer : NO ];
-
[win setTitle :@ "HelloWin Test" ];
-
NSButton * button = [ [ NSButton alloc ] initWithFrame : NSMakeRect ( 160, 20, 80, 35 ) ];
-
[button setTitle :@ "Quit" ];
-
-
NSView * contentView = [win contentView ];
-
MyController * controller = [ [MyController alloc ] init ];
-
-
[ [win contentView ] addSubview :button ];
-
[ contentView setAutoresizesSubviews : YES ];
-
[button setAutoresizingMask :NSViewMaxXMargin | NSViewMinXMargin | NSViewMaxYMargin ];
-
//Set target and action
-
[button setTarget :controller ];
-
[button setAction : @selector (onButtonClicked : ) ];
-
[win center ]; //Center main Window
-
[win makeKeyAndOrderFront :win ];
-
[win makeMainWindow ];
-
-
[ NSApp run ];
-
[button release ];
-
[win release ];
-
[pool drain ];
-
-
return 0;
-
}
我们可以看到下面两行新增加的代码
-
[ contentView setAutoresizesSubviews : YES ];????
-
[button setAutoresizingMask :NSViewMaxXMargin | NSViewMinXMargin | NSViewMaxYMargin ];
第一行代码并没有必要,因为是缺省行为,第二行代码我设置了3个常量值的组合,读者可以编译并运行这个程序,然后看看这个Button的行为有什么变化。另外读者可以尝试其他的组合,这些属性其实就是在Interface Builder中用鼠标点击Size属性时候,Nib处理程序为我们做的。
运行这个程序的时候我们可能发现,这个Button的样子怎么和我们见到的Mac程序里的Button不一样呢?这是因为NSButton缺省创建的时候,有些外观属性没有被设置,我们可以通过下面这行代码来改变Button的外观。
[button setBezelStyle:NSRoundedBezelStyle];
这个方法的参数也有很多选项,读者可以通过查阅文档来了解其他的值并了解他们的结果。另外我们还可以通过button setButtonType:来改变这个Button的类型,在Cocoa中,Push Button, Radio Button, CheckBox等都是NSButton,但是他们拥有不同的类型。读者也可以自己尝试一下。
下面我们看看在这个程序基础上写的另外一个程序。
-
#import
-
#define IDC_QUIT_BUTTON 101
-
#define IDC_CONFIRM_BUTTON 102
-
-
@interface MyController : NSObject
-
{
-
@public
-
NSWindow * window;
-
NSTextField * nameEdit;
-
}
-
- ( void ) onButtonClicked : ( id ) sender;
-
@end
-
-
@implementation MyController
-
- ( void ) onButtonClicked : ( id ) sender
-
{
-
NSButton * button = ( NSButton * ) sender;
-
if ( [button tag ] == IDC_QUIT_BUTTON )
-
{
-
[ NSApp terminate : NSApp ];
-
}
-
else if ( [button tag ] == IDC_CONFIRM_BUTTON )
-
{
-
NSBeginInformationalAlertSheet (@ "Information", @ "Ok", nil, nil,
-
window, nil, NULL, NULL, NULL,
-
@ "Your name is %s", [ [nameEdit stringValue ] UTF8String ] );
-
}
-
-
}
-
@end
-
-
void createContentView ( NSView * view, id object )
-
{
-
NSRect rcControl = NSMakeRect ( 10, 170, 90, 20 );
-
NSTextField * nameLabel = [ [ NSTextField alloc ] initWithFrame :rcControl ];
-
[nameLabel setEditable : NO ];
-
[nameLabel setTitleWithMnemonic :@ "User Name:" ];
-
[nameLabel setBezeled : NO ];
-
[nameLabel setBackgroundColor : [ NSColor windowBackgroundColor ] ];
-
[ view addSubview :nameLabel ];
-
-
rcControl = NSMakeRect ( 100, 170, 120, 20 );
-
NSTextField * nameEdit = [ [ NSTextField alloc ] initWithFrame :rcControl ];
-
[ view addSubview :nameEdit ];
-
( (MyController * ) object ) ->nameEdit = nameEdit;
-
-
NSRect rcView = [ [ view window ] contentRectForFrameRect : [ [ view window ] frame ] ];
-
rcControl = NSMakeRect (rcView. size. width - 80,
-
rcView. size. height - 35, 80, 25 );
-
NSButton * confirmButton = [ [ NSButton alloc ] initWithFrame :rcControl ];
-
[confirmButton setTag :IDC_CONFIRM_BUTTON ];
-
[confirmButton setTitle :@ "Confirm" ];
-
[confirmButton setButtonType :NSMomentaryLightButton ];
-
[confirmButton setBezelStyle :NSRoundedBezelStyle ];
-
[confirmButton setTarget : object ];
-
[confirmButton setAction : @selector (onButtonClicked : ) ];
-
[ view addSubview :confirmButton ];
-
-
return;
-
}
-
-
int main ( int argc, char *argv [ ] )
-
{
-
NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
-
[ NSApplication sharedApplication ];
-
-
//Create main window
-
NSRect rc = NSMakeRect ( 0, 0, 400, 200 );
-
NSUInteger uiStyle = NSTitledWindowMask | NSResizableWindowMask | NSClosableWindowMask;
-
NSBackingStoreType backingStoreStyle = NSBackingStoreBuffered;
-
NSWindow * win = [ [ NSWindow alloc ] initWithContentRect :rc styleMask :uiStyle backing :backingStoreStyle defer : NO ];
-
[win setTitle :@ "HelloWin Test" ];
-
//Create a button and set it as content view
-
NSButton * button = [ [ NSButton alloc ] initWithFrame : NSMakeRect ( 160, 20, 80, 35 ) ];
-
[button setTitle :@ "Quit Application" ];
-
[button setButtonType :NSMomentaryLightButton ];
-
[button setBezelStyle :NSRoundedBezelStyle ];
-
[button setTag :IDC_QUIT_BUTTON ];
-
-
NSView * contentView = [win contentView ];
-
-
MyController * controller = [ [MyController alloc ] init ];
-
controller ->window = win;
-
createContentView ( contentView, controller );
-
-
[ [win contentView ] addSubview :button ];
-
[ contentView setAutoresizesSubviews : YES ];
-
[button setAutoresizingMask :NSViewMaxXMargin | NSViewMinXMargin | NSViewMaxYMargin ];
-
//Set target and action
-
[button setTarget :controller ];
-
[button setAction : @selector (onButtonClicked : ) ];
-
[win center ]; //Center main Window
-
[win makeKeyAndOrderFront :win ];
-
[win makeMainWindow ];
-
-
[ NSApp run ];
-
[button release ];
-
[win release ];
-
[pool drain ];
-
-
return 0;
-
}
这个程序也很简单, 但是它也向我们解释了一些东西, 首先我用了一个C函数( createContentView )来处理界面创建的一部分工作,这说明了我们的C程序代码可以和Obj-C代码完美的结合在一起。
另外可以看到在MyController类中的onButtonClicked方法中,处理了两个不同Button的事件,它通过Control的tag属性来决定调用这个方法的是哪个Control。在onButtonClicked方法中,如果用户点击的是Confirm按钮, 程序使用了NSBeginInformationalAlertSheet:方法来显示了一个表单,这个方法的调用相比较于使用NSAlert类来说还是要方便一些的。
这个程序的设计并不符合Cocoa的指导原则,而且也没有很好的应用面向对象的方法来设计,同时我们可以看到这里面的大部分代码都是在处理一些外观或者布局方面的问题,在我们编写一个真正的应用程序时,这些代码经常是枯燥而且难于调试的。不过Apple提供了Interface Builder这个可视化的界面设计器,可以将你从这个繁重的体力劳动中解放出来。Interface Builder和XCode的结合,双剑合璧,正式Mac上应用程序开发的无敌组合。
这一系列的Tutorial的目的是为了让大家了解在没有Interface Builder的情况下, Cocoa程序是如何运作的, 我们了解了Target-Action模式的应用, 了解了如何通过Delegate来处理事件,也了解了在代码层面界面布局的方法。这些基础知识应该为将来更深入的理解Cocoa打下坚实的基础。