フォト
2009年11月
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30          
無料ブログはココログ

« 2009年1月 | トップページ | 2009年6月 »

2009年2月

2009年2月17日 (火)

【Cocoa】ドロア(NSDrawer)

 今回はドロアの使い方。NSDrawerを使う。参考にさせていただいたのはこちらの記事。

http://www.infoatmackers.jp/cocoa/programming/programming21.html

 最初ドロアを知らないときにInterfaceBuilderの中を覗いていてNSDrawerという文字を見て、「何か描画(Draw)する物?」とか勘違いしていた。辞書ひっぱってみたら「引き出し」って意味もあるんだな。NSDrawerは引き出しの方の意味。ウインドウの影からニョキッと出てくるパネルのような物だ。

 実際ドロアを使うだけなら、InterfaceBuilderだけでもできてしまうから驚き。

 ウインドウ上にボタンを1個用意して、そいつをクリックするとドロアが出たり入ったりするサンプルを作ってみることにする。

1.プロジェクトの作成

 いつものように「Cocoa Application」でプロジェクト作成。

2.InterfaceBuilderの起動

 「Resources」の「MainMenu.xib(English)」をダブルクリックしてInterfaceBuilderを起動する。

3.標準のウインドウを削除する

 「MainMenu.xib」ウインドウの中を見ると、メインウインドウをなる「Window(Window)」が入っているが、こいつを削除してしまう。マウスで選択しておいて「del」キーで消せる。

Drawer1

4.NSDrawerをMainMenu.xibに追加

 「Library」ウインドウの中の「Objects」の中を覗いて、「Cocoa」→「Application」→「Windows」と追っかけていくと、「Window and Drawer」というのが見つかる。こいつを「MeinMenu.xib」ウインドウにドラッグ&ドロップ。

Drawer2

 そうすると自動的に3つのオブジェクトが追加される。「Window(Window)」、「Drawer Content View」(なぜかドラッグ&ドロップ中はCustom Viewと表示される)、「Drawer」の3つだ。

 この3つ、実はそれぞれ一つずつLibraryから個別にドラッグ&ドロップして持ってこれるらしいのだが、そうした場合この3つを自力で接続してあげる必要があるらしい。「Window and Drawer」をドラッグ&ドロップしたときは、その辺を全部自動的にやってくれるらしい。

5.ドロアのデザイン

 ボタンをクリックすると顔を出すドロアの中身をデザインする。

 まず、「MainMenu.xib」の中の「Drawer Content View」をダブルクリック。すると、ドロアをデザインするためのウインドウが表示される。

Drawer3

 今回は難しいことをせず、文字だけ表示させておくことにする。ラベルを貼り付けて、適当な文字列を埋めておく。

Drawer4

6.ドロアの属性をいじる

 「MainMenu.xib」の中の「Drawer」をクリックして選択する。そうすると「Attributes」ウインドウで、「Edge」という属性をいじることができる。デフォルトでは「Right」が選択されている。これは、ドロアが顔を出す方向。「Right」ってことはウインドウの右側にニョキッとドロアが出てくる。ここで顔を出す方向を上下左右で選ぶことができる。

Drawer5

 あと「Size」ウインドウでドロアのサイズをいじれるんだが、とりあえず「Content」の「Width」だけ先程デザインしたドロアの幅に合わせておく。

Drawer8

 高さをいじってみたりしたんだが、実行しても値が反映されていないっぽい。使い方間違ってるんかな・・・。

7.メインウインドウ側のデザイン

 「MainMenu.xib」の「Window(Window)」をダブルクリックすると、メインウインドウのデザイン用ウインドウが表示される。こちらには、ドロアが表示されるきっかけのためのボタンを一つ用意する。

Drawer6

8.ボタンのアクション設定

 ボタンと、ボタンをクリックしたときのアクションメソッドを接続する。アクションメソッド自体は「Drawer」の中にもう用意されているので、自分で作成する必要は無い。

 ボタンから「Drawer」に向かってcontrol+ドラッグ&ドロップ。接続候補として「open」「close」「toggle」が表示されるが、今回は「toggle」を選ぶ。一回押す毎に出たり入ったりするアクションメソッドだ。

Drawer7

 試してないが、おそらく「open」はドロアを出す専用、「close」はドロアをしまう専用のメソッドなんだろう。

