天天看點

深入淺出 Cocoa 之 Core Data(3)- 使用綁定

 今天要完成的這個示例,有兩個 Entity:StudentEntity 與 ClassEntity,各自有一個名為 name 的Attribute 其中 StudentEntity 通過一個名為 inClass 的 relationship 與 ClassEntity關聯,而 ClassEntity 也有一個名為 students 的 relationship 與 Entity:StudentEntity 關聯,這是一個一對多的關系。此外 ClassEntity 還有一個名為 monitor 的 relationship 關聯到 StudentEntity,代表該班的班長。

代碼下載下傳:點選下載下傳

最終的效果圖如下:

深入淺出 Cocoa 之 Core Data(3)- 使用綁定
深入淺出 Cocoa 之 Core Data(3)- 使用綁定

下面我們一步一步來完成這個示例:

1,建立工程:

建立一個 Cocoa Application,工程名為:MacCoreData,并勾選 Create Document-Based Application 和 Use Core Data,在這裡要用到 Core Data 和 Document 工程模版,以簡化代碼的編寫。

深入淺出 Cocoa 之 Core Data(3)- 使用綁定

2,分類檔案:

在 MacCoreData 下建立 Src 和 Res 兩個 Group,并将 MyDocument.h 和 MyDocument 拖到 Src 下,将其他 xib 和 xcdatamodeld 拖到 Res 中。将檔案分類是個好習慣,尤其是對大項目來說。

深入淺出 Cocoa 之 Core Data(3)- 使用綁定

3,建立 Entity:

在工程中,我們可以看到名為 MyDocument.xcdatamodeld 的檔案,其字尾表明這是一個 core data model檔案,架構就是讀取該模型檔案生成模型的。下面我們選中這個檔案,向其中添加兩個實體。點選下方的 Add Entity 增加兩個新 Entity: ClassEntity 和 StudentEntity。

深入淺出 Cocoa 之 Core Data(3)- 使用綁定
深入淺出 Cocoa 之 Core Data(3)- 使用綁定

向 StudentEntity 中添加名為 name 的 string 類型的 Attribute,并設定其 Default Value 為學生甲,去除 Optional 前勾選狀态;

向 ClassEntity 中添加名為 name 的 string 類型的 Attribute,并設定其 Default Value 為XX班,去除 Optional 前勾選狀态;

選項 Optional 是表示該  Attribute 可選與否的,在這裡 name 都是必須的。

向 StudentEntity 中添加名為 inClass 指向 ClassEntity 的 Relationship,其 Inverse 欄要等 ClassEntity 添加了反向關系才能選擇,後面回提到;

向 ClassEntity 中添加名為 students 指向 StudentEntity 的 Relationship,其 Inverse 欄選擇 inClass,表明這是一個雙向關系,勾選 To-Many Relationship,因為一個班級可以有多名學生,這是一對多關系。設定之後,我們可以可以将 StudentEntity 的 inClass 關系的 Inverse 設定為 students了。

再向 ClassEntity 中添加名為 monitor 指向 StudentEntity 的 Relationship,表示該班的班長。

4,生成 NSManagedObject 類:

選中 StudentEntity,然後點選菜單 File-> New -> New file…,添加 Core Data -> NSManagerObject subclass, XCode 就會自動為我們生成 StudentEntity.h 和 StudentEntity.m 檔案,記得将這兩個檔案拖放到 Src Group 下。下面我們來看看這兩個檔案中有什麼内容:

StudentEntity.h

#import <Foundation/Foundation.h>

#import <CoreData/CoreData.h>

@class ClassEntity;

@interface StudentEntity : NSManagedObject {

@private

}

@property (nonatomic, retain) NSString * name;

@property (nonatomic, retain) ClassEntity * inClass;

@end

StudentEntity.m

#import "StudentEntity.h"

#import "ClassEntity.h"

@implementation StudentEntity

@dynamic name;

@dynamic inClass;

在前面手動代碼的示例中,我們是自己編寫 Run NSManagedObject的代碼,而現在,XCode 已經根據模型檔案的描述,自動為我們生成了,友善吧。有時候自動生成的代碼不一定滿足我們的需要,我們就得對代碼進行修改,比如對 ClassEntity 來說,班長隻能是其 students 中的一員,如果我們在 students 中移除了班長那個學生,那麼該班級的班長就應該置空。

選中 ClassEntity,重複上面的步驟,自動生成 ClassEntity.h 和 ClassEntity.m,下面我們根據需求來修改 ClassEntity.m。

