ホームページ
更新記録
Cocoa で作る「ちょっとしたプログラム」

Makuri の Cocoa Tips

  − 内容に不完全なところが多数見つかったので全面改定しました −

  − ( 過去のTipsも見てみる ) −
  − ( 過去のTips2も見てみる ) −

も く じ

  ボタンを押すと画像がでる

  最初っから画像がでてる

  ドラッグして画像を貼付ける

  ファイルをドラッグしてパスを貼付ける

  ドラッグして画像を貼付ける2

  フォントパネルについて

  オブジェクトの所有、リテイン、破棄について

  クローズボタンでアプリを終了

  日本語名ファイルへのアクセス(日本語文字列定数)


ボタンを押すと画像がでる

設 計

  例えば、以下のような見た目で、ボタンを押すと、あらかじめ内部に持っている画像(私のメインページから取ってきたカボチャの画像「k-diary.gif」)がパッと NSImageView の画面に現れるようなものを作ります。

  このウインドウの設計は、「Interface Builder」でちょちょいと作れます。NSObjectのサブクラスにMyObjectを作成し、アウトレットとアクションを加えてコード化します。またこれをコロッとインスタンス化して、アウトレットはウインドウ上の NSImageView へ、アクションへはボタンから配線します。
Cocoaはヤッパリ!をちょっと読んであれば定石ですよね。

コーディング

  画像を扱うときはNSImageを使い、これにファイルで指定した画像を取り込むやり方は、「Cocoaはヤッパリ!」のあちこちで紹介されていますので、これは良く解ります。画像ファイルはプログラムの内部に持つと言うことで、アイコンと同様のやり方で「Project Builder」に登録します。このやり方もバッチリです。ところが問題は、「このように自分で持っているファイルにアクセスするやり方は?」と言う、基本の基本が以外と盲点です。
  答えは超簡単で、NSImage 自身が、名前だけを聞いて読み込むメソッドをもっているのです。これは、NSImage の機能ですので、一般的なファイルを読み込むなら、過去のTipsのように「NSBundle」を使ってちょっと苦労があります。画像しか扱わないなら知らなくても済みますが、アプリが自分で持っているファイルが入っている場所を「Bundle」と呼び「NSBundle」クラスがこれを扱うものだと言う名前くらいは、知っていて損はないかと思います。


#import "MyObject.h"

@implementation MyObject

- (IBAction)myAction:(id)sender
{
    NSImage *img;

    img = [ NSImage imageNamed: @"k-diary" ];
    [ myOutlet setImage: img ];
}

@end
      


最初っから画像がでてる

設 計

  ボタンを押すも何もなくて、最初っからあらかじめ内部に持っている画像(ここではカボチャ)が NSImageView の画面に現れるようなものを作ります。

  このウインドウの設計は3通り考えられます。

設計1.「Interface Builder」で最初から貼付ける

  「Interface Builder」で NSImageView を貼付けたとき、この画像が「Project Builder」に登録してあれば、これをドラッグして貼付けることができます。このやり方では、コーディングは一切不要です。

設計2.デリゲートで貼付ける

  例えばNSObjectのサブクラスにMyObjectを作成し、アウトレットを加えてコード化します。またこれをコロッとインスタンス化して、アウトレットをウインドウ上の NSImageView と配線します。また、File'sOwner からこのインスタンスへ delegate として配線を張ります。コーディングに関しては下を
参照して下さい

設計3.NSImageView を改造する

  NSImageViewのサブクラスにMyImageViewを作成しコード化します。またウインドウ上にはいったん CustomView 貼付けて、そのインフォを表示して Attributes を MyImageView に切り替えます。これによって、全く設定のされていない NSImageView のサブクラスが張り付いた形になります。コーディングをしないと、実行時には何も見えません。コーディングに関しては下を参照して下さい

