Lesson 3:ちょっとプログラムらしいプログラムを作ってみよう


 前回、前々回とウインドウを表示するプログラムを作ってきましたが、 今回からはウインドウに色々なビューオブジェクトを貼り付けて、もう少し プログラムらしいものを作っていきましょう。

 今回は、これからも非常に良く使う事になる、文字列の入力と表示、 それとボタンを作成してみます。
 文字列の入力欄、表示欄、ボタンを準備して、ボタンを押すと、 入力欄に記述してある文字列を表示欄に表示するアプリケーションを作成します。

 まずは、今回のアプリケーション用に、新しいプロジェクトを作成しましょう。
 プロジェクトは「home」の下の「projects」フォルダに「Be1stApp.proj」の名前で保存してください。 プロジェクトの種類を選ぶ際に、前回作成した雛型「BaseApp」を選択します。 また、「Create Folder」をチェックしておきますと、プロジェクト名と同じ名前のフォルダを 自動的に作成しますので、これもチェックしておいてください。

 文字列の入力や表示、ボタンには、どんなビューオブジェクトを使用すれば良いでしょうか?  まずはBeBookを見てみましょう。「The Application Kit」だの「The Interface Kit」だの 色々なグループに分かれています。調べたいのはマンマシンインタフェースなので、 「The Interface Kit」を選択します。
 インタフェースキットに含まれるクラスが列挙されていますので、じっと見てみましょう。 なんか、「BButton」とか「BStringView」とか、あからさまなクラスがぽろぽろとありますよね。
 文字列の入力には、「BTextView」とか「BTextControl」とかが使えそうですね。
 こうやって、ちょっと見ただけでも使うべきクラスがすぐに判断できる。 これはBeOSのAPIが非常にシンプルでわかりやすいという事です。

 こうしてみると、今回は文字列の入力に「BTextControl」、表示に「BStringView」、 ボタンは「BButton」を使えばよさそうです。

 とりあえず入力欄と表示欄のオブジェクトを作成してみましょうか。
 BAppMainViewのコンストラクタを次のようにして、実行してみてください。

/**** ファイル名 : MainWindow.cpp ****/

BAppMainView::BAppMainView(BRect frame)
    :BView(frame,"bappmainview",B_FOLLOW_ALL,B_WILL_DRAW)
{
    BRect viewrect(Bounds());

    BTextControl *textctrl=new BTextControl(
                              BRect(8,8,viewrect.right-8,28),"textctrl",
                              NULL,"Default",NULL,
                              B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP);
    AddChild(textctrl);

    BStringView *strview=new BStringView(
                            BRect(8,38,viewrect.right-8,58),"strview",
                            "NoData",
                            B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP);
    AddChild(strview);
}

 実行したら、ウインドウの大きさを変えてみましょう。
 どちらもB_FOLLOW_LEFT_RIGHTを指定しているので、ウインドウの大きさに合わせて幅が変わります。 (といっても、表示欄は枠線が出ていないのでわかりにくいですが。)

 次にボタンを配置するのですが、ボタンを押した際の処理は何処に記述すればよいのでしょう?
 MS-Windowsでの開発を経験した方なら、すぐにメッセージを思いつくでしょう。(Motif経験者はコールバックって考えるかな?)
 BeOSのプログラムも、メッセージを使用したイベントドリブンのスタイルで開発します。
 簡単にいうと、色々な種類のメッセージを誰かに送信する事でイベントが発生します。メッセージを受け取る側は、そのメッセージを受け取ったら、それに対応した処理を行うようにします。(と今は思っててくれていいです。やってくうちに実態は理解できるようになりますから。)
 メッセージはシステムから送信されてくる場合もありますし、自分で作成したメッセージを送信する事もできます。

 メッセージの送信も受信も前回まで(今回も)のプログラムで行っていました。
 MainWindow.hで定義しているBAppMainWindowクラスのQuitRequested関数を見てください。
 実はQuitRequested関数はBAppMainWindowの実体であるmainwndがB_QUIT_REQUESTEDメッセージを受け取ると呼び出される関数です。B_QUIT_REQUESTEDはスレッドの終了を要求するメッセージです。(スレッドの説明は省きますが、mainwndも一つのスレッドです。)
 QuitRequested関数の戻り値にtrueを返すと、mainwndのスレッドが終了します。ただこれだけではアプリケーションは終了しないので、be_appのメンバ関数のPostMessage関数を使用して、be_appにもB_QUIT_REQUESTEDメッセージを送信して、アプリケーション自身も終了するように要求します。 be_appはB_QUIT_REQUESTEDメッセージを受け取ると、他にスレッドが動いていれば、そのスレッドにB_QUIT_REQUESTEDメッセージを送信して終了させ、アプリケーションを終了します。
