上次我们的程序在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事件做什么样的处理。这个方法有一个参数,可以是下面列出的常量的一个或者几个的组合。

  1.    NSViewNotSizable     = 0,
  2.    NSViewMinXMargin     = 1,
  3.    NSViewWidthSizable   = 2,
  4.    NSViewMaxXMargin     = 4,
  5.    NSViewMinYMargin     = 8,
  6.    NSViewHeightSizable  = 16,
  7.    NSViewMaxYMargin     = 32

通过这个函数,我们就可以控制程序中的Button如何响应Window的Size Change事件了,下面我们看看修改后的main函数。

  1. int main(int argc, char *argv[])
  2. {
  3.     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  4.     [NSApplication sharedApplication];
  5.    
  6.     //Create main window
  7.     NSRect rc = NSMakeRect(0, 0, 400, 200);
  8.     NSUInteger uiStyle = NSTitledWindowMask | NSResizableWindowMask | NSClosableWindowMask;
  9.     NSBackingStoreType backingStoreStyle = NSBackingStoreBuffered;
  10.     NSWindow* win = [[NSWindow alloc] initWithContentRect:rc styleMask:uiStyle backing:backingStoreStyle defer:NO];
  11.     [win setTitle:@"HelloWin Test"];
  12.     NSButton* button = [[NSButton alloc] initWithFrame:NSMakeRect(160, 20, 80, 35)];
  13.     [button setTitle:@"Quit"];
  14.  
  15.     NSView* contentView = [win contentView];
  16.     MyController* controller = [[MyController alloc] init];
  17.  
  18.     [[win contentView] addSubview:button];
  19.     [contentView setAutoresizesSubviews:YES];
  20.     [button setAutoresizingMask:NSViewMaxXMargin | NSViewMinXMargin | NSViewMaxYMargin];
  21.     //Set target and action
  22.     [button setTarget:controller];
  23.     [button setAction:@selector(onButtonClicked:)];
  24.     [win center]; //Center main Window
  25.     [win makeKeyAndOrderFront:win];
  26.     [win makeMainWindow];
  27.  
  28.     [NSApp run];
  29.     [button release];
  30.     [win release];
  31.     [pool drain];
  32.    
  33.     return 0;
  34. }

 

我们可以看到下面两行新增加的代码

  1. [contentView setAutoresizesSubviews:YES];
   
  2. [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,但是他们拥有不同的类型。读者也可以自己尝试一下。

下面我们看看在这个程序基础上写的另外一个程序。

  1. #import
  2. #define IDC_QUIT_BUTTON 101
  3. #define IDC_CONFIRM_BUTTON 102
  4.  
  5. @interface MyController : NSObject
  6. {
  7. @public
  8.  NSWindow* window;
  9.  NSTextField* nameEdit;
  10. }
  11. - (void) onButtonClicked:(id)sender;
  12. @end
  13.  
  14. @implementation MyController
  15. - (void) onButtonClicked:(id)sender
  16. {
  17.  NSButton* button = (NSButton*)sender;
  18.  if ([button tag] == IDC_QUIT_BUTTON)
  19.  {
  20.      [NSApp terminate:NSApp];
  21.  }
  22.  else if ([button tag] == IDC_CONFIRM_BUTTON)
  23.  {
  24.   NSBeginInformationalAlertSheet(@"Information", @"Ok", nil, nil,
  25.           window, nil, NULL, NULL, NULL,
  26.           @"Your name is %s", [[nameEdit stringValue] UTF8String]);
  27.  }
  28.  
  29. }
  30. @end
  31.  
  32. void createContentView(NSView* view, id object)
  33. {
  34.  NSRect rcControl = NSMakeRect(10, 170, 90, 20);
  35.  NSTextField* nameLabel = [[NSTextField alloc] initWithFrame:rcControl];
  36.  [nameLabel setEditable:NO];
  37.  [nameLabel setTitleWithMnemonic:@"User Name:"];
  38.  [nameLabel setBezeled:NO];
  39.  [nameLabel setBackgroundColor:[NSColor windowBackgroundColor]];
  40.  [view addSubview:nameLabel];
  41.  
  42.  rcControl = NSMakeRect(100, 170, 120, 20);
  43.  NSTextField* nameEdit = [[NSTextField alloc] initWithFrame:rcControl];
  44.  [view addSubview:nameEdit];
  45.  ((MyController*)object)->nameEdit = nameEdit;
  46.  
  47.  NSRect rcView = [[view window] contentRectForFrameRect:[[view window] frame]];
  48.  rcControl = NSMakeRect(rcView.size.width - 80,
  49.          rcView.size.height - 35, 80, 25);
  50.  NSButton* confirmButton = [[NSButton alloc] initWithFrame:rcControl];
  51.  [confirmButton setTag:IDC_CONFIRM_BUTTON];
  52.  [confirmButton setTitle:@"Confirm"];
  53.  [confirmButton setButtonType:NSMomentaryLightButton];
  54.  [confirmButton setBezelStyle:NSRoundedBezelStyle];
  55.  [confirmButton setTarget:object];
  56.  [confirmButton setAction:@selector(onButtonClicked:)];
  57.  [view addSubview:confirmButton];
  58.  
  59.  return;
  60. }
  1.  
  2. int main(int argc, char *argv[])
  3. {
  4.     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  5.     [NSApplication sharedApplication];
  6.    
  7.     //Create main window
  8.     NSRect rc = NSMakeRect(0, 0, 400, 200);
  9.     NSUInteger uiStyle = NSTitledWindowMask | NSResizableWindowMask | NSClosableWindowMask;
  10.     NSBackingStoreType backingStoreStyle = NSBackingStoreBuffered;
  11.     NSWindow* win = [[NSWindow alloc] initWithContentRect:rc styleMask:uiStyle backing:backingStoreStyle defer:NO];
  12.     [win setTitle:@"HelloWin Test"];
  13.     //Create a button and set it as content view
  14.     NSButton* button = [[NSButton alloc] initWithFrame:NSMakeRect(160, 20, 80, 35)];
  15.     [button setTitle:@"Quit Application"];
  16.     [button setButtonType:NSMomentaryLightButton];
  17.     [button setBezelStyle:NSRoundedBezelStyle];
  18.     [button setTag:IDC_QUIT_BUTTON];
  19.  
  20.     NSView* contentView = [win contentView];
  21.  
  22.     MyController* controller = [[MyController alloc] init];
  23.     controller->window = win;
  24.     createContentView(contentView, controller);
  25.  
  26.     [[win contentView] addSubview:button];
  27.     [contentView setAutoresizesSubviews:YES];
  28.     [button setAutoresizingMask:NSViewMaxXMargin | NSViewMinXMargin | NSViewMaxYMargin];
  29.     //Set target and action
  30.     [button setTarget:controller];
  31.     [button setAction:@selector(onButtonClicked:)];
  32.     [win center]; //Center main Window
  33.     [win makeKeyAndOrderFront:win];
  34.     [win makeMainWindow];
  35.  
  36.     [NSApp run];
  37.     [button release];
  38.     [win release];
  39.     [pool drain];
  40.    
  41.     return 0;
  42. }

 

这个程序也很简单, 但是它也向我们解释了一些东西, 首先我用了一个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打下坚实的基础。