9.コンパイル&ゴー

 以上で作成は終了。Xcode側に戻って実行してみる。ボタンをクリックするたびにニョキニョキしてくれる。

Drawer9

Drawer10

10.あとは?

 ドロアの上にボタンなりなんなり好きな部品を配置して、いつものように自分で用意したコントローラクラスのアクションメソッドやアウトレットと接続してやれば、いろいろできるんだろう、きっと。まだ試してないけど。

11.ドロアの使われ方

 AppleのHuman Interface Guidelinesっていうのがあって、その中にドロアについて書いてあった。「PartIII:Aqua Interface」→「Windows」→「Window Elements」だ。

http://developer.apple.com/documentation/UserExperience/Conceptual/AppleHIGuidelines/XHIGIntro/chapter_1_section_1.html

 和訳するのが大変なので、和訳サイト様を頼ることに(´¬`;)

http://potting.syuriken.jp/potting_conv/XHIG_J/XHIGIntro/chapter_1.html

 それによると、

ドロワーは、かなり頻繁に利用される必要があるものの、常時見えている必要はないというコントロールのためだけに使用してください

シートは第一に、「シートを使用するとき」(209ページ)で説明されているとおり、モード式ダイアログを置き換えることを意図していますが、ドロワーは付加機能を提供することを意図しています。

シートが開いたとき、それがウインドウの中心となり、ウインドウの内容を覆い隠します。ドロワーが開いたとき、親ウインドウのすべては今までどおり見えており、利用できます。

 などと言ったことが書かれていた。

 ダイアログやシートと違って、ドロアの場合だと親となるウインドウは常時見えていて、かつ操作も可能な状態だから、ダイアログやシート的な使い方はよろしくないってことなのかな・・・。

 確かにダイアログ的な使い方をするならダイアログ使えばいいしな。隠すことができるツールバーって言うかツールパネルみたいな感覚なんだろうか。

2009年2月15日 (日)

【Cocoa】リストビューって無い?

 VCLで言うところのTListBoxクラスとか.NET Frameworkで言うところのListBoxクラスとかに当たるクラスがCocoaに見当たらない。

 見た感じはテーブルビューの1列固定のやつで、ヘッダが無くて縦スクロールバーはあるんだけど横スクロールバーが無いやつ。

 探し足りないだけなのなら自分を恨んで終わりにするんだが・・・本当に無いんだろうか。

 システム内で使われてるリストボックス的な部品って無かったっけか・・・。システム環境設定の中の「ディスプレイ」タブ内の「解像度」の一覧が近いかな。

Listview

 システム環境設定のダイアログと、解像度のところをNSTableViewを使って似せて作った自作のアプリ(機能ゼロだけど)を並べてみたけど・・・・変わんないねこれ。

 結論。

 Cocoaでは、リストビューっぽいのを使いたければNSTableViewを使えば事足りる。

2009年2月14日 (土)

【Cocoa】アプリケーションの初期化

 前回NSTableViewの勉強をしていたときに、つまずいた話を一つ。

 結局前回のサンプルでは、NSTableViewから問い合わせがあった時に固定の文字列を返す物にしてしまった。

 が、実は最初のうちはNSArrayを使って文字列の配列を用意してそれをテーブルに表示する物を作成していた。ところが、NSArrayの作成に問題は無いのにテーブルには何も表示されないという現象にぶち当たってしまったのだ。

 いろいろ試しているうちに、ボタンを用意してそのアクションメソッド内から「NSTabeleView」の「reloadDataメソッド」(テーブルに表示するデータをリロードするメソッド)を呼び出すと、ちゃんと表示してくれるところまではたどり着いた。

 ってことは、NSArrayを作るタイミングが遅すぎる?

 この時、NSArrayの作成はApplicationからデリゲートして「applicationDidFinishLaunching」の中でやっていた。

 でも、このデリゲートメソッドでタイミングが遅いってんなら、どこでやるのがいい?

 そこで参考にしたのがこちらの記事。

「Cocoa探検隊」さんの探検レポート「awakeFromNibの落とし穴」
http://www013.upp.so-net.ne.jp/tanken/Tanken/no4_PitfallOfNib.html

 ここの記事は、今回俺が悩んでいることとちょっと趣旨が違うかもしれないのだが、ここにアプリケーションが起動するときに送られるメッセージの順番が載ってた。これは参考になりそう。

 で、この記事を手がかりにしつつ、今回自分が作ったサンプルでどのように動いているのか試してみた。

 アプリケーションが起動するときに呼ばれそうなデリゲートメソッド内でNSLog()を呼び出してみたところ、だいたい次の順番っぽい。

  • initメソッド(今回はここで文字列の準備もした)
  • numberOfRowsInTableViewメソッド(テーブルの行数の問い合わせ)
  • awakeFromNibメソッド
  • numberOfRowsInTableViewメソッド(テーブルの行数の問い合わせ)
  • tableviewメソッド(テーブル上に表示する値の問い合わせ、表示は2*4で8回)
  • applicationWillFinishLaunchingメソッド
  • applicationWillUpdateメソッド
  • applicationDidUpdateメソッド
  • applicationDidFinishLaunchingメソッド
  • applicationWillUpdateメソッド
  • applicationDidUpdateメソッド
  • tableviewメソッド(テーブル上に表示する値の問い合わせ、表示は2*4で8回)
  • applicationWillBecomeActiveメソッド
  • applicationDidBecomeActiveメソッド
  • applicationWillUpdateメソッド
  • applicationDidUpdateメソッド

 テーブルビューの行数の問い合わせが2回も発生していたり、一度表示する値を一通り問い合わせたかと思ったら再び画面のアップデートが発生してもう一度表示する値をなめ直したりと、素人目には不可解なところもあったりなかったり。

 それでも、最初俺が「applicationDidFinishLaunching」で文字列を作成していたタイミングでは、既に行数の確認は終わっていて明らかに遅そうだ。

 逆に、「init」で文字列を準備できれば確実に表示できることは確認できた。

 ちなみに、「awakeFromNib」内で文字列の用意をしてみたのだが、これだと表示域が4行でデータ行数が7行であるにもかかわらず縦スクロールバーが表示されない物ができてしまった。

 ここまで来てやっと気がついたこと。それは「文字列作ったら自分でリロードしてやればいいのか・・・」。

 ってことで、「applicationDidFinishLaunching」の中で文字列の作成→テーブルビューのreloadDataメソッドの呼び出しで、やっと期待通りに動作してくれた。

 我ながら遠回りしすぎてる気がしなくもない(´・ω・`)

