現象
スクロールバーは、マウスの左ボタンを離した後でも連続的にスクロールします。 スクロールバーの種類はこの問題には無関係であり、スクロールバーがウィンドウの一部であるか、スクロールバーコントロールであるかに関係なく、同じ問題が発生します。
原因
通常、この問題は、スクロールバーの通知メッセージの1つを受け取るときにスクロールのために実行されたアクションの結果として、メッセージ取得ループが実行されるときに発生します。スクロールするときに、Windows で内部メッセージ取得ループが開始されます。 このメッセージループのタスクでは、スクロールを追跡し、適切なスクロールバーの通知メッセージ、WM_HSCROLL、WM_VSCROLL を送信します。 WM_LBUTTONUP の受信後、スクロールは終了します。 スクロール中に別のメッセージループが開始された場合は、そのメッセージループによって WM_LBUTTONUP が取得されます。また、アプリケーションにはスクロールバーの内部メッセージ取得ループへのアクセス権がないため、WM_LBUTTONUP を正しくディスパッチすることはできません。 そのため、WM_LBUTTONUP は内部メッセージ取得者によって受信されることはありません。スクロールは終了しません。スクロールしているアプリケーションは、この問題を引き起こすために、明示的にメッセージを取得する必要はありません。 次の関数のいずれかを呼び出すか、スクロール中にメッセージ取得ループを含むメッセージを処理すると、WM_LBUTTONUP が失われる可能性があります。 以下の関数は、このカテゴリに分類されます。
DialogBox() DialogBoxIndirect() DialogBoxIndirectParam() DialogBoxParam() GetMessage() MessageBox() PeekMessage()
解決方法
スクロール中は、スクロールバーの内部のメッセージ以外のメッセージ取得ループによって、WM_LBUTTONUP メッセージをキューから取得することはできません。この問題を解決するには、次のようなアプリケーションが考えられます。
-
アプリケーションは、バックグラウンド処理を実装するためのメッセージ取得ループを実装します。たとえば、時間のかかる塗料を実行している間、バックグラウンド処理を実装します。
-
アプリケーションは、別のアプリケーションや DLL との通信を実装するためのメッセージ取得ループを実装します。 たとえば、スクロールするために、アプリケーションは別の場所からデータを受け取る必要があります。
可能な回避策
次の2つの回避策が考えられます。 最初の回避策は、多くのアプリケーションや Windows によって使用されます。ただし、まれな状況では、1つ目の回避策は実現できない場合があります。 この場合は、2つ目の回避策を使用することができます。 ただし、可能であれば、スクロール時にメッセージの取得を完全に実装しないようにしてください。
-
タイマーメッセージベースの処理を使います。 複雑な処理を小さいタスクに分割し、各タスクの開始と終了を把握して、タイマーメッセージに基づいて各タスクを実行します。 処理のすべてのコンポーネントが完了したら、タイマーを強制終了します。 この回避策の例については、以下を参照してください。
-
メッセージ取得ループを実装しますが、WM_LBUTTONUP が取得されていないことを確認します。 これは、フィルターを使用することで実現できます。 この回避策のいくつかの例については、以下を参照してください。
回避策1の例
アプリケーションには複雑な描画手順があります。 ScrollWindow () をスクロールすると、描画メッセージが生成されます。 描画中にバックグラウンド処理が行われます。
-
WM_PAINT メッセージが表示されたら、次の操作を行います。
-
BeginPaint () を呼び出します。
-
無効化された rect を、手順2で使用するグローバル rect 変数 (grcPaint など) にコピーします。 グローバルな rect は、前に取得した rect (grcPaint) と新しい無効化された rect (.ps Paint) の和集合となります。 このコードは、次のようになります。
RECT grcPaint; // Should be initialized before getting the // first paint message. : : UnionRect(&grcPaint, &ps.rcPaint,&grcPaint);
-
ValidateRect () を rcPaint とともに呼び出します。
-
EndPaint () に通話を発信します。
-
タイマーを設定します。
この方法では、無効な領域がなく、タイマーが設定されて WM_TIMER メッセージを生成する WM_PAINT メッセージは生成されません。
-
-
WM_TIMER メッセージを受信したら、大域 rect 変数を確認します。空でない場合は、セクションを撮り、ペイントします。 次に、塗装された領域が含まれなくなるようにグローバル rect 変数を調整します。
-
グローバル rect 変数が空になったら、タイマーを強制終了します。
回避策2の例
アプリケーションは、DDE または他のアプリケーションからの他のメカニズムによってデータを取得する必要があります。これは、ウィンドウに表示されます。 スクロールするために、アプリケーションは、サーバーアプリケーションからデータを要求してから取得する必要があります。Peekmessage 呼び出す () を設定して情報を取得するには、3種類のフィルターを使用できます。 フィルターを設定するには、Peekmessage 呼び出す () の uFilterFirst パラメーターと Ufilterfirst パラメーターを使用します。 uFilterFirst は、チェックする範囲の最初のメッセージを指定し、Ufilterfirst はチェックする範囲内の最後のメッセージを指定します。
-
必要なデータを取得するために、関連メッセージのみを確認して取得します。
-
キューのフォームを削除することなく WM_LBUTTONUP を確認します。キュー内にある場合は、中断します。 それ以外の場合は、すべてのメッセージを取得してディスパッチします。
-
WM_LBUTTONUP 未満で WM_LBUTTONUP よりも大きいメッセージをすべて取得しますが、WM_LBUTTONUP は取得しません。
詳細情報
問題の再現手順
WM_LBUTTONUP メッセージが失われる前のイベントのシーケンスを次に示します。
-
マウスを使用してスクロールバーをクリックします。
-
手順1で WM_NCLBUTTONDOWN メッセージが生成されます。
-
手順2では、Windows の内部メッセージループが開始されます。 このメッセージループは、スクロールバーに関連するメッセージを探します。 このメッセージループの目的は、適切な WM_HSCROLL または WM_VSCROLL メッセージを生成することです。 メッセージループとスクロールは WM_LBUTTONUP が受信されると終了します。
-
WM_HSCROLL または WM_VSCROLL メッセージを受信するときに、アプリケーションは、メッセージ取得ループに直接、またはメッセージを取得するための関数を呼び出します。
-
WM_LBUTTONUP は、手順4で説明したメッセージループによってキューから削除されます。 WM_LBUTTONUP はディスパッチされます。
-
手順5の WM_LBUTTONUP メッセージは、他の場所で送信されます。また、手順3で説明した内部メッセージ取得ループでは、受信しません。 手順3のメッセージループは、スクロールを停止する WM_LBUTTONUP を探しています。 受信されないため、スクロールバーはスクロールを続けます。