memopad 備忘録

step3:完成品のフィードバック対応

v0.1.0 を実際に使って出たフィードバックを 1 件ずつ記録し、対応版をリリースしていく。フィードバックが 0 件になった時点を v1.0 とする。着手日:2026-09-07

目次
  1. フィードバック一覧と対応バージョン
  2. FB-1:IME 入力のレスポンスが悪い(v0.2.0)
  3. FB-2:上部の高さと配置をメモ帳に合わせる(v0.3.0)
  4. FB-3:「背景色」「文字色」を「色変更」に統合(v0.4.0)
  5. FB-4:目に優しい配色 16 パターン(v0.5.0)
  6. FB-5:タブをクリックしても切り替わらない(v0.5.1)
  7. FB-6〜8:表示の細かな崩れ 3 件(v0.5.2)
  8. FB-11:起動がメモ帳よりワンテンポ遅い(v0.7.0)
  9. Windows Forms への作り直しと MemoPad への改名(v0.8.0)
  10. FB-13:タイトル行でウィンドウを動かせない(v0.9.0)
  11. FB-14・FB-15:メニューの見た目(v0.9.0/v0.9.1)
  12. FB-16:ドラッグ&ドロップで開くと編集済みになる(v0.9.1)
  13. FB-17:ホイールで最後の行まで届かない(v0.9.2)
  14. FB-18:折り返しなしで横にスクロールできない(v0.9.2)
  15. FB-19:大きなファイルで固まる(v0.10.0 で本文を Scintilla に変更)
  16. FB-21:開いた直後だけウィンドウ移動がカクつく(原因の特定)
  17. FB-21 の対策:移動・リサイズ中は折り返しの計算を止める(v0.10.1)
  18. つまずいた点のメモ

1. フィードバック一覧と対応バージョン

初回フィードバック(2026-09-07 受領)は 4 件。種類ごとにバージョンを分けて対応する。

番号種類内容対応版状態
FB-1改善IME 使用時にキーを打ってから一呼吸置いて文字が表示される。メモ帳と比べて明らかに遅いv0.2.0対応済み
FB-2修正ウィンドウ上部からエディタまでの高さをメモ帳と同等にし、上からタブ→メニュー→エディタの順に並べる。+ボタンはタブ名の近くに。フッターの高さもメモ帳に合わせるv0.3.0対応済み
FB-3変更「背景色」「文字色」を「色変更」メニューに統合し、1 つのウィンドウで両方を変えられるようにする。16 色は PICO-8 パレットに置き替えるv0.4.0対応済み
FB-4機能追加背景色と文字色を一度に変える「目に優しい配色」16 パターンを、プレビューを見て選べる方式で追加するv0.5.0対応済み

2 回目のフィードバック(2026-09-07、v0.5.0 のインストール確認時)。IME の体感速度、配色パターン、色変更、ドラッグ&ドロップは OK との確認をもらった。

番号種類内容対応版状態
FB-5不具合タブをクリックしても切り替わらない(メニューと文字入力は動く)v0.5.1対応済み

3 回目のフィードバック(2026-09-07、v0.5.1 で「タブ切り替えは改善された」との確認とあわせて)。

番号種類内容対応版状態
FB-6不具合検索・置換バーの閉じるボタン(×)が欠けて見えるv0.5.2対応済み
FB-7不具合配色パターンのプレビュー枠で英語の行が見えないv0.5.2対応済み
FB-8不具合右上の閉じる/最小化/最大化ボタンにマウスを載せると「□□□」と文字化けして見えるv0.5.2対応済み

4 回目のフィードバック(2026-09-07、v0.5.2 は OK との確認とあわせて)。

番号種類内容対応版状態
FB-9変更アプリ アイコンをユーザー提供のデザイン(緑の文書に白い罫線、16〜256 px の 7 サイズ入り .ico)に変更するv0.6.0対応済み
FB-10公開GitHub Pages の公開、README.md の追加、アプリの紹介ページの作成v0.6.0対応済み

5 回目のフィードバック(2026-09-08)。

番号種類内容対応版状態
FB-11性能メモ帳に比べて起動がワンテンポ遅い。軽いエディタとしての使い勝手に関わるので、メモ帳並みに短縮したいv0.7.0 → v0.8.0対応済み
FB-12変更FB-11 が達成できたら、名称を memopad から MemoPad に改め、アイコンをユーザー提供の新デザインに差し替えるv0.8.0対応済み

FB-11 はまず WPF のままできる対策を v0.7.0 で入れたが、約 520 ms → 505 ms にしかならず「メモ帳並み」には届かなかった。空の WPF ウィンドウでも約 400 ms かかることが計測で分かったため、メイン ウィンドウを Windows Forms に作り直して v0.8.0 で 221 ms(メモ帳は 211 ms)にした(詳細は 8 節9 節)。これを受けて FB-12 の改名とアイコン差し替えも v0.8.0 で行った。

6 回目のフィードバック(2026-09-08、v0.8.0 のインストール確認時)。起動の速さは「体感で速くなった、OK」、大きなファイルを読み込んだときの動作も「以前は緩慢だったが現バージョンは軽快。ここは重要なので変えないでほしい」との確認をもらった。

番号種類内容対応版状態
FB-13不具合ウィンドウ上部のドラッグでウィンドウを動かせない。上部をダブルクリックしても最大化/元のサイズに戻らない(v0.7.0 まではできていた)v0.9.0対応済み
FB-14確認ヘルプの「MemoPad について(A)」の (A) は何か変更なし
FB-15改善サブメニューの文字がやや上付きで、字下げが全角 2 文字分ほどあり右に寄りすぎている。全体的に整えてほしいv0.9.0対応済み

FB-13 は Windows Forms への作り直し(v0.8.0)で持ち込んだ不具合で、調べるとウィンドウ四辺のリサイズも同じ原因で効かなくなっていた(10 節)。FB-14 の (A) はメモ帳と同じアクセス キー表記なのでそのままにし、FB-15 のメニューはメモ帳と同じ位置に整えた(11 節)。「軽快さを変えないでほしい」という要望に対しては、本文の読み込みと描画には手を入れていないことに加え、13.4 MB(12 万行)のファイルを 0.42 秒で開けることを再計測して確かめた。

7 回目のフィードバック(2026-09-08、v0.9.0 の確認時)。メニューの文字の上下位置は OK との確認をもらった。

番号種類内容対応版状態
FB-15(続き)改善チェックの付かないメニューも、メモ帳では少しだけ字下げされている。アイコンが無いメニューは少し字下げするのが基本v0.9.1対応済み
FB-16不具合既存のファイルをドラッグ&ドロップして開くと、最初から編集された状態(タイトルの * とタブの●)になるv0.9.1対応済み

v0.9.0 では字下げを浅くしすぎて、文字がポップアップの左端に寄っていた。メモ帳と同じ控えめな字下げに直した(11 節)。FB-16 は RichEdit が自分でもドロップを処理してしまうのが原因で、ドロップ先を自前のものに差し替えて直した(12 節)。

8 回目のフィードバック(2026-09-11、v0.9.1 の確認時)。メニューの字下げと、ドラッグ&ドロップで開いた直後の状態は OK との確認をもらった。

番号種類内容対応版状態
FB-17不具合長いファイルをマウス ホイールでスクロールすると、最後の改行まで届かない(メモ帳では届く)。些細なことだが気になるv0.9.2対応済み
FB-18不具合(FB-17 の確認中に発見)「右端で折り返す」をオフにしても横スクロールバーが出ず、長い行の右端から先が見えないv0.9.2対応済み

FB-17 は Windows 標準の RichEdit のホイール処理の癖が原因で、ホイールだけ MemoPad が自前で送るようにした(13 節)。FB-18 は確認の途中で見つけた v0.2.0 からの不具合で、了承をもらって同じ版で直した(14 節)。「起動時間が増えないか」という確認の依頼には、修正前後の起動時間と大きなファイルの読み込み時間を実測して、変わらないことを示した(13.4)。

9 回目のフィードバック(2026-09-12、v0.9.2 を試用して)。FB-17・FB-18 の修正自体は問題なかったが、大きなファイルで固まる現象がそれ以上に重い問題として上がった。

番号種類内容対応版状態
FB-19不具合10 MB/12 万行の日本語ファイルを開くと、「右端で折り返す」のオン・オフに関わらず非常に長い時間固まる(一応は開く)。さらに一度固まったあとは、新規タブ・ウィンドウ移動・最大化など操作の種別に関わらずそのつど固まるようになるv0.10.0対応済み
FB-20対処固まる現象を抱えたままの版はエディタとして機能を満たしていないため、公開中のインストーラの配布を一旦停止する(サイトの公開は継続)対応済み

FB-19 は 8 回目のフィードバックの確認中にこちらでも把握していたもので、そのときは「折り返しオンで約 44 秒、オフなら約 3 秒」と報告していた。実機ではオフでも長時間かかり、報告した数字が実際と違っていた。対処として先に配布を止め、そのうえで原因を調べた。原因は本文に使っていた RichEdit が、日本語と英字の混ざった行の文字幅を GDI に何度も問い合わせることだった。RichEdit の設定でも、Win32 の Edit コントロールでも、Windows App SDK の WinUI でも避けられなかったため、v0.10.0 で本文のエディタを Scintilla に差し替えた。最長の無応答は 43,456 ms → 34 ms になった(15 節)。

10 回目のフィードバック(2026-09-13、v0.10.0 の確認時)。FB-19 の「開く速度」と「その後も固まる」はどちらも解消したとの確認をもらい、インストーラの公開も可となった(同日、v0.10.0 で配布を再開)。あわせて 1 件の確認を受けた。

番号種類内容対応版状態
FB-21確認→不具合大きなファイルを開いた直後だけ、すぐにウィンドウを移動させるとカクカクする(3 秒ほど待てばスムーズ)。遅延読み込みの処理をそちらに回しているためではないか。まず原因が知りたい(すぐの修正は不要)v0.10.1対応済み