【AppController.h】

#import <Cocoa/Cocoa.h>

@interface AppController : NSObject
{
    IBOutlet id _tableView;
    NSArray* ar;
}
- (id) init;
- (void)awakeFromNib;

//Applicationデリゲート
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
- (void)applicationWillUpdate:(NSNotification *)aNotification;
- (void)applicationDidUpdate:(NSNotification *)aNotification;
- (void)applicationWillBecomeActive:(NSNotification *)aNotification;
- (void)applicationDidBecomeActive:(NSNotification *)aNotification;

//アクションメソッド
- (IBAction)btnReload:(id)sender;

//データソース
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView;
- (id)tableView:(NSTableView *)aTableView
    objectValueForTableColumn:(NSTableColumn *)aTableColumn
    row:(NSInteger)rowIndex;
@end

【AppController.m】

#import "AppController.h"

@implementation AppController
//---------------------------------------------------------
- (void) makeArray
{//テーブルに表示する文字列達の作成
    ar = [[NSArray arrayWithObjects:
        @"Sunday",@"Monday",@"Tuesday",@"Wednesday",
        @"Thursday",@"Friday",@"Saturday",nil] retain];
}
//---------------------------------------------------------
- (id) init
{
    [super init];
//    [self makeArray];
    NSLog(@"init completed.");
    return self;
}
//---------------------------------------------------------
- (void)awakeFromNib
{
//  [self makeArray];
//  [_tableView reloadData];
    NSLog(@"awakeFromNib completed.");
}
//---------------------------------------------------------
//アクションメソッド
//---------------------------------------------------------
- (IBAction)btnReload:(id)sender
{//テーブルビューのリロード
    [_tableView reloadData];
    NSLog(@"btnReload completed.");
}
//---------------------------------------------------------
//Applicationデリゲート
//---------------------------------------------------------
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification
{
    NSLog(@"applicationWillFinishLaunching completed.");
}
//---------------------------------------------------------
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    [self makeArray];
    [_tableView reloadData];
    NSLog(@"applicationDidFinishLaunching completed.");
}
//---------------------------------------------------------
- (void)applicationWillUpdate:(NSNotification *)aNotification
{
    NSLog(@"applicationWillUpdate completed.");
}
//---------------------------------------------------------
- (void)applicationDidUpdate:(NSNotification *)aNotification
{
    NSLog(@"applicationDidUpdate completed.");
}
//---------------------------------------------------------
- (void)applicationWillBecomeActive:(NSNotification *)aNotification
{
    NSLog(@"applicationWillBecomeActive completed.");
}
//---------------------------------------------------------
- (void)applicationDidBecomeActive:(NSNotification *)aNotification
{
    NSLog(@"applicationDidBecomeActive completed.");
}
//---------------------------------------------------------
- (void)applicationWillTerminate:(NSNotification *)aNotification
{
    [ar release];
}
//---------------------------------------------------------
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
{
    return YES;
}
//---------------------------------------------------------
//データソース
//---------------------------------------------------------
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{//データの行数問い合わせに対する返答
    NSLog(@"numberOfRowsInTableView completed.");
    return [ar count];
}
//---------------------------------------------------------
- (id)tableView:(NSTableView *)aTableView
    objectValueForTableColumn:(NSTableColumn *)aTableColumn
    row:(NSInteger)rowIndex
{//
    NSLog(@"tableView completed.");
    if([[aTableColumn identifier] isEqual:@"col1"]){
        //1列目は行番号
        return [NSNumber numberWithInt:rowIndex];
    }
    else if([[aTableColumn identifier] isEqual:@"col2"]){
        //2列目は行番号に対応する文字列
        return [ar objectAtIndex:rowIndex];
    }
    else{
        //ここは処理しないはず
        return nil;
    }
}
//---------------------------------------------------------
@end