設計4.NSImageView を改造する2(これが本命か)

  NSImageViewのサブクラスにMyImageViewを作成しコード化します。またウインドウ上には普通にNSImageView を貼付けて、そのインフォを表示して Custom Class を MyImageView に切り替えます。こうすると、NSImageView のデザインは Interface Builder を使って簡単に出来ますが、initWithFrame: メソッドが使えなくなるので、初期化が必要な場合にはわざわざ設計3.を使うものなのかと思っていました。しかしCocoaはそんな間抜けではありませんでした。awakeFromNib と言う初期化メソッドがちゃんと用意されていたのです。(この設計4.は、2002.4.3に発見)
  それでは、コーディングに関しては下を参照して下さい

コーディング(設計2.の場合)

  先の「ボタンを押すと画像がでる」と全く同じコーディングになります。変わったのは1行だけですので、そこ以外は敢えて読まなくても良いです。ボタンに対応したアクションではなく、File'sOwner の delegate の applicationDidFinishLaunching メソッドの動作になっただけと言うことです。実行してみると、一瞬遅れて画像が現れることが解ります。


#import "MyObject.h"

@implementation MyObject

- (void)applicationDidFinishLaunching: (NSNotification *)aNote
{
    NSImage *img;

    img = [ NSImage imageNamed: @"k-diary" ];
    [ myOutlet setImage: img ];
}

@end
     

  以下のように、1行で書くこともできます。


#import "MyObject.h"

@implementation MyObject

- (void)applicationDidFinishLaunching: (NSNotification *)aNote
{
    [ myOutlet setImage: [ NSImage imageNamed: @"k-diary" ] ];
}

@end
     

コーディング(設計3.の場合)

  NSImageView に関して、全く独自に設定をすることになります。これは、initWithFrame メソッドの中で行います。NSImageView の場合であれば、お決まりのブロックは除外して考えると、見た目のデザインのために新しく設定しているのは setImageFrameStyle の1行だけですが、View によってはもっと複雑な設定が必要となるものもあるでしょう。
  この場合は、実行時に画像の現れに遅れはありません。想像ですが、設計1の場合には「Interface Builder」がこれと同等のソフトを見えないところに自動的に書いているのではないかと思います。


#import "MyImageView.h"

@implementation MyImageView

-(id)initWithFrame: (NSRect) frameRect
{
    [ super initWithFrame: frameRect ];
    [ self setImageFrameStyle: NSImageFrameGrayBezel ];
    [ self setImage: [ NSImage imageNamed: @"k-diary" ] ];

    return self;
}

@end
     

コーディング(設計4.の場合)

  この場合は、awakeFromNib と言う初期化メソッドの中に、追加分の画像張り付けの処理だけを書けばよいです。設計2.のシンプルさと、設計3.の初期化処理機能を合わせ持ち、実行時に画像の現れに遅れはなく、これが Interface Builder の開発者の意図した使い方のようです。Nib の無い時代からの古風な人は、initWithFrame: を使った設計3.を使うので、サンプルソフトには設計3.が多いようですが。


#import "MyImageView.h"

@implementation MyImageView

-(void)awakeFromNib
{
    [ self setImage: [ NSImage imageNamed: @"k-diary" ] ];
}

@end
     


ドラッグして画像を貼付ける

  − NSImageView へのドラッグ − 

  NSView へのドラッグの話題を、新たに
下に追加しました。

設 計

  ボタンを押すのも最初っから張り付いているのも、いま一つプログラムを書いた快感に乏しいですが、ドラッグして張り付いたとなると、それだけでも結構感動的(おおげさかな)なのではないでしょうか。任意のファイルを貼付ける技さえ取得すれば、その後はその画像を煮るなり焼くなり、いろいろなプログラムに発展できるような気がしてきます。
  ウインドウの見た目は、上の最初っから張り付いているのと同じにします。上の設計4をベースにコーディングだけの変更となりますので、見た目の絵は省略します。設計3をベースにしたものは、過去のTips2にあります。
  ドロップできるファイルは1つでTiff画像のみと言う条件を入れて、そうでないものを持ってきても反応しないようにして見ます。試しに放り込むTiff画像は何にしようかと悩んだら、リンゴ+shift+4でカシャッと一発やるのが簡単かと思います。拡張子は隠れていても、ちゃんと受け付けてくれます。