(QuitRequested関数のように何かイベントが起こった際に呼び出される関数をフック関数と呼びます。)

 ボタンが押された際に処理をするようにするには、ボタンを押したときにメッセージを送信するようにして、そのメッセージを受け取ったらボタンに対応した処理を行うようにします。
 メッセージも独自のメッセージを作成して、フック関数にはMessageReceived関数を使用するのですが・・・。ここで下手な説明をするよりも、まずはプログラムを見て下さい。

/**** ファイル名 : BaseApp.h ****/

#ifndef BASEAPP
#define BASEAPP
//---------------------------------------------------------------------
#include <Be.h>
#include "MainWindow.h"
//---------------------------------------------------------------------
#define APPLICATION_SIGNATURE   "application/x-vnd.vow-be1stapp"
//---------------------------------------------------------------------
class BaseApp : public BApplication
{
public:
    BaseApp()
        :BApplication(APPLICATION_SIGNATURE)
    {
        BAppMainWindow *mainwnd=new BAppMainWindow(
            BRect(MAINWINDOW_POSITION_LEFT,MAINWINDOW_POSITION_TOP,
                  MAINWINDOW_POSITION_WIDTH+MAINWINDOW_POSITION_LEFT-1,
                  MAINWINDOW_POSITION_HEIGHT+MAINWINDOW_POSITION_TOP-1),
                  MAINWINDOW_TITLE);
        mainwnd->Show();
    };
};
//---------------------------------------------------------------------
#endif

/**** ファイル名 : MainWindow.h ****/

#ifndef MAINWINDOW
#define MAINWINDOW
//---------------------------------------------------------------------
#include <Be.h>
//---------------------------------------------------------------------
#define MAINWINDOW_TITLE            "Button,TextControl,StringView Application"
#define MAINWINDOW_POSITION_LEFT    100
#define MAINWINDOW_POSITION_TOP     100
#define MAINWINDOW_POSITION_WIDTH   400
#define MAINWINDOW_POSITION_HEIGHT  120
#define MAINWINDOW_WINDOWSTYLE      (B_TITLED_WINDOW)
//---------------------------------------------------------------------
#define MSG_COPY 'cpyb'
//---------------------------------------------------------------------
class BAppMainView : public BView
{
public:
    BStringView *strview;
    BTextControl *textctrl;
    BAppMainView(BRect frame);
};
//---------------------------------------------------------------------
class BAppMainWindow : public BWindow
{
public:
    BAppMainView *mainview;
    //-----------------------------------------------------------------
    BAppMainWindow(BRect frame,const char *title);
    virtual void MessageReceived(BMessage *msg);
    //-----------------------------------------------------------------
    bool QuitRequested()
    {
        be_app->PostMessage(B_QUIT_REQUESTED);
        return true;
    };
};
//---------------------------------------------------------------------
#endif

/**** ファイル名 : MainWindow.cpp ****/

//---------------------------------------------------------------------
#include "MainWindow.h"
//---------------------------------------------------------------------
BAppMainWindow::BAppMainWindow(BRect frame,const char *title)
    :BWindow(frame,title,MAINWINDOW_WINDOWSTYLE,0)
{
    mainview=new BAppMainView(Bounds());    
    AddChild(mainview);
}
//---------------------------------------------------------------------
void BAppMainWindow::MessageReceived(BMessage *msg)
{
    switch(msg->what)
    {
        case MSG_COPY:
            mainview->strview->SetText(mainview->textctrl->Text());
            break;
        default:
            BWindow::MessageReceived(msg);
    }
}
//---------------------------------------------------------------------
BAppMainView::BAppMainView(BRect frame)
    :BView(frame,"bappmainview",B_FOLLOW_ALL,B_WILL_DRAW)
{
    BRect viewrect(Bounds());
    BButton *copybtn=new BButton(BRect(viewrect.right-72,68,
                                       viewrect.right-8,88),"copybtn",
                                 "COPY!!",new BMessage(MSG_COPY),
                                 B_FOLLOW_RIGHT | B_FOLLOW_TOP);
    AddChild(copybtn);

    textctrl=new BTextControl(BRect(8,8,viewrect.right-8,28),"textctrl",
                              NULL,"Default",NULL,
                              B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP);
    AddChild(textctrl);

    strview=new BStringView(BRect(8,38,viewrect.right-8,58),"strview",
                            "NoData",
                            B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP);
    AddChild(strview);
}
//---------------------------------------------------------------------

