HMDT - Logic and Intuition -

about HMDT

Cocoa Programming Tips 1001

Application Kit

NSResponder

Application Kit - NSResponder

マウスクリック、ドラッグイベントをつかまえる

Keywords: mouseDown, mouseUp, mouseDragged

マウスを使って、ある Responder をクリックしたり、ドラッグしたりすると、イベントが発生するんだ。それらをつかまえるには mouseDown:mouseUp:mouseDragged: メソッドをオーバーライドする。

Application Kit/NSResponder.h

- (void)mouseDown:(NSEvent*)theEvent;
- (void)mouseUp:(NSEvent*)theEvent;
- (void)mouseDragged:(NSEvent*)theEvent;

これらを使えば、簡単にマウスイベントを利用することができるよ。

MouseEventViewm (sample)

- (void)mouseDown:(NSEvent*)theEvent
{
    NSLog("Mouse is pressed at %@¥n", 
            [event locationInWindow]);
}

- (void)mouseUp:(NSEvent*)theEvent
{
    NSlog("Mouse is released at %@¥n", 
            [event locationInWindow]);
}
 
- (void)mouseDragged:(NSEvent*)theEvent
{
    NSlog("Mouse is dragged to %@¥n", 
            [event locationInWindow]);
}

Application Kit - NSResponder

ダブルクリックをつかまえる

Keywords: mouseUp, clickCount

ユーザがダブルクリックしたかどうか、知るにはどうしたらいいか?それは、mouseUp: のところで、NSEvent の clickCount を調べるんだ。

DoubleClickView.m (sample)

- (void)mouseUp:(NSEvent*)event
{
    if([event clickCount] == 2) {
        // Double click
        NSLog(@"I got double click!¥n");
    }
}

ちなみに、クリックカウントは何回まで追跡できるのか?どうも、何回でもできるらしい。ダブルクリックや、トリプルクリックだけでなく、100th クリックも利用できるわけだ。だれも使わないと思うが。

Application Kit - NSResponder

コンテキストメニューを設定する

Keywords: menu, setMenu

コンテキストメニューってあるよな。ctrl キーといっしょにクリックするか、右クリックで出てくるメニュー。Cocoa では、コンテキストメニューをサポートしている。NSResponder のレベルで管理することになるんだ。

コンテキストメニューの設定、取り出しには menu, setMenu を使う。

Application Kit/NSResponder.h

- (void)setMenu:(NSMenu *)menu;
- (NSMenu *)menu;

実は、いちばん簡単にコンテキストメニューを設定する方法は、Interface Builder を使う方法だ。IB でオブジェクトを貼付けると、ほとんどのやつは、その outlet に menu という項目があるはずだ。それに IB で作ったメニューを指定してやればいい。それだけで、ctrl + クリックでメニューが出るようになる。ただし、メニューのアイテムに target を指定してやらないと enable にならないので注意。

■関連リンク:
コンテキストメニューを動的に変化させる (NSView)

Application Kit - NSResponder

ファースト・レスポンダが変更されるときの流れ

Keywords: acceptsFirstResponder, resignFirstResponder, becomeFirstResponder

ファースト・レスポンダが変更されるとき、どういう流れになっているのか調べてみるよ。

例として、TextFiled が 2 つ(A と B)がある状態を考えてみよう。いま、A にフォーカスが当たっていて、キーボードから文字を入力すると A に入れられる。それを、Tab を使って B にフォーカスを移そうとするんだ。

使われるメソッドは、次の 3 つ。acceptsFirstResponderresignFirstResponder、そして becomeResponder だ。

Application it/NSResponder.h

- (BOOL)acceptsFirstResponder;
- (BOOL)becomeFirstResponder;
- (BOOL)resignFirstResponder;

まず、ウィンドウが TextFiled B に、お前はファースト・レスポンダになれるのか?って聞くんだ。そのために acceptsFirstResponder を送るんだ。この場合、TextField B は、なれるので YES を返す。

次に、ウィンドウは、今度は TextField A(現在のファースト・レスポンダ)に、ファースト・レスポンダを変えたいんだけど、準備できてるか?って聞くわけだ。それには resignFirstResponder が使われる。TextField A は、別にかまわないなら YES を返す。だめなら、たとえば、いまかな漢字変換している最中だから、後にしてくれ!っていうときは NO を返すんだ。

TextField A が、ファースト・レスポンダ変えてもいいぜ、っていったら、いよいよファースト・レスポンダを変える。まず TextField B に becomResponder を送って、お知らせする。そして、ウィンドウの outlet であるファースト・レスポンダを TextField B に変えて、おしまいだ。

自分で作ったビューで挙動を変えたいときは、上の 3 つのメソッドをオーバーライトすれば OK だ。

Application Kit - NSResponder

.nib の中にある First responder とは

Keywords: First Responder