コーディング

  ドラッグされたファイルを見るには「Pasteboard」がキモになります。ドラッグされたものから「Pasteboard」と言うものを手に入れて、これからファイル名や画像データなどを取り出して使うと言うものです。プログラム中では「pbd」と言う名前で扱って見ましたので、これが出てくるところだけ読めば、なるほどと解るかと思います。
  自分がドラッグを受け付ける宣言は、registerForDraggedTypes です。この辺の説明は、Cocoaはヤッパリ!のONLINEの記事の「8. 簡易ランチャーを作る」に解りやすく紹介されていますので、そちらを御覧になるのが一番の近道です。結局のところ、registerForDraggedTypes の宣言をすることで、ドラッグ操作に関する6個のメソッドを実装することが必要になりますので、一見してメソッドがたくさんあって難しいプログラムに見えてしまいます。しかし、良く考えてみるとこの6個のメソッドは、ドラッグと言う動作をコーディングするのに必要なものであって、無駄にたくさんある訳ではないので、順に意味を追って考えてみれば、動作のフローを理解するのは難しくありません。
  とは言っても、見るのとやるのは大違いで、実際にこの6個のメソッドを実装してバグの無いソフトを作るには、出来上がり品をコピーしてくるのが手っ取り早いと思います。このページではプログラムリストをjpeg画像では無く、ちゃんとコピペできるテキストで作りましたので、どんどんコピッて行って下さい。

  まずは .h ファイルを見てみます。インスタンス変数を4つ使っていますが、見れば意味が解ると思います。
  メソッドは初期化メソッドを含む必須の7つですと言うことで、従来は全部 .h ファイルに書くようにしてました。もちろん書いていけない訳ではありませんが、書くのが面倒なのも事実です。この7つのメソッドはシステムが定義してシステムが呼ぶものですので、将来的なこのファイルの利用を考えても、他のファイルがこの .h ファイルを参照したときにこの7つのメソッドのことを教える必要はありません。と言う訳で、すっきりと省略することにしました。


/* MyImageView */

#import <Cocoa/Cocoa.h>

@interface MyImageView : NSImageView
{
    NSPasteboard *pbd;
    NSString *path;
    NSImage *img;
    BOOL dropSign;
}
@end
    

  肝心の .m ファイルは以下の通り。7個ものメソッドを実装しなければいけないので、どうしても長くなってしまいますので読む気が失せますが、各メソッドごとに落ち着いて読めば、何も難しいことはしていません。
  途中でTiffファイルの判定をして、Tiff画像のみを受け付けるようにしています。拡張子が"Tiff","tiff","TIFF"などのものを見つけるアルゴリズムとしてはこれで良いのだろうと思いますが、本質的に「これがTiff画像である」という判定法として、もっとスマートなものがありそうな気がするのですが、なにぶん初心者のため、まだここまでは解っていません。


#import "MyImageView.h"

@implementation MyImageView

-(void)awakeFromNib
{
    [ self registerForDraggedTypes: [ NSArray arrayWithObjects: NSTIFFPboardType, nil ] ];
    dropSign = NO;
}

-(unsigned int)draggingEntered: (id) sender
{
    NSArray *arOfFilename;

    pbd = [ sender draggingPasteboard ];		// ペーストボード取得
    [ pbd retain ];
    arOfFilename = [ pbd propertyListForType: NSFilenamesPboardType ];
                                                        // ファイル名配列を取得
    if ( [ arOfFilename count ] > 1 )
        return NSDragOperationNone;			// 複数なら拒否!

    path = [ arOfFilename lastObject ];			// ファイルへのパスを取得
    //[ path retain ];
    if ( [ [ path pathExtension ] caseInsensitiveCompare: @"Tiff" ] != NSOrderedSame )
        return NSDragOperationNone;			// Tiff以外は拒否!

    dropSign = YES;
    [ self setImageFrameStyle: NSImageFrameGroove ];	// 反応を表示
    return NSDragOperationGeneric;			// ドロップOK!
}

