Lesson 10:ファイルに保存する(1)


 前回はファイルを読み込む処理を作成しましたので、次に作るのは当然、ファイルに保存する処理です。ファイル保存を行う処理を今回と次回の二回に分けてやってみたいと思います。
 ファイルを読み込む処理にFileメニューのOpenを使用しました。保存にはSaveとSaveAsを使用するようにします。SaveとSaveAsの違いは、Saveは名前がついていない場合だけファイル選択画面を表示するのに対し、SaveAsでは常にファイル選択画面を表示してファイル名の入力を促す点です。今回は簡単なSaveAsのみ作成し、Saveについては次回作成します。

 また、BeOS5 Personal Editionが入手できましたので、今回からはR5を使用しての開発となります。

 まずはファイルを保存するにはどのような方法を用いなければならないかを調べなければなりません。ファイル読み込みに使用したものとして、BFilePanel、BEntry、BFile等があります。保存する場合もほぼ同じものが使用できるであろう事は推測できますので、そこを中心に調べてみましょう。
 まずBTextViewの内容をBFileで出力するにはBFileのWriteAt関数にBTextViewのText関数の内容を渡してやれば良いでしょう。
 BFileにファイル名をセットするにはBEntryに予めファイル名をセットしておいて、それをBFileのSetTo関数に渡してやればできそうです。
 BEntryにファイル名をセットするにはBFilePanelのメッセージからファイル名を求めてやれば良いわけです。
 こうして見てみるとファイルを開く処理とそれほど大きな違いはないように思えます。あとは違っている部分に注意しながら使用する技術を調べていきましょう。

 まず、ファイル選択画面ですが、ファイルを開く際にはファイル選択のみで良かったのですが、ファイルを保存するとなると、ファイル名が入力できるようになっていなければなりません。このような選択画面もBFilePanelには用意されていて、BFilePanelのコンストラクタの引数にB_SAVE_PANELを渡してやれば良いだけです。
 このようにしてやると、画面を表示した際にファイル名の入力欄を持つようになります。

 B_SAVE_PANELとすることで見栄えに変化がありますが、実はそれ以上に大きな違いが生まれています。ファイルを開くために使用した際には、ファイルの選択内容はB_REFS_RECEIVEDメッセージにentry_refのかたちで通知され、それをRefsReceived関数で処理します。ところがB_SAVE_PANELとしている場合は、B_SAVE_REQUESTEDメッセージにディレクトリはentry_refのかたちで、ファイル名は文字列として通知されます。このため、 B_SAVE_PANELとして使用する場合はRefsReceived関数ではなく、MessageReceived関数で処理する必要があります。

 まずは、B_SAVE_PANELメッセージを受け取るところまでを作ってみましょう。

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

#ifndef MAINWINDOW 
#define MAINWINDOW 
//--------------------------------------------------------------------- 
#include <Be.h> 
#include "BMemoView.h" 
//--------------------------------------------------------------------- 
#define MAINWINDOW_TITLE            "BTinyEditor" 
#define MAINWINDOW_POSITION_LEFT    100 
#define MAINWINDOW_POSITION_TOP     100 
#define MAINWINDOW_POSITION_WIDTH   600 
#define MAINWINDOW_POSITION_HEIGHT  400 
#define MAINWINDOW_WINDOWSTYLE      (B_DOCUMENT_WINDOW) 
//--------------------------------------------------------------------- 
#define MSG_OPEN    'mopn' 
#define MSG_SAVE    'msav' 
#define MSG_SAVEAS  'msva' 
//--------------------------------------------------------------------- 
class BEditorView : public BView 
{ 
public: 
    BMemoView *memo; 
    BEditorView(BRect frame); 
}; 
//--------------------------------------------------------------------- 
class BEditorWindow : public BWindow 
{ 
private:
    BFilePanel *save_filepanel;
public: 
    BEditorView *mainview; 
    //----------------------------------------------------------------- 
    BEditorWindow(BRect frame,const char *title); 
    ~BEditorWindow();
    virtual void MessageReceived(BMessage *msg); 
    void OpenFile(BEntry *entry);
    //----------------------------------------------------------------- 
    bool QuitRequested() 
    { 
        be_app->PostMessage(B_QUIT_REQUESTED); 
        return true; 
    }; 
}; 
//--------------------------------------------------------------------- 
#endif 

 privateメンバとしてsave_filepanelを追加している点、デストラクタを定義している点が前回との違いです。

 MainWindow.cppは変更・追加した関数のみを記述しておきます。

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

