さて、RTWEditorのUser.hとRTWEditorProc.hに行く前に、ワイド文字版REDIT.h等の今回アップデートした部分を解説します。

 

1.CTAB-指定ページのタイトルを取得するGetTitle(int)を追加
こちらは大した変更はなく、従来カレントページの未処理していた関数に引数を与えて非カレントページにも処理ができるようにしました。

 

//カレントページを設定する
bool CTAB::SetPage(int cur) {

    int ret = SendMessage(m_hWnd, TCM_SETCURSEL, cur, 0);
    if(ret == -1)
        return FALSE;
    else
        return TRUE;
}


//指定ページのタイトルを取得する
WCHAR* CTAB::GetTitle(int item) {

    if(item >= TabCount())
        return 0;
    static WCHAR Buff[MAX_PATH];    //ラベル文字列収容バッファ
    TCITEMW tcItem;                    //タブコントロール情報構造体
    tcItem.mask = TCIF_TEXT;
    tcItem.cchTextMax = MAX_PATH;
    tcItem.pszText = Buff;
    TabCtrl_GetItem(m_hWnd, item, &tcItem);
    return Buff;
}

 

2.CREDIT-文字列の検索、置換に関わるコードをRichEdit20に合わせて見直しを実施

それに比べるとCREDITクラスは「20年前のRICHED32.DLLから現在のRICHED20.DLLへ移行することによる見直し(注)」といえます。

注:RICHED32.DLLのVer 1.0とRICHED20.DLLのVer 2.0および3.0では、大きな仕様の相違があります。

 

(1)所謂ワイド文字対応

   変数がchar→WCHAR、定数が""→L""、関数が(関数名)(引数)→(関数名)W(引数)等の修正を行っています。

 

(2)コンストラクターのDLL読み込み

    当然以下のような変更を行っています。

    //リッチエディットコントロールDLLの読込み
    m_hRtLib = LoadLibraryW(L"RICHED20.DLL");
 

(3)ファイル入出力

    ワイド文字対応のリッチテキストコントロールは、

    ①リッチテキスト→ANSIとBOM無UTF-8対応

    ②テキスト→ANSI、BOM無UTF-8、BOM無UTF-16対応

になっています。

【RTWEditor】ECCSkeltonのCREDITクラスのファイル入出力関数と一応の完成

↑でも書きましたが、「BOM無UTF-16」は鬼っ子なのでBOMを付けることにして、次のようにしています。(なお、コールバック関数は変更ありません。)

 