/**** ファイル名 : main.cpp ****/

//---------------------------------------------------------------------
#include "BaseApp.h"
//---------------------------------------------------------------------
int main(void)
{
    new BaseApp();
    be_app->Run();
    delete be_app;
    return true;
}
//---------------------------------------------------------------------

 さてと、各ファイルを順を追ってみてみましょう。
 まず、BaseApp.h。ここでは、
#define APPLICATION_SIGNATURE   "application/x-vnd.vow-be1stapp"
と、APPLICATION_SIGNATUREを変更して、アプリケーションのMIMEタイプを前回までに使用していたものとは違うものに変更しています。こうしておくことで、前回までのアプリケーションと今回のアプリケーションが違うものであることを明示します。

 MainWindow.hではウインドウのタイトルの文字列も変更していますが、重要なのは次の二点です。
 一点目は、先程のプログラムでBAppMainViewクラスのコンストラクタ内で宣言していたtextctrlとstrviewを、BAppMainViewのメンバとして宣言していることです。ここで宣言しておけば、publicのメンバですから、他のクラスからtextctrlとstrviewを使用できるようになります。
 二点目は、MSG_COPYを'cpyb'と定義していることですが・・・、これはボタンを作成する部分で説明します。

 MainWindow.cppにはボタンの作成と、ボタンを押した際のメッセージの処理を追加しています。
 まずは、ボタンの作成。今回使用したBButtonクラスのコンストラクタの引数は、(BRect frame,const char *name,const char *label,BMessage *message, uint32 resizingMode,uint32 flags)と、なっており、この中で、flags以外の引数に値をセットしています。
 残りの引数で初めて出てくるのは、labelとmessageでしょう。labelにはボタンに表示する文字列を指定します。そしてmessageには、ボタンが押された際に送られるメッセージを指定します。
 メッセージはBMessageクラスのコンストラクタに32ビットの値を渡して作成します。ここで渡している値は、 MainWindow.hでdefineしているMSG_COPYです。(MSG_COPYの宣言が'cpyb'と、4文字をシングルクォーテーションで囲っていることに注意して下さい。) メッセージの識別にも、このMSG_COPYを使用します。
 あとは作成した実体をAddChildすれば、ボタンが追加されます。

 次にMSG_COPYメッセージの受信です。
 B_QUIT_REQUESTEDメッセージには専用のフック関数「QuitRequested」がありますが、 MSG_COPYメッセージは独自に作成したメッセージですから、専用のフック関数がありません。 その場合には、汎用的にメッセージ処理に使用するMessageReceived関数を使用します。
 MessageReceived関数は、引数「BMessage *msg」を受け取ります。このmsgが受信したメッセージです。
 受信したメッセージを識別するには、BMessageのwhat変数の内容を調べます。 もしwhat変数がMSG_COPYであれば、ボタンを押した際の処理を行ってやればよいのです。
 ここで注意しなければならない点は、独自に処理を行うメッセージ以外のメッセージを受信したときです。その場合は、クラスの継承元(今回はBWindow)のMessageReceived関数に、msgを渡してそちらで処理してもらうようにします。

 最後に入力欄に入力されている文字列を表示欄に出力する方法です。
 ここで必要なのは入力欄(BTextControl)から文字列を取得する方法と、表示欄(BStringView)に文字列を設定する方法です。 Be Bookを見れば察しがつくとは思いますが、BTextControlにも、BStringViewにもメンバ関数に、Text関数と、SetText関数という、それらしい関数があります。(実際この関数で正しいのですが)
 あとは、入力欄の文字列をText関数で取得して、出力欄にSetText関数で設定してやれば良いだけです。

 さて、今回はこれでおしまいです。BMessageクラスとMessageReceived関数はこれからも度々使用する事になります。この使い方がわからない限り、BeOSでプログラムを組むのは困難でしょうから、しっかり覚えておいてください。

ソースリスト
圧縮ファイル
R5 Intel環境で確認
Be1stApp19991201.zip
ソースファイル
BaseApp.h
main.cpp
MainWindow.cpp
MainWindow.h

次の項目へ

トップページへ戻る