Be Newsletter

Volume II, Issue 13; April 1, 1998


DEVELOPERS' WORKSHOP: BeOSプログラミングの基礎: Part 2

By Eric Sheppherd

 

私の前回の記事「BeOSプログラミングの基礎 Part1」において、我々は一つのビューを含む一つのウィンドウからなる基礎的なBeOSアプリケーションを作成する様子を見ました。 今週我々はこれに少しの拡張を行うこととし、ウィンドウにメニューバーを加え、メニュー中でなされるユーザの[各メニューアイテムの]選択をどうやって扱うのかを示します。

しかし、そこまで行く前に、私は最初にたびたびみなさんを混乱させるポイントをはっきりさせることから始めることにして、BeOSが使う座標系について簡単におさらいをしておこうと思います。 あなたが[下記のSet()関数で指定した]矩形を持っている場合を例とします:

 
   rect.Set(0, 0, 5, 7);

この矩形の上左端は(0, 0)で、下右端が (5,7)です。 見た目は、こんな感じになります:

   
   012345
   ******0
   ******1
   ******2
   ******3
   ******4
   ******5
   ******6
   ******7

なぜなら、あなたが指定する座標は(0,0), (1,0), (2,0), (3,0), (4,0), および(5,0)の点からなる最上列を含むからです。 それらを足し上げていくと、矩形の幅は6ピクセルで、高さは8ピクセルであることが分かります。

あなたがこの矩形のWidth()[関数]を呼び出すと、結果は 5 (5-0)になります。 Height()は 7 (7-0)を返します。 これが、大部分の混乱のタネになります。 あなたが矩形の実際の幅や高さをピクセル値として知りたい場合、これら2つの関数の結果にそれぞれ1を加える必要があります。【訳注:要は、始点は座標としては(0,0)なんですが、これが実態である上左端のピクセルを指しているからな訳ですね】