Startup1

2009年2月11日 (水)

【Cocoa】NSTableViewのお勉強

 バインディングの記事を書きたかったが、あまりに俺の頭がついて行けてないので、いったん保留することに決定。煮詰まりそうだ・・・難しく考えすぎてる?

 それで、今度はテーブルビューの使い方を勉強した。クラス名は「NSTableView」。BorlandのVCLだと「TStringGrid」に相当する部品。行と列があって、それぞれのマス目に文字列を表示できる。

 勉強のネタ本にしている「たのしいCocoaプログラミング」には、「文字列以外に画像やボタンなども入れることができる」と書いてある。スゲー。

■NSTableViewの基本

 使い方というかテーブル表示の実現の仕方が、VCLのTStringGridとはまるで異なっているようだ。

 まず表示するデータはNSTableViewのオブジェクトに突っ込んでいくようなことはしないらしい。あくまでNSTableViewは見た目の表示部分だけを司り、表示内容となるデータは別個用意する必要があり、そのデータ形式はどんなんでもいいらしい。

 ではNSTableViewは、別個に用意された形式不明のデータをどうやって表示するのかというと、NSTaableViewから来る(最低限)二つの問い合わせに答えるプログラムを用意することで実現しているらしいのだ。

 二つの問い合わせとは「データは何行あるのか」と「X列Y行のデータは何か」というもの。

 たとえ1万行あるデータであったとしても、実際にウインドウ上に5行しか表示しないにであれば、この問い合わせは5回しか発生せず、スクロールして新たな行を表示する必要が出た時点で、その分だけしか問い合わせされないというのだ。

 その二つの問い合わせとは、以下の二つのメソッドになる。

- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
- (id)tableView:(NSTableView *)aTableView
    objectValueForTableColumn:(NSTableColumn *)aTableColumn
    row:(NSInteger)rowIndex

 この二つは「NSTableDataSource」プロトコルに定義されているメソッドで、他にも「ソートの昇順降順が入れ替わりました」とか「X列Y行にデータが書かれました」とかいったメソッドがある。

 これらのメソッドは「クラス」ではなく「プロトコル」として定義されているので、このメソッドを使用するために特定のクラスを継承したりする必要はなく、何かしらのオブジェクトにこの名前のメソッドを実装するだけでいいらしい。それがプロトコルという物だそうだ。

■実践

 簡単な例で使用方法を確認してみた。

 目指すところは、2列7行のテーブル表示。1列目は0〜6までの数字を表示する。2列目には「Sunday」〜「Saturday」までの7つの曜日を表示する。

1.プロジェクトの生成

 いつものようにXcodeを起動。新規プロジェクトで「Cocoa Application」を選択。プロジェクト名は「TableView」にした。

