前回、

【RTWEditor】CREDITクラス「文字列の検索、置換」で躓く

と書きましたが、本日自己解決し、ECCSkeltonのCREDITを書き直すとともに、BCCSkeltonのCREDITも書き直しましたので告知いたします。また、

何故なんだ?

の理由は次のように言えると思います。

 

(1)BCCSkeltonのCREDITクラスは、RichEdit 1.0("RICHED32.DLL")ベースで書いていましたが、新しいバージョンのリッチエディット(ECCSkeltonもBCCSkeltonもRichEdit 2.0と3.0(共に"RICHED20.DLL")とは「EM_FINDTEXT」の仕様が異なり、20年前のコードでは動作が意図通りにはなりませんでした。(注)

注:
FR_DOWNについて

Microsoft RichEdit 2.0以降: (FR_DOWNが)設定されている場合、検索は現在の選択範囲の末尾からドキュメントの末尾までです。設定されていない場合、検索は現在の選択範囲の末尾から文書の先頭までです。

Microsoft RichEdit 1.0: FR_DOWN フラグは無視されます。 検索は、常に現在の選択範囲の末尾からドキュメントの末尾までです。」(Microsoft Doc)
EM_FINDTEXTでは、WPARAMに検索オプションのフラグを与えますが、検索範囲はLPARAMにFINDTEXT構造体のポインターを入れてメンバーのchrg(chrg.cpMinとchrg.cpMaxで構成されるCHARRANGE構造体)で指定します。しかし、↑の文章では何か現在のリッチエディットでの選択範囲と連動しているような印象を与えませんでしょうか?これの本来の意図はリッチエディットコントロールで文字列が選択されている場合(chrg.cpMin < chrg.cpMax、選択されていない場合chrg.cpMin == chrg.cpMax)でも、検索範囲は選択範囲を外してFINDTEXT構造体を指定しなさいよ、ということかと。また、この後に、
FINDTEXT.chrgのcpMinメンバーは常に検索の開始点を指定し、cpMax は終点を指定します。逆方向に検索する場合cpMinはcpMax以上である必要があります。前方検索の場合、cpMaxの値-1は、検索範囲をテキストの末尾まで拡張します」(Microsoft Doc)

とあり、ここの記述は、RichEdit 1.0では常に「前方検索」、即ち下方(「下へ」)検索だったので該当せず、BCCSkeltonのコードを見ていただけると分かるように下方検索アルゴリズムで上方検索(「上へ」)を実装していたので混乱しました。またRichEdit 2.0以降は、「後方検索」、即ち「上方検索」の場合は、(下方検索ではcpMaxを-1して文末を指定するように)cpMaxを0として文頭を指定しなければなりません。その結果、検索中は常に「cpMax < cpMin(下方検索では「cpMax > cpMin」なので、両者の関係が逆)」の関係が続くことになります。これも「体感的に理解しがたく」、末尾にあるテストコードを書いて実験して確かめました。

 

(2)仕様の相違をよく読んでいなかった私も悪いのですが、Microsoft Docも英文の自動翻訳で日本語訳が誤解を招くものであったことも遠因となっています。上でも指摘していますが、ダイアログの表記の「上下」と検索の「前後」、それも「後」は「逆方向」などと表記の統一性が乏しく感じられます。

 

いずれにせよ、EM_FINDTEXTのフラグにFR_DOWNを与えていなかったBCCSkeltonのCREDITクラスのコードでは不味なので、すべて見直し、新しく(コメントをいっぱいつけて)書き直しました。

 

ポイントは、

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

で「検索・置換ダイアログ」から送られてくるメッセージに、以下の3つのカテゴリーの情報が入っているということです。

(1)(「キャンセル」ボタンやシステムボタンで)ダイアログを消すフラグ

   FR_DIALOGTERM(0x00000040)

(2)押されたプッシュボタンのフラグ

   FR_FINDNEXT  (0x00000008)-「次を検索」
   FR_REPLACE   (0x00000010)-「置換して次へ」
   FR_REPLACEALL(0x00000020)-「全て置換」
(3)検索オプションフラグ(他は言語系が異なるのでCREDITクラスでは無視しています)

   FR_DOWN      (0x00000001)
   FR_MATCHCASE (0x00000004)
   FR_WHOLEWORD (0x00000002)
   注:この検索オプションフラグをEM_FINDTEXTの検索フラグへ渡してやる必要があります。

書き直した結果、前よりも大分コード量が減少したのはありがたかったです。もうBCCSkeltonでもRICHED32のVer 1.0を使うことはないと思い、CREDITクラスはECCSkeltonから逆移植しました。

これで後はファイル入出力だけに専念できそうです。

 

【ご参考-EM_FINDTEXTのテストコード】

/////////////////////
//EM_FINDTEXTWの実験
/////////////////////

FINDTEXTW ft;
ft.chrg.cpMin = 0;
ft.chrg.cpMax = -1;                // 検索範囲
ft.lpstrText = L"agreement";    //検索文字列
int count = 0;
WCHAR buff[256];

MessageBoxW(m_hWnd, L"下方検索", L"EM_FINDTEXTWテスト", MB_OK);
while((ft.chrg.cpMin = SendMessageW(m_Edit.m_hWnd, EM_FINDTEXTW, FR_DOWN, (LPARAM)&ft)) != NOMORE) {    //FR_MATCHCASEとFR_WHOLEWORDも なお、FR_DOWNは1です。
    count++;
    SendMessageW(m_Edit.m_hWnd, EM_SCROLLCARET, 0, 0);    //キャレット位置を表示する
    wsprintfW(buff, L"%dで%d回目が見つかりました", ft.chrg.cpMin, count);
    MessageBoxW(m_hWnd, buff, L"EM_FINDTEXTWテスト-下方検索", MB_OK);
    ft.chrg.cpMin += lstrlenW(ft.lpstrText);    //下方検索では検索文字列の先頭を指すft.chrg.cpMinを検索文字列長分先に進めることが必要
}
wsprintfW(buff, L"現在のft.chrg.cpMin = %d", ft.chrg.cpMin);    //EM_FINDTEXTで検索に失敗するとNOMORE(-1、即ち65535)が返されます。
MessageBoxW(m_hWnd, buff, L"現在のft.chrg.cpMin", MB_OK);
MessageBoxW(m_hWnd, L"下方検索を終了します", L"EM_FINDTEXTWテスト", MB_OK);
count = 0;
ft.chrg.cpMax = 0;
ft.chrg.cpMin = 65535;
MessageBoxW(m_hWnd, L"上方検索", L"EM_FINDTEXTWテスト", MB_OK);
while((ft.chrg.cpMin = SendMessageW(m_Edit.m_hWnd, EM_FINDTEXTW, 0, (LPARAM)&ft)) != NOMORE) {    //FR_MATCHCASEとFR_WHOLEWORDも
    count++;
    SendMessageW(m_Edit.m_hWnd, EM_SCROLLCARET, 0, 0);    //キャレット位置を表示する
    wsprintfW(buff, L"%dで%d回目が見つかりました", ft.chrg.cpMin, count);
    MessageBoxW(m_hWnd, buff, L"EM_FINDTEXTWテスト-上方検索", MB_OK);

    //上方検索ではft.chrg.cpMaxが0で、ft.chrg.cpMinが検索の度に繰り下がり、検索文字列の先頭を指すのでそのままでよい。
}
MessageBoxW(m_hWnd, L"上方検索を終了します", L"EM_FINDTEXTWテスト", MB_OK);