//--------------------------------------------------------------------------
// リッチエディットコントロール用テキスト、リッチエディットファイル書込関数
// ファイル名 pszFileName のファイルが RichEdit Controlへ書きこまれる
//--------------------------------------------------------------------------
bool CREDIT::SaveFile(LPWSTR pszFileName) {

    EDITSTREAM eds;        //EDITSTREAM構造体
    bool nFileType;        //rtfかtxtかの判別フラグ
    if(StrStrW(pszFileName, L".rtf"))        nFileType = TRUE;    //rtfファイルとして
    else if(StrStrW(pszFileName, L".RTF"))    nFileType = TRUE;    //rtfファイルとして
    else                                    nFileType = FALSE;    //textファイルとして
    HANDLE hFile = CreateFile(pszFileName, GENERIC_WRITE, 0,
                        NULL, CREATE_ALWAYS,
                        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        MessageBoxW(m_hWnd, L"ファイルが開けませんでした", L"エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    eds.dwCookie = (DWORD)hFile;
    eds.dwError = 0;
    eds.pfnCallback = SaveRichEditProc;
    if(nFileType) {
        int ret = MessageBoxW(m_hWnd, L"UFT-8 RTFで保存しますか\n(「いいえ」はANSI RTF)", L"書き込み確認", MB_YESNO | MB_ICONQUESTION);
        if(ret == IDYES)
            SendMessageW(m_hWnd, EM_STREAMOUT, (WPARAM)(CP_UTF8 << 16) | SF_USECODEPAGE | SF_RTF, (LPARAM)&eds);
        else
            SendMessageW(m_hWnd, EM_STREAMOUT, (WPARAM)SF_RTF, (LPARAM)&eds);
    }
    else {
        int ret = MessageBoxW(m_hWnd, L"BOM付UFT-16で保存しますか\nBOM無UFT-8で保存しますか(「いいえ」)\n(「キャンセル」はANSI)", L"書き込み確認", MB_YESNOCANCEL | MB_ICONQUESTION);
        if(ret == IDYES) {
            //BOMの書き込み - WindowsはIntel系でUTF-16 FF FE(リトルエンディアン)
            BYTE BOM[2] = {0xFF, 0xFE};
            DWORD dwWritten;
            WriteFile(hFile, BOM, 2, &dwWritten, NULL);
            SendMessageW(m_hWnd, EM_STREAMOUT, (WPARAM)SF_TEXT | SF_UNICODE, (LPARAM)&eds);
        }
        else if(ret == IDNO)
            SendMessageW(m_hWnd, EM_STREAMOUT, (WPARAM)(CP_UTF8 << 16) | SF_USECODEPAGE | SF_TEXT, (LPARAM)&eds);
        else
            SendMessageW(m_hWnd, EM_STREAMOUT, (WPARAM)SF_TEXT, (LPARAM)&eds);
    }
    CloseHandle(hFile);
    SendMessageW(m_hWnd, EM_SETMODIFY, (WPARAM)FALSE, 0);
    SetFocus(m_hWnd);
    return TRUE;
}

//--------------------------------------------------------------------------
// リッチエディットコントロール用テキスト、リッチエディットファイル読込関数
// ファイル名 pszFileName のファイルが RichEdit Controlへ読みこまれる
//--------------------------------------------------------------------------
bool CREDIT::LoadFile(LPWSTR pszFileName) {

    EDITSTREAM eds;        //EDITSTREAM構造体
    bool nFileType;        //rtfかtxtかの判別フラグ
    if(StrStrW(pszFileName, L".rtf"))        nFileType = TRUE;    //rtfファイルとして
    else if(StrStrW(pszFileName, L".RTF"))    nFileType = TRUE;    //rtfファイルとして
    else                                    nFileType = FALSE;    //textファイルとして
    HANDLE hFile = CreateFile(pszFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING,
                        FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        MessageBoxW(m_hWnd, L"ファイルが開けませんでした", L"エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    eds.dwCookie = (DWORD)hFile;
    eds.dwError = 0;
    eds.pfnCallback = ReadRichEditProc;
    if(nFileType) {
        int ret = MessageBoxW(m_hWnd, L"UFT-8 RTFで読み込みますか\n(「いいえ」はANSI RTF)", L"読み込み確認", MB_YESNO | MB_ICONQUESTION);
        if(ret == IDYES)
            SendMessageW(m_hWnd, EM_STREAMIN, (WPARAM)(CP_UTF8 << 16) | SF_USECODEPAGE | SF_RTF, (LPARAM)&eds);
        else
            SendMessageW(m_hWnd, EM_STREAMIN, (WPARAM)SF_RTF, (LPARAM)&eds);
    }
    else {
        int ret = MessageBoxW(m_hWnd, L"BOM付UFT-16で読み込みますか\nBOM無UFT-8にしますか(「いいえ」)\n(「キャンセル」はANSI)", L"読み込み確認", MB_YESNOCANCEL | MB_ICONQUESTION);
        if(ret == IDYES) {
            //BOMの読み込み - WindowsはIntel系でUTF-16 FF FE(リトルエンディアン)
            BYTE BOM[2];
            DWORD dwRead;
            ReadFile(hFile, BOM, 2, &dwRead, NULL);
            if(BOM[0] != 0xFF || BOM[1] != 0xFE) {
                MessageBoxW(m_hWnd, L"BOM付UFT-16ファィルではありません", L"エラー", MB_OK | MB_ICONERROR);
                CloseHandle(hFile);
                return FALSE;
            }
            SendMessageW(m_hWnd, EM_STREAMIN, (WPARAM)SF_TEXT | SF_UNICODE, (LPARAM)&eds);
        }
        else if(ret == IDNO)
            SendMessageW(m_hWnd, EM_STREAMIN, (WPARAM)(CP_UTF8 << 16) | SF_USECODEPAGE | SF_TEXT, (LPARAM)&eds);
        else
            SendMessageW(m_hWnd, EM_STREAMIN, (WPARAM)SF_TEXT, (LPARAM)&eds);
    }
    CloseHandle(hFile);
    SendMessageW(m_hWnd, EM_SETMODIFY, (WPARAM)FALSE, 0);
    SetFocus(m_hWnd);
    return TRUE;
}


3.文字列の検索・置換ダイアログ処理

コモンダイアログに文字列の検索ダイアログと文字列の置換ダイアログがあり、それぞれFINDREPLACE(W)構造体を使って、FindText関数とReplaceText関数で呼び出します。(注)

注:引数を設定する関数としてCREDITのSetFRDlg関数を使います。

//--------------------------------------
// 検索・置換用FINDREPLACE構造体の初期化
//--------------------------------------
FINDREPLACEW* CREDIT::SetFRDlg(WCHAR sign) {

    DWORD flag;
    switch(sign) {
    case L'F':
    case L'f':
        flag = FR_DOWN | FR_FINDNEXT;
        break;
    case L'R':
    case L'r':
        flag = FR_DOWN | FR_REPLACE;
        break;
    default :
        return 0;
    }

    static FINDREPLACEW fr;
    ZeroMemory(&fr, sizeof(FINDREPLACEW));
    fr.lStructSize = sizeof(FINDREPLACEW);
    fr.hwndOwner = m_hParent;
    fr.hInstance = NULL;
    fr.Flags = flag;
    fr.lpstrFindWhat = m_FindWhat;
    fr.lpstrReplaceWith = m_ReplaceWith;
    fr.wFindWhatLen = MAX_PATH;
    fr.wReplaceWithLen = MAX_PATH;
    fr.lCustData = NULL;
    fr.lpfnHook = NULL;
    fr.lpTemplateName = NULL;
    return &fr;
}

 

しかし、このダイアログはユーザーの選択した「次を検索(FR_FINDNEXT)」、「単語単位で探す(FR_WHOLEWORD)」、「大文字と小文字を区別する(FR_MATCHCASE)」、「上へ(FR_DOWN)」、「下へ(なし)」、「置換して次へ(FR_REPLACE)」、「全て置換(FR_REPLACEALL)」というメッセージをダイアログメッセージとして送るだけしかしません。

ということで、先ずはダイアログメッセージを送れるように、そのメッセージをOSに登録し、登録したメッセージをCREDITのメンバー変数m_frMsgに記録します。

 

    //検索・置換メーッセージの登録
    m_frMsg = RegisterWindowMessageW(FINDMSGSTRINGW);

 

次にダイアログメッセージをキャッチする為に、メインウィンドウのm_hDlg変数に検索ダイアログや文字列の置換ダイアログのモードレスダイアログのハンドルを設定します。(序にCREDITクラスのm_hFRDlgウィンドウハンドルにも設定しておきます。このモードレスダイアログハンドルは、メインウィンドウ(CMyWnd)のLoop関数内のIsDialogMessage関数で仕訳けられます。)

 

bool CMyWnd::OnFind() {

    m_hDlg = m_Edit.m_hFRDlg = FindTextW(m_Edit.SetFRDlg(L'F'));
    return TRUE;
}

bool CMyWnd::OnReplace() {

    m_hDlg = m_Edit.m_hFRDlg = ReplaceTextW(m_Edit.SetFRDlg(L'R'));
    return TRUE;
}

 

で、キャッチするにはOnOthers関数を使って、記録したm_frMsgを捕まえてCREDITクラスのFindReplace関数を呼び出します。

 

bool CMyWnd::OnOthers(UINT Msg, WPARAM wParam, LPARAM lParam) {

    //CREDITクラスは文字列検索・置換用にユーザーメッセージを作るので、
    //このような処理を行います。CREDITファイルを覗いてください。
    if(Msg == m_Edit.m_frMsg)
        m_Edit.FindReplace(lParam);    //検索・置換処理
}


FindReplace関数は、その下請けのWasFound関数を使って次のような処理をします。(注)

注:前に書いたように、BCCSkelton時代のリッチテキスト1.0ではFR_DOWNが何の意味も無かったのですが、リッチテキスト2.0からきちんと下方向検索、上方向検索を行えるようになりました。

 

//----------------
// 検索・置換関数
//----------------
bool CREDIT::WasFound(FINDTEXTW* ft, UINT flag) {

    return ((ft->chrg.cpMin = SendMessageW(m_hWnd, EM_FINDTEXTW,
                                flag, (LPARAM)ft)) != NOMORE);
}

WasFound
関数はFINDTEXT構造体ポインターの指すchrg.cpMinというキャラクターポインターに見つかった検索文字列の下位アドレスを代入して、「まだ検索できる(TRUE)」または「もうないよ(FALSE)」を返します。なお、「もうないよ」の場合、chrg.cpMinには-1(NOMORE)が代入されています。

//------------------------------
// 検索・置換ダイアログ処理関数
//------------------------------
void CREDIT::FindReplace(LPARAM lParam) {

    static int fcount = 0;    //検索回数のカウンター
    static int rcount = 0;    //置換回数のカウンター
    static int fc, rc;        //表示用のカウンター(上記が0に初期化されるので)
    static FINDTEXTW ft;    //FINDTEXTW構造体-メンバーにchrgとlpstrTextを持つ
    WCHAR FRMsg[MAX_PATH];    //出力メッセージ用文字配列
    LPFINDREPLACEW fr = (LPFINDREPLACEW)lParam;    //構造体FINDREPLACEWへのポインター
    //検索範囲の設定
    if(!fcount) {                                //初回だけ実行
        SendMessageW(m_hWnd, EM_EXGETSEL, 0,
                    (LPARAM)&ft.chrg);            //選択範囲を取得
        if(fr->Flags & FR_DOWN) {                //下方検索なら
            ft.chrg.cpMin = ft.chrg.cpMax;        //選択範囲の末尾(ft.chrg.cpMax)から
            ft.chrg.cpMax = -1;                    //文末までを検索
        }
        else                                    //上方検索なら選択範囲の先頭(ft.chrg.cpMin)から
            ft.chrg.cpMax = 0;                    //文頭迄(cpMax < Minでなければならない)を検索
    }
    //検索文字列の設定
    ft.lpstrText = m_FindWhat;
    //検索・置換処理
    if(fr->Flags & FR_DIALOGTERM) {        //検索・置換ダイアログが終了した場合
        fc = fcount;    rc = rcount;    //表示用に記録する
        m_hFRDlg = 0;                    //ハンドルを初期化
        fcount = 0;                        //検索カウンターを初期化
        rcount = 0;                        //置換カウンターを初期化
        SetFocus(m_hWnd);                //リッチエディットコントロールにフォーカスを当てる
    }
    else if(fr->Flags & FR_FINDNEXT) {    //「次を検索」の場合
        if(WasFound(&ft, (fr->Flags & FR_DOWN) | (fr->Flags & FR_MATCHCASE) | 
                    (fr->Flags & FR_WHOLEWORD))) {                //見つかった場合
            fcount++;                                            //回数を記録
            SendMessageW(m_hWnd, EM_SETSEL, ft.chrg.cpMin,
                        ft.chrg.cpMin + lstrlenW(m_FindWhat));    //検索文字列を選択
            SendMessageW(m_hWnd, EM_SCROLLCARET, 0, 0);            //キャレットの位置を表示する
            SetFocus(m_hWnd);            //リッチエディットコントロールにフォーカスを当てる
            if(fr->Flags & FR_DOWN)        //下方向検索の場合
                ft.chrg.cpMin += lstrlenW(m_FindWhat);            //次の検索開始位置に進める
        }
        else {                                                    //もう見つからない場合
            SendMessageW(m_hFRDlg, WM_CLOSE, 0, 0);                //検索・置換ダイアログを閉じる
            wsprintfW(FRMsg, L"検索文字列は %d回見つかりました", fc);
            MessageBoxW(m_hParent, FRMsg, L"メッセージ", MB_OK | MB_ICONINFORMATION);
        }
    }
    else if(fr->Flags & FR_REPLACE) {    //「置換して次に」の場合
        if(WasFound(&ft, (fr->Flags & FR_DOWN) | (fr->Flags & FR_MATCHCASE) | 
                    (fr->Flags & FR_WHOLEWORD))) {                //見つかった場合
            SendMessageW(m_hWnd, EM_SETSEL, ft.chrg.cpMin, ft.chrg.cpMin + lstrlenW(m_FindWhat));
            SendMessageW(m_hWnd, EM_SCROLLCARET, 0, 0);            //キャレット位置を表示する
            //メッセージボックスで確認して検索文字列を置換文字列に置換する
            if(MessageBoxW(m_hParent, L"置換しますか?", L"確  認",
                            MB_YESNO | MB_ICONQUESTION) == IDYES) {
                SendMessageW(m_hWnd, EM_REPLACESEL, TRUE, (LPARAM)m_ReplaceWith);
                rcount++;                                        //置換回数を記録
            }
            fcount++;                                            //回数を記録
            SendMessageW(m_hWnd, EM_SETSEL, ft.chrg.cpMin, ft.chrg.cpMin + lstrlenW(m_ReplaceWith));
            SendMessageW(m_hWnd, EM_SCROLLCARET, 0, 0);            //キャレット位置を表示する
            SetFocus(m_hWnd);            //リッチエディットコントロールにフォーカスを当てる
            if(fr->Flags & FR_DOWN)        //下方向検索の場合
                ft.chrg.cpMin += lstrlenW(m_ReplaceWith);        //次の検索開始位置に進める
        }
        else {                                                    //もう見つからない場合
            SendMessageW(m_hFRDlg, WM_CLOSE, 0, 0);                //検索・置換ダイアログを閉じる
            wsprintfW(FRMsg, L"検索文字列を %d検索し、%d回置換しました", fc, rc);
            MessageBoxW(m_hParent, FRMsg, L"メッセージ", MB_OK | MB_ICONINFORMATION);
        }
    }
    else if(fr->Flags & FR_REPLACEALL) {//「すべて置換」の場合
        ft.chrg.cpMin = 0;        //検索開始位置を文頭とし、
        ft.chrg.cpMax = -1;        //検索終了位置を文末とする
        while(WasFound(&ft, (fr->Flags & FR_DOWN) | (fr->Flags & FR_MATCHCASE) |
                    (fr->Flags & FR_WHOLEWORD))) {
            fcount++;                                            //回数を記録
            SendMessageW(m_hWnd, EM_SETSEL, ft.chrg.cpMin, ft.chrg.cpMin + lstrlen(m_FindWhat));
            SendMessageW(m_hWnd, EM_SCROLLCARET, 0, 0);            //キャレット位置を表示する
            SendMessageW(m_hWnd, EM_REPLACESEL, TRUE, (LPARAM)m_ReplaceWith);
            rcount++;                                            //置換回数を記録
            if(fr->Flags & FR_DOWN)        //下方向検索の場合
                ft.chrg.cpMin += lstrlenW(m_ReplaceWith);        //次の検索開始位置に進める
        }
        SendMessageW(m_hFRDlg, WM_CLOSE, 0, 0);                    //検索・置換ダイアログを閉じる
        wsprintfW(FRMsg, L"検索文字列を %d回置換しました", rc);
        MessageBoxW(m_hParent, FRMsg, L"メッセージ", MB_OK | MB_ICONINFORMATION);
    }
    SetFocus(m_hWnd);        //リッチエディットコントロールにフォーカスを当てる
}

 

FindReplace関数は可也コメントを書いているので何をやっているのかよく分かると思います。BCCSkeltonでは、リッチテキスト1.0がFR_DOWNがあっても無くても下方検索しかしないので、独自に「下方検索方法で上方検索を行う」ようにしていましたが(注)、きちんと動くようになったのでコードもスッキリしました。

注:その為に移植した直後、検索・置換動作がおかしくなりましたが...汗笑;

 

将に20年の埃を払って大掃除をした気分でした。