-(unsigned int)draggingUpdated: (id) sender
{
    if( dropSign )
        return NSDragOperationGeneric;			// ドロップOK!
    else
        return NSDragOperationNone;			// 拒否!
}

-(void)draggingExited: (id) sender
{
    dropSign = NO;
    [ self setImageFrameStyle: NSImageFrameGrayBezel ];	// 反応を消す
    [ pbd release ];
}

-(BOOL)prepareForDragOperation: (id) sender
{
    return YES;						// ドロップOK!
}

-(BOOL)performDragOperation: (id) sender
{
    img = [ [ NSImage alloc ] initWithPasteboard: pbd ];
    [ self setImage: img ];
    return YES;						// ドロップOK!
}

-(void)concludeDragOperation: (id) sender
{
    dropSign = NO;
    [ self setImageFrameStyle: NSImageFrameGrayBezel ];	// 反応を消す
    [ pbd release ];
}
 
@end
     

ここまで、2002.4.16 に改定しました。


ファイルをドラッグしてパスを貼付ける

設 計

  上でうまく画像が張付いたので、この調子で同じようにと思って、任意のファイルをドラッグしてテキストボックスに放り込むと、テキストボックスにそのファイルのパスが書き込まれるのをやってみました。

  ただパスが知りたいだけなら、こんなアプリを作ってパスを調べなくても、Terminal アプリを起動してこれにファイルを放り込めば、パスを知ることができます。しかし、ちょっとした自作のプログラムで、あるファイルを読み込んで何か処理して別の名前で書き出すようなのを作る場合に、こうやってユーザーの目の前にパスが見えて、手で書き直すこともできて、プログラムからもこれが簡単にアクセスできると言うのは、大変便利なはずです。

苦 労 談

  テキストボックスにドラッグする場合でも、上で書いたプログラムがあるからこれを流用してちょっと変更と思っていたら、これが一筋縄では行かなくて何日も苦労してしまいました。変更箇所が多かったので、あえてここに新しいTipsとして書いてみます。
  画像をドラッグするのをテキストボックスにドラッグするのに変更するにあたって、初心者の私には、大きな山が三つありました。まず最初は、このテキストボックスを何のサブクラスで作るかと言うことです。通常 InterfaceBuilder の cocoa-views ウインドウに用意されているのが NSTextField なので、素直にこのサブクラスで作ると、全く動かない訳ではないのですが動作が不自然で、個人使用のちょっとしたプログラムと言えども、許容できない範囲です。設定を直すとかそう言う問題では無くて、NSTextView のサブクラスで書けば良いのだと解るのに1日以上かかってしまいました。NSTextView の場合、ここに書かれた文字列へのアクセスメソッドは、stringValue ではなく string になります。NSTextView はより本質的な文字列表示オブジェクトで、ドラッグ動作もきれいに出来るのは良いのですが、その代わりに見栄えが良くないと言う問題があって、この一つの山を越えただけでは答えとは言えません。
  かっこいいAquaインターフェースに慣れ親しんでしまうと、いくら正しく動作すると言っても、テキストボックスに枠がないのではみっともなくて気持ち良く使えません。枠を描くメソッドが標準装備されていないのです。さて、どこに枠を描き込んだら良いのでしょうか?これが2つめの山です。普通に考えて、自分で作った NSTextView のサブクラスの中に、drawRect: を装備して枠を描画するのではダメでした。どうやら、自分の superview にあたるウィンドウ(正確にはウィンドウと同じ大きさの NSView らしい)に枠を描く必要があるようです。その場合、自分の superview にあたるウィンドウの座標系で、自分自身の枠の位置がどこであるかを知る必要があります。きっとそう言うメソッドがあるはずと信じて探してみると、確かにありました。frame と言うメソッドです。(フレームと言う言葉はいろいろな意味を持っているので、単純すぎてなかなか気付かず、
過去のTips2では難しいやり方を先に見つけてしまいました。)
  さて最後にこれをどんなタイミングで描いたら良いかと言うのが最後の山です。デリゲートを使って出来なくはありませんが、いちいち配線をしなければならないのは避けたいです。ドラッグできるテキストボックスが、ウインドウ上に一つと決まっているならまだ良いですが、ちょっとしたプログラムと言ってもこんなテキストボックスをいくつも使うかも知れないし、2つにしたいなと思ったら InterfaceBuilder 上でコピペでポンと出来なければ、Cocoaらしくありません。そのためにはもう一息頑張りたいところです。いろいろ調べた結果、NSNotification と言う通知のシステムを利用することで解決できました。

