プロトコルを定義する
汎用性を高めるための工夫として、対象となるモデルクラスのオブジェクトが採用(adopt)するプロトコルを定義する事にします。プロトコルを定義しておけば"RMGRoot.h"をインポートする必要がなくなり独立性が高まるメリットがあります。NVKTableViewは自分が使うメソッドの宣言だけを取り込みたいわけですから、プロトコルを使うのが適しています。
プロトコル名はNVKPasteboardSupportとします。このプロトコルでは、NVKTableViewが使うメソッドの宣言だけを定義します。
プロトコルはヘッダファイルで定義します。新規ヘッダファイルはCocoaグループではなく、C and C++グループに入っています。
//
// NVKPasteboardSupport.h
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008 . All rights reserved.
//
@protocol NVKPasteboardSupport
+ (void)copyObjects:(NSArray *)objects
toPasteboard:(NSPasteboard *)pboard;
+ (void)pasteFromPasteboard:(NSPasteboard *)pboard
controller:(NSArrayController *)controller;
+ (NSString *)dataType;
+ (NSString *)orderKey;
+ (NSString *)addActionName;
+ (NSString *)deleteActionName;
+ (NSString *)cutActionName;
+ (NSString *)pasteActionName;
+ (NSString *)dragCopyActionName;
+ (NSString *)dragOperationActionName;
@end
NVKTableViewを作る
インターフェイスファイルの説明
新規ファイルを作成する手順はこれまでに何度もでてきたので省略し、インターフェイスファイルの説明に入ります。
インスタンス変数
配列コントローラを参照するインスタンス変数arrayControllerを追加します。配列コントローラはアウトレットではなく単なるインスタンス変数にしました。これはバインディング情報から配列コントローラを得る事ができる事がわかったので、入手を自動化した事による変更です。これでアウトレットを接続する手間が省けました。
メソッドの宣言
サブクラスで使いそうなメソッドの宣言を追加します。
- targetClassは配列コントローラに指定されたエンティティに対応するクラスのクラスオブジェクトを返すメソッドです。
- pasteBoardHas:は汎用ペーストボードに引数で指定された型のデータがあるかどうかを返すユーティリティメソッドです。
- setActionName:はアンドゥマネージャにアクション名を設定するメソッドです。
//
// NVKTableView.h
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface NVKTableView : NSTableView
{
NSArrayController *arrayController;
}
- (Class)targetClass;
- (BOOL)pasteboardHas:(NSString *)theType;
- (void)setActionName:(NSString *)actionName;
@end
配列コントローラからエンティティに対応するクラスのクラスオブジェクトを得る
LissajousTableViewでは[Lissajous dataType]の様にクラス名を直接ソースコードに記述しており、このために汎用性が低下していました。このLissajousの部分をクラスオブジェクトに置き換えられれば、汎用性を高める事ができます。
以下の手順で、配列コントローラからエンティティに対応するクラスのクラスオブジェクトを入手できる事がわかりました。
- 配列コントローラに設定されているエンティティ名をentityNameメソッドで得る。
- エンティティ名からエンティティオブジェクト(NSEntityDescriptionクラスのオブジェクト)を作る。それにはentityForName:inManagedObjectContext:メソッドを使う。
- エンティティに対応するクラス(カスタムクラスが設定されていればそのカスタムクラス)の名前をエンティティオブジェクトのmanagedObjectClassNameメソッドで得る。
- NSClassFromString()を使って、クラス名からクラスオブジェクトを得る。
3.まではデベロッパドキュメントをたどっていく事で比較的容易に分かるのですが、4.がなかなかわかりませんでした。この手順を実装したのが以下のtargetClassメソッドです。
//
// NVKTableView.m
//
// 配列コントローラに設定されているエンティティ名に対応するクラスのクラスオブジェクトを返す
- (Class)targetClass
{
NSEntityDescription *entity = [NSEntityDescription entityForName:[arrayController entityName]
inManagedObjectContext:[arrayController managedObjectContext]];
return NSClassFromString([entity managedObjectClassName]);
}
初期化メソッド
配列コントローラをバインディング情報から得るコードを追加しています。これはAppleのサンプルプログラムDragAppを参考にしました。エラーチェックを省いているので、バインディングを設定してから実行して下さい。
ドラッグを受け入れるデータタイプを登録する部分を変更しています。Lissajousと直接クラス名を書いていましたが、これを[self targetClass]と書き換えます。
//
// NVKTableView.m
//
#pragma mark -
#pragma mark Initializer
// 配列コントローラをバインディング情報から得るので、バインディングを設定してから実行する事
- (void)awakeFromNib
{
NSSortDescriptor *orderSort;
arrayController = [[self infoForBinding:NSContentBinding] objectForKey:NSObservedObjectKey];
orderSort = [[NSSortDescriptor alloc] initWithKey:@"order" ascending:YES];
[arrayController setSortDescriptors:[NSArray arrayWithObject:orderSort]];
[orderSort release];
[self registerForDraggedTypes:[NSArray arrayWithObject:[[self targetClass] dataType]]];
}
ユーティリティメソッド
pasteBoardHas:はLissajousTableViewと変わりません。
setActionName:メソッドは、引数がnilかどうかをチェックするコードを追加しました。
//
// NVKTableView.m
//
#pragma mark -
#pragma mark Utility methods
// 汎用ペーストボード上に指定されたタイプのデータがあるかどうかを返す
- (BOOL)pasteboardHas:(NSString *)theType
{
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
NSArray *types = [NSArray arrayWithObject:theType];
return ([pasteboard availableTypeFromArray:types] != nil);
}
- (void)setActionName:(NSString *)actionName
{
NSUndoManager *undoManager = [[arrayController managedObjectContext] undoManager];
if(![undoManager isUndoing] && ![undoManager isRedoing] && actionName != nil)
[undoManager setActionName:NSLocalizedString(actionName,nil)];
}
メニューアイテムの検証
ペーストメニューを検証する際に、対象となるクラスのデータタイプがペーストボード上に存在するかどうかを見る必要があります。ここもLissajousと直接クラス名を書いていた部分を[self targetClass]と書き換えます。
//
// NVKTableView.m
//
#pragma mark -
#pragma mark PasteBoard methods
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
if([menuItem action] == @selector(cut:) ||
[menuItem action] == @selector(copy:) ||
[menuItem action] == @selector(delete:) )
{
return [[arrayController selectedObjects] count] > 0;
}
if([menuItem action] == @selector(paste:))
return [self pasteboardHas:[[self targetClass] dataType]];
return YES;
}
コピーメソッド
同様の書き換えがcopy:メソッドでも発生します。
//
// NVKTableView.m
//
- (IBAction)copy:(id)sender
{
[[self targetClass] copyObjects:[arrayController selectedObjects]
toPasteboard:[NSPasteboard generalPasteboard]];
}
削除メソッド
delete:メソッドでは、アクション名を設定するメソッドの引数を書き換えます。
//
// NVKTableView.m
//
- (IBAction)delete:(id)sender
{
NSManagedObject *selectedObject;
NSEnumerator *enumerator = [[arrayController selectedObjects] objectEnumerator];
while(selectedObject = [enumerator nextObject])
[[arrayController managedObjectContext] deleteObject:selectedObject];
[self setActionName:[[self targetClass] deleteActionName]];
}
カットメソッド
cut:メソッドでも同様に、アクション名を設定する部分を書き換えます。
//
// NVKTableView.m
//
- (IBAction)cut:(id)sender
{
[self copy:sender];
[self delete:sender];
[self setActionName:[[self targetClass] cutActionName]];
}
ペーストメソッド
paste:メソッドでは対象クラスとアクション名の両方を書き換えます。
//
// NVKTableView.m
//
- (IBAction)paste:(id)sender
{
[[self targetClass] pasteFromPasteboard:[NSPasteboard generalPasteboard]
controller:arrayController];
[self setActionName:[[self targetClass] pasteActionName]];
}

ホーム
前へ