バーが消える / 隣のモニタにはみ出す / 撮影中に画面が固まる — Windows スクショツール開発記 #2
先に結論
検索でたどり着いた方向けに、要点だけ。
- バーがドック切替で消えるバグは、新しいバーを「表示する」操作と、表示と非表示の連動を司るところの 2 か所がそれぞれ少しずつ噛み合っていなかった、という重なりだった
- マルチモニタで表示が隣のモニタにはみ出すのは、表示位置の計算が単一モニタを前提にしていたため。所属モニタの範囲に収めるよう調整して解消
- 撮影中に画面が固まるのは、Windows 標準の撮影処理が時間のかかる同期処理だから。撮影を別スレッドに逃がす + 撮影中だとわかる UI を出す、で対処
- ブラウザのスクロール撮影で URL 入力経路は削除した。すでに開いているブラウザに後から繋ぐのは構造的にできないので、撮影手段は 1 系統に絞ったほうが分かりやすい、という判断
連載の他記事
- #1 Claude Code を 10 並列で動かして、PR が 7 本同時に上がった日
- #2 バーが消える / 隣のモニタにはみ出す / 撮影中に画面が固まる(この記事)
- #3 「撮ったのに見えない」を構造で防ぐ — アーキテクチャの話
- #4 40MB を諦めた話と、リリース準備の取捨選択
1. バーがドック切替で消えるバグ
どんな現象だったか
このスクショツールには、画面の上端 / 左端 / 右端のいずれかに張り付く小さなバーがあります。普段はわずかにはみ出した部分だけ見えていて、マウスを近づけるとスライドダウンで本体が出てくる、という UI です。
このバーのドック位置を「上 → 左」のように設定で切り替えると、新しい位置にバーが出てこない、という報告がありました。
何が起きていたか
調べていくと、問題は 2 つ重なっていました。
ひとつは、新しいバーを生成したあとの「表示する」操作が抜けていたこと。中身としては存在しているのに、ウィンドウとして実体化させる手続きが踏まれていなかったので、本人としては「見えているはず」のつもりが見えない、という状態でした。
もうひとつは、ドック切替時に古いバーを片付ける処理と、新しいバーを準備する処理が、ひとつながりに走っていたこと。マウスの位置を見張る仕組み(マウスフック)の取り外しと再設置がほぼ同時に走るので、新しいバーが反応してくれないタイミングがありました。
ひとつ目だけだと「最初の 1 回だけ出ない」、ふたつ目だけだと「ときどき出ない」、両方が組み合わさるので 再現性が安定して『出ない』 という見た目になっていたようです。
直し方
- 切替の処理を 次のフレームに少しだけ遅らせる ようにして、片付けと準備のあいだにひと呼吸入れる(WPF だと
Dispatcher.BeginInvokeで deferred に走らせる、というやつですね) - バーを「使える状態にする」操作の中で、ウィンドウとして実体化していなければ表示する、というステップを足す
これで、ドックを切り替えても、次の瞬間に新しい位置にきちんと出てくるようになりました。
2. マルチモニタで表示が隣のモニタにはみ出す問題
どんな現象だったか
横に 2 枚モニタを並べた環境で、左モニタの右辺にバーを配置すると、わずかに見えている帯の部分が隣の右モニタにはみ出してしまう、という見た目の問題がありました。
何が起きていたか
表示位置の計算が、ひとつのモニタしかない前提で組まれていたのが原因でした。マルチモニタの環境では座標が連続しているので、ある位置 + 幅の組み合わせが、そのまま隣のモニタに突き抜けてしまうことがあります。
直し方
バーが属しているモニタの範囲に、表示位置をきちんと収めるようにしました。これで、はみ出した帯は、所属するモニタの中で完結するようになりました。
このあたりの動作は、マルチモニタを CI 環境で再現するのが難しいので、座標の計算ロジックだけを切り出して、そこに対して単独で動作確認できるようにしておきました。実機で目で確認するパスと、ロジック単体で検証するパスを分けるイメージです。
3. 撮影中に画面が固まる問題
どんな現象だったか
ウィンドウ撮影(指定したアプリのウィンドウだけを画像にする機能)を使うと、ツール自体の画面が数秒間固まる、という体感の問題がありました。
何が起きていたか
ウィンドウ撮影は、内部的に PrintWindow という Windows の API を呼び出して画像を取得します。これが「呼び出したら結果が返ってくるまで待つ」タイプの同期処理で、対象ウィンドウの内容によっては数秒単位で待たされることがあります。
この待ち時間の間、UI スレッド(画面の描画を担当しているスレッド)がほかのことをできなくなるので、ツール自体のウィンドウやバーが固まって見えてしまう、という流れでした。
直し方
2 つの対策を組み合わせました。
ひとつは、撮影処理を別スレッドに逃がすこと。Task.Run でワーカースレッド側に処理を投げて、UI スレッドは生かしておく、いわゆる非同期化ですね。これで、撮影中も画面側は固まらなくなりました。
もうひとつは、撮影中だとわかる小さな UI を出すこと。逃がしただけだと「いま何が起きているのか」がわからなくなるので、撮影中ですよと知らせる小さな表示を出すようにしました。
これで「フリーズしている」と「待っている」の体験差がはっきりして、不安なくウィンドウ撮影を待てるようになった気がします。
4. ブラウザのスクロール撮影で URL 入力経路を削除した話
当初考えていたこと
ブラウザのページ全体を縦長に撮影する機能(スクロール撮影)について、最初は 2 つの経路 を用意するつもりでした。
- すでに開いているブラウザに後から繋いで、いま見ているページをそのまま撮る
- URL を入力ダイアログで受け取って、ツールが自前でブラウザを起動して撮りに行く
1 つ目はそもそも構造的に難しい
ブラウザを外から自動操作する口として CDP(Chrome DevTools Protocol)というのがあるのですが、これを有効にするには起動時に --remote-debugging-port のようなフラグを付けておく必要があります。すでに動いているブラウザに、後からこのフラグを有効化する方法は無さそうでした。セキュリティ上の意図的な制約っぽいですね。
ということは、ユーザーが「いま見ているそのページ」を、ツールから直接撮るのは、構造的に成立しません。
2 つ目も削除した理由
URL を入れて撮るほうは、技術的には動きます。ただ、結局削除しました。
理由をいくつか並べると、
- ログインが必要なページや、Cookie がないと正しく表示されないページが撮れない。結局ユーザーは「ふだんのブラウザで開いてからスクショ」という二段階を踏むことになる
- 撮影手段が「URL 入力」と「いま開いてるブラウザ」の 2 系統に分かれていると、毎回どちらを使うか考えないといけない
- 内部的にも、2 つの経路を別々に育てていくと、修正と機能追加が二重に発生する
そこで、ツール側が 専用のブラウザを別プロファイルで起動して、そこで撮りたいページを開いて撮影する、というやり方に一本化しました。Chrome / Edge を --remote-debugging-port 付きで起動するので、CDP の自動操作も問題なく使えますし、専用プロファイルにログイン状態を貯めれば 2 回目以降はそのまま使い回せます。
撮影フローが 1 系統になって、ユーザーから見ても「専用のブラウザを使う」という単純な約束ごとになりました。
振り返り
この日に上がった機能の実装は、それぞれにこんな手応えがありました。
- タイミングが噛み合わないバグは、原因を完全に特定するより、動きをひと呼吸ずらす方向で試すほうが、自分のケースでは先に状況が変わってくれた気がする
- マルチモニタの問題は、CI で再現するのが難しいので、ロジック部分だけ切り出して単独で確かめられるようにしておくと、自分には扱いやすかった
- 同期で固まる問題は、技術的に逃がすだけでなく、待っているとわかる UI を一緒に置くところまでをワンセットで考えると、自分の中ではしっくりきた
- 機能を削る判断は、実装の難しさよりも「ユーザーの認知負荷」を理由にするほうが、自分の中で納得感が残りやすかった気がします
次の #3 では、これらの実装を支えていた 撮影と保存先の関係をどう設計するか という、少し根っこの話を書きます。「撮ったはずなのに見えない」というバグを、構造的に出にくくするための工夫の話です。