2.InterfaceBuilderを起動

 ResourceにあるMainMenu.xibをダブルクリックしてInterfaceBuilderを起動する。

3.コントローラオブジェクトの用意

 まずはコントローラオブジェクトを用意する。Libraryから「NSObject」を「MainMenu.xib」ウインドウにドラッグ&ドロップ。Identityウインドウでクラス名として「AppController」と命名しておく。

Tableview1

 今回はアクションもアウトレットも使わないので、このままクラスファイルとして出力してしまう。MainMenu.xibウインドウの 「AppController」を選択した状態で、メニューの「File」→「Write Class Files ...」をクリック。ファイルを保存する。ファイル名は「AppController.m」ヘッダファイルもチェックを入れておく。

4.ウインドウにテーブルビューを配置

 続いてウインドウ上にテーブルビューを配置する。部品自体はLibraryの「Cocoa」→「Views&Cells」→「Data Views」の中にあるので、そいつをウインドウにドラッグ&ドロップ。

Tableview2

5.データソースの接続

 今回のプロジェクトでデータソースの役をするのは、先程作成した「AppController」ということにする。つまり、テーブルビューはAppControllerに対して「何行あるのか」とか「X列Y行のデータは何か」という問い合わせをするように仕向ける。

 やり方はアクションなどを接続するときと同様。ウインドウ上のテーブルビューを選択した状態で、「control+ドラッグ」で「AppController」にドロップし、表示される接続候補から「dataSource」を選ぶ。

Tableview3

 このとき少しコツがいる。

 最初に選択するテーブルビュー側なんだが、部品を一回クリックするだけでは上記のような操作はできない。Identityウインドウのクラス名のところを見ると「NSScrollView」が選択されてしまっているのだ。

Tableview4

 テーブルビューが選択されている状態で、もう一度テーブルビューをクリックすると、「NSTableView」をうまく選択できる。この状態でないといけない。

 だからといって、テーブルビューをダブルクリックすると、「NSTableColumn」が選択されてしまう。ダブルクリックをするのではなくクリックを2回、だ。

Tableview5

6.列に識別子を付ける

 これは、実際に「X列Y行のデータは何か」の問い合わせが合った際に、行は整数型で問い合わせが来るんだけど、列の方はここで設定する識別子で来ることになるらしいので、ここで設定する。

 今度はテーブルビューをダブルクリックして「NSTableColumn」を選択する。テーブルに列は2つあるはずなので、それぞれの列をダブルクリックで選択して、識別しを設定する。

 設定する箇所は、「Attributes」ウインドウの「Identifier」。

Tableview6

 ここでは単純に「col1」、「col2」としておいた。

 ここまでできたらInterfaceBuilderを終了、データを保存しておく。

7.ヘッダファイルの記述

 Xcodeに戻って、「AppController.h」にいくつか記述を追加する。一つは親クラス。いつものように「NSObject」にする。もう一つは、テーブルビューからの問い合わせに対する応答をするための二つのメソッド宣言の追加。

【AppController.h】

#import <Cocoa/Cocoa.h>

@interface AppController : NSObject{
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView;
- (id)tableView:(NSTableView *)aTableView
    objectValueForTableColumn:(NSTableColumn *)aTableColumn
    row:(NSInteger)rowIndex;
@end

8.メソッドの実装その1(データは何行あるのか)

 次は「AppController.m」の記述。

 まず「データは何行あるか」の問い合わせに応答する「numberOfRowsInTableView」メソッドだ。今回は7行固定なので、

- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
    return 7;
}

 とした。これが例えば配列クラスをデータソースにしていて、データ数も可変な場合は、NSArrayクラスのcountメソッドをreturnしてあげるなどで応用できる。

9.メソッドの実装その2(X列Y行のデータは何か)

 次に、「X列Y行のデータは何か」に応答する「tableView:objectValueForTableColumn:row:」メソッドだ。

 行の方はrowで指定されるNSInteger型の変数に格納されてくるので、そのまま使えそう。

 列の方はobjectValueForTableColumnで指定される変数を使うのだが、こちらは「NSTableColumn」オブジェクトへのポインタとなっている。要は列を表すオブジェクトだな、これ。さっきテーブルビューをダブルクリックして選択したやつだ。

 この列のオブジェクトから識別子を取り出すには、「identifier」メソッドを使用する。さらに、例えばその列の識別子が「col1」と等しいかどうかを判断するには、「isEqual」メソッドを使って「@"col1"」と比較すればいい。

 1列目なら、rowIndexで渡された0〜6までの数値を文字列にして返す。

 2列目なら、rowIndexで渡された0〜6に対応する曜日の文字列を返す。

 というわけで、ソースはこんな風になった。

