イベント駆動型プログラム


 もしかして:イベント工藤型プログラム(バーロー

 はい、準備編第3回始まり始まりー。予告通りイベント駆動型プログラムについてのお話です。


 C言語の入門書にあるようなプログラムといえば、

1. 逐次構造(コードに書かれた順番で実行する)

2. 選択構造(条件によって処理を選ぶ)

3. 反復構造(条件によって処理を反復する)

を組み合わせてできていて、一連の処理を実行し終わるとプログラムは終了します。途中でユーザの入力がある時は、入力が終わるまで実行を一旦停止して待ちます。

 上のようなプログラムのことを、フロー駆動型プログラムと呼んだりします。流れ図(フローチャート)的な考え方ですね。


 ところが! Mac(やWindowsなど)のアプリケーションには明らかに違った特徴があります。

1. 起動後にとりあえず処理が済んでもすぐに終了しない

2. ユーザの入力の種類がいくつもあって、タイミングや順序も自由

3. ユーザの入力に応じてそれぞれ適切な処理を実行する

などです。このような特徴を持ったプログラムのことを、イベント駆動型プログラムと呼びます。イベントとは処理を実行するきっかけになる出来事のことで、ユーザの入力以外にもタイマーやシステムメッセージなどがあります。


 さて、Macではイベント駆動型プログラムを実現するのに、古くからEvent Managerが使われていました。これは、WaitNextEvent関数を使ってイベントループを書き、イベントの種類ごとにさばいて処理する、という流れになります。単純なアプリケーションでもイベントの種類が多いので結構大変……。

 そんな事情でCarbonから導入されたのがCarbon Event Managerです! こちらはイベントハンドラ(コールバック関数)を書いてウインドウやコントロールに登録し、その後RunApplicationEventLoop関数を呼び出す流れです。

 この講座ではちょっとでも現代的な方を……ということで、Carbon Event Managerを採用します。


 では、さっそくサンプルコードへ! イベント駆動型のHelloWorldプログラムです。ウインドウを閉じるか中身の部分をクリックすると終了します。

 ウインドウ関係(Window Manager)の関数やグラフィックス(QuickDraw)の解説は省略ー。今後、扱っていくつもり……。今回はイベント駆動の仕組みに注目して読んでみてください!

/*  [*1]  */
#include<Carbon.h>

/*  関数宣言  */
void initialize(void);
void createMainWindow(void);
/*  [*2]  */
pascal OSStatus onDraw(EventHandlerCallRef nextHandler, EventRef event, void *data);
pascal OSStatus onClose(EventHandlerCallRef nextHandler, EventRef event, void *data);

/*  メインウインドウ  */
WindowRef MainWindow = NULL;

/*------------------------------------------------------------------------------
    メイン関数
------------------------------------------------------------------------------*/
void main(void) {
    initialize();
    createMainWindow();
    /*  イベントループ開始!  */
    RunApplicationEventLoop();
}

/*----------------------------------------------------------------------------*/
void initialize(void) {
    /*  マウスカーソル初期化(時計やぐるぐる→矢印)  */
    InitCursor();
    /*  本当はここで環境チェックとか  */
}

/*----------------------------------------------------------------------------*/
void createMainWindow(void) {
    const WindowAttributes attributes =
        kWindowStandardDocumentAttributes | kWindowStandardHandlerAttribute;
    const Rect bounds = {0, 0, 300, 400};
    /*  EventTypeSpecはイベントの種類を表す構造体  */
    /*  「ウインドウの中身を描画する」  */
    const EventTypeSpec typeDraw = {kEventClassWindow, kEventWindowDrawContent};
    /*  「ウインドウを閉じる」と「ウインドウの中身の部分でクリック」  */
    const EventTypeSpec types[] = {
        {kEventClassWindow, kEventWindowClose},
        {kEventClassWindow, kEventWindowHandleContentClick}};
    
    CreateNewWindow(kDocumentWindowClass, attributes, &bounds, &MainWindow);
    /*  ウインドウを作れなかったら続行する意味がないので終了  */
    if (MainWindow == NULL)
        ExitToShell();
    
    /*  [*3]  */
    SetWindowTitleWithCFString(MainWindow, CFSTR("HelloWorldCarbon"));
    RepositionWindow(MainWindow, NULL, kWindowCascadeOnMainScreen);
    /*  イベントハンドラ登録!  */
    InstallWindowEventHandler(
        MainWindow, NewEventHandlerUPP(onDraw), 1, &typeDraw, NULL, NULL);
    /*  イベントハンドラ登録!(イベントの種類が配列)  */
    InstallWindowEventHandler(
        MainWindow, NewEventHandlerUPP(onClose), GetEventTypeCount(types), types, NULL, NULL);
    ShowWindow(MainWindow);
}

/*----------------------------------------------------------------------------*/
pascal OSStatus onDraw(EventHandlerCallRef nextHandler, EventRef event, void *data) {
#pragma unused(nextHandler, event, data)
    const RGBColor textColor = {0x0000, 0x6666, 0xFFFF};
    const RGBColor shadowColor = {0x6666, 0x6666, 0x6666};
    
    TextSize(48);
    MoveTo(52, 153);
    RGBForeColor(&shadowColor);
    /*  [*4]  */
    DrawString("\pHello, World!");
    MoveTo(50, 150);
    RGBForeColor(&textColor);
    DrawString("\pHello, World!");
    return noErr;
}

/*----------------------------------------------------------------------------*/
pascal OSStatus onClose(EventHandlerCallRef nextHandler, EventRef event, void *data) {
#pragma unused(nextHandler, event, data)
    /*  イベントループから脱出ー  */
    QuitApplicationEventLoop();
    return noErr;
}

/*------------------------------------------------------------------------------
    *1
    Carbon用ヘッダファイル(アンブレラヘッダ、要は全部入り)
    使うものだけ書く方法もあって、その方がコンパイルは速くなります
    
    *2
    pascal……? 怪しげですね(笑
    コールバック関数はPascal呼び出し規約に合わせるため、こう宣言します
    昔のシステムがPascal言語で開発されていた名残とか……
    
    *3
    CFSTR("〜")とは……? これはCFString(Core Foundation)です
    CFStringのことは「雑多な話」の方にまとめたので、よろしければどうぞ!
    
    *4
    "\p〜"とは……? これはPascal文字列です
    Pascal文字列は1バイト目に長さが入って2バイト目からが内容
    標準C言語の文字列は、{'S','T','R','I','N','G','\0'}で「STRING」
    Pascal文字列では、{6, 'S','T','R','I','N','G'}で「STRING」
    原理上、長さは255バイトまでになります(うーん
------------------------------------------------------------------------------*/

 ビルド方法は前回と違うところだけ……。

 「Create Build Commands...」のダイアログでは、

1. 「Program Type」は「Application」

2. 「Target」は「PowerPC Only」

3. 「PowerPC Model」は「Carbon」

を選択します。


 右側の「General Options...」ボタンからクリエータコード、「Carbon Options...」ボタンからメモリのサイズなどの設定もできます。まあ、今はそのままで。


 ビルドできたらさっそく実行してみてください! ウインドウの移動やリサイズ、OS XならDockへの出し入れなどができますね。コードに書いてもいないのになぜ……?

 このあたりはCarbon Event Managerの最重要ポイントなので、次回に詳しく解説したいと思います。


 準備編はこれでおしまい! お疲れ様です。ここまでの内容でご意見等(「ここ間違ってる!」とか「ここ分からん!」とか「何が何やらさっぱり??」とか)ありましたら、ウェブ拍手などで言っていただければ可能な範囲で対応します。

 そんなこんなで次回からはようやく本編、第1回は(上にも書いたように)Carbon Event Managerを取り上げます。それでは!


前へ - 目次 - 次へ