Cocoa アプリケーションの .nib ファイルを Interface Builder で開くと、First Responder っていうアイコンが、最初っからできてるよな。これに対して、Copy とか Paste とかのアクションはつながってるでしょ。でも、これって何?インスタンスなの?誰かの outlet なの?どこに実体があるの?そもそも実体があるの?薄いグレーで名前が表示されてるのが気になるよな、、、

結論から言ってしまうと、こいつは nil だ。いままで nil にアクションを送っていたのだ。へ?何それ?この仕組みを解き明かしてみよう。

(注意:ここでいってる "First Responder" は、.nib のウィンドウの中に出てくるアイコンのことだ。NSWindow の outlet である firstResponder とは違うからね。そこんとこ、勘違いしないよーに!)

AppKit では、NSControl のサブクラスはアクションを送ることができるんだ。たとえば、NSButton ならボタンが押されときにアクションを送る、とかね。アクションは、NSApplication の sendAction:to:from: を経由して、ターゲットに送られるんだ。

Application Kit/NSApplication.h

- (BOOL)sendAction:(SEL)theAction to:(id)theTarget from:(id)sender;

sender に送り手となるオブジェクト、theTarget に送る相手のオブジェクト、theAction にアクションを指定するんだ。Interface Builder で、普通にオブジェクト同士をつなげた場合は、sendertheTarget に、それぞれ指定したオブジェクトが入る。

じゃ、なげる相手として First Responder を指定した場合は?この場合、sender はアクションをなげるオブジェクトだけど、theTarget に nil が指定されるんだ。そして、NSApplication は、アクションを送る相手を探す。どうやって探すかって?responder chain を使うんだ。responder chain に対して、どんどんアクションをなげていく。誰かが受け取ったら、そこでおしまいだ。

Application Kit - NSResponder

レスポンダ・チェインをたぐる

Keywords: responder chain

responder chain の話をする前に、ちょっと思い出しておこう。まず、NSApplication は keyWindow を持っている。さらに Window のリストも持っている。そして NSWindow は、firstResponder を持っている。各 View は nextResponder を持っている。ここまで OK?

  1. まず、NSApplication は keyWindow にアクションをなげる。NSWindow はアクションをもらったら、firstResponder に対してアクションをなげて、そいつがアクションを受け取るかどうか評価する。受け取ったら、そこでおしまい。
  2. 受け取らなかったら、そいつの nextResponder にアクションをなげる。


  3. ぜんぶの responder を走査したら、次は NSWindow 自分自身になげる。
  4. その次は Window の delegate。


  5. keyWindow で受け取られなかったら、次は main window になげる。そこでも同じことが行われる。


  6. main window の評価が終わったら、次は NSApplication 自身。
  7. それでもだめなら、application の delegate。そこで、おしまい。

こういう流れね。responder chain は、target に nil が指定されたときに使われる。つまり、アクションの相手が不明なときや、動的に変化するとき(first responder とか)に使われるんだ。相手が決まっているときは、Interface Builder でつないでやって、直接なげればいい。

Application Kit - NSResponder

レスポンダ・チェインを表示する

Keywords: responder chain

Cocoa では、イベントが発生したら、NSWindow から NSResponder に、順々に投げられるんだ。まず最初に、ファースト・レスポンダに投げる。ファースト・レスポンダがそのイベントに対して処理をしないならば、次のレスポンダに投げる。このレスポンダの連なりをレスポンダ・チェインって呼ぶんだ。こいつを表示して見よう。実際にアプリケーションを作るときは、表示させる必要はまったくない。単に、実験として、どういう chain を作っているのか、確かめるのが目的だ。

NSResponder から、次の responder を得るには、nextResponder を使う。

Application Kit/NSResponder.h

- (NSResponder *)nextResponder;

これを使って、次のような関数、showResponderChain() を作ってみた。

FirstResponderWindow.m (sample)

void showResponderChain(NSResponder* responder)
{
    NSLog(@"- Show responder chain?   ");
    
    while(true) {
        NSLog(@"%@", NSStringFromClass([responder class]));
        responder = [responder nextResponder];
        
        if(responder == nil) {
            break;
        }
        printf("-> ");
    }
}

この関数に、responder chain の先頭を渡してやる(途中でもいいけど)。そこから連なる responder chain を表示するんだ。たとえば、ウィンドウに貼付けてある、NSTextView が first responder だった場合は、次のような responder chain ができあがってるんだ。

Result

- Show responder chain
   NSTextView
-> _NSKeyboardFocusClipView
-> NSTextField
-> NSView
-> NSWindow

という感じで、イベントが NSTextView を先頭に、次々と渡っていくわけだ。で、てきとうなところで、処理されるわけね。

■関連リンク:
ファースト・レスポンダの変更をつかまえる (NSWindow)

■サンプルダウンロード:
ResponderChain.tar.gz

back to top content

Copyright © 2002-2006 HMDT. All rights reserved.