コーディング

  まずは .h ファイルですが、これはあえて書くまでもありませんね。上と同じに、書かなくても動作する宣言は省略することにしました。


/* MyTextView */

#import <Cocoa/Cocoa.h>

@interface MyTextView : NSTextView
{
    NSPasteboard *pbd;
    NSString *path;
    BOOL dropSign;
}
@end
    

  次に .m ファイルです。先に述べた画像のドラッグに比べて、本質的な追加分を説明します。
  まず、awakeFromNib メソッドの中で NSNotificationCenter に対して、通知の予約をしています。自分の window が、ウインドウが更新されましたと言う通知を送ったら、自分の中の myBounds: メソッドを呼んで下さいと言う予約です。
  次が新しい枠描きメソッド myBounds: です。まず、描き込むべき自分の superview を lockFocus して描画対象とします。次に、ウィンドウから見た自分の枠の rect である aRect を作っています。その先は、自分の気に入ったデザインで枠を描いています。最後に [ self display ] とやっているのは、自分の枠の内部にまで描画がはみ出してしまったので、自分を上書きしてきれいに整えています。
  その次のメソッド setDropSign: は、ドラッグに対する反応を一般的に定義できるように、将来性を考えて作りました。先の画像のドラッグの例では、枠の形の定義を変えて反応していましたが、何にドラッグするかで反応のプログラムは様々です。わざわざ新しいメソッドを作っても、損は無いはずです。
  それ以降のメソッドは、説明する必要はないでしょう。面白そうだなと思ったら、どんどんコピッて行って下さい。


#import "MyTextView.h"

@implementation MyTextView

-(void)awakeFromNib
{
    [ self registerForDraggedTypes: [ NSArray arrayWithObjects: NSFilenamesPboardType, nil ] ];
    [ [ NSNotificationCenter defaultCenter ]
        addObserver : self
        selector : @selector(myBounds:)
        name : NSWindowDidUpdateNotification
        object : [ self window ]
    ];
    dropSign = NO;
}

- (void)myBounds:(NSNotification *)aNotification
{
    NSRect  aRect;
    float  c;

    [ [ self superview ] lockFocus ];
    aRect = [ self frame ];
    aRect = NSOffsetRect( aRect, 0.5, 0.5);
    c = 0.9; [ [ NSColor colorWithDeviceRed:c green:c blue:c alpha:1.0 ] set ];
    NSFrameRectWithWidth( aRect , -2.5 );
    c = 0.6; [ [ NSColor colorWithDeviceRed:c green:c blue:c alpha:1.0 ] set ];
    NSFrameRectWithWidth( aRect , -1.0 );
    c = 0.9; [ [ NSColor colorWithDeviceRed:c green:c blue:c alpha:1.0 ] set ];
    NSFrameRectWithWidth( aRect , 1.0 );
    [ [ self superview ] unlockFocus ];
    [ self display ];
}

-(void) setDropSign: (BOOL) ds
{
    dropSign = ds;
    if( dropSign )
        [ self setBackgroundColor: [ NSColor lightGrayColor ] ];
    else
        [ self setBackgroundColor: [ NSColor whiteColor ] ];
    [ self display ];
}

