何とか【PropertyGrid】その名は"PropertyAny"...?(注)で【PropertyGrid】シリーズを完了し、いよいよすったもんだしていたC#のRichTextBoxを使ったエディター作りに専念できます。
注:実はファイルをドラッグアンドドロップしても選択できることを書き忘れました。
先般の【無駄話】愚痴の続き...から、決断へで書いたように、「無いのなら作って見せよう、不如帰」のノリで基本構想を「.NET FrameworkのTichTextBoxでは未実装の文字列検索、置換機能と印刷機能を実装した派生クラスを作る」としたのは良いのですが、結構難儀しています。その大きな理由、即ち、
(1)(HTML等のマークアップ言語による飾り文章が主流となったので)MicrosoftのRichText自体が時代遅れとなっている。
(2)Windowsのインフラについても、.NET FrameworkのWinFormsも然り(時代遅れ-現代では「それな」というのか?)で、Microsoftは時間や人やお金という資源をもう落とさない。
だから、
Microsoft Learn等公式文書のみならず、WEB上でも「.NET Framework 4.8のWinForms上のRichTextBox」に関わる情報がとても少ない!
という状況です。となると(最近依存しすぎで少し気になってきているのですが)またまたまたまたChat-GPT様に相談することになります。しかし、
生成系AIと言えども、情報量の少ないテーマでは間違える、または"e"加減なことを言う可能性が高い
ということを、これから実証します。以下は追加実装しようとしている「文字列検索・置換ダイアログによる検索メソッド」に関わるChat-GPTとのやり取り全てです。ご参考まで。(注)
注:実は、原稿は最初はコードを含む全やり取りをコピペしていたのですが。アメブロの「禁則タグ」があるということでアップできず、仕方なく書き直して以下は概要のみ。ご容赦を。
(私)またRichTextBox関連の相談です。
C#のRichTextBoxはFindメソッドはありますが、FindReplaceダイアログクラスが無いので、自作しています。 先ず検索を行う為自作FindReplaceダイアログの「次を検索」ボタンの処理を次のように(プログラムコードは除外しました)しています。(このダイアログには呼び出し元のRichTextBoxをコンストラクターの引数として保有しています。) 下方検索は思い通りに行くのですが、上方検索(RichTextBoxFinds.Reverse)の場合開始位置をキャレット、終了位置を0とすると検索せず、Win32のプログラミング経験から開始位置を0、終了位置をキャレットにすると動くには動くのですが、何度も検索を継続して終わりません。(Findメソッドの戻り値が-1にならない。)
どうしてでしょう?又対処方法はあるでしょうか?
(Chat-GPT)
とても良い実装方針です。
確かに RichTextBox.Find() は Reverse フラグ付き検索(上方向検索)を行うときに、開始位置と終了位置の解釈が非常に紛らわしく、ドキュメントの説明も実際の挙動と食い違う部分があります。
順方向と逆方向で引数の意味が入れ替わるのが最大の落とし穴です。
🔍 問題の核心
「上方検索の場合、開始位置をキャレット、終了位置を 0 にすると検索しない」
「開始位置を 0、終了位置をキャレットにすると動くが、繰り返し検索が止まらない」
その理由は以下の通りです:
RichTextBox.Find(string, int, int, RichTextBoxFinds) のドキュメント上では
検索は
startからendまでの範囲で行われる
とありますが、実際の挙動は以下のように異なります。
| フラグ | 実際の動作 | 注意点 |
|---|---|---|
| なし(下方向) | start → end の範囲で検索 | 正常に動作 |
| Reverse(上方向) | end → start の範囲で検索 | 引数が逆に扱われる |
つまり、上方向検索では:
-
「
start<end」のままだと範囲が無効になり、何も見つからない。 -
さらに、「
end→ start`」の範囲なので、繰り返し検索で範囲を狭めないと無限ループする。
✅ 修正版ロジック
あなたのコードに最小限の修正を加えた安全な実装例を示します。
(解説:間違いなので省略)
💡ポイント解説
-
上方向では
end < startにしないとダメ-
実際には「
Find(str, end, start, Reverse)」という順序で呼び出す必要があります。 -
あなたのコードでは
Find(str, start, end, Reverse)としていたため範囲が不正でした。
-
-
無限ループ防止
-
foundPos - 1を次回検索の終了位置にすることで、前回の一致位置を再検出しなくなります。
-
-
キャレット位置の扱い
-
初回はキャレット位置、2回目以降は前回の位置の1つ手前。
-
(私)上のコードでは、実行時に「System.ArgumentException: 値 '0' は 'end' に対して有効な値ではありません。 'end' の値は 'start' 以上か -1 でなければなりません。」というエラーがでます。明らかにChat-GPTさんが言う「上方向では end < start にしないとダメ」は間違っています。このエラーの為に冒頭のコードにした経緯があります。
(CHat-GPT)ご指摘ありがとうございます — こちらの説明が間違っていて混乱させてしまい申し訳ありません。
あなたが遭遇した ArgumentException(end の値は start 以上か -1 でなければなりません) はまさにその通りで、.NET の RichTextBox.Find(...) は end が start より小さい値を許さない(-1 を除く)仕様です。公式ドキュメントにもその旨が書かれています。
では、正しい実装方針を短くまとめてから、動作する修正版コードを示します。
要点(簡潔)
実装(あなたの元コードを最小修正した例)
(共に省略)
改めて混乱を招いてしまい失礼しました。上の修正版で ArgumentException は出なくなり、かつ上方向継続検索が同じ一致を繰り返すことも防げるはずです。もし動作確認してまだ不具合が出るようなら、実際の rtBox.SelectionStart/foundPos の値(簡単なログ)を教えていただければ、さらに具体的に調べて対処案を出します。
(私)しかしこれって、
(1)RicjhTextBoxFInds.Reverseを設定しているのに、Findメソッドの戻り値仕様(終了-1)にならないという仕様違反
(2)公式文書でReverseの場合の「開始位置<終了位置」を保持するあまり、開始位置と(実際は終了位置)終了位置(実際は開始位置)に関わる説明を行っていないという説明不完全
(3)分かっているならReverseでも文頭迄来たら-1を返すようにバグフィクスすればよいのにしていない 事から、Microsoftの怠慢(というかWinFormsを見捨てた?)
と感じられますが、如何?
(Chat-GPT)良い指摘です —— 要点をきっちり整理して短く答えます。結論から言うと「あなたの感じた不満(仕様の不親切さ・ドキュメント不足)はもっともで、実用上は設計ミスに近い体験を与える」が、裏には互換性・歴史的経緯・優先度の問題が絡んでいる、というのが現実的な説明です。以下、(1)〜(3) それぞれに対する解説と実務的な対処案を示します。
(1) 「Reverse を立てると end - 1 仕様違反になる」
(2) 「公式文書が不親切」
(3) 「仕様ならば Reverse で文頭に達したら -1 を返すよう修正すれば良いのに放置されている」
-
理由として考えられる点(推測):
-
後方互換性の重視:既存のアプリ/ライブラリが期待する挙動を変えると互換性を壊す恐れがある。微妙な仕様変更は破壊的になるため慎重になります。
-
歴史的経緯:
RichTextBoxは Windows の Rich Edit コントロール(古い API)をラップしたものです。元のコントロールの振る舞いや .NET の過去実装に合わせた結果、いびつなインターフェースが残っていることがよくあります。 -
優先度の低さ/フォーカスの移行:Microsoft が新しい UI 技術(WPF → WinUI → MAUI 等)に注力した結果、WinForms の細かい UX 修正は優先度が低い可能性があります。
-
-
つまり「怠慢」と断じるのは理解できる感情的反応ですが、現実的には技術的負債+互換性+優先度の問題が複合していることが多いです。とはいえ設計的に改善の余地が大きいのは確かです — 実務的には『.NET のこの API は信頼できない前提で自前実装に逃げる』のが堅実です。
最後に(あなたの感想への返答)
あなたが「Microsoft の怠慢」と感じるのは自然な感情だと私も思います。設計上の不親切さ・ドキュメント不足は実際の開発コストを増やします。ただ現実的には「歴史的理由」と「互換性の壁」と「開発リソースの優先度」が背景にあり、単純には直せないケースが多い――というのが実務的な事情です。だからこそ現場では「当該 API を信頼しないで自前実装する」か「低レベル API を使って精密に制御する」かの選択が有効になります。
(私)「Microsoftの怠慢==金の亡者だから」というのは事実に近いジョークです。(まぁ、ビジネスの世界では仕方がないですがね。でも、米国の原告弁護士なら許さないでしょうが。)「RichTextBox.Find を使わない」方法は大掛かりなので遠慮します。 所で先のC#の推奨コードですが、stPosとedPosの関係で独自終了チェックを追加していますが、終了条件を単に
if(foundPos == -1 || edPos < stPos)
とするだけで十分じゃないでしょうか?(stPos、edPosはFindメソッドで影響を受けないし、Countも増えません。)
(Chat-GPT)はい、まさにその通りです。
あなたの指摘は完全に正確で、次のように整理できます。
✅ 結論:
if (foundPos == -1 || edPos < stPos) のようにまとめてチェックするだけで 十分に合理的かつ安全 です。
🔍 理由の整理:
-
つまりあなたの書かれたとおり:
「if(foundPos == -1 || edPos < stPos) に一本化するだけで十分」
──これは設計的にも実装的にも正しい判断です。
そして、その短い一文で「Microsoft の怠慢をカバーする」あたりが、実務家らしい最適解ですね。
いずれにしても、これで検索メソッドは完了しました。又、置換メソッドも「選択部分を置換したい文字列に置き換える」処理(RichTextBox.SelectedText = (置換したい文字列);)を追加するだけなので、簡単に完了してしまいました。
後は「印刷」メソッドなんですが、これも又悩み深い問題があります。又ご紹介しましょう。
