ユーザーがサイズ変更可能なウィンドウの隅をつかんで移動すると、ウィンドウは最初にウィンドウの内容を移動し、次にサイズ変更されているウィンドウにWM_SIZEを発行します。
したがって、さまざまな子コントロールの動きを制御したい、ちらつきをなくしたいダイアログでは、ユーザーは最初に、ウィンドウがどのように見えるかをOSがどのように考えるかを確認します(AFAICT、OSは移動にbitbltアプローチを使用しているため) WM_SIZEを送信する前にウィンドウ内にあるもの)-その後、ダイアログが子コントロールの移動やサイズ変更などを処理できるようになります。その後、強制的に再描画する必要があります。これにより、ちらつきが発生します(非常に少しでも)。
私の主な質問は次のとおりです。ウィンドウにこの愚かなbitbltのことを行わないように強制する方法はありますか? ウィンドウのサイズが変更されると移動するコントロール、または親のサイズが変更されるとサイズが変更されるコントロールを備えたウィンドウの場合は、間違いなく間違いになります。いずれにせよ、OSにプレペイントを行わせることは、作品をねじ込むだけです。
しばらくの間、CS_HREDRAWおよびCSVREDRAWクラスフラグに関連しているのではないかと思いました。ただし、実際には、OSからウィンドウの消去を要求されたくないのです。OSが最初にウィンドウの内容を変更せずに、自分で再描画を実行したいだけです(つまり、表示を元の状態にしたいと思います)。ユーザーがサイズ変更を開始する前-OSからのビットブリットなし)。また、OSがすべてのコントロールに再描画する必要があることを通知したくありません(サイズ変更によって実際に隠されたり表示されたりしたものでない限り)。
私が本当に欲しいもの:
- 画面上で何かが更新される前に、子コントロールを移動およびサイズ変更します。
- 移動またはサイズ変更されたすべての子コントロールを完全に描画して、新しいサイズと場所にアーティファクトなしで表示されるようにします。
- 子コントロール自体に影響を与えることなく、子コントロールの間にスペースを描画します。
注:手順2と3は逆にすることができます。
上記の3つのことは、DeferSetWindowPos()をWS_CLIPCHILDRENとしてマークされたダイアログリソースと組み合わせて使用すると正しく発生するように見えます。
上記をメモリDCに対して実行し、WM_SIZEハンドラーの最後で1つのbitbltのみを実行できれば、さらに小さなメリットが得られます。
私はこれでしばらく遊んでいますが、2つのことから逃れることはできません。
私はまだWindowsが「予測bitblt」を実行するのを抑制することができません。 回答:この動作を無効にするためにWM_NCCALCSIZEをオーバーライドするソリューションについては、以下を参照してください。
子コントロールがダブルバッファに描画するダイアログを作成する方法がわかりません。回答:ダイアログをダブルバッファリングするようにWindows OSに要求する方法については、以下のJohnの回答(回答としてマーク)を参照してください(注:ドキュメントによると、これにより、ペイント操作間のGetDC()は許可されません)。
私の最終解決策(特にジョンK.に貢献してくれたすべての人に感謝します):
多くの汗と涙の後、私は次のテクニックがAeroとXPの両方で、またはAeroが無効になっている場合でも問題なく機能することを発見しました。フリックは存在しません(1)。
- ダイアログプロシージャをフックします。
- WM_NCCALCSIZEをオーバーライドして、Windowsにクライアント領域全体を検証させ、bitbltは行わないようにします。
- WM_SIZEをオーバーライドして、表示されているすべてのウィンドウに対してBeginDeferWindowPos / DeferWindowPos/EndDeferWindowPosを使用してすべての移動とサイズ変更を行います。
- ダイアログウィンドウがWS_CLIPCHILDRENスタイルであることを確認します。
- CS_HREDRAW | CS_VREDRAWは使用しないでください(ダイアログは使用しないため、通常は問題になりません)。
レイアウトコードはあなた次第です-レイアウトマネージャーのCodeGuruまたはCodeProjectで例を見つけたり、独自の例を作成したりするのは簡単です。
以下に、ほとんどの方法で使用できるコードの抜粋を示します。
LRESULT ResizeManager::WinProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch (msg)
{
case WM_ENTERSIZEMOVE:
m_bResizeOrMove = true;
break;
case WM_NCCALCSIZE:
// The WM_NCCALCSIZE idea was given to me by John Knoeller:
// see: http://stackoverflow.com/questions/2165759/how-do-i-force-windows-not-to-redraw-anything-in-my-dialog-when-the-user-is-resiz
//
// The default implementation is to simply return zero (0).
//
// The MSDN docs indicate that this causes Windows to automatically move all of the child controls to follow the client's origin
// and experience shows that it bitblts the window's contents before we get a WM_SIZE.
// Hence, our child controls have been moved, everything has been painted at its new position, then we get a WM_SIZE.
//
// Instead, we calculate the correct client rect for our new size or position, and simply tell windows to preserve this (don't repaint it)
// and then we execute a new layout of our child controls during the WM_SIZE handler, using DeferWindowPos to ensure that everything
// is moved, sized, and drawn in one go, minimizing any potential flicker (it has to be drawn once, over the top at its new layout, at a minimum).
//
// It is important to note that we must move all controls. We short-circuit the normal Windows logic that moves our child controls for us.
//
// Other notes:
// Simply zeroing out the source and destination client rectangles (rgrc[1] and rgrc[2]) simply causes Windows
// to invalidate the entire client area, exacerbating the flicker problem.
//
// If we return anything but zero (0), we absolutely must have set up rgrc[0] to be the correct client rect for the new size / location
// otherwise Windows sees our client rect as being equal to our proposed window rect, and from that point forward we're missing our non-client frame
// only override this if we're handling a resize or move (I am currently unaware of how to distinguish between them)
// though it may be adequate to test for wparam != 0, as we are
if (bool bCalcValidRects = wparam && m_bResizeOrMove)
{
NCCALCSIZE_PARAMS * nccs_params = (NCCALCSIZE_PARAMS *)lparam;
// ask the base implementation to compute the client coordinates from the window coordinates (destination rect)
m_ResizeHook.BaseProc(hwnd, msg, FALSE, (LPARAM)&nccs_params->rgrc[0]);
// make the source & target the same (don't bitblt anything)
// NOTE: we need the target to be the entire new client rectangle, because we want windows to perceive it as being valid (not in need of painting)
nccs_params->rgrc[1] = nccs_params->rgrc[2];
// we need to ensure that we tell windows to preserve the client area we specified
// if I read the docs correctly, then no bitblt should occur (at the very least, its a benign bitblt since it is from/to the same place)
return WVR_ALIGNLEFT|WVR_ALIGNTOP;
}
break;
case WM_SIZE:
ASSERT(m_bResizeOrMove);
Resize(hwnd, LOWORD(lparam), HIWORD(lparam));
break;
case WM_EXITSIZEMOVE:
m_bResizeOrMove = false;
break;
}
return m_ResizeHook.BaseProc(hwnd, msg, wparam, lparam);
}
サイズ変更は、実際には次のようにResize()メンバーによって行われます。
// execute the resizing of all controls
void ResizeManager::Resize(HWND hwnd, long cx, long cy)
{
// defer the moves & resizes for all visible controls
HDWP hdwp = BeginDeferWindowPos(m_resizables.size());
ASSERT(hdwp);
// reposition everything without doing any drawing!
for (ResizeAgentVector::const_iterator it = m_resizables.begin(), end = m_resizables.end(); it != end; ++it)
VERIFY(hdwp == it->Reposition(hdwp, cx, cy));
// now, do all of the moves & resizes at once
VERIFY(EndDeferWindowPos(hdwp));
}
そして、おそらく最後のトリッキーなビットは、ResizeAgentのReposition()ハンドラーで見ることができます。
HDWP ResizeManager::ResizeAgent::Reposition(HDWP hdwp, long cx, long cy) const
{
// can't very well move things that no longer exist
if (!IsWindow(hwndControl))
return hdwp;
// calculate our new rect
const long left = IsFloatLeft() ? cx - offset.left : offset.left;
const long right = IsFloatRight() ? cx - offset.right : offset.right;
const long top = IsFloatTop() ? cy - offset.top : offset.top;
const long bottom = IsFloatBottom() ? cy - offset.bottom : offset.bottom;
// compute height & width
const long width = right - left;
const long height = bottom - top;
// we can defer it only if it is visible
if (IsWindowVisible(hwndControl))
return ::DeferWindowPos(hdwp, hwndControl, NULL, left, top, width, height, SWP_NOZORDER|SWP_NOACTIVATE);
// do it immediately for an invisible window
MoveWindow(hwndControl, left, top, width, height, FALSE);
// indicate that the defer operation should still be valid
return hdwp;
}
「トリッキー」とは、破壊されたウィンドウをいじるのを避け、表示されていないウィンドウに対してSetWindowPosを延期しようとしないことです(これは「失敗する」と文書化されているため)。
私は、いくつかのコントロールを非表示にし、かなり複雑なレイアウトを使用して優れた成功を収めている実際のプロジェクトで上記をテストしました。ダイアログウィンドウの左上隅を使用してサイズを変更した場合でも、Aeroがなくてもちらつきはありません(1)(IE、FireFoxなどのハンドルをつかむと、ほとんどのサイズ変更可能なウィンドウで最もちらつきと問題が発生します)。
十分な関心があれば、CodeProject.comまたは同様の場所の実際の実装例を使用して調査結果を編集するように説得することができます。私にメッセージを送ってください。
(1)かつてそこにあったものの上に、1回のドローを避けることは不可能であることに注意してください。変更されていないダイアログのすべての部分について、ユーザーには何も表示されません(ちらつきはまったくありません)。しかし、状況が変わった場合、ユーザーに見える変化があります。これは避けることは不可能であり、100%の解決策です。