【AppController.m】

#import "AppController.h"

@implementation AppController
//-------------------------------------------------------
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
    return 7;
}
//-------------------------------------------------------
- (id)tableView:(NSTableView *)aTableView
    objectValueForTableColumn:(NSTableColumn *)aTableColumn
    row:(NSInteger)rowIndex
{
    if([[aTableColumn identifier] isEqual:@"col1"]){
        //1列目の場合
        //rowIndexをそのまま文字列にして返す
        return [NSString stringWithFormat:@"%d",rowIndex];
    }
    else if([[aTableColumn identifier] isEqual:@"col2"]){
        //2列目の場合
        //0:Sunday 1:Monday.....を返す
        switch(rowIndex){
            case 0:
                return @"Sunday";break;
            case 1:
                return @"Monday";break;
            case 2:
                return @"Tuesday";break;
            case 3:
                return @"Wednesday";break;
            case 4:
                return @"Thursday";break;
            case 5:
                return @"Friday";break;
            case 6:
                return @"Saturday";break;
            default:
                return @"unknown";break;
        }
    }
    else{
        //それ以外の場合(ここは処理しないはず)
        return @"";
    }
}
//-------------------------------------------------------
@end

 できあがりはこんな感じ。

Tableview7

2009年2月 7日 (土)

Win→Mac移行作業、その後

 Cocoaの方はバインディングで行き詰まってしまったので、気分転換。

 Macを使い始めてほぼ1ヶ月が経過した。今のところいたって快適だ。操作感がとても心地いい。Macを購入する前に抱いていた不安はどこへやら、である。

 とはいえ、やはり一部どうしてもMacでは解決できない部分もあったりする。特にSony関係(パソリとかWalkman関連とか)。そんなときはBootCampやVMWare Fusionが強い味方だ。こいつがいるおかげで安心できる部分もあるのかもしれない。

 それでも、事実BootCampやVMWare Fusionを起動する機会も徐々に減ってきている。こんな俺でもMac色に染まりつつあるようだと思える。

 使っているうちに使い方などについての勘違いや、当初困っていた操作方法の解決案も、俺なりにいくつか見いだすことができた。

■マウスの中クリックについて

 FireFoxで、リンクに対するマウスの中クリックを新規タブに割り当てたいのに、どうしてもExposeが起動してしまって希望の動作にできなかったことを書いたが、この原因がわかった。中クリックでExposeが起動しないようにすることもできた。

 そもそも、iMacにはMighty Mouseというのが標準添付されている。が、Windowsで使用していたUSBマウスでも普通に接続して使用することができる。このMighty Mouseを接続しているときとその他のマウスを接続しているときとで、環境設定でのマウスの設定項目に差があることに後から気がついた。

 まずこれがMighty Mouseを接続しているときのマウスの設定画面。

Mouseconfig1

 左右ボタン、真ん中のトラックボール、マウスの横についてるボタンのそれぞれをカスタマイズすることができる。標準では、トラックボールのクリック操作でExposeが起動される設定になっていた。

 次にこれがWindowsで使っていたUSBマウスを接続したときのマウスの設定画面。ちなみに接続していたのは2ボタン+ホイールの物。

Mouseconfig2

 ボタンをクリックしてExposeの起動を設定するようなところが無くなっている。

 で。

 最初Mighty Mouseを接続してトラックボールクリックにExposeを割り当てている状態でWindowsUSBマウスに切り替えると、設定項目は無いのだがホイールクリックでExposeが起動してしまっていたのだ。

 いったんMighty Mouseに接続しなおしてトラックボールに「切」を設定して、その後WindowsUSBマウスに切り替えたら、何の問題も無く中クリックが使えるようになった。

 これを、何も知らないうちにWindowsUSBマウスに付け替えてしまってしまったために、変なところで悩んでしまった。これで無事解決。後で以前の記事も直しておく。