推測のとおりだった。Scintilla は折り返し位置の計算を 10 ms ずつに区切って空き時間に進めるため、その約 4 秒の間はウィンドウの移動が計算と順番待ちになる。別プロセスからの計測で、忙しい区間(0.4〜4.2 秒)の応答時間の中央値が 9.7 ms=ちょうど 1 回分の計算時間であること、折り返しオフではこの区間が存在しないことを確かめた(15.9)。そのうえで対策 4 案を入力レスポンス・起動時間と合わせて実測し、「ウィンドウの移動・リサイズ中だけ計算を止める」案を v0.10.1 で採用した(ドラッグ中の位置の更新間隔 71.4 ms → 16.7 ms。入力・起動・見た目は変化なし。16 節)。

FB-9 は src/memopad/memopad.ico を差し替え(アイコンを描いていた docs/tools/make-icon.ps1 は不要になったので削除)、インストーラにも SetupIconFile で同じアイコンを付けた。タイトル行の左端のアイコンは exe から取り出しているので自動で変わる。FB-10 はリポジトリ直下に README.md、docs/index.html に紹介ページを置き、GitHub Pages で main ブランチの /docs を公開した(紹介ページ:mrgarita.github.io/memopad、備忘録はその下の site/)。インストーラは GitHub Releases に置く。

2. FB-1:IME 入力のレスポンスが悪い(v0.2.0)

2.1 現象

「メモ帳に比べ、キータッチしたときに一呼吸置いてからエディタ部分に表示される。キー入力に表示が追い付いていない。シンプルなメモ帳でこのレスポンスの悪さは致命的」というフィードバック。IME(日本語入力)のときに特に気になるとのこと。

2.2 調べ方

体感を数値にするため、2 段階で計測した。

  1. アプリ内部の処理時間。環境変数 MEMOPAD_PERF=1 で有効になる診断ログ(Services/PerfLog.cs)を組み込み、キー押下(KeyDown)→本文変更(TextChanged)→描画(CompositionTarget.Rendering)の各時刻と、ウィンドウ メッセージ・Dispatcher の処理時間を %TEMP%\memopad-perf.log に記録した。
  2. 入力から画面に出るまでの時間。PowerShell から keybd_event でキーを 1 つ送り、画面上部をキャプチャし続けてピクセルが変わるまでの時間を測るスクリプトを書いた。IME は 半角/全角 キーを送って ON にし、「nihongo…」とローマ字を 1 文字ずつ送る。同じ手順をメモ帳にも当てて比べる。キャプチャ 1 回に約 18 ms かかるので、精度はその程度。

2.3 結果

アプリ内部は速かった。キー押下から本文変更まで 3〜5 ms、描画要求まで +2 ms で、115 KB のファイルでも変わらない。ステータスバー更新などの自前処理は 0.01 ms。つまり アプリのコードが遅いのではなかった。

一方、画面に出るまでの時間はメモ帳の 2.5 倍あった。WPF の TextBox は、UI スレッドが描画内容を作ってから別の描画スレッドが DirectX で描き、それを DWM(デスクトップ合成)が画面に出す、という 3 段階を経る。その途中で垂直同期に合わせて待つため、1〜2 フレーム(16〜33 ms)分の遅れが構造的に乗る。テーマ(Fluent)を外しても、ソフトウェア描画に切り替えても変わらなかった。

エディタIME 入力(ひらがな合成中)半角入力
メモ帳(Windows 11 Store 版)約 19 ms約 17 ms
memopad v0.1.0(WPF TextBox)約 50 ms約 31〜49 ms
WPF TextBox+Fluent テーマ無効約 50 ms
WPF TextBox+ソフトウェア描画約 45 ms約 41 ms
Win32 RichEdit(Windows Forms の RichTextBox)約 22 ms約 20 ms
Win32 Edit(Windows Forms の TextBox)約 28 ms約 20 ms
memopad v0.2.0(RichEdit に置き換え)約 21 ms約 16 ms
キー送信から画面に文字が出るまで(IME 入力、単位 ms、短いほど良い) メモ帳 19 memopad v0.1.0(WPF TextBox) 50 RichEdit 単体(試作) 22 memopad v0.2.0(RichEdit) 21 016335060 ※ 16 ms = 60 Hz の 1 フレーム。キャプチャの分解能が約 18 ms なので目安の値
図 2-1:入力から表示までの遅延。v0.2.0 でメモ帳と同等になった

2.4 原因のまとめ

v0.1.0:WPF TextBox キー入力IME → WPF UI スレッドレイアウト・描画命令 描画スレッド垂直同期を待って DirectX 描画 DWM 合成次のフレームで表示 画面約 50 ms v0.2.0:Win32 RichEdit(WindowsFormsHost で埋め込み) キー入力IME → RichEdit UI スレッドで GDI 描画その場でウィンドウに描く DWM 合成次のフレームで表示 画面約 21 ms 黄色の箱が余分に待つ段階。WPF は描画を別スレッドに渡し、垂直同期に合わせて提示するため、メモ帳より 1〜2 フレーム遅れる
図 2-2:描画経路の違い。アプリのコードではなく WPF の表示パイプラインが原因だった

2.5 対策:エディタを RichEdit に置き換える

本文の編集部分だけを Windows 標準の RichEdit(msftedit.dll)にした。Windows Forms の RichTextBox を継承した Views/PlainTextEdit.cs を作り、WindowsFormsHost で WPF の画面に埋め込む。メニュー・タブ・検索バー・ダイアログはこれまでどおり WPF なので、見た目とテーマはそのまま。

項目やったこと
プレーン テキスト化EM_SETTEXTMODETM_PLAINTEXT にする(本文が空のうちに送る必要がある)。貼り付けても書式が付かず、フォント・色は全体で 1 つになる
元に戻すTM_MULTILEVELUNDOEM_SETUNDOLIMIT=1000。Ctrl+Z/Ctrl+Y は RichEdit が処理し、Ctrl+Y だけアプリ側で Redo を呼ぶ
折り返しWindows Forms の WordWrap はハンドルを作り直して履歴が消えるので、EM_SETTARGETDEVICE を直接送る
ズームZoomFactorEM_SETZOOM)。Ctrl+ホイールは WM_MOUSEWHEEL を横取りしてアプリのズームに回し、ステータスバーの % と連動させる
ショートカットWindowsFormsHost の中では WPF の InputBinding が効かない。ProcessCmdKey で押されたキーを Commands.All の KeyGesture と突き合わせて実行する。Alt+F などのアクセス キーも同じ経路でメニューを開く
右クリックWM_CONTEXTMENU を受けて WPF の ContextMenu(元に戻す〜すべて選択、検索)を出す
ファイルのドロップRichEdit 自身の OLE ドロップを止め(EnableAutoDragDrop=false)、DragDrop でファイルを開く
余白EM_SETRECT で左に 8 px の余白。サイズ変更で戻るので都度設定
ダーク テーマスクロールバーに SetWindowTheme(hwnd, "DarkMode_Explorer")
ステータスバー更新全文を読む処理は連続入力中に毎回動かさず、入力が途切れたとき(Dispatcher の Background)に 1 回だけ行う

2.6 置き換えで起きた問題と対処

RichEdit の文字が透けて見える状態
図 2-3:置き換え直後。本文が薄く透け、背後のウィンドウまで見えている
不透明化した v0.2.0 のメイン画面
図 2-4:ウィンドウを不透明にし、日本語フォントの自動選択を有効にした後

2.7 確認

背景色を紺、文字色を黄にしたところ
図 2-5:背景色・文字色の変更も RichEdit で動く(BackColorForeColor
ダーク テーマで検索した状態
図 2-6:ダーク テーマ。検索の一致箇所が選択され、スクロールバーもダーク用

step2 の撮影スクリプト(docs/tools/capture-memopad.ps1)を回帰確認に使い、メニュー・検索/置換・行へ移動・フォント・色・テーマ・タブ・未保存確認・ページ設定・バージョン情報がすべて動くことを確認した。遅延の再計測は表 2-3 の最終行のとおり。

診断ログの使い方:レスポンスの問題が再発したときは、環境変数 MEMOPAD_PERF=1 を設定して memopad を起動すると %TEMP%\memopad-perf.log にキー押下と本文変更の時刻が記録される。通常起動では何も書かない。

3. FB-2:上部の高さと配置をメモ帳に合わせる(v0.3.0)

memopad v0.1.0(左)とメモ帳(右)の比較
図 3-1:フィードバックで示された比較。左が memopad v0.1.0、右がメモ帳。上部の高さと並び順、フッターの高さが違う

3.1 現象と目標

v0.1.0 は「タイトル バー→メニュー→タブ→本文」の順で、本文が始まるまでがメモ帳より 50 px 以上低い。メモ帳は「タブ(タイトル バーに同居)→メニュー→本文」の順で、+ボタンは最後のタブのすぐ右にある。フッター(ステータスバー)も memopad のほうが高い。

同じ大きさ(1000×720 物理 px、表示スケール 150 %)で並べて撮り、上端から本文までとステータスバーの高さを画像から測った。

項目(論理 px)メモ帳memopad v0.1.0memopad v0.3.0
上端から本文まで7512774
 タイトル行(タブを含む)4130(標準のタイトル バー)+ 49(メニュー)+ 48(タブ)42(タブを含む)
 メニュー行3232
ステータスバー314732
v0.1.0、v0.3.0、メモ帳の上部を並べた比較
図 3-2:左から v0.1.0、v0.3.0、メモ帳(上部 260 px を切り出し)。v0.3.0 は本文の開始位置がメモ帳と 1 px 差

3.2 やったこと

項目内容
タイトル バーを自前で描くWPF の WindowChromeCaptionHeight=42UseAeroCaptionButtons=False)で標準のタイトル バーを外し、タイトル行にアイコン・タブ・+ボタン・最小化/最大化/閉じるボタンを置いた。ボタンのグリフは Segoe Fluent Icons。ダブルクリックで最大化、ドラッグで移動、右クリックでシステム メニューは WindowChrome がそのまま面倒を見る
ガラス領域は使わないGlassFrameThickness=0。ガラス領域があると RichEdit が透ける(2.6 節)。角丸は DWMWA_WINDOW_CORNER_PREFERENCE で DWM に頼む
タブの見せ方選択中のタブとメニュー行を同じ色にしてつなげ、タイトル行は少し濃くする(メモ帳と同じ)。Fluent の ListBoxItem は余白が大きいので、タブ用の簡素なテンプレートに差し替えた
メニュー行を 32 px にFluent の MenuItem は高さ約 58 px あり、Menu の高さを固定すると項目が描画されなくなる。トップ レベルの項目だけ簡素なテンプレート(サブメニューの Popup 込み)にし、サブメニューの項目は Fluent のまま
ステータスバーを 32 px にStatusBarItem の Padding と文字サイズを詰めた
最大化時の余白WindowChrome は最大化するとリサイズ枠の分だけ画面外にはみ出すので、最大化中はルートに 7 px の余白を付ける
v0.3.0 のメイン画面
図 3-3:v0.3.0。タイトル行にタブと+、その下にメニュー、本文の順
v0.3.0 のダーク テーマでタブを 2 つ開いたところ
図 3-4:ダーク テーマ。未保存のタブには●が付く
v0.3.0 のファイル メニュー
図 3-5:メニュー行を詰めても、サブメニューは Fluent の見た目のまま
計測の落とし穴:最初の計測では本文の開始位置が 7 px ずれて見えた。設定ファイルに「最大化」が保存されていたため、最大化中の余白が写り込んでいた。撮影スクリプト(capture-memopad.ps1)は設定を退避してから撮るので、そちらの値を採用した。