在 - (void)removeStudentsObject:(StudentEntity *)value 的開頭添加如下代碼:

    if (value == [self monitor])

        [self setMonitor:nil];

在 - (void)removeStudents:(NSSet *)value 的開頭添加如下代碼:

    if ([value containsObject:[self monitor]])

這樣當我們在 students 中删除一個學生時,就會檢測該學生是不是班長,如果是,就将該班的班長置空。

5,下面來生成 UI 界面:

在這裡,我們是通過切換 view 的方法來顯現學生與班級兩個界面,是以我們需要主界面,班級以及學生共三個界面。向 MyDocument.xib 中添加如下一個 popup button 和一個 NSBox。并删除 popup 控件中的 menu item,因為我們要通過代碼來添加班級,學生項的。

深入淺出 Cocoa 之 Core Data(3)- 使用綁定

然後在 Res 中添加兩個新 Empty xib 檔案:StudentView.xib 和 ClassView.xib,分别向這兩個 xib 檔案中拖入一個 Custom View,然後在這個 view 添加相關控件構成 UI。記得設定 ClassView 中兩個 tableView 的列數為 1,拖入一個 PopupButtonCell 到 StudentView 中班級那一列。效果如下:

深入淺出 Cocoa 之 Core Data(3)- 使用綁定
深入淺出 Cocoa 之 Core Data(3)- 使用綁定

6,添加 ViewController:

下面我們建立 ViewController 來在程式中轉載 xib 檔案,顯示和切換 view。為了便于切換 view,我們建立一個繼承自 NSViewController 的名為:ManagedViewController的類(記得不要建立該類對應的 xib 檔案!建立一個 NSObject子類,然後修改其父類為 NSViewController),然後讓 StudentViewController 和 ClassViewController 從它繼承。

ManagedViewController 類的代碼如下:

ManagedViewController.h

#import <Cocoa/Cocoa.h>

@interface ManagedViewController : NSViewController {

    NSManagedObjectContext * managedObjectContext;