//--------------------------------------------------------------------- 
BEditorWindow::BEditorWindow(BRect frame,const char *title) 
    :BWindow(frame,title,MAINWINDOW_WINDOWSTYLE,0) 
{ 
    mainview=new BEditorView(Bounds());    
    AddChild(mainview);
    save_filepanel=new BFilePanel(B_SAVE_PANEL);
} 
//--------------------------------------------------------------------- 
BEditorWindow::~BEditorWindow()
{
    delete save_filepanel;
} 
//--------------------------------------------------------------------- 
void BEditorWindow::MessageReceived(BMessage *msg) 
{ 
    switch(msg->what) 
    { 
        case B_UNDO: 
        case B_COPY: 
        case B_CUT: 
        case B_PASTE: 
        case B_SELECT_ALL: 
            ((BMemoView *)FindView("memo"))->MessageReceived(msg); 
            break; 
        case MSG_SAVE: 
            break; 
        case MSG_SAVEAS:
            save_filepanel->SetTarget(this);
            save_filepanel->Show(); 
            break;
        case B_SAVE_REQUESTED:
            BAlert *altmsg = new BAlert("Message", 
                                        "B_SAVE_REQUESTED","OK"); 
            altmsg->Go();
            break;        
        default: 
            BWindow::MessageReceived(msg); 
    } 
}
//--------------------------------------------------------------------- 

 コンストラクタでsave_filepanelを作成し、デストラクタで削除しています。
 MessageReceived関数には二ヶ所の変更点があります。一つはメニューのSaveAsを選ばれた際に送られてくるMSG_SAVEASメッセージの処理で、ここではファイル選択画面の表示を行っています。
 もう一点は、ファイル選択画面から送られてくるB_SAVE_REQUESTEDメッセージの処理ですが、この中で見慣れないクラス「BAlert」を使用しています。このクラスは単独のメッセージをもつウィンドウ(メッセージボックス)を表示するためのクラスです。実際に動かしてB_SAVE_REQUESTEDメッセージを受け取っても、その際に画面に変化が無いのでは本当に受け取る事ができたかを確認する事ができないので、BAlertでメッセージを表示するようにしました。
 あと、注意しなければならないのは、save_filepanelのメッセージ送信先をSetTargetで自分自身(this)としている事です。これはBFilePanelのデフォルトのメッセージ送信先がbe_app_messengerとなっているためです。(これを読み落としていたために、はじめはSetTargetを使用していませんでした。そのため、うまくメッセージを受け取る事ができず、少し悩んでしまいました。)

 メッセージを受け取る事ができたら、いよいよファイルの保存を行いましょう。まずは、やり方のはっきりしているファイル保存部分を独立した関数(SaveFile)として定義します。

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

