第12章
第12章
シートとパネル
今まではMyClipウィンドウを閉じる時に未保存のデータは自動で保存するか、あるいは保存せずにアプリケーションを終了するようにしていました。
しかしMacではメインウィンドウを閉じる時やアプリケーションを終了する時に未保存のデータが残っていた場合は保存をするか、しないか、あるいはメインウィンドウのクローズ(もしくはアプリケーションの終了)をキャンセルするかを警告ダイアログでユーザに尋ねるのがデフォルトの動き(振る舞い behavior)です。この章ではこの警告ダイアログをMacで一番良く使われているシート形式で実装したいと思います。そして第2節ではパネルと呼ばれるウィンドウの一種を表示する方法を学習します。
12. 1. 1 ダイアログ
第8章でも説明いたしましたが、ププリケーションにはメインウィンドウとキーウィンドウがあります。メインウィンドウはユーザが作りたい画像や文章や音楽のデータを表示しているウィンドウでそのウィンドウのうえで作業(変更や追加)を行うこともできます。
またユーザからの変更や追加などの操作を受け付けることのできるウィンドウのことをキーウィンドウと呼びます。メインウィンドウはキーウィンドウでもあるわけです。しかしこのキーウィンドウはその時々によって変わります。画像処理ソフトのツールとよばれるウィンドウなどもそうですが、キーウィンドウになり得るものとしてもっとも代表的なものがダイアログと呼ばれるウィンドウやパネルやシートでしょう。
ダイアログはユーザに重要な情報を提供したりデータを失う可能性のある操作の前に警告したり、あるいはユーザからなにがしかの情報が欲しい時に表示されます。対話という意味になるダイアログが現れている場合はそのダイアログがキーウィンドウになっています。ダイアログに返答することによってキーウィンドウは再びメインウィンドウに戻ってきます。
メインウィンドウは多くの場合キーウィンドウでもありますが、あえてキーウィンドウにはならないように設定することもできます。その場合には作業はすべて補助的ウィンドウで行うようにし、その作業の結果がメインウィンドウに表現されることになります。
12. 1. 2 アプリケーションモーダルとドキュメントモーダル
ダイアログが現れた場合は、その内容を確認したという意思表示をするために「OK」というボタンを押すか、あるいは表示された選択肢のどれかを選ぶか、あるいはうながされている情報を入力するまではそのアプリケーションはそのほかの操作をうけつけなくなる場合があります。この状態のことをアプリケーションモーダルと呼びます。アプリケーションモーダルのダイアログにはウィンドウのサブクラスのパネルかウィンドウ自身が使われます。またこのようにアプリケーションモーダルなダイアログウィンドウのことをモータルウィンドウと呼ぶこともあります。
ダイアログにはこのように独立したパネルやウィンドウとして現れる場合もありますがメインウィンドウのタイトルバーのあたりからシートとして現れるダイアログもあります。このシートタイプのダイアログが現れた場合はそのダイアログに答えるまでそのウィンドウでのほかの操作はできなくなりますが、そのウィンドウとは関係していないアプリケーションの操作は行うことができます。この状態のことをドキュメントモーダルと呼びます。
この章ではまずシートによるダイアログを説明して、次にパネルによるダイアログを説明いたします。
MyClipではメインウィンドウはひとつですが、画像を何枚も開けるアプリケーションや文章をいくつも開けるアプリケーションではメインウィンドウは複数になることになります。このようなアプリケーションの場合にはメニューバーの「ウィンドウ」メニューに現在開いているメインウィンドウの一覧が表示されます。逆に言えばこの一覧に表示されていないウィンドウはメインウィンドウではありません。 またメインウィンドウ以外のウィンドウがアクティブになっている場合はescキーを押すことで閉じることもできます。
12. 1. 3 警告シートを表示する
シートタイプのダイアログは警告ダイアログとして使われる場合が多いみたいです。その代表的なものとしては次の2つがあります。
・何かを削除する場合の警告
・保存をしていない状態でドキュメント(ウィンドウ)を閉じようとした場合やアプリケーションを終了させようとした場合の警告
この章では「Save Before Quit アラート」と呼ばれている2番目のアラートシートをMyClipに実装します。また15章では「削除する場合の警告シート」も実装いたします。
●Interface Builderでの作業
今回はMainMenu.xibでの変更は接続が一箇所増えるだけです。いつものように前回までのプロジェクトフォルダのバックアップをとっておいてください。プロジェクトを立ち上げてMainMenu.xibをダブルクリックで開きます。MainMenu.xibウィンドウでFile's OwnerからControllerへ右クリック + ドラッグで接続線を引きます。
図File's Ownerのdelegateの接続1 Xcode 3.1
図File's Ownerのdelegateの接続1 Xcode 3.2
DRAFT
Controllerが強調表示されましたらマウスを離します。接続先一覧のOutletsの中に唯一存在しているdelegateに接続します。
図File's Ownerのdelegateの接続2 Xcode 3.1
図File's Ownerのdelegateの接続2 Xcode 3.2
DRAFT
接続を確認してみます。MainMenu.xibウィンドウでControllerを右クリックしてControllerの接続一覧を表示してください。Referencing Outletsグループのdelegate項目がmultipleとなっていると思います。multipleの左のディスクロージャー(▶)をクリックして下の階層も確認してみてください。にFile's Ownerからの接続が増えていると思います。
図:Controllerの接続一覧 Xcode 3.1
図Controllerの接続一覧 Xcode 3.2
DRAFT
Interface Builderでの作業はこれで完了しました。作業を保存してXcodeのプロジェクトウィンドウに戻ってコーディングしていきます。今回コーディングの対象となるのはControllerオブジェクトだけです。これまでにコーディングを頻繁に繰り返してきました。今回はControllerオブジェクトの全コードを再掲いたします。いつものとおり強調箇所が追加・参考されるコードです。
●Controllerオブジェクト コードリスト
□Controller.h
メソッド宣言が2つ増えています。
#import
<Cocoa/Cocoa.h>
#import
"Model.h"
@interface
Controller : NSObject {
Model *model;
IBOutlet NSTextView *textView;
IBOutlet NSWindow *window;
}
-
(NSApplicationTerminateReply) showAlertSheet:(id)sender;
- (void)
alertDidEnd:(NSAlert *)alert returnCode:(int)returnCode
contextInfo:(void *)contextInfo;
-
(void)readFromFile;
-
(IBAction)writeToFile:(id)sender;
@end
- showAlertSheet:メソッドはアラートシート(警告シート)を表示するための定義(実装)をまとめた自作メソッドです。したがってメソッド宣言をしておかなければ警告もしくはエラーとなります。
- alertDidEnd:
returnCode: contextInfo:メソッドはアラートシートを表示するためにNSAlertクラスに用意されているインスタンスメソッドの中で用意されているモーダルデリゲートメソッドです。少し話しが難しいかもしれませんがデリゲートメソッドの一種なのでヘッダファイルでメソッド宣言する必要はありません。たんに分かりやすくするためにヘッダファイルに記述しているだけなのでこの宣言は削除していただいても大丈夫です。
なおこの- alertDidEnd:
returnCode: contextInfo:モーダルデリゲートメソッドはシートを閉じたときの振る舞い(動き)を定義するためのメソッドです。シートはシート上に表示されているボタンのどれかを押した時に閉じられることになっています。
□Controller.m
これまでに変更を頻繁に行ってきました。今回は念のために全コードを再掲いたします。
#import
"Controller.h"
static
NSMutableString *filePath;
@implementation
Controller
-
(id)init
{
self = [super init];
if (self) {
model = [[Model alloc] init];
filePath = [[NSMutableString alloc]
initWithCapacity:0];
[filePath appendString:NSHomeDirectory()];
[filePath
appendString:@"/Library/Application Support/MyClip"];
[[NSFileManager defaultManager] createDirectoryAtPath:filePath
withIntermediateDirectories:YES attributes:nil error:NULL];
[filePath appendString:@"/ MyClip.data"];
[self readFromFile];
}
return self;
}
-
(void)awakeFromNib
{
[textView setString:@""];
if ([model string]) {
[textView insertText:[model string]];
[window
setDocumentEdited:NO];
[textView
scrollRangeToVisible:NSMakeRange(0, 0)];
}
}
/* Text Viewの内容が変更された時にそのText
Viewから送られてくるデリゲートメソッド */
-
(void)textDidChange:(NSNotification *)aNotification
{
[model setString:[[textView textStorage]];
[window setDocumentEdited:YES];
}
/* ウィンドウが閉じようとする時にそのウィンドウから送られてくるデリゲートメソッドです */
-
(BOOL)windowShouldClose:(id)aWindow
{
if ([window isDocumentEdited]) {
if ([self showAlertSheet:self]
== NSTerminateCancel)
return NO;
if ([self showAlertSheet:self]
== NSTerminateNow)
return YES;
}
[NSApp terminate:window];
return YES;
}
/* アプリケーションが終了しようとする時にNSApplicationのインスタンスから送られてくるデ リゲートメソッドです */
-
(NSApplicationTerminateReply)applicationShouldTerminate:
(NSApplication *)sender
{
if ([window isDocumentEdited]) {
[self showAlertSheet:self];
return NSTerminateCancel;
}else {
return NSTerminateNow;
}
}
// アラートシートの表示
-
(NSApplicationTerminateReply)showAlertSheet:(id)sender
{
NSAlert *alert;
alert = [[[NSAlert alloc] init]
autorelease];
[alert
addButtonWithTitle:@"Save"];
[alert
addButtonWithTitle:@"Cancel"];
[alert addButtonWithTitle:@"Don't
Save"];
[alert setMessageText:@"Do you want to
save the changes you made in the
document “MyClip”?"];
[alert setInformativeText:
@"Your changes will be lost if you
don't save them."];
[alert setAlertStyle:NSWarningAlertStyle];
/* シート形式でアラートを表示する
*/
[alert beginSheetModalForWindow:window
modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:
contextInfo:)
contextInfo:nil];
return NSTerminateCancel;
}
//
アラートシートを閉じたときの振る舞い
-
(void) alertDidEnd:(NSAlert *)alert returnCode:(int)returnCode
contextInfo:(void *)contextInfo
{
if (returnCode == NSAlertFirstButtonReturn)
{
[self writeToFile:self];
[NSApp terminate:self];
}
if (returnCode ==
NSAlertSecondButtonReturn) {
;
}
if (returnCode == NSAlertThirdButtonReturn)
{
[window setDocumentEdited:NO];
[NSApp terminate:self];
}
}
-
(void)readFromFile
{
[model setString:[NSKeyedUnarchiver
unarchiveObjectWithFile:filePath]];
}
-
(IBAction)writeToFile:(id)sender
{
[NSKeyedArchiver archiveRootObject:[model string]
toFile:filePath]; [window
setDocumentEdited:NO];
}
-
(void)dealloc
{
[model release];
[super dealloc];
}
@end
●実行
コーディングが終わりましたら。コンパイルして実行してみください。Macのアプリケーションとしてもっとも標準的な動き(振る舞い)になったと思います。
図アラートシート Xcode 3.1
図アラートシート Xcode 3.2
DRAFT
●コード説明
コード説明をする前に少し言っておいたほうが良いことがあります。今回アラートシートを表示するために記述したコード数は多くの読者の想像を超えている量だと思います。しかしコード的に無駄な部分があるわけでもありません。実際にこれほどのコード数が必要だと言う事です。しかしこの「Save Before Quit 」アラートのようなアプリケーションとして当然実装しておくべき機能についてはCocoaフレームワークに用意されているテンプレートを使うことやそのほかの方法で一切コーディングすることもなくアプリケーションに実装するができます。その方法についてはまたのちの章で説明いたします。そして実践編ではさらに詳しく説明されています。
□デリゲートメソッド 1
- (void)textDidChange:(NSNotification
*)aNotification
このメソッドはMyClipの最初のころから使っているNSTextViewのスーパークラスであるNSTextで宣言されているデリゲートメソッドです。詳細についてはすでにご理解いただけていると思います。
□デリゲートメソッド 2
- (BOOL)windowShouldClose:(id)aWindow
このメソッドは前章で使用したwindowWillClose:デリゲートメソッドと対をなしているものです。windowWillClose:メソッドが送信された場合はレシーバのウィンドウは必ず閉じられます。それに対してwindowShouldClose:メソッドは戻り値がYESの場合はレシーバのウィンドウは閉じられますが戻り値がNOの場合はレシーバのウィンドウは閉じられません。
なおNSWindow Class Referenceでこのメソッドの引数名は「window」になっているかもしれません。しかしwindowという名前(識別子)はすでにインスタン変数のなかでアウトレットとして使っています。インスタン変数についてはそのクラスのなかでのグローバル変数と考えていただいて問題ありません。それに対して引数として受け取った変数名はそのメソッドの中で定義されたローカル変数として扱われることはC言語と同じです。
もしグローバル変数とローカル変数の名前が衝突した場合はローカル変数が優先されることになっていますが、Xcode の処理系では警告として扱われます。そこで引数の変数名を「aWindow」に変えています。なお今回の場合はこのaWindow引数とwindowアウトレットは同じオブジェクトを参照していることになります。
if ([window isDocumentEdited]) {
if ([self
showSheet:self] == NSTerminateCancel)
return NO;
if ([self
showSheet:self] == NSTerminateNow)
return YES;
}
[NSApp terminate:self];
return YES;
まずアウトレットのwindowにisDocumentEditedメッセージを送ってドキュメントが編集されているか確認しています。この場合レシーバに引数のaWindowを使っても効果は同じことになります。
もし[window isDocumentEdited]メッセージ式がNOを返した場合は次に掲示するコードが実行されMyClipはウィンドウを閉じるという過程をとおり越して終了します。
[NSApp terminate:self];
return YES;
アプリケーションを終了するメッセージ式のあとから return YES; でYESを返しています。この式はアプリケーションにとって無意味なコードです。しかしこのwindowShoulClose:メソッドはBOOL値を返すことになっていますのでこの式を記述しておかなければ処理系から警告が出ます。その警告を出さないために一応YESを返す式を記述しています。
[window isDocumentEdited]がYESを返した場合は次の2つの式が実行されます。
if ([self
showSheet:self] == NSTerminateCancel)
return NO;
if ([self
showSheet:self] == NSTerminateNow)
return YES;
この2つのif文ではアラートシートを作成して表示するshowSheet:メソッドを呼び出しています。そしてその戻り値によってwindowShouldClose:メソッドが返す値を変えています。
しかしこの2つのif文の中で本当に意味があるのはひとつ目のif文だけです。この最初のif文はアラートシートで「Cancel」ボタンがクリックされた場合にウィンドウを閉じるのを中止してアプリケーションの終了もとりやめます。2つ目のif文ではこのif文が真の場合にはすでにアプリケーションは終了しています。コード全体を分かりやすくするために記述していますが、この2つ目のif分は削除してもアプリケーションの動作に影響を与えることはまったくありません。
□デリゲートメソッド 3
- (NSApplicationTerminateReply)applicationShouldTerminate:
(NSApplication
*)sender
applicationShouldTerminate:というメソッド名からもアプリケーションを終了しても良いかどうかを尋ねるメソッドだと分かっていただけると思います。引数の(NSApplication *)senderはアプリケーションに唯一存在するNSApplicationのインスタンスを表していますが今回の定義のなかでこの引数を実際に使うことはありません。
戻り値のNSApplecationTerminateReplyという型は列挙型をtypedefしています。実際に返される値はNSInteger型でもなく普通のint型です。メソッドの実装でははじめに次のif文でドキュメントが編集されているか(未保存であるか)を確認しています
if ([window
isDocumentEdited])
もし編集されていれば次の2つの式が実行されます。
[self showSheet:self];
return NSTerminateCancel;
最初の式でまずアラートシートを表示して、それからアプリケーションの終了をキャンセルするNSTerminateCancelをこのデリゲートメソッドの戻り値として返しています。このアラートシートを表示するメッセージ式とNSTerminateCancelをreturnする式の順序が逆の場合は、シートは表示されずにウィンドウも閉じられないことになります。
列挙型NSApplicationTerminateReplyのそれぞれの値(あたい)は次のようになっています。
・NSTerminateCancel = 0
・NSTerminateNow = 1
・NSTerminateLater = 2
もしドキュメントが編集されていなければ戻り値としてNSTerminateNowが返されてアラートシートが表示されることもなくMyClipは終了することになります。
return NSTerminateNow;
□アラートシートの表示
アラートシートの表示にはMac OS X 10.0から使えるC言語で記述されたシート表示用の関数を使う方法とMac OS X 10.3から使えるようになったNSAlertクラスを使う方法があります。現在ではNSAlertクラスを使うことが推奨されています。本書でもNSAlertクラスを使用してアラートシートを表示する方法を説明いたします。
アラートシートの使用は次の3つのステップをふまえて実現します。
ステップ1 NSAlertのインスタンスの作成と初期化
ステップ2 NSAlertの表示
ステップ3 NSAlertに表示されているどれかのボタンが選ばれたあとの処理
(アラートシートはボタンが押された段階で消滅します。)
次のメソッドはステップ1とステップ2をまとめるために定義した自作メソッドです。
- (NSApplicationTerminateReply) showAlertSheet:(id)sender
□ステップ1 NSAlertのインスタンス作成と初期化
この自作メソッドではまずNSAlertオブジェクトを格納する変数を宣言しています。
NSAlert *alert;
次にNSAlertのインスタンスを作成して一時的オブジェクトとしてalert変数に代入しています。一時オブジェクトのライフサイクル(破棄されるまでの期間)は、一時オブジェクト作成後の最初のメソッド実行が終わる時点までと考えていただいて結構です。気になる方はNSLogとretainCountを使ってコンソール画面でテストしてみるとよく分かると思います。
alert = [[[NSAlert
alloc] init] autorelease];
alertシートにボタンを作成するには次のメッセージを使います。引数で指定された文字列がボタンのタイトルになります。ボタンが配置される位置はメッセージ式の実行順序にしたがってシートの右から左にむかって配置されていきます。
[alert
addButtonWithTitle:@"Save"];
[alert
addButtonWithTitle:@"Cancel"];
[alert
addButtonWithTitle:@"Don't Save"];
アラートシートには太字でメッセージテキストが表示されます。そのメッセージテキストを設定するには次のように記述します。
[alert
setMessageText:@"Do you want to save the changes you made in the
document “MyClip”?"];
さらにアラートシートには追加の情報テキストも表示することができます。この追加の情報テキストは太字ではない通常の書体で表示されます。
[alert
setInformativeText:@"Your changes will be lost if
you don't save them."];
アラートシートにはアラートの種類を表すアイコンが表示されます。アイコンの種類はsetAlertStyle:メソッドで設定します。
[alert
setAlertStyle:NSWarningAlertStyle];
引数には列挙型の整数値を指定しますがこの整数値はNSUIntegerになっています。次に各数値を掲示しますがNSCriticalAlertStyle以外はそのアプリケーションのアイコンがそのまま表示されます。
・NSWarningAlertStyle = 0
・NSInformationalAlertStyle = 1
・NSCriticalAlertStyle = 2
図クリティカルアラートシート
□ステップ2 NSAlertの表示
アラートシートを表示するにはalertオブジェクトに次のメッセージを送ります。
- (void)beginSheetModalForWindow:(NSWindow
*)window
modalDelegate:(id)modalDelegate
didEndSelector:(SEL)alertDidEndSelector
contextInfo:(void *)contextInfo
サンプルコードでは次のようになっています。
[alert
beginSheetModalForWindow:window modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:
contextInfo:)
contextInfo:nil];
第1引数にはシートが表示されるウィンドウを指定します。第2引数にはオブジェクトのデリゲート先を指定するようにオブジェクトのモーダルデリゲート先を指定します。第3引数にはモーダルデリゲートメソッドのSEL型を指定します。第4引数は通常nilを指定します。
以上でステップ2の「アラートシートの表示」まで完了しました。表示されたアラートシートはボタンが押されるまでは消滅することはありません。今回のサンプルでは「Save」ボタンがデフォルトボタンとなっていてリターンキーを押すことでもSaveボタンをクリックしたことになります。このように1番右のボタンはタイトル名に関係なくデフォルトボタンになります。
また右から2番目のボタンはタイトル名を「Cancel」とすればescキーを押してもこのCancelボタンをクリックしたことになります。ただし2番目のボタンのタイトルをCancelとした場合のときだけ有効です。
自作メソッドshowAlertSheetの最後では次のメソッドで戻り値を返しています。
return
NSTerminateCancel;
showAlertSheetメソッドはNSApplicationTerminateReply型を返すことになっています。このコードを記述しなければコンパイル時に警告が出されます。またアラートシートはボタンをクリックするまでは消滅しませんと言いましたが、当然アプリケーションが終了してしまった場合はその限りではありません。ここではNSTerminateCancelを返すことによって取りあえずアプリケーションが終了することを回避しています。
この章の最初で説明したようにシートはアプリケーションモーダルではなくドキュメントモーダルです。モーダルなシートを表示していてもアプリケーションがその状態で停止しているわけではありません。もしこの最後のコードでNSTerminateNowを返すようにするとアプリケーションは即刻終了してしまいます。
なおこのNSTerminateCancelを返すことについてはもっとスマートなコーディングがないかなとも思いますが、ADCの説明でもだいたい同じような手法が使われています。
□ステップ3 NSAlertに表示されているどれかのボタンが選ばれたあとの処理
アラートシートのボタンがクリックされた場合はNSAlertのモーダルデリゲートとして次のシグネチャのメソッドを呼び出すようにすることが推奨されています。
- (void) alertDidEnd:(NSAlert *)alert
returnCode:(int)returnCode
contextInfo:(void *)contextInfo
このモーダルデリゲートメソッドの第1引数では表示するalertを指定し、第2引数では押されたボタンを表す整数値が収まる変数名を、第3引数では(void
*)という型が示すとおり自由に使える何かを指定します。そしてこの3つの要件が揃っていればシグネチャ名が違っていても問題ありません。しかし実際にはこのシグネチャを使うものだと決めておいたほうが便利でしょう。そしてこの中でもっとも必要となるのはreturnCodeというラベルの付いているreturnCode引数の値でしょう。前述のようにこの引数の値がどのボタンが押されたかを表しています。そしてこの引数の値も列挙型になっています。
・NSAlertFirstButtonReturn = 1000
・NSAlertSecondButtonReturn = 1001
・NSAlertThirdButtonReturn = 1002
First, Second, Thirdは右からの左へのボタンの並び順どおりになっています。そしてif文を用いて押されたボタンに応じた処理を行っています。
if (returnCode == NSAlertFirstButtonReturn) {
[self writeToFile:self];
[NSApp
terminate:self];
}
if (returnCode == NSAlertSecondButtonReturn) {
;
}
if (returnCode == NSAlertThirdButtonReturn) {
[window setDocumentEdited:NO];
[NSApp
terminate:self];
}
2番目のif文はCancelボタンが押された場合にはシートが閉じられること以外に何の処理も行われないことを表しています。なおシートはボタンを押すと同時に閉じられる(消滅する)ことが最初から決まっています。
3番目のif文ではアプリケーションを終了する前にウィンドウのsetDocumentEditedの値をNOに設定しています。少し不思議な感じがしますがこのメッセージ式を削除すると3番目のボタン、つまり「Don't Save」ボタンを押した場合に正常な動作をしません。興味のある方はこの[window
setDocumentEdited:NO];をコメントアウトしてコンパイルして試してみてください。
なおif文は上から順番に実行されていきます。したがってもっとも該当する可能性の高いif文を最初に持ってくるのが常套手段です。また前述のif文では条件評価に使われている値は整数値です。このように条件評価が整数値で行われる場合はswitch文を用いるほうがパフォーマンス的に有利になることが多いです。しかしADCのサンプルではまず可読性を優先させるために読みやすいif文が多用されています。またこのサンプルではモーダルデリゲートメソッドが実行される時はおおむねアプリケーションの終了時になると思われます。したがってそれほどアプリケーションのパフォーマンスにこだわる必要もありません。しかしここではこの節の最後として前述の3つのif文をswitch文に変えた場合のコード例を掲載しておきます。
switch (returnCode) {
case
NSAlertFirstButtonReturn:
[self
writeToFile:self];
[NSApp terminate:self];
break;
case NSAlertThirdButtonReturn:
[window
setDocumentEdited:NO];
[NSApp terminate:self];
break;
}
NSAlertSecondButtonReturnのcaseを記述することについては可読性を向上させること以外の意味はありません。したがってこのコード例では記述していません。
12. 2 環境設定パネル
前節のアラートシートについてはまた15章でも登場してきます。この節ではシートとよく対比されるパネルについて環境設定パネル(Preferences Panel)をMyClipに実装しながら説明していきたいと思います。
パネルのためのNSPanelクラスはNSWindowのサブクラスとして定義されています。そのせいかNSPanelについて説明されているガイドはADCのなかでも意外と少ないみたいです。ただしNSOpenPanel、NSSavePanel、NSColorPanel、NSFontPanelなどの個別のパネルについての説明は充実しています。NSPanelの特徴を列記すると次のようになります。
●NSPanelの特徴
・NSPanelはNSWindowのサブクラスになります。
・NSPanelはキーウィンドウになることはできますがメインウィンドウにはることはできません。
・NSPanelがキーウィンドウになっている時はescキーを押すことによって閉じることができます。
・NSPanelは一度作成すると可視・不可視に関わらずアプリケーション終了時まで存在し続けます。
・NSPanelはアプリケーションが非アクティブの場合は表示されません。
・NSPanelはほかのモーダルウィンドウなどによってアプリケーションモーダルな状況になっていてもキーウィンドウとして機能させることができます
●NSPanelの設定
では早速NSPanelを実装していきましょう。
前節までのMyClipプロジェクトを残しておかれる場合は作業を開始する前にバックアップをとってください。バックアップがすみましたらプロジェクトウィンドウを立ち上げてMainMenu.xibをダブルクリックして開きます。
【Xcode 3.1の場合】
LibraryパネルのObjectsタブを選びLibrary→Cocoa→Applicationとディスクロージャーを開いていくとWindowsというグループが現れます。このWindowsグループを選択すると各種ウィンドウオブジェクトの一覧が表示されます。今回はその中からPanelをMainMenu.xibウィンドウへドラッグ&ドロップします。
図NSPanelオブジェクト Xcode 3.1
【Xcode 3.2の場合】
LibraryパネルのObjectsタブを選びLibrary→Cocoa→Applicationとディスクロージャーを開いていくとWindowsというグループが現れます。このWindowsグループを選択すると各種ウィンドウオブジェクトの一覧が表示されます。今回はその中からPanelをMainMenu.xibウィンドウへドラッグ&ドロップします。
図NSPanelオブジェクト Xcode 3.2
DRAFT
次にMainMenu.xibウィンドウでPanel(Window)を選択します。そしてインスペクタパネルで1番左のAttributesタブを選びます。インスペクタパネルのタイトルがPanel Attributesになったことを確認してから各設定値を次のように変更します。
Title
MyClip Preferencesにします。
NSPanelのタイトルバーに表示されるパネルのタイトルを設定しています。
Controls
Resizeのチェックを外します。
NSPanelのリサイズ機能を無効にします。
Behavior
Release When Closedのチェックを外します。
NSPanelがクローズされてもオブジェクトは破棄されずに残るようにしておきます。
Visible At Launchのチェックを外します。
アプリケーション起動時にはNSPanelは非表示にしておきます。
図Panel Attributes Xcode 3.1 図の大きさは適宜調整のほどお願いいたします。
図Panel Attributes Xcode 3.2
DRAFT
この設定のなかで特に説明が必要なのはBehavior(振る舞い)グループの2つでしょう。
NSPanelはMainMenu.xibウィンドウの中にドラッグしました。つまりMainMenu.xibに含まれている(登録されている)ことになります。MainMenu.xibに含まれているオブジェクトはアプリケーションの起動時に順次実体化されることになっています。つまりこの環境設定パネル(MyClip Preferencesパネル)もアプリケーション起動時にオブジェクトとして生成されます。
Visible At Launch
はLaunch(起動)した時のVisible(可視性)を設定する項目です。ここにチェックを入れるとMyClipを起動した時にMyClip Preferencesパネルは見えている状態(表示されている状態)になり、チェックを外すと見えていない状態(表示されていない状態)になります。
このことについては実際にチェックを入れた状態と外した状態でコンパイルを行って試してみるとすぐにご理解いただけると思います。なお残念なことにCocoa Simulatorではチャックの有無にかかわらずMyClip Preferencesパネルが表示された状態で起動してしまい、テストにはなりません。
Release When Closed
この項目はパネルをクローズした場合にそのパネルを破壊するか(メモリから解放するか)どうかを設定します。この項目にチェックを入れた場合はMyClip Preferencesパネルを閉じた場合にはNSPanelオブジェクトも破棄(解放)されます。環境パネル(Preferences Panel)はアプリケーション起動時に何度も開く可能性があります。アプリケーションの起動時から終了時まで目に見えるかどうかは別として存在し続けるようにしておくべきでしょう。
なお、この項目にデフォルトでチェックが入っているのはおそらくメモリ消費量を節約する目的のためだと思いますがMyClipのような至ってコンパクトなアプリケーションの場合にはメモリの使用量の節約を考えるより、CPUへの負荷を減らすことを優先させるべきだという気がします。パネルを破棄して必要な時にはまた生成するということになればCPUへの不可は増えることになります。
※Release When Closedにチェックを入れた場合はそれに対応できるようにコードなども変更しなければ実行時エラーが起こります。
●NSPanelの表示
NSPanelを表示させる方法はとても簡単です。まずMainMenuバー (MainMenu.xibウィンドウではありません) のNewApplicationメニューのPreferences...アイテムを選択します。もしMainMenuバーが表示されていない場合はMainMenu.xibウィンドウでMainMenuオブジェクトをダブルクリックして表示させます。
このPreferences...アイテムからMainMenu.xibウィンドウのPanel(MyClip Preferences)に接続線を引きますが、まえにも言いましたがメニューアイテムは一度選択してから接続線を引かないとメニューアイテム自身がドラッグされてしまう場合があります。まずPreferences...を選択してからPanel (MyClip Preferences)まで右クリック + ドラッグで接続線を引いていきます。そして図NSPanelオブジェクトの接続1のようにPanel(MyClip Preferences)が強調表示されましたらマウスボタンを離します。
図NSPanelオブジェクトへの接続1 Xcode 3.1 図の大きさは適宜調整のお願いいたします。
図NSPanelオブジェクトへの接続1 Xcode 3.2
DRAFT
マウスボタンを離すとReceived Actionsという一覧が表示されます。その中から
makeKeyAndOrderFront:
というメソッドをクリックして接続を確定してください。クリックすることによって接続が確定します。
図NSPanelオブジェクトへの接続2 Xcode 3.1 図の大きさは適宜調整お願いいたします。
図NSPanelオブジェクトへの接続2 Xcode 3.2
DRAFT
接続を確認する場合はPanelオブジェクトを右クリックすると接続の一覧が表示されます。
図NSPanelオブジェクトの接続確認 Xcode 3.1 図の大きさは適宜調整お願いいたします。
図NSPanelオブジェクトの接続確認 Xcode 3.2
DRAFT
今回使用したmakeKeyAndOrderFront:メソッドはすでにオブジェクトとして生成されていて非表示になっているNSWindowやNSPanelを表示させるために使われるもっとも一般的なNSWindowクラスのインスタンスメソッドです。詳しくはNSWindow Class Referenceで確認してみてください。
12.
2. 2 環境設定パネルとメインウィンドウの表示位置の調整
●環境設定パネルの表示位置
まず環境設定パネルが表示される位置を決めます。Panel(MyClip Preferences)を選択してインスペクタパネルで左から3番目のSizeタブを選びます。いつものとおりインスペクタパネルのタイトルがPanel Sizeに変わります。
図Panel Size インスペクタ1 Xcode 3.1 図の大きさは適宜調整のほどお願いいたします。
図Panel Size インスペクタ1 Xcode 3.2
DRAFT
Initial Positionというデスクトップを模したところに表示されている位置がパネルを起動した時に表示される位置です。インスペクタパネルはいったんこのままにしておいてXcodeにもどってコンパイルして実行してみてください。MyClipが起動しましたら「MyClip」メニューから「Preferences...」を選びます。
図Preferencesパネル1 図の大きさは適宜調整のほどお願いいたします。
Sizeインスペクタパネルで表示されていた位置と同じ位置に表示されます。
この時にデスクトップのどこかをクリックしてみください。アクティブなアプリケーションがFinderに変わりPreferencesパネルが消えます。もう一度MyClipのウィンドウをクリックしてみてください。アクティブアプリケーションがMyClipに変わりMyClip Preferencesパネルが再び表示されます。
この動きがアプリケーションのメインウィンドウ以外の標準的な振る舞いになります。この設定はNSPanel AttributesインスペクタパネルのBehaviorブループで
Hide On Deactivate
をチェックしておくことによって実行されます。このチェックを外すとMyClipが非アクティブになってもMyClip Preferencesパネルは消えません。
話しを戻します。Preferencesパネル(環境設定パネル)の一般的な表示場所はデスクトップの左上あたりか、もしくはデスクトップの中央になると思います。今回は表示場所を左上にしたいと思います。Panel Sizeインスペクタパネルに戻りInitial Positionに表示されているパネルの位置を左上にドラッグしてSimulateで試してみましょう。さきほども話しましたようにSimulateではパネルは最初から表示されていまいますが表示位置の確認だけなので問題はないでしょう。お気に入りの場所になるまでこの作業を繰り返してもらっても構いません。
図Panel Sizeインスペクタ2 Xcode 3.1 図の大きさは適宜調整のほどお願いいたします。
図Panel Sizeインスペクタ2 Xcode 3.2
DRAFT
図:Preferencesパネル2 図の大きさは適宜調整のほどお願いいたします。
●メインウィンドウの表示位置
環境設定パネルの表示位置が決まりましたら次はメインウィンドウの表示位置が気になります。メインウィンドウの表示位置もデスクトップの左上あたりか中央にするのがMacアプリケーションの標準的な表示位置ですが現在はメインウィンドウも環境設定パネルも左上にあって見にくいです。そこでメインウィンドウは中央に表示することにいたします。
Interface BuilderでMyClipウィンドウを選択してWindow Sizeインスペクタパネル表示させてください。今度はInitial
Positionのミニチュアのウィンドウではなく、本当のメインウィンドウをドラッグして位置を決めたいと思います。メインウィンドウをだいたいの位置で結構ですのでデスクトップの中央にドラッグしてください。
図メインウィンドウの表示位置 Xcode 3.1 図の大きさは適宜調整お願いいたします。
図メインウィンドウの表示位置 Xcode 3.2
DRAFT
しかしメインウィンドウをデスクトップ画面の中央にドラッグしてもWindow SizeインスペクタのInitial Positionのミニチュアウィンドウの位置は変わりません。実際にSimulateを実行してみてください。メインウィンドウの表示位置が左上に寄ったままになっていることがわかります。
図Window Sizeインスペクタ1 Xcode 3.1 図の大きさは適宜調整お願いいたします。
図Window Sizeインスペクタ1 Xcode 3.2
DRAFT
Simulateを終了させください。Window SizeインスペクタでInitial Positionのすぐ上のUse Currentボタンをクリックします。するとInitial Positionの中のミニチュアウィンドウも中央に移動します。作業を保存してSimulateでも試してみてください。メインウィンドウが中央に表示されます。このようにUse
Currentボタンを押すまではInterface Builderでのウィンドウやパネルの作業位置とアプリケーションが起動した時の表示位置が同じになることはありません。
図Window Sizeインスペクタ2 Xcode 3.1 図の大きさは適宜調整のほどお願いいたします。
図Window Sizeインスペクタ2 Xcode 3.2
DRAFT
●正確に中央に表示する
これでメインウィンドウはおおむね中央に表示されるようになりました。しかしあくまでも手作業で中央に移動しただけです。もっと正確に中央に表示させるにはコーディングが必要になります。
Interface Builderでの作業を保存してXcodeのプロジェクトウィンドウに戻ります。Controller.mをダブルクリックして開いてください。そしてawakeFromNibメソッドに強調表示されているコードを追加します。記述する場所もサンプルのとおりに最後の行にしてください。
Controller.m
- (void)awakeFromNib
{
[textView
setString:@""];
if ([model string]) {
[textView
insertText:[model string]];
[window
setDocumentEdited:NO];
[textView
scrollRangeToVisible:NSMakeRange(0, 0)];
}
[window center];
}
説明の必要のないメッセージ式だと思います。「ビルドして進行」をクリックして実行してみてください。Preferencesパネルも表示させてみましょう。
図MyClip実行画面 図の大きさは適宜調整のほどお願いいたします。
12. 2. 3 メニューアイテムのタイトル変更
この項ではメニューアイテムのタイトルを変更します。MainMenu.xibファイルを開いてInterface Builderを起動してください。MainMenuバー(MainMenuウィンドウ)でNewApplicationメニューをクリックすると下の階層が開きます。もしMainMenuバーが開いていなければMainMenu.xibウィンドウでMainMenuオブジェクトをダブルクリックすると開くことができます。
NewApplicationメニューの下の階層では次の3つのメニューアイテムのタイトルを変更します。タイトルの変更は変更するメニューアイテムを選択してインスペクタパネルのTitle欄から変更することも出来ます。しかしMenuバーではメニューおよびメニューアイテムをダブルクリックして編集可能にして入力するほうが確実に変更できるみたいです。
●NewApplicationメニューアイテムのタイトル変更
About NewApplication → About MyClip
Hide NewApplication → Hide MyClip
Quit NewApplication → Quit MyClip
上記は表にしたほうが良くてまた時間があれば表にしてください。私はこのままでも結構です。
なお、NewApplicationというメニューのタイトルを変更する必要はありません。このタイトルはプログロムの内部で変更されることになっています。
図メニューアイテムのタイトル変更1 Xcode 3.1 図の大きさは適宜調整お願いいたします。
図メニューアイテムのタイトル変更1 Xcode 3.2
DRAFT
続けてHelpメニューのメニューアイテムのタイトルも変更します。Helpメニューをクリックして下の階層を表示させてください。そして次のように変更します。このメニューでは変更するメニューアイテムはこのひとつだけになります。
●Helpメニューアイテムのタイトル変更
NewApplication Help → MyClip Help
図メニューアイテムのタイトル変更2 Xcode 3.1 図の大きさは適宜調整お願いいたします。
図メニューアイテムのタイトル変更2 Xcode 3.1
DRAFT
お疲れ様でした。これでシートとパネルについての説明を終わります。次章では環境設定パネルに実際の機能を実装していきたいと思います。そしてユーザデフォルトという便利な機能についても説明いたします。
This site is available in Safari and Snow Leopard. | (c) viva Cocoa 2006 - 2010 |