-(unsigned int)draggingEntered: (id) sender
{
    NSArray *arOfFilename;

    arOfFilename = [ [ sender draggingPasteboard ] propertyListForType: NSFilenamesPboardType ];
                                                        // ファイル名配列を取得
    if ( [ arOfFilename count ] > 1 )
        return NSDragOperationNone;			// 複数なら拒否!

    path = [ arOfFilename lastObject ];			// ファイルへのパスを取得
    [ path retain ];

    [ self setDropSign: YES ];				// 反応を表示
    return NSDragOperationGeneric;			// ドロップOK!
}

-(unsigned int)draggingUpdated: (id) sender
{
    if( dropSign )
        return NSDragOperationGeneric;			// ドロップOK!
    else
        return NSDragOperationNone;			// 拒否!
}

-(void)draggingExited: (id) sender
{
    [ self setDropSign: NO ]; 				// 反応を消す
    [ path release ];
}

-(BOOL)prepareForDragOperation: (id) sender
{
    return YES;						// ドロップOK!
}

-(BOOL)performDragOperation: (id) sender
{
    [ self setString: path ];
    [ self display ];
    return YES;						// ドロップOK!
}

-(void)concludeDragOperation: (id) sender
{
    [ self setDropSign: NO ];	 			// 反応を消す
    [ path release ];
}

@end
     

ここまで、2002.4.16 に改定しました。


ドラッグして画像を貼付ける2

  − NSView へのドラッグ − 

設 計

  順番が後先になって申し訳ありませんが、テキストを張り付けた話の後で、もう一度画像をドラッグして貼付ける話題を追加します。
  上のテキスト張り付けの例で、CustomView へのドラッグで枠の無いものに枠を付ける技を身に付けましたので、それなら、もっとも根っこの NSView に画像を貼付ける場合にも、同様に自作の枠が描けるかなと思ってやってみました。
  NSView でドラッグに反応したり、画像を書き込んだりするには、基本に戻って drawRect: を装備することになります。

コーディング

  まずは .h ファイルですが、これもあえて書くまでもありませんね。


/* MyView */

#import <Cocoa/Cocoa.h>

@interface MyView : NSView
{
    NSPasteboard *pbd;
    NSString *path;
    NSImage *img;
    BOOL dropSign;
}
@end
    

  次に .m ファイルです。先に述べた画像の NSImageView へのドラッグと比べるよりも、すぐ上のテキストのドラッグの例と比べる方が解りやすいと思います。
  変化分を説明すると、drawRect: メソッドが追加されたこと(これは下のプログラムを読んで下さい。)、drawRect: にドラッグ反応の処理が移ったので setDropSign: メソッドが軽くなったこと、本質的では無いけれど「Tiff以外は拒否」の処理を加えたこと、そして当たり前だけれど performDragOperation: メソッドの中で画像を読み込んでいること、の4点だけです。けっこう汎用性のあるドラッグのTipsになってきたかなと、内心うれしいです。


#import "MyView.h"

@implementation MyView

-(void)awakeFromNib
{
    [ self registerForDraggedTypes: [ NSArray arrayWithObjects: NSFilenamesPboardType, nil ] ];
    [ [ NSNotificationCenter defaultCenter ]
        addObserver : self
        selector : @selector(myBounds:)
        name : NSWindowDidUpdateNotification
        object : [ self window ]
    ];
    dropSign = NO;
}

- (void)myBounds:(NSNotification *)aNotification
{
    NSRect  aRect;
    float  c;

    [ [ self superview ] lockFocus ];
    aRect = [ self frame ];
    aRect = NSOffsetRect( aRect, 0.5, 0.5);
    c = 0.9; [ [ NSColor colorWithDeviceRed:c green:c blue:c alpha:1.0 ] set ];
    NSFrameRectWithWidth( aRect , -2.5 );
    c = 0.6; [ [ NSColor colorWithDeviceRed:c green:c blue:c alpha:1.0 ] set ];
    NSFrameRectWithWidth( aRect , -1.0 );
    c = 0.9; [ [ NSColor colorWithDeviceRed:c green:c blue:c alpha:1.0 ] set ];
    NSFrameRectWithWidth( aRect , 1.0 );
    [ [ self superview ] unlockFocus ];
    [ self display ];
}