//--------------------------------------------------------------------- 
void BEditorWindow::SaveFile(BEntry *entry)
{
    BFile fil;
    off_t wsize;
    off_t tsize;
    bool errflg;

    try
    {
        fil.SetTo(entry,B_READ_WRITE | B_CREATE_FILE);
        tsize=strlen(((BTextView *)FindView("memo"))->Text());
        wsize=fil.WriteAt(0,((BTextView *)FindView("memo"))->Text(),
                          tsize);
        
        errflg=(tsize!=wsize);
    }
    catch(...)
    {
        errflg=true;
    }

    if(errflg)
    {
        BAlert *altmsg = new BAlert("Error", "File write failed!!","OK",
                                     NULL,NULL,B_WIDTH_AS_USUAL,
                                     B_WARNING_ALERT); 
        altmsg->Go();
    }
}
//--------------------------------------------------------------------- 

 関数の引数はOpenFileと同じようにBEntryとして、保存先のファイルを指定するようにしています。
 filにSetTo関数でファイル名と、B_READ_WRITE | B_CREATE_FILEとして、リード・ライトのアクセス権とファイルを作成するようにセットしておきます。
 あとは保存対象のBMemoViewの文字長を取得し、WriteAt関数でBMemoViewの内容をファイルに書き込んでいます。
 関数の最後でerrflgの内容をみて、trueならばBAlertでエラーメッセージを出力しています。ファイル書きこみ部分をtryで囲んでいますので、この中で例外(エラー)が発生した場合は、その下のcatchで囲んだ部分が処理されます。また、書きこみ結果のサイズをBMemoViewの文字長と比較して正常に書きこめたかの確認もしています。こうすることで、ファイルの書き込みに失敗した場合はerrflgをtrueになるようにしています。

 同じように、前回作成したOpenFile関数にもエラーメッセージを表示する処理を組み込みましょう。

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

//--------------------------------------------------------------------- 
void BEditorWindow::OpenFile(BEntry *entry)
{
    if(entry->Exists())
    {
        BFile fil;
        off_t size;
        
        try
        {
            fil.SetTo(entry,B_READ_ONLY);
            fil.GetSize(&size);
            ((BTextView *)FindView("memo"))->SetText(&fil,0,size);
        }
        catch(...)
        {
            BAlert *altmsg = new BAlert("Error", 
                                        "File read failed!!","OK",
                                        NULL,NULL,B_WIDTH_AS_USUAL,
                                        B_WARNING_ALERT); 
            altmsg->Go();
        }
    }
}
//--------------------------------------------------------------------- 

 さて、本題のファイル保存ですが、SaveFile関数ができましたので、次はB_SAVE_REQUESTEDメッセージからディレクトリとファイル名を取得してSaveFile関数を呼び出す部分を作らなければなりません。
 ファイルを読み込む際には"refs"の名称の情報をメッセージから取得し、それをBEntryにセットしました。今回はB_SAVE_REQUESTEDメッセージからディレクトリとファイル名を取得してBEntryにセットしなければなりません。
 まずはディレクトリとファイル名を取得します。ディレクトリは"directory"の名称でentry_ref型の情報です。ファイル名は"name"の名称で、型はB_STRING_TYPEとなっています。B_STRING_TYPEの説明を読むと、終端がNULLの文字列のようです。それぞれFindRef関数、FindString関数で取得できます。
 BEntryのSetTo関数を調べてみると、ディレクトリとファイル名を合成して渡すには、ディレクトリはBDirectoryクラスにセットしておく必要があるようですので、BDirectoryクラスを用意して、SetTo関数を使って ディレクトリの情報をentry_refからセットしておきます。あとはディレクトリとファイル名をSetToで合成してBEntryに渡し、それを用いてSaveFileを呼び出します。

 MessageReceived関数のB_SAVE_REQUESTEDの処理の部分のみを記述しておきます。

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

        case B_SAVE_REQUESTED:
            {
                entry_ref ref;
                BString nam;
                BDirectory dir;
                BEntry entry;

                msg->FindRef("directory",&ref);
                msg->FindString("name",&nam);
                dir.SetTo(&ref);

                if(entry.SetTo(&dir,nam.String())==B_OK)
                {
                    SaveFile(&entry);
                    return;
                }
            }
            break;        

 ファイル名を取得する際にchar型の配列ではなくBStringクラスを使用していますが、これは折角だから使った事の無いクラスを使ってみたかっただけで、深い意味はありません。今回のような使い方では、せいぜい配列の大きさを考慮する必要が無いといった程度のメリットでしょうか。

 これで、ファイルの読み込みと、ファイル名を入力してのファイルの保存ができるようになりました。

 次回でファイル保存は一応の終了となる予定です。

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

次の項目へ

トップページへ戻る