4. FB-3:「背景色」「文字色」を「色変更」に統合(v0.4.0)

4.1 現象

v0.1.0 では「書式」メニューに「背景色」「文字色」のサブメニューが別々にあり、どちらも同じ 16 色+「その他の色」が並んでいた。片方を選ぶたびにメニューを開き直すので使い勝手が悪い、というフィードバック。あわせて 16 色は Windows の基本 16 色から、参照ファイルの PICO-8 パレットに置き替える。

4.2 やったこと

番号名前番号名前
0 #0000008 #FF004D
1濃い青 #1D2B539オレンジ #FFA300
2濃い紫 #7E255310 #FFEC27
3濃い緑 #00875111 #00E436
4 #AB523612 #29ADFF
5濃い灰 #5F574F13 #83769C
6明るい灰 #C2C3C714ピンク #FF77A8
7 #FFF1E815 #FFCCAA
v0.4.0 の書式メニュー
図 4-1:書式メニュー。「背景色」「文字色」が「色変更...」にまとまった
色変更ダイアログで背景色に濃い青、文字色に黄を選んだところ
図 4-2:色変更ダイアログ。背景色と文字色を並べて選び、下のプレビューで確かめる
濃い青の背景に黄の文字
図 4-3:OK を押した後の本文
その他の色から開く色の設定ダイアログ
図 4-4:「その他の色...」は Windows 標準の色の設定ダイアログ(変更なし)

5. FB-4:目に優しい配色 16 パターン(v0.5.0)

5.1 要望

テーマのように全体の配色をまとめて変える機能として、目に優しい配色 16 パターン(参照ファイル「テキストエディタ配色パターン」)を用意し、プレビューを見て選べる方式にしてほしい、というフィードバック。背景色と文字色を一度に変える。

5.2 やったこと

16 パターン(HTML で再現したもの。名前はアクセント色、色コードは背景 / 文字):

生成り
あいうえお Aa Bb 0123
#F7F3E9 / #3B372E
ソフトグレー
あいうえお Aa Bb 0123
#F2F2F0 / #333333
セピア
あいうえお Aa Bb 0123
#F4ECD8 / #4A3F35
ミント
あいうえお Aa Bb 0123
#EAF4F0 / #2C3E3A
ラベンダー
あいうえお Aa Bb 0123
#F1EEF7 / #35313F
ピーチ
あいうえお Aa Bb 0123
#FBEEE6 / #4A3226
スカイ
あいうえお Aa Bb 0123
#EAF2F8 / #2A3B4A
サンド
あいうえお Aa Bb 0123
#F5EFE6 / #453D33
チャコール
あいうえお Aa Bb 0123
#2B2B2B / #DCDCDC
あいうえお Aa Bb 0123
#1F2B24 / #D6E4DA
あいうえお Aa Bb 0123
#1B2430 / #D7DEE8
あいうえお Aa Bb 0123
#232323 / #E0DCD3
ダークパープル
あいうえお Aa Bb 0123
#241F2E / #E1DCE8
コーヒー
あいうえお Aa Bb 0123
#2A211B / #E5DACB
スレート
あいうえお Aa Bb 0123
#262B30 / #DCE1E5
ワイン
あいうえお Aa Bb 0123
#2A1E22 / #E5D9DB
v0.5.0 の書式メニュー
図 5-1:書式メニューに「配色パターン...」が加わった
配色パターン ダイアログで「夜」を選んだところ
図 5-2:配色パターン ダイアログ。カードを選ぶと下のプレビューが変わる
配色パターン「夜」を反映した本文
図 5-3:「夜」を反映した本文

6. FB-5:タブをクリックしても切り替わらない(v0.5.1)

6.1 現象

v0.5.0 をインストールして確認中、タブをクリックしても切り替わらない。メニューと文字入力は動く。Ctrl+Tab やプログラムからの切り替え(新しいタブを開いたとき)は動いていたため、撮影スクリプトの確認では気づけなかった。

6.2 原因

v0.3.0 でタブ用に差し替えた ListBoxItem のスタイルに Focusable=False を付けていた。WPF の ListBoxItem はマウスで押されたとき、まず自分にフォーカスを移し、それが成功したときだけ選択処理を行う。フォーカスを取れない設定にしたことで、クリックしても選択されなくなっていた(フォーカスが本文から離れないように、という意図だったが逆効果)。

6.3 対処と確認

操作v0.5.0(インストール済み)v0.5.1
Ctrl+N でタブ追加*タイトルなし 2 に切り替わる同じ
1 つ目のタブをクリック切り替わらない(*タイトルなし 2 のまま)タイトルなし に切り替わる
2 つ目のタブをクリック*タイトルなし 2 に切り替わる
クリック後にキー入力本文に入る本文に入る(abc → abcxyz)

7. FB-6〜8:表示の細かな崩れ 3 件(v0.5.2)

欠けて見える検索バーの閉じるボタン
図 7-1:FB-6。×の文字グリフが幅 32 px のボタンに収まっていない
修正後の検索バー
図 7-2:v0.5.2。Segoe Fluent Icons の閉じるグリフにし、幅と高さを隣のボタンに揃えた
英語の行が切れている配色パターンのプレビュー
図 7-3:FB-7。プレビュー枠の高さを 88 px に固定していたため、フォントが大きいと 2 行目が隠れる
修正後の配色パターン ダイアログ
図 7-4:v0.5.2。最小高さに変え、フォントの大きさに合わせて枠が伸びる(色変更ダイアログも同様)
□□□と表示されるツールチップ
図 7-5:FB-8。キャプション ボタンのスタイルに付けたアイコン フォント(Segoe Fluent Icons)をツールチップが継承し、日本語が□になっていた
修正後のツールチップ「最小化」
図 7-6:v0.5.2。アイコン フォントはグリフを描く部分だけに付け、ツールチップは通常のフォントで出る
確認のしかたを変えた点:3 件ともユーザーの環境(表示スケール 150 %、大きめのフォント設定)で見えた崩れで、既定設定で撮る撮影スクリプトでは出なかった。確認用の撮影は、設定を退避しない版(ユーザーの設定のまま起動して撮る)も併用する。また撮影スクリプトは、前面ウィンドウが自分で起動した memopad でなければキーを送らずに中断するようにした(ユーザーが操作中に別アプリへキーが飛ぶのを防ぐ)。

8. FB-11:起動がメモ帳よりワンテンポ遅い(v0.7.0)

8.1 現象と測り方

メモ帳を起動するとすぐウィンドウが出るのに、memopad は一呼吸置いてから出る。FB-1 と同じく、まず数値にした。docs/tools/measure-startup.ps1 は対象を起動し、別プロセスから 1 ms 間隔で (1) 対象プロセスの可視ウィンドウが現れるまで、(2) 画面のピクセル(本文中央とタイトル行)の色が変わらなくなるまで、を測る。8 回起動して 1 回目(コールド)を除いた中央値を採る。

対象ウィンドウ表示まで描画完了まで
メモ帳(Windows 11 の Store 版)約 200 ms約 300 ms
memopad v0.6.0約 520〜550 ms約 650 ms

ウィンドウが出るまでで 2.5 倍以上、300 ms 以上の差がある。これが「ワンテンポ」の正体。

8.2 起動処理の内訳

環境変数 MEMOPAD_PERF=1 で有効になる診断ログ(Services/PerfLog.cs)に、プロセス起動からの経過時間を段階ごとに記録する Mark を足した。温まった状態(2 回目以降)の v0.6.0 の内訳は次のとおり。

v0.6.0 の起動処理の内訳 0100200300 400500600700800 ms .NET ランタイム起動(60 ms) WPF Application の初期化(81 ms) 設定 JSON の読み込み(31 ms)→ v0.7.0 で 18 ms Fluent テーマの適用(47 ms) MainWindow の XAML 読み込み(106 ms) 最初のタブ:Windows Forms と RichEdit の生成(57 ms) レイアウトとテンプレート適用(50 ms) WindowChrome の初期化(42 ms) Direct3D デバイス生成と表示(80 ms)→ v0.7.0 で 4 ms Show の完了(69 ms) 最初の描画(66 ms) ウィンドウ表示(約 536 ms)
図 8-1:v0.6.0 の起動処理の内訳。橙は v0.7.0 で短縮できた段階、青は Windows Forms 側、灰は WPF と .NET の固定費