それでは今週の課題に移りましょう: Menu World。 Menu Worldは、私の前回の記事で使ったHello Worldコードを基にしています; 実際に、あなたがその記事をまだ読んでいないのであれば、私はBeウェブ・サイトに行って、読んでおくことを勧めます(念のため、置き場所はここです: www.be.com/aboutbe/benewsletter/volume_II/Issue7.html#Workshop。 また、あなたはHello Worldのソース・コードをダウンロードして、これを見ておきたいと思うかもしれません: ftp://ftp.be.com/pub/samples/preview/intro/helloworld_article.zip

さらにあらかじめ断っておきますが、この記事ではBMessage(これは利用者の操作をあなたのアプリケーションに伝える手段です)を、それがどのように機能するかに関する詳しい説明無しに使うことを指摘しておかなければなりません。 この特集の次回の記事で、messaging[メッセージング]についてもっと細かく検討します。

ここに上げたのは、Menu Worldのためのinclude文です:


 #include <Application.h> 
 #include <Window.h> 
 #include <View.h> 
 #include <MenuBar.h>
 #include <Menu.h> 
 #include <MenuItem.h>
 #include <string.h>

とても単純で自明であるmain()の説明から始めましょう。 これを[単純なのにわざわざ説明に]含めた理由は、前回の記事とわずかに中身が異なっているからです-−今や、これ[main()関数]はHelloAppオブジェクトをスタック上に割り当てており、これはC++で推奨されているやり方なのです(私は進歩を好みます)。 それ[HelloAppオブジェクト]がスタック上に割り当てられるので、我々はこいつを削除し忘れないように覚えている必要が無いことに注意して下さい。これはmain()が実行環境に制御を戻す場合に自動的に後始末をしてくれるからです。

 
 void main(void) {
   HelloApp theApp;    // The application object
   theApp.Run();
 }

また、我々は以下の定数を、Menu Worldが提供するメニューアイテムのためのBMessageコマンド・コードに使います:

  
 const uint32 MENU_FILE_NEW        = 'MFnw';
 const uint32 MENU_FILE_OPEN       = 'MFop';
 const uint32 MENU_FILE_CLOSE      = 'MFcl';
 const uint32 MENU_FILE_SAVE       = 'MFsv';
 const uint32 MENU_FILE_SAVEAS     = 'MFsa';
 const uint32 MENU_FILE_PAGESETUP  = 'MFps';
 const uint32 MENU_FILE_PRINT      = 'MFpr';
 const uint32 MENU_FILE_QUIT       = 'MFqu';
 const uint32 MENU_OPT_HELLO       = 'MOhl';

それではHelloViewクラスを見て、前回以降これに新しいメソッドが一つ加わったことと、幾つかのprivateデータがわったことを確認しましょう。 これは今のところそれほどわくわくする材料ではありませんが、我々がしかるべき段階まで到達したら、メニューで少々実験を行います:

   
 class HelloView : public BView {
   public:
     HelloView(BRect frame);
     virtual void Draw(BRect updateRect);
     void SetString(const char *s);
 
   private:
     char message[128];
 };

HelloViewクラスがどんなメッセージを送信するかをあなたが構成出来るように、SetString()関数が加えられました。 メッセージは、以下のように、メッセージ文字列に格納されます:

   
 void HelloView::SetString(const char *s) {
   if (strlen(s) < 127) {
     strcpy(message, s);
   }
 }
 

SetString()は文字列が長すぎないことを確認した後、メッセージ・フィールドにそれをコピーします。

メッセージ文字列を使えるように、Draw()関数を変更しなければいけません:

    
 void HelloView::Draw(BRect updateRect) {
   MovePenTo(BPoint(20,75));      // Move pen
   DrawString(message);
 }

最後に、HelloViewコンストラクタは、メッセージ文字列を初期化するためにアップデートされなくてはなりません:

   
 HelloView::HelloView(BRect frame)
       : BView(frame, "HelloView", B_FOLLOW_ALL_SIDES,
         B_WILL_DRAW) {
   SetString(STRING_HELLO);
 }

我々がアップデートされたHelloWindowクラスを見るに至って、事態は本当に面白くなってきます:

  
 class HelloWindow : public BWindow {
   public:
     HelloWindow(BRect frame);
     virtual bool QuitRequested();
     virtual void MessageReceived(BMessage *message);
 
   private:
     BMenuBar  *menubar;
     HelloView *helloview;
 };

HelloWindowクラスの中では、前回以降MessageReceived()関数が新たに加えられています。 我々は今や、メニューバーや、ウィンドウに付与されたHelloViewへのポインターを格納するためのフィールドを持っていることに気づいて下さい。

これらのメソッドを一つずつ見ていきましょう。 コンストラクタは、メニューバーを生成し、インストールするためにアップデートされました。 こんなふうになります:

  
 HelloWindow::HelloWindow(BRect frame)
       : BWindow(frame, "MenuWorld", B_TITLED_WINDOW,
         B_NOT_RESIZABLE | B_NOT_ZOOMABLE) {
   BRect r;
   BMenu *menu;
   BMenuItem *item;
 
   // Add the drawing view
 
   r = Bounds();
   r.top = 20;
   AddChild(helloview = new HelloView(r));
 
   // Add the menu bar
 
   r.top = 0;
   r.bottom = 19;
   menubar = new BMenuBar(r, "menu_bar");
   AddChild(menubar);

これは第一印象ほど複雑ではありません。なぜなら中身の大半は既にやったことの繰り返し的な内容だからです。

我々は、Hello World用に行ったのと同じように、ウィンドウ矩形の境界[値]を得るところから始めています。 我々は、独自のHelloViewを生成したいわけです。 しかし、メニューバーのための余裕を残すために、我々は矩形の上端の値を20(メニューバーの高さは20ピクセルにして、HelloViewの上に配置するつもりです)に設定しました。 次に、我々はHelloViewを生成し、ウィンドウに付与します。 メンバー変数helloviewにも、ビューへのポインターを格納することに気づいて下さい。

次に我々はメニューバーを生成して、それをウィンドウに付与します。 我々はr.topを0に、r.bottomを19に設定し、メニューバーがウィンドウの上端から20ピクセル分を確保するように指示し、「menu_bar」という名前のBMenuBarを生成します。 メニューバーへのポインターは、変数メニューバーに格納されます。 それから、メニューバーはAddChild()を呼ぶことによってウィンドウに加えられます。

この段階で、我々のウィンドウは2つのビューを持つ点に注意してください: 1つはHelloViewで、1つはBMenuBar(これはBMenuから派生したもので、BMenuはBViewから派生しています)です。

次にFileメニューが生成されます:


   menu = new BMenu("File");

この行は、「File」という名前の付いた空のメニューを生成するだけです。 ようやく、下記のようにメニューにアイテムを加える段階に達しました:

 
   menu- AddItem(new BMenuItem("New",
                   new BMessage(MENU_FILE_NEW), 'N'));

コードのこの行では、「New」選択肢(Command+Nというキーボード・ショートカット付き)を、新しいBMenuItemオブジェクトを生成することによって付け加え、これをAddItem()呼び出しを使ってメニューに加えます。

ユーザがメニューアイテムを選択すると、1つのBMessageがメニューアイテムを含むウィンドウ(メッセージの宛先は変更できますが、これ以上深堀りするのはやめておきましょう)に送られます。 ウィンドウに送られるメッセージは、あなたがBMenuItemを生成するときに指定したモデル・メッセージをコピーすることによって生成されます。

この場合、我々はモデル・メッセージをコマンド・コードMENU_FILE_NEWに指定します。 我々は、私の次回の記事でメッセージについてより詳細に検討します。

我々は、残りのメニュー項目を加え続けます。 BMenuItemコンストラクタのキーボード・ショートカット引数は省略可能[オプション]である点に注意して下さい; あなたがそれを指定しなければ、メニューアイテムはキーボード・ショートカットを持ちません。 また我々は、分割線をあちこちで加えるためにAddSeparatorItem()関数を使い、メニューを読み易くします:

 
   menu- AddItem(new BMenuItem("Open" B_UTF8_ELLIPSIS,
                   new BMessage(MENU_FILE_OPEN), 'O'));
   menu- AddItem(new BMenuItem("Close",
                   new BMessage(MENU_FILE_CLOSE), 'W'));
   menu- AddSeparatorItem();
   menu- AddItem(new BMenuItem("Save",
                   new BMessage(MENU_FILE_SAVE), 'S'));
   menu- AddItem(new BMenuItem("Save as" B_UTF8_ELLIPSIS,
                   new BMessage(MENU_FILE_SAVEAS)));
   menu- AddSeparatorItem();
   menu- AddItem(new BMenuItem("Page Setup" B_UTF8_ELLIPSIS,
                   new BMessage(MENU_FILE_PAGESETUP)));
   menu- AddItem(new BMenuItem("Print" B_UTF8_ELLIPSIS,
                   new BMessage(MENU_FILE_PRINT), 'P'));
   menu- AddSeparatorItem();
   menu- AddItem(new BMenuItem("Quit",
                   new BMessage(MENU_FILE_QUIT), 'Q'));
 

いったん全てのアイテムがメニューに加えられたら、我々はBMenuBarクラスのAddItem()関数を呼び出して、メニューをメニューバーに付加します:

 
   menubar- AddItem(menu);

次に我々はオプションメニューを生成しますが、これはアイテムをたった1つ持つだけです。

  
   menu = new BMenu("Options");
   item = new BMenuItem("Say Hello",
            new BMessage(MENU_OPT_HELLO));

「Say Hello」メニューアイテムは、我々のウィンドウ中にあるHelloViewで表示される2種類の文字列を切り換えるために使います: 「Hello World」と「Goodbye World」。 このメニューアイテムは、「Hello World」が表示される場合にはその隣にチェック・マークが付き、「Goodbye World」が表示される場合には付きません。 「Hello World」を初期値[デフォルト]にして、「Say Hello」アイテムが最初はチェックされていることを確認しましょう:

  
   item- SetMarked(true);

SetMarked()にtrueを渡すことで、メニューアイテムの前にチェック・マークが付けられ、一方falseを渡すと、チェック・マークは削除されます。

次に、我々はメニューに「Say Hello」アイテムを加え、メニューバーにオプションメニューを加えます。


   menu- AddItem(item);
   menubar- AddItem(menu);

最後に、我々はShow()を呼び出すことで、ウィンドウを可視化します。

 
   Show();
 }

 QuitRequested() is unchanged from Hello World.

MessageReceived()は、メニューバーでユーザが選択をした後で、それに対する対応と[状況別の]仕分けのための大量の拾い上げをする場所です。【訳に自信なし】 ユーザがメニュー・オプションを選択すると、該当するBMessageが生成され、メニューを含むウィンドウに送られます。 ウィンドウのMessageReceived()関数は、このメッセージを受け取り、処理します:


 void HelloWindow::MessageReceived(BMessage *message) {
   switch(message- what) {
     case MENU_OPT_HELLO:
       /* see below for the code that goes here */
       break;
 
     default:
       BWindow::MessageReceived(message);
       break;
   }
 }

BMessageオブジェクトのpublicフィールドの1つに、「what」と呼ばれるものがあります; それは、BMessageを生成した時に指定されたコマンド・コードを含みます。 したがって、我々は「what」フィールドの値を見ることで、そのBMessageがどんな種類のメッセージであるのかが分かります。

さしあたっては、我々が処理する唯一のメッセージはMENU_OPT_HELLOであり、これはユーザがオプションメニューで「Say Hello」アイテムを選んだ時に送られます(HelloWindowコンストラクタの中身を見ると、「Say Hello」アイテムに対するモデルBMessageがこのコマンド・コードを持っています)。 他のあらゆるメッセージは、それ以上の処理を行うためにBWindow::MessageReceived()に転送されます。

「Say Hello」アイテムは、ビュー内に表示されるテキストを「Hello World」か「Goodbye World」に、相互に切り換えます。 また、それは「Hello World」が表示されていればメニューアイテムにチェックボックスを付け加え、「Goodbye World」が表示されていれば[このチェックボックスを]削除します。 コードを見てみましょう。

 
     case MENU_OPT_HELLO:
       {
         BMenuItem *item;
         const char *s;
         bool mark;
 
         message- FindPointer("source", (void **) &item);
         if (item- IsMarked()) {
           s = STRING_GOODBYE;
           mark = false;
         }
         else {
           s = STRING_HELLO;
           mark = true;
         }
         helloview- SetString(s);
         item- SetMarked(mark);
         helloview- Invalidate();
       }
       break;

ユーザがメニューアイテムを選択した場合にあなたのMessageReceived()関数が受け取るBMessageは、3つの追加フィールドを持つメニューアイテムが生成された時に指定されるモデル・メッセージです:

「when」フィールドは、アイテムの選択された時間を指定するB_INT64_TYPE値を含んでおり、この時間は1970年1月1日の午前12:00:00を基準に、マイクロ秒単位で測定されます。

「source」フィールドはB_POINTER_TYPE値を含みますが、これはBMenuItemオブジェクトそのものに対するポインターです。

「index」フィールドはB_INT32_TYPE値を含みますが、これはメニュー内で選択されたメニューアイテムのもともとの位置を指定します。値 0 は、メニュー中の最初のアイテムです。

メニューアイテムの隣にあるチェック・マークを切り換えるために、我々はそのBMenuItemオブジェクトへのポインターを必要とします。 我々はそれをウィンドウ・オブジェクト中に貯めておくことも出来るのですが、サンプル・コードとしての役割を考えると、BMessage中の「source」フィールドから取って来てしまう方がずっと面白いので、我々はそうすることにします。 我々は、FindPointer()関数を使って、そのポインターをメッセージから取り出し、それを「item」と呼ばれる変数に格納します。

次に我々は、item-IsMarked()を呼び出し、メニューアイテムが現在チェックされているか否かを決定します。 それがチェックされていれば、IsMarked()はtrue(これはビュー内のテキストが、現在「Hello World」であることを意味します)を返します。 我々は「Goodbye World」という文字列へのポインターをセットアップして、その変数のマークをfalseに設定します。 IsMarked()がfalse(これは「Goodbye World」が表示されていることを意味します)を返す場合、我々は文字列ポインターを「Hello World」に対して設定し、マークをtrueにします。

そして我々‖あることは表示して、item-SetMark()を呼び出すそのテキストを変更するそのHelloViewのSetString()関数が向くコールや外れたそのチェック・マーク; マークがfalseなら、チェック・マークは削除されます。 マークがtrueなら、チェック・マークが付加されます。

次に、我々はHelloViewを無効化します。 ビューを無効化することで、アプリケーション・サーバーはそのビュー全体をDraw()しようとします。 これは、そのビューの内容をリフレッシュするために必要です。

BMenuItemクラスは、あなたが試してみたいと思うような幾つかの他の関数、つまりSetEnabled()やIsEnabled()といったものを持っており、これはあなたにメニューアイテムが選択不可能(文字が薄い色で、選択不可能になっている)か否かを制御させてくれる。あるいはSetLabel()やLabel()という関数も持っており、これらはメニューが描画された際に、メニューアイテムとして表示されるテキストを設定したり、取り出したり出来る。

人生をより心安くするために、あなたは今週のプロジェクトの完全なソース・コードをダウンロードすることができます: ftp://ftp.be.com/pub/samples/preview/intro/menuworld_article.zip

今週の記事でBMessageの難しい問題の缶を開けてしまったので、私はBeOSプログラミングの基礎シリーズのパート3では、BeOSのメッセージ・サービスをもう少し詳しくカバーしておくことこそが適切であると思います。 とりあえず、BMenuBar、BMenuならびにBMenuItemクラスを試してみて、あなたがどこまでそれを探求できるかを確認しておいてください。 それでは6週間後位にまたお会いしましょう。

 


コピーライト©;1998年 Be社。 Beは登録商標です、そして、BeOS、BeBox、BeWare、GeekPort、BeロゴとBeOSロゴはBe社の商標です。 その他にここで触れたあらゆる商標は、それらの各々の所有者の所有物です。 このサイトについてのコメントがありますか? webmaster@be.com宛にメールを書いて下さい。