    NSArrayController * contentArrayController;

@property (nonatomic, retain) NSManagedObjectContext * managedObjectContext;

@property (nonatomic, retain) IBOutlet NSArrayController *contentArrayController;

#import "ManagedViewController.h"

@implementation ManagedViewController

@synthesize managedObjectContext;

@synthesize contentArrayController;

- (void)dealloc

{

    self.contentArrayController = nil;

    self.managedObjectContext = nil;

    [super dealloc];

// deal with "Delete" key event.

//

- (void) keyDown:(NSEvent *)theEvent

    if (contentArrayController) {

        if ([theEvent keyCode] == 51) {

            [contentArrayController remove:nil];

        }

        else {

            [super keyDown:theEvent];

    }

    else {

        [super keyDown:theEvent];

在上面代碼中,我們有一個 NSManagedObjectContext * managedObjectContext 指針,它指向 MyDocument 架構中的NSManagedObjectContext對象,後面我們會說到,至于 NSArrayController * contentArrayController,它是一個 IBOutlet,将與xib 中建立的 NSArrayController關聯,後面也會說到。在這裡引入 contentArrayController 是為了讓 delete 能夠删除記錄。

ClassViewController 類的代碼如下:

ClassViewController.h

@interface ClassViewController : ManagedViewController {

ClassViewController.m

#import "ClassViewController.h"

@implementation ClassViewController

- (id)init

    self = [super initWithNibName:@"ClassView" bundle:nil];

    if (self) {

        [self setTitle:@"班級"];

    return self;

StudentViewController 類的代碼如下:

StudentViewController.h

@interface StudentViewController : ManagedViewController {

StudentViewController.m

#import "StudentViewController.h"

@implementation StudentViewController

    self = [super initWithNibName:@"StudentView" bundle:nil];

        [self setTitle:@"學生"];

在這兩個子類中,我們在 init 方法中載入 xib 檔案,然後設定其 title。

7,建立 NSArrayController,關聯對象

現在回到 xib 中來,選中 StudentView.xib,設定StudentView 的 File's Owner 的類為 StudentViewController;使用 Control-Drag 将 File's Owner 的 view 指向 custom view。

向其中拖入兩個 NSArrayController:ClassPopup 和 Students。

設定 ClassPopup 的 Object Controller Mode 為 Entity Name,實體名為:ClassEntity,并勾選 Prepare Content。

設定 Students 的 Object Controller Mode 為 Entity Name,實體名為:StudentEntity,并勾選 Prepare Content。

深入淺出 Cocoa 之 Core Data(3)- 使用綁定

上面的這些操作,ClassPopup ArrayController 管理 ClassEntity 的資料,Students ArrayController 管理 StudentEntity 的資料,後面我們就要将控件與這些 ArrayController 綁定起來。下面我們将這兩個 NSArrayController 的 ManagedObjectContext 參數與 ManagedViewController(File's Owner) 中的 managedObjectContext 綁定起來,這樣 NSDocuments 的 NSManagedObjectContext 就作用到的 ArrayController 中來了。下面隻示範了 ClassPopup,請自行完成 Students 的綁定:

深入淺出 Cocoa 之 Core Data(3)- 使用綁定

前面我們在 ManagedViewController 建立了一個 IBOutlet contentArrayController,現在是将它關聯的時候了,使用 Control-Drag 将 File's Owner 的 contentArrayController 關聯到 Students。

重複上面的過程,選中 ClassView.xib,将 File's Owner 的類為 ClassViewController,并将其 view 指向 custom view。

向其中拖入三個 NSArrayController:Classes,MonitorPopup 和 Students。

設定 Classes 的 Object Controller Mode 為 Entity Name,實體名為:ClassEntity,并勾選 Prepare Content。

将 Classes 的 ManagedObjectContext 參數與 ManagedViewController(File's Owner) 中的 managedObjectContext 綁定起來。

注意:這裡沒有對 MonitorPopup 和 Students 進行修改。

使用 Control-Drag 将 File's Owner 的 contentArrayController 關聯到 Classes。

将 Students 和 MonitorPopup 的 Content set 綁定到 Classes 的  Model key path: students,表示這兩個 ArrayController  是管理對應 ClassEntity 的 students 的資料。

至此,模型, ArrayController 都準備好了,下面我們将控件綁定到這些對象上。上面已經夠繁瑣的了,下面我們得更加仔細,很容易出錯的。

選中 StudentView.xib,展開 Custom View 中的 TableView,直到我們看到名稱和班級兩個 Table Column。

選中名稱列,将其 value 綁定到 Students,model key path 為:name,表明第一列顯示學生的名稱;

選擇班級列,注意這一列是popup button cell,

将其 Content 綁定到 ClassPopup;

将其 ContentValues 綁定到 ClassPopup,model key path 為:name,表明第二列的選項為班級的名稱;

将其 Selected Object 綁定到 Students,model key path 為:inClass;表明将學生添加為選中班級的一員;

選中 + button,使用 Control+Drag将其托拽到 Students 上,選擇 add: 動作關聯;

選中 - button,使用 Control+Drag将其托拽到 Students 上,選擇 remove: 動作關聯;

選中 - button,将其 Eanbled 綁定到 Students, ctroller key 為:canRemove;

以上操作是将添加,删除學生的操作直接與 Students ArrayController 綁定,無需編寫一點兒代碼!

深入淺出 Cocoa 之 Core Data(3)- 使用綁定

選中 ClassView.xib

展開 Custom View 中的班級表,,直到我們看到班級 Table Column:選擇班級列,将其 value 綁定到 Classes,model key path 為:name,表明這一列顯示班級的名稱;

選中 Box,将其 Title 綁定到 Classed,model key path 為:name,并設定下方的 No Selection Placeholder 為:No Selection,Null Placeholder 為:Unnamed Class。 表明 box 顯示的資訊為選中班級的資訊,如果沒有選中任何班級,則顯示 No Selection。

展開 Box

選中 Pop up button

将其 Content 綁定到 MonitorPopup;

将其 ContentValues 綁定到 MonitorPopup,model key path 為:name,表明其選項為班級中的學生的名稱;

将其 Selected Object 綁定到 Classes,model key path 為:monitor;表明将選中的學生當作該班級的班長;

展開學生 tabel view,直到我們看到學生這個 Table Column。

選擇學生列,将其 Value 綁定到 Students,Model key path 為:name,表明學生清單顯示該班級中所有學生的名稱。

選中 + button,使用 Control+Drag 将其托拽到 Classes 上,選擇 add: 動作關聯;

選中 - button,使用 Control+Drag 将其托拽到 Classes 上,選擇 remove: 動作關聯;

選中 - button,将其 Eanbled 綁定到 Classes, ctroller key 為:canRemove;

以上操作是将添加,删除班級的操作直接與 Classes ArrayController 綁定。

深入淺出 Cocoa 之 Core Data(3)- 使用綁定

至此,綁定也大功告成,如果你的程式運作不正确,多半是這地方的關聯與綁定錯了,請回到這部分,仔細檢查每一項。

8,顯示,切換 view。

現在到了設定主界面的時候,修改 MyDocument.h 中的代碼如下:

@class ManagedViewController;

@interface MyDocument : NSPersistentDocument {

    NSBox *         box;

    NSPopUpButton * popup;

    NSMutableArray *viewControllers;

    NSInteger       currentIndex;

@property (nonatomic, retain) IBOutlet NSBox *          box;

@property (nonatomic, retain) IBOutlet NSPopUpButton *  popup;

- (IBAction) changeViewController:(id)sender;

- (void) displayViewController:(ManagedViewController *)mvc;

修改 MyDocument.m  中的代碼如下:

#import "MyDocument.h"

@implementation MyDocument

@synthesize popup;

@synthesize box;

    self = [super init];

        // create view controllers

        //

        viewControllers = [[NSMutableArray alloc] init];

        ManagedViewController * mvc;

        mvc = [[ClassViewController alloc] init];

        [mvc setManagedObjectContext:[self managedObjectContext]];

        [viewControllers addObject:mvc];

        [mvc release];

        mvc = [[StudentViewController alloc] init];

- (void) dealloc

    self.box = nil;

    self.popup = nil;

    [viewControllers release];

- (NSString *)windowNibName

    // Override returning the nib file name of the document

    // If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead.

    return @"MyDocument";

- (void)windowControllerDidLoadNib:(NSWindowController *)aController

    [super windowControllerDidLoadNib:aController];

    // init popup

    //

    NSMenu *menu = [popup menu];

    NSInteger itemCount = [viewControllers count];

    for (NSInteger i = 0; i < itemCount; i++) {

        NSViewController *vc = [viewControllers objectAtIndex:i];

        NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[vc title]

                                                      action:@selector(changeViewController:)

                                               keyEquivalent:@""];

        [item setTag:i];

        [menu addItem:item];

        [item release];

    // display the first controller

    currentIndex = 0;

    [self displayViewController:[viewControllers objectAtIndex:currentIndex]];

    [popup selectItemAtIndex:currentIndex];

#pragma mark -

#pragma mark Change Views

- (IBAction) changeViewController:(id)sender

    NSInteger tag = [sender tag];

    if (tag == currentIndex) {

        return;

    currentIndex = tag;

    ManagedViewController *mvc = [viewControllers objectAtIndex:currentIndex];

    [self displayViewController:mvc];

- (void) displayViewController:(ManagedViewController *)mvc

    NSWindow *window = [box window];

    BOOL ended = [window makeFirstResponder:window];

    if (!ended) {

        NSBeep();

    NSView *mvcView = [mvc view];

    // Adjust window's size and position

    NSSize currentSize      = [[box contentView] frame].size;

    NSSize newSize          =  [mvcView frame].size;

    float deltaWidth        = newSize.width - currentSize.width;

    float deltaHeight       = newSize.height - currentSize.height;

    NSRect windowFrame      = [window frame];

    windowFrame.size.width  += deltaWidth;

    windowFrame.size.height += deltaHeight;

    windowFrame.origin.y    -= deltaHeight;

    [box setContentView:nil];

    [window setFrame:windowFrame display:YES animate:YES];

    [box setContentView:mvcView];

    // add viewController to the responder-chain

    [mvcView setNextResponder:mvc];

    [mvc setNextResponder:box];

在 MyDocument 中,我們建立了兩個 ManagedViewController,并将 managedObjectContext 傳入其中。這兩個ViewController分别代表班級與學生兩個界面,然後通過 popup button 的選擇在他們之間切換顯示;在 displayViewController 中,我們還根據目前界面的大小來調整主界面的大小。這需要我們設定主界面中 box 的自動大小,打開 MyDocument.xib,作如下設定:

深入淺出 Cocoa 之 Core Data(3)- 使用綁定

然後,使用 Control+Drag,将 File's Owner的 popup 和 popup button相聯,box 與 box相聯,并将 popup button 的 action 設定為 File's Owner 的 - (IBAction) changeViewController:(id)sender。

至此,所有的工作都完成了。編譯運作程式,如果不出意外的話,我們應該可以添加學生,班級,并設定學生的班級,班級的班長等資訊了。

繼續閱讀