特定の 1 か所が遅いのではなく、WPF の固定費が薄く広く積み上がっている。dotnet-trace で JIT イベントを数えると、起動中に 1,523 メソッドが JIT コンパイルされていた(P/Invoke の呼び出しスタブ約 560、ジェネリック型の実体化約 470、memopad 自身のコード約 150)。同梱の .NET ランタイムは事前コンパイル(ReadyToRun)済みだが、スタブと実体化は実行時に作られる。

8.3 試したことと効果

試したこと効果(ウィンドウ表示まで)採否
アプリ本体の ReadyToRun 発行(PublishReadyToRun約 10 ms 短縮採用(無害)
フレームワークまで一体で事前コンパイルする複合 ReadyToRun(PublishReadyToRunComposite温まった状態で約 40 ms 短縮。ただしインストール直後の初回起動が 1.2 秒 → 4.3 秒に悪化(100 MB 超の一体イメージをウイルス対策がスキャンする)、容量 +20 MB不採用
Fluent テーマを外す(旧来の Aero2 テーマ)約 60 ms 短縮。見た目(ダーク テーマ、Windows 11 風のメニュー)を失う不採用
ランタイムのオプション(TieredPGO 無効、並行 GC 無効)差なし
WPF の描画をソフトウェアにする(RenderMode.SoftwareOnlyDirect3D デバイスの生成(約 80 ms)が消え、描画完了が約 50 ms 早まる。memopad の WPF 部分はタイトル行・メニュー・ステータスバーだけで本文は GDI の RichEdit なので、見た目・速度に差は出ない採用
設定 JSON をソース ジェネレーターで変換(JsonSerializerContext31 ms → 18 ms採用
Windows Forms/msftedit.dll/System.Drawing/Fluent のアセンブリを別スレッドで先読み(Services/StartupWarmup.cs約 10 ms 短縮(CPU の空いているコアで並列に読み込む)採用
フォント(Yu Gothic UI、Segoe Fluent Icons)の別スレッド先読み差なし不採用
メニュー項目の遅延生成メニュー部分の XAML 読み込みは 11 ms しかなく、作り込む価値なし不採用

8.4 v0.7.0 の結果

対象ウィンドウ表示まで(中央値)描画完了まで(中央値)
メモ帳205 ms約 300 ms
memopad v0.6.0521 ms669 ms
memopad v0.7.0505 ms619 ms

描画完了は 50 ms 早くなったが、ウィンドウが出るまでは 16 ms しか縮まらず、体感はほぼ変わらない。WPF のまま「メモ帳並み」にはできなかった。

8.5 WPF の下限と Windows Forms との比較

どこまで縮められるかを知るため、最小構成のアプリを同じ条件(self-contained 発行、同じ端末)で測った。

ウィンドウが表示されるまでの時間の比較 メモ帳 最小構成の Windows Forms(RichEdit+メニュー+ステータスバー) 最小構成の WPF(メニュー+テキスト ボックスだけ) memopad v0.7.0 memopad v0.6.0 205 ms 162 ms 398 ms 505 ms 521 ms
図 8-2:ウィンドウが表示されるまでの時間(2 回目以降の中央値)。緑の点線がメモ帳の水準
選択と結果:(A) v0.7.0 の水準(約 500 ms)で受け入れる、(B) メイン ウィンドウを Windows Forms で作り直す、の 2 案を出し、ユーザーは (B) を選んだ。作り直した v0.8.0 でウィンドウ表示は 221 ms になり、メモ帳(211 ms)と並んだ。作業の中身は次の 9 節に書く。

9. Windows Forms への作り直しと MemoPad への改名(v0.8.0)

9.1 方針:本文はそのまま、ガワだけ載せ替える

本文の編集領域はもともと Windows Forms のコントロール(RichEdit を継承した Views/PlainTextEdit.cs)で、v0.2.0 以降それを WPF の WindowsFormsHost に埋めて使っていた。つまり いちばん重要な部分は最初から Windows Forms で、WPF はその周り(タイトル行・メニュー・検索バー・ステータスバー)を描くためだけに 400 ms を払っていたことになる。そこで周りだけを Windows Forms に載せ替えた。

v0.7.0 と v0.8.0 の構成の違い v0.7.0(WPF がガワ) WPF Window(WindowChrome) タイトル行(ListBox のタブ+ボタン) Menu(Fluent テーマ) WindowsFormsHost RichEdit(PlainTextEdit) StatusBar v0.8.0(Windows Forms がガワ) Form(WM_NCCALCSIZE で枠を自前化) TitleBar(TabButton+ボタン) MenuStrip(FluentMenuRenderer) RichEdit(PlainTextEdit、そのまま流用) StatusBarPanel 赤=WPF、青=Windows Forms。ダイアログ(フォント・色変更・配色パターン・行へ移動・ページ設定・保存確認・バージョン情報)は WPF のまま残し、初めて開くときに読み込む。
図 9-1:ガワを WPF から Windows Forms に載せ替えた。本文の RichEdit は v0.2.0 のまま使い回している

9.2 作ったもの

ファイル役割
Program.cs入口。App.xaml をやめ、WPF に触れずに設定を読んでフォームを出す
Views/MainForm.csメイン ウィンドウ。WM_NCCALCSIZE で標準のタイトル バーと枠を外し、WM_NCHITTEST でリサイズとドラッグ移動を自分で答える
Views/TitleBar.csタイトル行。タブ(TabButton)と+・最小化・最大化・閉じるを並べる
Views/FluentMenuRenderer.csMenuStrip を Windows 11 風に描く(角丸のホバー、薄い枠線、チェックと右矢印は Segoe Fluent Icons)
Views/StatusBarPanel.csステータスバー。改行コードとエンコードはクリックでメニューを出す
Views/MainForm.Menu.cs / MainForm.Find.csメニューと右クリック メニュー/検索・置換バー
Services/WpfHost.csWPF のダイアログを遅延初期化する。起動時は読み込まず、最初にダイアログを開くときだけ WPF を立ち上げる
Services/ThemeService.csFluent テーマ(ThemeMode)の代わりに、ライト/ダークの配色を ThemePalette として自前で持つ

消したのは App.xamlMainWindow.xaml(と分離コード)、EditorView.xamlCommands.cs、値コンバーター 2 つ。ダイアログ 7 種は WPF のまま、オーナーだけ Window からウィンドウ ハンドルに変えて流用した。

v0.8.0 のメイン画面(ライト)
図 9-2:v0.8.0 のメイン画面。タブ→メニュー→本文の並びと寸法は v0.3.0 のまま
v0.8.0 のダーク テーマ
図 9-3:ダーク テーマ。Fluent テーマの代わりに配色を自前で持つ
2 つのタブと未保存の印
図 9-4:タブは Button を継承したコントロール。未保存は●、右端に閉じる×
MemoPad についてダイアログ
図 9-5:ダイアログは WPF のまま。名称とアイコンを新しくした(FB-12)

9.3 つまずいた点

症状原因と対処
タブが支援技術(スクリーン リーダー)から見えない最初はタイトル行を 1 枚のコントロールに自前描画し、AccessibleObject の子として公開したが、UI Automation には名前の無い枠としてしか出なかった(Windows Forms の素の Control は子要素を UI Automation へ渡さない)。タブとボタンを Button を継承した実体のあるコントロールに変え、AccessibleNameAccessibleRole を付けたら、名前・位置・クリックがそのまま伝わるようになった。FB-5 で作ったタブ クリックの確認スクリプトもこれで動く
選択中のタブだけ×が消えるタブの背景を×より後に描いていた。タブをすべて描いてから×を描く順に変えた
ステータスバーのメニューを閉じると落ちるClosed イベントの中で ContextMenuStripDispose していた。後始末が破棄済みのオブジェクトに触れて例外になる。BeginInvoke でメッセージを処理し終えてから捨てるようにした
エンコードのメニューが画面からはみ出す項目の左端を基準に右へ開いていた。右端を基準に左へ開くようにした
最大化するとウィンドウが画面外へはみ出すWM_NCCALCSIZE で枠を外すと、最大化時は見えない枠の分だけ外へ出る。GetSystemMetricsForDpi で枠の太さを取り、その分だけ内側に寄せた
撮影スクリプトでメニューの ESC が効かないWindows Forms のメニューは ESC 1 回ではメニュー モードを抜けない(1 回目でドロップダウン、2 回目で選択解除)。スクリプト側を ESC 2 回に直した。あわせて、撮影前にマウス カーソルを画面の隅へ寄せてツールチップの写り込みを防ぐようにした

9.4 結果

対象ウィンドウ表示まで(中央値)描画完了まで(中央値)
メモ帳(Windows 11 の Store 版)211 ms467 ms
MemoPad v0.6.0(WPF)521 ms669 ms
MemoPad v0.7.0(WPF+短縮策)505 ms619 ms
MemoPad v0.8.0(Windows Forms)221 ms332 ms

ウィンドウが出るまでは 505 ms → 221 ms と 2.3 倍速くなり、メモ帳(211 ms)に並んだ。本文が描き終わるまでは 332 ms で、メモ帳(467 ms)より速い。step1 で洗い出した機能はすべて動くことを、撮影スクリプト(docs/tools/capture-memopad.ps1)とタブ クリックの確認スクリプトで確かめている。

ウィンドウが表示されるまでの時間の推移 メモ帳 MemoPad v0.8.0(Windows Forms) MemoPad v0.7.0(WPF+短縮策) MemoPad v0.6.0(WPF) 211 ms 221 ms 505 ms 521 ms 同じ端末で 10 回ずつ起動し、1 回目(コールド)を除いた中央値。緑の点線がメモ帳の水準
図 9-6:ウィンドウが表示されるまでの時間。v0.8.0 でメモ帳の水準に並んだ

9.5 FB-12:MemoPad への改名と新しいアイコン

FB-11 が達成できたので、名称を memopad から MemoPad に改め、アイコンをユーザー提供の新デザイン(クリーム色の文書に緑の罫線)に差し替えた。

対象変更
実行ファイルmemopad.exeMemoPad.exeAssemblyName
画面の表示タイトル バー、メニューの「MemoPad について」、各ダイアログの見出しとメッセージ
設定の保存先%APPDATA%\MemoPad\settings.json(Windows のパスは大文字小文字を区別しないので、これまでの設定はそのまま引き継がれる)
インストーラアプリ名・ショートカット・関連付けの表示名、出力ファイル名 MemoPad-setup-0.8.0.exe
アイコンsrc/memopad/memopad.ico(16〜256 px の 6 サイズ)と、紹介ページ用の PNG・favicon

リポジトリ名・フォルダー名・名前空間(Memopad)はそのままにした。GitHub の URL が変わると既存のリンクが切れるうえ、識別子は英語のままという方針に沿うため。

10. FB-13:タイトル行でウィンドウを動かせない(v0.9.0)

10.1 現象

v0.8.0 をインストールしたユーザーから「ウィンドウ上部のドラッグでの移動ができなくなっている。ウィンドウ上部をダブルクリックした際のウィンドウ サイズ変更もできない」というフィードバックを受けた。v0.7.0(WPF)までは普通にできていた操作で、Windows Forms への作り直しで持ち込んだ不具合。手元で調べたところ、ウィンドウの四辺をドラッグしてのリサイズも同じ原因で効かなくなっていた(フィードバックには挙がっていなかったが、あわせて直した)。

10.2 原因:当たり判定を子コントロールが横取りしていた

MemoPad は標準のタイトル バーを WM_NCCALCSIZE で外し、「ここはタイトル行(HTCAPTION)」「ここは左の縁(HTLEFT)」といった判定を MainFormWM_NCHITTEST で答えている。ところが Windows は、その座標にある いちばん手前の子ウィンドウ に当たり判定を尋ねる。v0.7.0 までの WPF はウィンドウ全体が 1 つの HWND だったので親がそのまま答えられたが、Windows Forms はコントロールごとに HWND があるため、タイトル行を覆う TitleBar、四辺を覆う RichEdit やステータスバーが「ここは普通のクライアント領域(HTCLIENT)」と答えてしまい、親の答えは使われない。

当たり判定の流れ(v0.8.0 と v0.9.0) v0.8.0(動かせない) タイトル行の空きをドラッグ TitleBar(子ウィンドウ) 「ここは普通のクライアント(HTCLIENT)」 Form(MainForm) HTCAPTION と答えるが、ここまで来ない → 移動もダブルクリックの最大化もリサイズもできない v0.9.0(動く) タイトル行の空きをドラッグ TitleBar(ChromeHitTest) 「自分は透明(HTTRANSPARENT)」 Form(MainForm) 「ここはタイトル行(HTCAPTION)」 → 移動・最大化・スナップ・リサイズは Windows が処理
図 10-1:子ウィンドウが当たり判定を横取りしていた。HTTRANSPARENT を返すと判定が親へ落ちる

どのウィンドウにマウス入力が届くかは WindowFromPoint で調べられる。v0.8.0 では、フォーム自身は正しく答えているのに、入力先はすべて子コントロールだった。

位置フォームの答え入力が届く先(v0.8.0)子の答え(v0.9.0)
タイトル行の空きHTCAPTION(2)TitleBarHTTRANSPARENT(-1)
左右の縁HTLEFT / HTRIGHT(10 / 11)RichEditHTTRANSPARENT(-1)
下の縁HTBOTTOM(15)ステータスバーHTTRANSPARENT(-1)
タブ・ボタンの上HTCLIENT(1)TabButtonHTCLIENT(1)=今までどおり

10.3 直し方:子から HTTRANSPARENT を返す

Views/ChromeHitTest.cs を追加した。子コントロールが WM_NCHITTEST を受け取ったら、その座標を親フォームに聞き返し(MainForm.IsWindowFrameAt)、タイトル行の空きかウィンドウの縁だったら HTTRANSPARENT(自分は透明。下のウィンドウに聞いてほしい、という意味)を返す。すると判定が親へ落ち、ドラッグ移動・ダブルクリックでの最大化/復元・スナップ・右クリックのシステム メニュー・四辺のリサイズを、いつもどおり Windows が処理してくれる。タブや+ボタンの上は今までどおり子が受け取るので、クリックの動作は変わらない。

直したコントロール覆っていた場所
TitleBar(タブとキャプション ボタンを含む)タイトル行全体、上端と右上隅
PlainTextEdit(RichEdit)本文が届く左右の縁
StatusBarPanel下の縁と左右の下隅
ChromeMenuStrip / ChromePanel(メニュー・検索バー・本文の入れ物)左右の縁

10.4 確かめ方

UI Automation では「マウスで掴んで動かす」操作を再現できないので、mouse_event で実際にドラッグとダブルクリックを行い、ウィンドウの位置とサイズが期待どおり変わるかを確認した(タブのクリック不具合〈FB-5〉を撮影スクリプトで見逃した反省から、v0.5.1 以降は実操作での確認を必ず 1 つ用意している)。

操作期待v0.9.0 の結果
タイトル行の空きをドラッグウィンドウが同じ量だけ動く(+120, +60) の操作で (+120, +60) 移動
タイトル行をダブルクリック最大化する最大化した
最大化中にもう一度ダブルクリック元のサイズに戻る戻った
右の縁を +100 ドラッグ幅が 100 増える1000 → 1100
下の縁を +80 ドラッグ高さが 80 増える700 → 780

11. FB-14・FB-15:メニューの見た目(v0.9.0/v0.9.1)

11.1 FB-14:「MemoPad について(A)」の (A)

(A) は About(バージョン情報)の頭文字を割り当てたアクセス キーの表記で、日本語版のメモ帳も「メモ帳について(A)」と出す昔からの決まり。AltHA でダイアログが開く。他の項目(終了(X)、保存(S) など)とも揃っているので、そのままにした。

11.2 FB-15:字下げが広すぎる/文字が上付き

「サブメニューの文字列がやや上付きでバランスが悪い」「メニューからサブメニューが字下げで表示されるが、字下げの幅が文字 2 文字分ほどあり右寄りすぎる」というフィードバック。メモ帳のメニューと並べて、ポップアップの左端から文字までの距離と、項目の中心に対する文字の上下位置を実測した(表示スケール 150 %、単位は実ピクセル)。

項目メモ帳MemoPad v0.8.0MemoPad v0.9.0MemoPad v0.9.1
字下げ(チェックの無いメニュー)約 21 px54 px17 px25 px
字下げ(チェックの付くメニュー)約 58 px54 px50 px62 px
項目の中心と文字の中心のずれほぼ 05.5 px 上1.5 px 下1.5 px 下
項目の高さ50 px48 px48 px48 px

原因は 2 つあった。

v0.9.0 ではアイコン欄を外した分そのままにしたので、今度は文字がポップアップの左端に近づきすぎた(17 px)。 「アイコンが無いメニューも少し字下げするのが基本」というフィードバックを受け、v0.9.1 で表題だけを 5 論理 px 右へ寄せてメモ帳と同じ見え方にした(25 px)。項目の余白(ToolStripMenuItem.Padding)でもポップアップの 余白(ToolStripDropDownMenu.Padding)でも左右の位置は動かせないので、FluentMenuRenderer で 文字を描く矩形をずらしている。ショートカットの表示(右寄せ)と、メニュー バーの項目はずらさない。

v0.8.0 のファイル メニュー。文字が右に寄っている
図 11-1:v0.8.0。使っていないアイコン欄の分だけ字下げが深く、文字も少し上に寄っている
v0.9.0 のファイル メニュー。字下げが浅くなった
図 11-2:v0.9.1。メモ帳と同じ位置に文字が来る(チェックの付く表示メニューだけは従来どおり深く字下げする)

12. FB-16:ドラッグ&ドロップで開くと編集済みになる(v0.9.1)

12.1 現象

エクスプローラーから既存のテキスト ファイルを本文の上にドラッグ&ドロップして開くと、開いた直後なのに タイトルに *、タブに●が付いて「編集済み」の状態になる。本文の内容と文字数は正しく、そのまま閉じようとすると 保存の確認ダイアログが出てしまう。メニューの「開く」やエクスプローラーからの「プログラムから開く」では起きない。

ドロップ直後にタブへ●が付いている
図 12-1:v0.9.0 まで。開いた直後なのにタブが●(編集済み)になる
ドロップ直後でも編集済みにならない
図 12-2:v0.9.1。ファイルを開くだけの状態になる

12.2 原因:ドロップを RichEdit にも処理させていた

本文の RichEdit は OLE のドロップ先を自分で登録していて、Windows Forms はそこに相乗りする形で DragDrop イベントを出す。MemoPad はそのイベントでファイルを開いていたが、イベントを出したあとに RichEdit 自身のドロップ処理が続く。プレーン テキスト モードでは実際には何も貼り付けられないものの、 変更通知(EN_CHANGE)だけが上がるため、直前に読み込んだばかりの文書が「編集済み」と判定されていた。

ドロップ先のタブが空の「タイトルなし」だと、そのタブを使い回してファイルを読み込む(メモ帳と同じ挙動)ので、 読み込んだ文書そのものに●が付く。本文に何か書いてある状態で落としたときは別のタブが作られるため、 新しいタブは●にならず、現象が見えにくかった。

12.3 直し方:ドロップ先を自前のものに差し替える

Views/FileDropTarget.csIDropTarget の実装を追加し、RichEdit のハンドルができたところで RevokeDragDropRegisterDragDrop と差し替える。受け付けるのはファイル(CF_HDROP)だけで、 ドロップされたデータは RichEdit に渡さない。これで「ファイルを開くだけ」の動作になる。 ファイルを開く処理自体は BeginInvoke で後回しにして、ドロップの処理中にドラッグ元を待たせないようにした。

落とす場所受け取るものv0.9.1 の動き
本文(RichEdit)FileDropTarget(自前)開くだけ。編集済みにならない
タイトル行・メニュー・ステータスバーフォームの DragDrop(従来どおり)開くだけ
ファイル以外(文字列など)受け付けない(従来どおり)

12.4 確かめ方

エクスプローラーを開いてファイルの項目の位置を UI Automation で取り、mouse_event で実際にドラッグして MemoPad に落とすスクリプトで確認した。自前のウィンドウから DoDragDrop で送る合成のドロップでは、 RichEdit に拒否されて再現しなかった(エクスプローラーが渡すデータには複数の形式が入っているため挙動が違う)。 再現には本物のドラッグが要る、という点も含めての確認になった。

手順v0.9.0v0.9.1
空のタブに本文へドロップ*dd_sample.txt(編集済み)dd_sample.txt
入力済みのタブがある状態で本文へドロップ新しいタブで開く(編集済みにはならない)同じ
タイトル行へドロップdd_sample.txt同じ

13. FB-17:ホイールで最後の行まで届かない(v0.9.2)

13.1 現象

「長いファイルをマウス ホイールでスクロールしたとき、最後の改行までスクロールされなかった。メモ帳では問題なく最後の改行まで届く。些細なことだが気になる」というフィードバック(再現用のファイルも受け取った)。 ファイルの末尾が改行で終わっていると、その改行のあとに空の行が 1 行あり、カーソルもそこへ置ける。メモ帳はホイールでこの空行が見える位置まで送れるが、 MemoPad は最後の文字の行が画面の下端に来たところで止まり、それ以上回しても動かなかった。

ホイールで送り切っても 45 行目が下端に来たところで止まっている
図 13-1:v0.9.1。ホイールを回し続けても 45 行目(最後の行)が下端で止まり、その下の空行は画面の外のまま
45 行目の下に空行が見えるところまで送れている
図 13-2:v0.9.2。45 行目の下の空行まで送れる(メモ帳と同じ)

13.2 原因:RichEdit のホイール処理は末尾の空行を数えない

ホイールで一番下まで送ったときの位置を、スクロールバーの ▼(1 行ずつ送る)や「一番下へ」(SB_BOTTOM)と比べた。 値はスクロール位置(px。表示スケール 150 %、1 行 26 px)。ファイルの末尾の形を 3 通りに変えると、ホイールだけが、最後の改行のあとの空行 1 行分手前で止まることが分かった。

ファイルの末尾ホイール(40 ノッチ)▼ で 1 行ずつ一番下へ(SB_BOTTOM)
改行 1 つ(受け取ったファイル)901(26 px 手前)927927
改行なし901901901
改行 2 つ927(26 px 手前)953953

MemoPad の本文は Windows 標準の RichEdit(msftedit.dll、この端末では 10.0.26100)で、ホイールの処理も RichEdit 自身が行っていた (1 ノッチで 3 行を約 0.3 秒かけて動かす、なめらかスクロール)。メモ帳の本文も RichEdit 系の部品だが、読み込んでいるモジュールを調べると、 アプリに同梱した新しい RichEdit(riched20.dll 16.0、Office 系)だった。こちらではこの癖が直っている。 Office の DLL は再配布できないので、MemoPad の側で対処する。

ホイールで送り切ったときに止まる位置(v0.9.1 と v0.9.2) v0.9.1(RichEdit 自身のホイール処理) 42 行目 … 43 行目 … 44 行目 … 45 行目(最後の文字の行) (末尾の改行のあとの空行) 画面 画面の外 → 最後の文字の行が下端に来たところで止まる (▼ や PageDown なら空行まで届く) v0.9.2(MemoPad が自前で送る) 42 行目 … 43 行目 … 44 行目 … 45 行目(最後の文字の行) (末尾の改行のあとの空行) 画面 → スクロールバーで一番下へ送ったときと  同じ位置(nMax − nPage)まで届く
図 13-3:RichEdit 自身のホイール処理は、末尾の空行を 1 行分数えずに止まる。v0.9.2 ではスクロールバーと同じ上限まで送る

13.3 直し方:ホイールだけ MemoPad が自前でなめらかに送る

Views/SmoothWheelScroller.cs を追加し、PlainTextEditWM_MOUSEWHEEL(Ctrl・Shift を押していないもの)を RichEdit に渡さず、このクラスに回すようにした。Ctrl+ホイールのズームと Shift+ホイールは今までどおり。

項目v0.9.1(RichEdit 自身)v0.9.2(SmoothWheelScroller)
1 ノッチの量3 行(78 px)3 行(78 px)
動き始め約 50 ms 後約 15〜30 ms 後
動き終わり約 0.32 秒(等速、約 30 コマ/秒)約 0.25 秒(止まり際ゆるやか、約 60 コマ/秒)
末尾が改行のファイル空行の 1 行手前で止まる空行まで届く
上へ送り切ったとき先頭から 1〜2 px ずれて止まることがある先頭(0)で止まる
送っている途中で Ctrl+Home先頭へ戻ったあとも残りの動きが続き、113 px の位置で止まる先頭で止まる
タッチパッド相当(delta −1 を 120 回)78 px(1 ノッチと同じ)

13.4 確かめ方

実際のマウス入力(mouse_event)でホイールを回し、別プロセスから GetScrollInfo でスクロール位置を読んで確かめた。 表の値は「ホイールで送り切った位置 / スクロールバーの一番下の位置」。

確認v0.9.1v0.9.2
受け取ったファイル(末尾に改行 1 つ)901 / 927927 / 927
末尾に改行なし/改行 2 つ901 / 901、927 / 953901 / 901、953 / 953
折り返しなし・長い行のファイル(横スクロールバーは v0.9.2 だけ出る、14 節1,525 / 1,5511,577 / 1,577
12 万行:一番下から上へ 3、下へ 10 ノッチ3,119,471 / 3,119,471
12 万行:先頭から 40 ノッチ3,120 px(40 × 78 px どおり)
ズームを 1 段拡大してから 1 ノッチ84 px84 px
Ctrl+ホイール(ズーム)、Shift+ホイール修正前後で同じ動き

「今回の修正で起動時間が増えないか」という確認の依頼があったので、修正前(v0.9.1)と修正後を配布版と同じ条件(self-contained・ReadyToRun)で発行し、 交互に計測した。どちらも差は計測のばらつきの範囲で、増えていない。大きなファイルの読み込みも変わらない。

計測v0.9.1v0.9.2
起動:ウィンドウ表示(10 回の中央値、2 巡)226.5 ms、223.5 ms223 ms、219.5 ms
起動:描画完了(同上)348 ms、333.5 ms335.5 ms、336 ms
英数字 11.8 MB・12 万行を開き、UI が応答できるまで(5 回の中央値)2,011 ms2,017 ms
別に見つかった課題:日本語を含む 10.4 MB・12 万行のファイルを「右端で折り返す」オンで開くと、UI が約 44 秒応答しなくなる。 v0.9.1 でも同じで、今回の修正とは関係がない。このときは「折り返しなしなら約 3 秒」と報告したが、 実機で試すと折り返しのオン・オフに関わらず長時間かかることが分かり、FB-19 として扱うことになった。

14. FB-18:折り返しなしで横にスクロールできない(v0.9.2)

14.1 現象

FB-17 を確かめる途中で見つけた。「右端で折り返す」をオフにすると、長い行は右端で切れたまま横スクロールバーが出ない。 End でカーソルを行末(306 列目)へ動かしても表示が横に動かず、右端から先の文字を見る手段がなかった。 メモ帳は折り返しなしのとき横スクロールバーを出し、カーソルに合わせて横にも送る。本文を RichEdit に置き換えた v0.2.0 からの不具合で、 了承をもらって FB-18 として v0.9.2 で直した。

カーソルは 306 列目にあるが、表示は行頭のままで横スクロールバーもない
図 14-1:v0.9.1。ステータスバーは「列 306」なのに表示は行頭のまま。横スクロールバーも出ない
カーソルの位置まで横に送られ、下に横スクロールバーが出ている
図 14-2:v0.9.2。カーソルの位置まで横に送られ、下に横スクロールバー(右寄りのつまみ)が出る

14.2 原因:Windows Forms の WordWrap が true のままだった

Windows Forms の RichTextBox は、自分の WordWrap プロパティが true(既定)だと、ウィンドウを作るときに 横スクロールバー(WS_HSCROLL)と、カーソルに合わせて横に送る指定(ES_AUTOHSCROLL)を付けない。 MemoPad は折り返しを切り替えても元に戻す履歴が消えないよう、WordWrap を使わず RichEdit に直接 EM_SETTARGETDEVICE を送って切り替えている (WordWrap を変えるとウィンドウが作り直され、履歴が消える)。そのため WordWrap は既定の true のまま残り、 折り返しを外しても横に送る手段がなかった。本文のウィンドウのスタイルを調べると、実際に 2 つとも付いていなかった。

14.3 直し方

14.4 確かめ方

横スクロールバーの有無は、本文の欄の高さ(出ていれば 1 本分低くなる)と縦のスクロール範囲で確かめた(150 %、単位は px)。 長い行は 300 文字 × 80 行、短い行は 1 行 15 文字前後 × 60 行の検証用ファイル。

状態横スクロールバー本文の欄の高さ縦の一番下
長い行・折り返しありで起動なし5619,871
→ 表示メニューで折り返しなしへあり5351,577
→ 折り返しありへ戻すなし5619,871(EM_SHOWSCROLLBAR を入れる前は 9,897 にずれていた)
長い行・折り返しなしで起動あり5351,577
短い行だけ・折り返しなし/ありなし5611,031

15. FB-19:大きなファイルで固まる(v0.10.0 で本文を Scintilla に変更)

15.1 現象

v0.9.2 を実際に使ってもらったところ、次の報告を受けた(2026-09-12)。

2 つ目の「一度固まるとその後も固まる」は、最初の読み込みだけの問題ではないことを示している。 レイアウトの計算結果が持ち越されず、画面を作り直すたびに大きな文書の計算をやり直している可能性が高い。

15.2 対処:先に配布を止める

ユーザーの判断は「固まるという現象を抱えた v0.9.2(以前のバージョンも含め)は、エディタとしての機能を満たしていない」というもの。 これを受けて、原因調査より先に配布の停止を行った。

対象対処
GitHub Releases のインストーラ(v0.6.0〜v0.9.1 の MemoPad-setup-*.exeすべて削除。リリース ページ自体と自動生成のソース zip は残し、本文に停止の理由を明記
紹介ページ・READMEダウンロードのボタンと手順をお知らせに差し替え
備忘録サイト(このページを含む)公開を継続。経過はこの節に追記していく
ソース コード公開を継続

修正が済んで同じファイルが実用的な時間で開けることを確かめたら、あらためて配布を再開する。 → 2026-09-13 に v0.10.0 で配布を再開した15.9)。

15.3 測り直し:報告した数字が違っていた

まず実機と同じ手順(アプリにファイルのパスを渡して起動する)で測り直した。docs/tools/measure-openfile.ps1 を作り、 ウィンドウが出てから WM_NULL に答えられるようになるまでを外から測る。

操作MemoPad v0.9.2 折り返しオン同 折り返しオフメモ帳
開く(応答が戻るまで)43.5 秒87.3 秒0.41 秒
最大化28.6 秒55.7 秒0 秒
元のサイズへ28.6 秒55.7 秒0 秒

13.4 で「折り返しなしなら約 3 秒」と書いたのは誤りで、折り返しなしのほうが倍遅い。 前回はアプリの外から測る手順ではなく、別の条件で測った数字をそのまま載せてしまっていた。 また、1 回のリサイズで 28.6 秒=全文のレイアウト 2 回分かかっている。余白を付ける EM_SETRECTOnResize のたびに呼んでおり、WM_SIZE による再計算に加えてもう 1 回走っていた。

15.4 原因:日本語と英字が混ざった行の「文字幅の問い合わせ」

本文のスレッドの命令ポインタを 2 ms ごとに拾い、どのモジュールで時間を使っているかを集計した (SuspendThreadGetThreadContextResumeThread。win32u.dll のエクスポート表を解析して関数名まで対応付けた)。 30,000 行の読み込み中に 432 サンプル。

関数割合内容
NtGdiGetWidthTable55.3 %GDI の文字幅テーブルの取得
NtGdiGetCharWidthW28.0 %1 文字ずつの幅の取得
NtGdiGetCharSet6.0 %文字セットの問い合わせ
MsftEdit.dll 自身7.2 %RichEdit 本体の処理

つまり 時間のほとんどは RichEdit ではなく、GDI へ文字幅を尋ねるシステム コールだった。 どういうときに遅いのかを、行の中身を変えながら 30,000 行で測って絞り込んだ。

1 行の中身(30,000 行)読み込み
英数字だけ147 ms
日本語だけ228 ms
日本語+英字の混在7,224 ms

日本語と英字が同じ行に混ざると、GDI の文字幅キャッシュが効かなくなって 30〜50 倍遅くなる。 行数に比例して増える(7,500 行 1.8 秒 → 120,000 行 29 秒)。今回のファイルは「空白+行番号+日本語+英字」で、 日本語の実文書ではごくありふれた形なので、まともに直撃していた。

RichEdit 側の設定で回避できないか、次をすべて試したが効果はなかった。

15.5 メモ帳が速い理由と、Windows App SDK を抱える案

メモ帳の本文は RichEditD2DPT というウィンドウ クラスだった。これは Windows App SDK の WinUIEdit.dll(DirectWrite 描画の新しい RichEdit)が提供するもので、msftedit.dll には無い。 「Windows App SDK を同梱すれば使えるのでは」と考えて実際に試した。

試したこと結果
WinUIEdit.dllLoadLibrary読み込めるが RichEditD2DPT は登録されない
DllGetVersion を呼ぶ/エクスポート 24 個を確認クラスを登録する関数は無い
CreateWindowEx("RichEditD2DPT", …)失敗(err=1407 クラスが存在しない)
Windows App SDK 1.8 を参照し、XAML Islands で WinUI を初期化初期化後も登録されない。WinUI の TextBox/RichEditBox を置いても、その名前の子ウィンドウはできない

メモ帳の速さは NotepadTextBox というメモ帳独自の実装によるもので、公開された部品ではなかった。 しかも、同じ 10.4 MB のファイルを WinUI のコントロールに入れると次のとおりで、そもそも速くならない

方式10.4 MB・12 万行
WinUI 3 TextBox28.4 秒(時間の 92 % は WinUIEdit.dll の中)
WinUI 3 RichEditBox52.4 秒
現行 RichEdit(比較)29.0 秒

配布物も 153.6 MB → 251.8 MB(ファイル数 265 → 585)に増え、WinUI の初期化で起動も約 190 ms 延びる。 FB-11 で 505 ms → 221 ms にした成果を失ううえ、肝心の問題が直らないので、この案は捨てた。

15.6 見方を変える:問題は「合計時間」ではなく「止まっている時間」

メモ帳をよく観察すると、縦スクロールの範囲が「行数 × 1 行の高さ」ちょうど(120,001 × 42 = 5,040,042)で、 開いた直後から最後まで変わらなかった。つまりメモ帳は全行を測らずに見積もり、画面に出た行だけ測っている。 だから折り返しがオンでも一瞬で開ける。

これまで(RichEdit / Edit / WinUI):一度に全部計算する レイアウト計算(この間ずっと操作できない) 操作できる → 43 秒の無応答。リサイズのたびに再発 v0.10.0(Scintilla):空き時間に細切れで計算する 操作できる → 合計時間は同じでも、途切れずに操作できる(最長の無応答 34 ms)
図 15-1:同じ計算でも、まとめて行うか空き時間に分けて行うかで「固まる/固まらない」が変わる

そこで計測の指標を「合計時間」から「操作を受け付けない時間の最長」に変え、 docs/tools/measure-freeze.ps1 で測り直した。この指標で候補を比べると次のようになる。

本文のコントロール読み込み中の最長の無応答最大化元のサイズ
メモ帳(参考)0 ms0 ms0 ms
Scintilla0〜22 ms0 ms0 ms
Win32 の Edit コントロール3,800〜28,000 ms同左同左
RichEdit(v0.9.2)43,456 ms19,944 ms20,006 ms

Win32 の Edit コントロールも候補だった(折り返しオフなら 0.2 秒で開ける)が、折り返しオンだと フォント次第で 3.8〜28 秒の無応答が残り、多段階の「元に戻す」も自前で作る必要があった。 Scintilla は折り返しの計算自体には時間がかかる(Consolas で 11〜33 秒)ものの、それを空き時間に分けて進めるので 一度も画面が止まらない

15.7 v0.10.0:本文を Scintilla に差し替えた

採用前に、これまでのフィードバックで作り込んだ 2 つを実測して確かめた。

確認RichEdit(現行)EditScintilla
起動:ウィンドウ表示(同一条件・中央値)148.5 ms119.5 ms167 ms
入力→表示(FB-1。20 回の中央値)16.9 ms16.8 ms16.7 ms

入力レスポンスは 3 つとも計測の下限(画面の 1 フレーム=16.7 ms)に張り付いており、差は無い。 起動は配布版で 229 ms → 256 ms(同じ端末のメモ帳は 202 ms)で、約 30 ms の増加に収まった。

実装で押さえた点は次のとおり。

10.4 MB・12 万行の日本語ファイルを開いた MemoPad v0.10.0 の画面
図 15-2:v0.10.0 で 10.4 MB・12 万行(504 万文字)の日本語ファイルを開いたところ。待たされずに操作できる

15.8 確かめ方

確認v0.9.2v0.10.0
10.4 MB・12 万行を開く:最長の無応答43,456 ms34 ms
同:最大化/元のサイズ19,944 / 20,006 ms0 / 0 ms
起動:ウィンドウ表示(配布版・10 回の中央値)229 ms256 ms
ホイールで最後の空行まで(docs/tools/test-scroll.ps1OKOK
折り返しオフの横スクロール(同上)OKOK
多段階の元に戻す/やり直し(docs/tools/test-editing.ps1OKOK
保存の往復(CRLF/LF の維持、日本語)OKOK(10 項目すべて)
タブのクリック切り替え(docs/tools/test-tabclick.ps1OKOK
インストーラの大きさ51.2 MB54.3 MB(+3.1 MB)
残った課題:本文が UI Automation に出なくなった(ControlType.Pane・パターンなし)。 RichEdit は ControlType.Document + TextPattern/ValuePattern で読めていたので、 スクリーン リーダーなどの支援技術に対しては後退している。Scintilla の SCI_SETACCESSIBILITY は GTK 版だけの機能で Windows では効かない。独自のアクセシブル オブジェクトを返す・WM_GETOBJECT を 自前で処理する・UI Automation の問い合わせに答えず MSAA へ橋渡しさせる、の 3 つを試したが出せなかった。 UI Automation プロバイダー(IRawElementProviderSimpleITextProvider)の自前実装が要るため、 別の課題として扱う。

15.9 v0.10.0 の確認と、開いた直後だけ操作がカクつく件

v0.10.0 を使ってもらい、2026-09-13 に次の返事を受けた。

大きなファイル(big-ja-120k.txt)の開く速度とその後の「固まる」部分は解消した。折り返しあり・なしに関わらずストレスなく起動できるようになった。インストーラの公開は可とする。

これを受けて 15.2 の配布停止を解除し、v0.10.0 のインストーラを GitHub Releases に置いて配布を再開した。 不具合が残る v0.6.0〜v0.9.1 のインストーラは取り下げたままにする。

あわせて、次の確認を受けた。

大きなファイルを開いた時だけ、すぐにウィンドウを移動させたりするとカクカクする。3 秒ほど待ってからウィンドウ移動する分には問題なくスムーズになる。これは遅延読み込みにおける処理をそちらにまわしているから生じることだと推測するがどうか?

推測のとおりだった。Scintilla が折り返し位置の計算を「空き時間に少しずつ」進めている間だけ、ウィンドウの移動がその計算と順番待ちになる。以下は確かめ方と数字。

測り方

アプリにファイルのパスを渡して起動し、別プロセスから 12 ms ごとに次の 3 つを記録した(マウス・キーボードは一切触らない)。

結果

条件忙しい区間その間の CPUその間の往復時間収まったあと
折り返しオン(開いた直後)ウィンドウが出た 0.4 秒後〜4.2 秒後1 コアをほぼ占有(500 ms/500 ms)中央値 9.7 ms(90 % 点 10.0 ms・最大 12.4 ms)CPU 0・往復 0.1 ms
折り返しオフ(開いた直後)なしほぼ 00.3 ms同左
折り返しオン・ウィンドウ幅を変えた直後変更から 4.6 秒1 コアをほぼ占有中央値 10.7 msCPU 0・往復 0.3 ms
10.4 MB・12 万行を開いた直後、UI スレッドが返事をするまでの時間(12 ms ごとに外から計測) Scintilla が折り返しを計算している約 3.8 秒 1 回分の計算=約 10 ms ↑ 開いた瞬間の読み込み(85 ms)。この 1 回だけは縦軸の上限を超える 0 8 16 ms 0 2 秒 4 秒 6 秒 8 秒 折り返しオン(この間はウィンドウ移動がカクつく) 折り返しオフ(計算が要らないので最初から静か) 縦軸は WM_NULL の往復時間。ウィンドウの移動もこれと同じ列に並ぶので、待たされた分だけ動きが飛ぶ
図 15-3:開いた直後の UI スレッドの応答。折り返しオンでは約 4 秒のあいだ「約 10 ms 待たされる」状態が続き、そのあと 0.1 ms に戻る

なぜカクつくのか

Scintilla は折り返し位置の計算を一度にやらず、10 ms ずつに区切って空き時間に進める(Windows 版では 10 ms 間隔のタイマーで 1 回分を回す。タイマーのメッセージは順番待ちの列が空のときだけ届く低優先度のもの)。 計測した往復時間の中央値 9.7 ms・90 % 点 10.0 ms は、この 1 回分の長さがそのまま出たもの。

ウィンドウをドラッグしている間、Windows はアプリと同じスレッドの中で移動用のループを回している。 マウスの移動と折り返しの計算が同じ列に交互に並ぶので、1 コマ描くたびに最大 10 ms 余分に待つ。 画面の 1 コマは 16.7 ms(60 Hz)なので、半分以上を計算に持っていかれる計算になり、動きが飛んで見える。 全行の計算が終わるとタイマーは止まり、往復は 0.1 ms に戻る――ユーザーの言う「3 秒ほど待つとスムーズ」はこれ。

これは FB-19 で RichEdit から Scintilla に替えたときの仕組みそのものでもある。 同じ量の計算を、RichEdit は一度にやって 43 秒固まり、Scintilla は 10 ms ずつに割るので固まらない。 ただし割って進めている間は、他の操作と CPU を分け合うことになる。トレードオフの表と裏の関係にある。

今回のファイルは 1 行 41 文字で、このウィンドウ幅では実際には 1 行も折り返さない(縦スクロールの範囲は 120,000 行のまま変わらない)。 それでも「折り返す必要がない」と判断するには 120,000 行すべての幅を測る必要があるので、同じ約 4 秒がかかる。 折り返しオフでこの区間がまったく無いのは、Scintilla が画面に出ている行の幅しか測らないため。

ここまでで分かったこと

直す場合の候補は次の 4 つ。いずれも入力レスポンス(FB-1)と起動時間(FB-11)に 響きうるため、ユーザーの判断で実測してから決めることにした(結果と採用した案は 16 節)。

ねらい
ⓐ 移動・リサイズ中だけ計算を止めるドラッグしている間は計算を止め、放したら再開する。計算の総量は変えない
ⓑ DirectWrite + レイアウトの別スレッド化(SCI_SETLAYOUTTHREADS計算そのものを別スレッドへ逃がす。Scintilla 5.3 以降の機能(同梱は 5.6.3)
ⓒ 文字幅キャッシュを増やす(SCI_SETPOSITIONCACHE同じ文字の幅を測り直す回数を減らす
ⓓ DirectWrite だけにする文字幅の問い合わせ経路を GDI から変える

16. FB-21:開いた直後のウィンドウ移動がカクつく(v0.10.1)

原因(15.9)が分かったところで、対策の 4 案を切り替えられるビルドを作り、 ドラッグの滑らかさ・入力レスポンス・起動時間・大きなファイルの読み込みを同じ端末で測って比べた。

16.1 ドラッグの滑らかさ

大きなファイルを開いた直後(=折り返しの計算中)にタイトル行を 3 秒ドラッグし、マウスを 8 ms ごとに動かしながら、 ウィンドウの位置が実際に更新される間隔を測った。

条件3 秒間の更新回数間隔の中央値90 % 点最大
参考:小さいファイル(計算が無い状態)16116.7 ms32.4 ms34.1 ms
現行 v0.10.04171.4 ms81.4 ms92.1 ms
ⓐ 移動・リサイズ中だけ計算を止める15716.7 ms32.3 ms35.3 ms
ⓑ DirectWrite + 別スレッド11516.9 ms62.3 ms72.5 ms
ⓐ+ⓑ16116.7 ms32.1 ms34.2 ms
ⓒ 文字幅キャッシュ増4271.4 ms81.8 ms82.2 ms
ⓓ DirectWrite だけ4171.7 ms81.4 ms131.4 ms
ドラッグ中にウィンドウの位置が更新される間隔(中央値・ms。短いほどなめらか) 参考:小さいファイル(計算なし) 16.7 現行 v0.10.0 71.4 ⓐ 移動中だけ計算を止める 16.7 ⓑ DirectWrite + 別スレッド 16.9 ⓒ 文字幅キャッシュ増 71.4 ⓓ DirectWrite だけ 71.7 016.7335067 ※ 16.7 ms = 画面の 1 コマ。3 秒間ドラッグし、マウスは 8 ms ごとに動かして計測
図 16-1:ⓐ は「計算が無い状態」と同じ 16.7 ms まで戻る。ⓒ・ⓓ は効果がない

16.2 入力レスポンス・起動時間・読み込み

「軽快さを損なわない」ことが第一なので、残りの 3 つも測った。入力レスポンスは docs/tools/measure-input.ps1(今回追加。キーを送ってから画面のピクセルが変わるまでを外から測る)、 起動は measure-startup.ps1、読み込みは measure-openfile.ps1

入力→表示
(空文書・20 回の中央値)
起動:ウィンドウ表示/描画完了
(8 回の中央値)
大きなファイルを開く
(応答が戻るまで)
折り返しの計算が
終わるまで
現行 v0.10.014.9 ms267 / 479 ms256 ms4.1 秒
15.5 ms266 / 496 ms273 ms4.1 秒
15.4 ms266 / 483 ms462 ms0.13 秒
ⓐ+ⓑ16.6 ms273 / 469 ms446 ms0.13 秒

入力→表示は 4 つとも画面の 1 コマ(16.7 ms)以内で、差は計測のばらつきの範囲。起動時間も差が無い。 ⓑ だけは大きなファイルを開く時間が 256 ms → 462 ms に増える(DirectWrite の初期化の分)。

ⓑ の副作用:文字の見た目が変わる。同じ文章を GDI と DirectWrite で描いて画素を比べると、 文字の形と位置はほぼ同じだが、にじみ(アンチエイリアス)の出方が違う(サンプルした画素の 10.3 % で差、最大の色差 185)。 メモ帳と同じ見た目を保つ方針なので、これは採用のハードルになる。

16.3 採用:ⓐ(移動・リサイズ中だけ計算を止める)

ドラッグは「計算が無い状態」と同じ数値まで完全に戻り、入力・起動・見た目・読み込み時間はどれも変わらない。 変更も小さく副作用が読みやすいので ⓐ を採用した。ⓑ は「計算そのものを 4.1 秒 → 0.13 秒に縮める」魅力があるが、 描画エンジンが変わる・読み込みが 0.2 秒増える・ドラッグは ⓐ ほど完全にならない、の 3 点で見送った。

実装は 2 か所だけ。Scintilla の空き時間の処理は 10 ms 間隔のタイマー(番号 2)で回っているので、 そのタイマーだけを通さないようにする。

// Views/PlainTextEdit.cs
case WM_TIMER when PauseIdleWork && (int)m.WParam == ScintillaIdleTimerId:
    return;                       // 折り返しの計算を通さない(止めている間だけ)

// Views/MainForm.cs
case WM_ENTERSIZEMOVE:            // 移動・リサイズのループに入った
    foreach (var t in _tabs) t.Editor.Edit.PauseIdleWork = true;
    break;
case WM_EXITSIZEMOVE:             // 放した
    foreach (var t in _tabs) t.Editor.Edit.PauseIdleWork = false;
    break;

画面に見えている範囲の折り返しは描画のときに計算されるので、止めていても表示は正しい。 実際に右端をドラッグして幅を 1500 → 630 px に縮めても、本文は新しい幅で折り返されて表示された。 放した時点で計算は再開し、最後まで終わることも確かめた(3.6 秒で完了)。

16.4 確かめ方

確認v0.10.0v0.10.1
開いた直後のドラッグ:位置の更新間隔(中央値)71.4 ms16.7 ms
同:3 秒間の更新回数41 回159 回
ドラッグ中に計算が止まっているか(WM_NULL の往復)0.74 ms0.01 ms
放したあとに計算が再開して終わるかOK(3.6 秒で完了)
入力→表示(20 回の中央値)14.9 ms15.9 ms
起動:ウィンドウ表示/描画完了(8 回の中央値)267 / 479 ms260 / 445 ms
10.4 MB・12 万行を開く(応答が戻るまで)256 ms363 ms
多段階の元に戻す/やり直し・保存の往復(test-editing.ps1OKOK(10 件すべて)
ホイールで最後の空行まで・折り返しオフの横スクロール(test-scroll.ps1OKOK
タブのクリック切り替え(test-tabclick.ps1OKOK
残った課題:折り返しの計算中に文字を打つと、1 文字出るのに 117〜133 ms かかる(計算が終われば 15.7 ms に戻る)。 ⓑ でも 118 ms で改善しなかった。レイアウトの結果が出るまで描画を待つ必要があるためで、別の課題として扱う。

17. つまずいた点のメモ