- (void)drawRect:(NSRect)aRect
{
    if( dropSign ){
        [ [ NSColor lightGrayColor ] set ];
        NSRectFill( aRect );
    }else{
        [ [ NSColor whiteColor ] set ];
        NSRectFill( aRect );
        if( img )
            [ img compositeToPoint:NSMakePoint(0.0,0.0) operation:NSCompositeCopy ];
    }
}

-(void) setDropSign: (BOOL) ds
{
    dropSign = ds;
    [ self display ];
}


-(unsigned int)draggingEntered: (id) sender
{
    NSArray *arOfFilename;

    arOfFilename = [ [ sender draggingPasteboard ] propertyListForType: NSFilenamesPboardType ];
                                                        // ファイル名配列を取得
    if ( [ arOfFilename count ] > 1 )
        return NSDragOperationNone;			// 複数なら拒否!

    path = [ arOfFilename lastObject ];			// ファイルへのパスを取得
    [ path retain ];

    if ( [ [ path pathExtension ] caseInsensitiveCompare: @"Tiff" ] != NSOrderedSame )
        return NSDragOperationNone;			// Tiff以外は拒否!

    [ self setDropSign: YES ];				// 反応を表示
    return NSDragOperationGeneric;			// ドロップOK!
}

-(unsigned int)draggingUpdated: (id) sender
{
    if( dropSign )
        return NSDragOperationGeneric;			// ドロップOK!
    else
        return NSDragOperationNone;			// 拒否!
}

-(void)draggingExited: (id) sender
{
    [ self setDropSign: NO ]; 				// 反応を消す
    [ path release ];
}

-(BOOL)prepareForDragOperation: (id) sender
{
    return YES;						// ドロップOK!
}

-(BOOL)performDragOperation: (id) sender
{
    img = [ [ NSImage alloc ] initWithContentsOfFile: path ];
    [ self display ];
    return YES;						// ドロップOK!
}

-(void)concludeDragOperation: (id) sender
{
    [ self setDropSign: NO ];	 			// 反応を消す
    [ path release ];
}

@end
    

ここまで、2002.4.16 に追加しました。


フォントパネルについて

  フォントなんてどうでもいいやと最初は思っていたのですが、どうでもいいからこそコードの中で


[ NSFont fontWithName: @"HiraKakuPro-W3" size: 36 ]
    

ってな具合に直接書き込んでしまいたい。でも「ヒラギノ角ゴシック」がこう言う名前だと言うことを、どうやって知ったら良いのか、と言うことで、フォント名を調べる簡単なアプリを作り、「
6. フォント名を知りたい」として公開しました。
  結果として出来上がったアプリは簡単なのですが、フォントパネルの使い方のノウハウは、初心者には結構難しいと感じられます。「 6. フォント名を知りたい」の記事が、良い Tips になっていると思われますので、参考にして下さい。

ここまで、2002.5.10 に追加しました。


オブジェクトの所有、リテイン、破棄について

  オライリージャパン社から近頃刊行された「入門Cocoa」と言う本を買って、読みはじめました。分厚い本なのでまだ全部は読み終わっていません。その中で、5.2章「オブジェクトの所有、リテイン、破棄」は、Cocoaでのプログラミングを始めた人にとって、大変重要な内容なので是非とも良く理解したいのですが、大変難解な説明で何回読み直しても明確なイメージが掴めませんでした。そこで思い立って、自分の文章に書き直してみることにしました。
  漫然と読むよりも、自分の言葉で書いてみることで、より一層理解を深めることができました。せっかく書いたものを隠しておいてももったいないので、「