■メールの振り分けタイミング

 メールクライアントにはThunderbirdを使用している。Windowsでは秀丸メールを使用していた。

 秀丸メールを使用していたときは一度受信したメールは振り分けせずに置いておいて、一通り読み終わった後に一括振り分けをしていた。場合によっては一通り読み終わる前にメールクライアントを一回閉じて別の作業をしたり、次の受信をして急ぎのメールを先に読んだりすることもあって、ちゃんと読み終わるまでは振り分けしたくない気持ちがあったからだ。

 ところがThunderbirdでは受信、即振り分けという動作をしており、この動作は標準ではどうにもならなそうだ。

 Thunderbirdのツールバーには「前へ」「次へ」と言うボタンがある。このボタンで未読のメールだけを追いかけることが可能だ。で、これは後から気がついたのだが、「進む」「戻る」というボタンもある。これは自分が見たメールの参照履歴を追いかけてくれる。一度見て参照済みにしてしまっても「戻る」ボタンでもう一度見れることに気がついた。

Thunderbird1

 今までボタンを使って参照することをしてこなかった俺には、このボタンの存在に気がつくことができなかった。人によっては「そんな当たり前なことを」と思うのかもしれないが・・・。

 とにかくこれで俺も、まだ見ていないメールが振り分け先のフォルダに移動してしまっていても、割と簡単に追いかけることができるようになった。そんなわけで今は、この4つのボタンで事なきを得ている。

 あともう一つ。名前は失念してしまったが一括振り分けを後から自分のタイミングで行うThunderbird用のプラグインを見つけたのだが、使い方に癖があり俺には合わないかと思い、使用をあきらめた。

■イメージビュアー

 Windowsで使用していたIrfanViewに代わる物をさがしていたのだが、iMacを購入したときに一緒に購入したPhotoshop ElementsにBridgeというソフトが付いていて、こいつがかなり俺の希望を満たしてくれることに後から気がついた。今はこちらを使用している。

■アプリランチャー

 Windowsの時はKick Inというソフトを使用していた。できるだけマウスの移動距離が短くて、アプリのグループ化ができて、管理が簡単なやつがいいと思い探していたが、結局今は「FinderPop」を使用している。

 こいつは、デスクトップを右クリックするとポップアップメニューが出てくるが、その中に設定したアプリのメニューが追加されるというもの。

Finderpop1

 アプリのショートカットを作って特定のディレクトリに放り込む作業が面倒ではあるが、一度設定してしまえば非常に使い心地はいい。

2009年2月 5日 (木)

【Cocoa】アプリケーション設定の保存と読み込み

 アプリケーション設定の保存と読み込みについて勉強してみた。

 Windowsアプリであればレジストリを使ったりiniファイルを使ったりして設定を保存するのが一般的だと思う。ファイルとして保存するなら、場所は「Documents and Settings」配下とかインストール先の「Programs Files」配下とか?

 Mac(OS X)の場合を調べて見ると、「/Users/ユーザ名/Library」配下にファイルを作って保存するのが一般的らしい。Finderでのぞいてみると、ユーザホーム内にある「ライブラリ」というディレクトリがそれだ。特にCocoaアプリであれば、「NSUserDefaults」というクラスを使うと、この辺に保存と読み込みをしてくれるらしい。

 このNSUserDefaultsを調べ始めると、NSDictionaryを使ったデフォルトの設定だとかバインディングといった話が出てきて収集がつかなくなってきたので、とりあえず簡単なところから確認しつつまとめてみる。

■NSUserDefaultsを利用したときの保存のされ方

 「/Users/ユーザ名/Library/Preferences」ディレクトリ内に、プロパティリストという形式のファイルで保存される。ファイル名の拡張子は「.plist」。

 Xcodeをインストール済みの場合はProperty List Editorというのがインストールされるらしく、「.plist」のファイルを開こうとするとこのツールが起動して内容を参照・編集することができるようだ。

 ファイル名の方は、Xcodeの方で「Resources」の下に「Info.plist」というのがあって、この中の「Bundle Identifier」の設定値が使われる。ここに設定される値は逆DNS表記っていうらしく、urlを逆から書いていって最後にアプリの名前をくっつける書き方。こうすることで一意性を保つらしい。

Savesettings1

 デフォルトでは「com.yourcompany.${PRODUCT_NAME:identifier}」と設定されているのだが、俺の場合はこのブログのurlを使って、「com.cocolog-nifty.take-blizzard.${PRODUCT_NAME:identifier}」としてみた。${PRODUCT_NAME:identifier}はプロジェクトの名前だろうか。

 今回プロジェクトの名前は「SaveSettings」としているので、最終的に保存されるplistファイル名は「com.cocolog-nifty.take-blizzard.SaveSettings.plist」ということになる。