入門 Cocoa (makuri版)」として公開しました。興味のある方は、読んでみて下さい。
  勉強の成果をもとに、上の方の記事でドラッグを扱っている例題に、ちゃんと release を追加し、無意味な retain をコメント化してみました。今までは、実際の動作がおかしいので、しかたなく retain を使って勝手に破棄されるのを止めていたのですが、オブジェクトの所有、リテイン、破棄についてちゃんと理解した今では、明確にどうすれば良いかが解るようになりました。一歩一歩ですね。

ここまで、2002.5.18 に追加しました。


クローズボタンでアプリを終了

  ちょっとしたプログラムの場合、終わったらさっさと終了してもらって良い場合が多い。ウインドウの左上の赤いクローズボタンを押しただけで、アプリを完全に終了させる方法を考えてみた。NSWindow の持つ close メソッドを、サブクラスを作って上書きし、File's Owner に terminate: メッセージを送れば良い。
  具体的にはまず、NSWindow のサブクラス MyWindow を作り、myOutlet を追加して、Window のインスタンスから File's Owner に配線を張る。

  MyWindow.h がこれ。


/* MyWindow */

#import <Cocoa/Cocoa.h>

@interface MyWindow : NSWindow
{
    IBOutlet id myOutlet;
}
- (void)close;
@end
    

  MyWindow.m がこれ。


#import "MyWindow.h"

@implementation MyWindow

- (void)close
{
    [ super close ];
    [ myOutlet terminate:self ];
}

@end
    

  簡単です。

ここまで、2002.5.29 に追加しました。


日本語名ファイルへのアクセス(日本語文字列定数)

  Cocoaプログラムの中で、ファインダーで行うようなファイルの操作を行うには、NSFileManager さんにおでまし頂いて、メッセージを聞いてもらう。例えば、「/Users/makuri/aaa.txt」と言うファイルを消去したい場合には、以下のように書けば良い。


    NSString *adr = @"/Users/makuri/aaa.txt";
    NSFileManager *fman = [ NSFileManager defaultManager ];
    [ fman removeFileAtPath: adr handler: nil ];
    

  消去以外の他のファイル操作に関しては、NSFileManager のマニュアルを参照して頂くとして、詳しく説明するつもりはない。問題なのは、ファイル名が日本語の場合である。


    NSString *adr = @"/Users/makuri/あああ.txt";
    NSFileManager *fman = [ NSFileManager defaultManager ];
    [ fman removeFileAtPath: adr handler: nil ];
    

  上記のように書いても、全く意図したように動作せず、「あああ.txt」と言うファイルを消去してくれない。
  こんな基本的なことを、今頃悩んでいる自分にもあきれるが、古い人間なもので、プログラムを書く際に日本語を使うとろくなことが無いと思い込んでいるところがあって、めったに日本語を使わないので、良く分かっていないのである。とあるアプリが勝手に作ってくれる日本語名のファイルを自動消去する「ちょっとしたプログラム」を作ろうとしてつまずいてしまった。
  調べてみると、この問題は、ファイル名に限ったことではなく、「@"/Users/makuri/あああ.txt"」と言う文字列定数が、Cocoaとして正しく認識されていないことが原因であった。おそらく、Shift JIS で書かれているソースコードが、そのまま Unicode 文字列に代入されてしまって、文字化けしているのだろうと思われる。(でも、ソースファイルをUnicodeで書いても、やっぱりダメだ。)
  そう言えばこの問題は、以前に「フォントパネル」の時にも悩んで、試行錯誤的に解決したような記憶がよみがえってきた。半年以上も前のことで、自分ですっかり忘れていたとは情けない。
  正解は、以下。


    NSString *adr = [ NSString stringWithCString: "/Users/makuri/あああ.txt" ];
    NSFileManager *fman = [ NSFileManager defaultManager ];
    [ fman removeFileAtPath: adr handler: nil ];
    

  解ってみれば簡単な解決法だし、理由も解らなくはないけれど、何か腑に落ちない。何で日本語の文字列定数を扱う場合には、Const 領域と Ram 領域(こんな古代の表現が現代でも通用するのか?知らないが)の両方を消費しなければならないんだ。

ここまで、2003.1.6 に追加しました。