■NSUserDefaultsでplistファイルを読む

 コーディングは次のようになる。

    NSUserDefaults* ud = [NSUserDefaults standardUserDefaults];

 これだけで、今回の場合は「/Users/ユーザ名/Library/Preferences/」にあるplistファイル「com.cocolog-nifty.take-blizzard.SaveSettings.plist」を読み込んでくれた。

■値を取り出す方法

 NSUserDefaultsオブジェクトの中(plistファイルの中もそうだけど)には、「キー」と「値」の組み合わせで格納されている。この辺Windowsのレジストリやiniファイルと同じ感じ。なので、値を取り出すときはキーを指定することになる。

 例えばint型の値を取り出すときは、

    int i = [ud integerForKey:@"キー名"];
    //udはNSUserDefaultsのオブジェクト

 で取り出せる。int型以外いろいろな型の取り出しメソッドが用意されている。

■値を変更する方法

 値を変更するには「どのキー」を「どういう値」に変更するかを言うので、変更の際には「キー」と「値」を指定する。

 例えばint型の値を変更するには、

    int i = 0;
    [ud setInteger:i forKey:@"キー名"];
    // udはNSUserDefaultsのオブジェクト

 とすると、NSUserDefaultsオブジェクト内の値を変更できる。

■plistファイルに出力する方法

 set〜:forKey:メソッドで値をいくら書き換えても、それはメモリ上の値の操作に過ぎない。その結果をplistファイルに出力するには「synchronize」メソッドを使用する。

    [ud synchronize];
    // udはNSUserDefaultsのオブジェクト

■実際のコード

 今回読み込み保存の対象にしたのは、ウインドウの位置とサイズ。アプリ起動時に設定を読み込んでウインドウに反映し、終了時に保存する。

 アプリの起動のタイミングはNSApplicationクラスをデリゲートして、「applicationWillFinishLaunching」というメソッドでつかまえる。終了のタイミングは同じくNSApplicationクラスをデリゲートして「applicationWillTerminate」メソッドでつかまえる。ウインドウの位置とサイズにアクセスするため、NSWindowクラス用のアウトレットを用意している。

【AppController.h】

#import <Cocoa/Cocoa.h>

@interface AppController : NSObject{
    IBOutlet id mainWindow;
}
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification;
- (void)applicationWillTerminate:(NSNotification *)aNotification;
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:
        (NSApplication *)theApplication;
@end

【AppController.m】

#import "AppController.h"
@implementation AppController

//-----------------------------------------------------------
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification
{//アプリケーション開始
    NSUserDefaults* ud = [NSUserDefaults standardUserDefaults];
    NSRect rect;

    rect.origin.x = [ud integerForKey:@"x"];
    rect.origin.y = [ud integerForKey:@"y"];
    rect.size.height = [ud integerForKey:@"h"];
    rect.size.width = [ud integerForKey:@"w"];

    //念のため4つとも読み出しができた時だけウインドウに反映する。
    if((rect.origin.x!=0)&&(rect.origin.y!=0)&&
        (rect.size.height!=0)&&(rect.size.width!=0)){
        [mainWindow setFrame:rect display:YES];
    }
}
//-----------------------------------------------------------
- (void)applicationWillTerminate:(NSNotification *)aNotification
{//アプリケーション終了
    NSUserDefaults* ud = [NSUserDefaults standardUserDefaults];
    NSRect rect = [mainWindow frame];
    [ud setInteger:rect.origin.x forKey:@"x"];
    [ud setInteger:rect.origin.y forKey:@"y"];
    [ud setInteger:rect.size.height forKey:@"h"];
    [ud setInteger:rect.size.width forKey:@"w"];
    [ud synchronize];
}
//-----------------------------------------------------------
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:
    (NSApplication *)theApplication
{//窓閉じ即終了
    return YES;
}
//-----------------------------------------------------------
@end

 で、このアプリを起動して終了すると、plistファイルが出力されていることが確認できた。

Savesettings2

 調べている最中だが、どうもバインディングというのを使うともっと簡単に設定の保存読み出しができるようだ。ある程度まとまったら、また記事にする予定。

« 2009年1月 | トップページ | 2009年6月 »