Wie erzwinge ich, dass Windows in meinem Dialogfeld nichts neu zeichnet, wenn der Benutzer die Größe meines Dialogfelds ändert?

Wenn der Benutzer eine Ecke eines Fensters mit geänderter Größe ergreift und es dann verschiebt, verschiebt Windows zuerst den Inhalt des Fensters und gibt dann eine WM_SIZE für das Fenster aus, dessen Größe geändert wird.

In einem Dialogfeld, in dem ich die Bewegung verschiedener untergeordneter Steuerelemente steuern und das Flackern beseitigen möchte, sieht der Benutzer zunächst, wie das Fenster nach Ansicht des Betriebssystems aussehen wird (da das Betriebssystem in AFAICT beim Verschieben einen bitweisen Ansatz verwendet) Dinge im Fenster herum vor dem Senden der WM_SIZE) - und nurdann Hat mein Dialog die Möglichkeit, die untergeordneten Steuerelemente zu verschieben oder ihre Größe zu ändern usw. Danach muss er das Neupainting erzwingen, was nun (zumindest) zu Flimmern führt.

Meine Hauptfrage ist:Gibt es eine Möglichkeit, Windows dazu zu zwingen, dieses blöde Bitblt-Ding NICHT zu tun? Dies ist definitiv falsch, wenn die Größe eines Fensters mit Steuerelementen geändert wird, die sich beim Ändern der Größe des Fensters oder der Größe des übergeordneten Fensters ändern. So oder so, wenn das Betriebssystem einen Voranstrich vornimmt, sind die Arbeiten erledigt.

Ich dachte für eine Weile, dass es mit CS_HREDRAW und CSVREDRAW Klassenflags zusammenhängen könnte. Die Realität ist jedoch, dass ich nicht vom Betriebssystem aufgefordert werden soll, das Fenster zu löschen. Ich möchte mich nur neu malen, ohne dass das Betriebssystem zuerst den Inhalt meines Fensters ändert (dh ich möchte, dass die Anzeige so ist, wie sie war) bevor der Benutzer mit der Größenänderung begann - ohne Bitblit vom Betriebssystem aus). Und ich möchte nicht, dass das Betriebssystem jedem Steuerelement mitteilt, dass es neu gezeichnet werden muss (es sei denn, es wurde tatsächlich durch die Größenänderung verdeckt oder aufgedeckt).

Was ich wirklich will:

Verschieben und Ändern der Größe von untergeordneten SteuerelementenVor Alles wird auf dem Bildschirm aktualisiert.Zeichnen Sie alle verschobenen oder in der Größe geänderten untergeordneten Steuerelemente vollständig, damit sie ohne Artefakte an ihrer neuen Größe und Position angezeigt werden.Zeichnen Sie die Abstände zwischen den untergeordneten Steuerelementen, ohne die untergeordneten Steuerelemente selbst zu beeinflussen.

HINWEIS: Die Schritte 2 und 3 können umgekehrt werden.

Die obigen drei Dinge scheinen korrekt zu passieren, wenn ich DeferSetWindowPos () in Kombination mit der als WS_CLIPCHILDREN gekennzeichneten Dialogressource verwende.

Ich würde einen zusätzlichen kleinen Vorteil erhalten, wenn ich das oben genannte mit einem Speicher-DC ausführen und dann nur ein einzelnes Bitblt am Ende des WM_SIZE-Handlers ausführen könnte.

Ich spiele jetzt schon eine Weile damit und kann zwei Dingen nicht entkommen:

Ich kann Windows immer noch nicht daran hindern, ein "Predictive Bitblt" auszuführen.Antwort: Im Folgenden finden Sie eine Lösung, die WM_NCCALCSIZE überschreibt, um dieses Verhalten zu deaktivieren.

Ich kann nicht sehen, wie man ein Dialogfeld erstellen kann, in dem seine untergeordneten Steuerelemente in einen doppelten Puffer gezeichnet werden.Antwort: Informationen dazu, wie Sie das Windows-Betriebssystem auffordern, den Dialog doppelt zu puffern, finden Sie in der Antwort von John (als Antwort markiert).

Meine endgültige Lösung (Vielen Dank an alle, die dazu beigetragen haben, insbesondere an John K.):

Nach viel Schweiß und Tränen stellte ich fest, dass die folgende Technik sowohl in Aero als auch in XP oder mit deaktiviertem Aero einwandfrei funktioniert. Flicking ist nicht vorhanden (1).

Haken Sie den Dialog Proc.Überschreiben Sie WM_NCCALCSIZE, um Windows zu zwingen, den gesamten Clientbereich zu validieren, und keine Bitblöcke zu erstellen.Überschreiben Sie WM_SIZE, um alle Ihre Bewegungen und Größenänderungen mit BeginDeferWindowPos / DeferWindowPos / EndDeferWindowPos für alle sichtbaren Fenster durchzuführen.Stellen Sie sicher, dass das Dialogfeld den WS_CLIPCHILDREN-Stil aufweist.Verwenden Sie NICHT CS_HREDRAW | CS_VREDRAW (Dialoge nicht, daher im Allgemeinen kein Problem).

Der Layout-Code liegt ganz bei Ihnen - es ist einfach genug, Beispiele für Layout-Manager in CodeGuru oder CodeProject zu finden oder eigene zu erstellen.

Hier sind einige Code-Auszüge, die Ihnen den größten Nutzen bringen sollten:

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);
}

Die Größenänderung wird wirklich vom Resize () -Mitglied durchgeführt, wie folgt:

// 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));
}

Und vielleicht ist das letzte knifflige Teil im Reposition () - Handler von ResizeAgent zu sehen:

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;
}

Das 'schwierige' ist, dass wir es vermeiden, mit zerstörten Fenstern herumzuspielen, und wir versuchen nicht, ein SetWindowPos auf ein nicht sichtbares Fenster zu verschieben (da dies als "wird fehlschlagen" dokumentiert ist).

Ich habe das oben genannte in einem realen Projekt getestet, das einige Steuerelemente verbirgt und ziemlich komplexe Layouts mit ausgezeichnetem Erfolg verwendet. Es gibt auch ohne Aero kein Flackern (1), auch wenn Sie die Größe in der oberen linken Ecke des Dialogfelds ändern (die meisten Fenster mit geänderter Größe zeigen das größte Flackern und die meisten Probleme, wenn Sie diesen Griff greifen - IE, FireFox usw.).

Wenn es genug Interesse gibt, könnte ich überredet werden, meine Ergebnisse mit einer echten Beispielimplementierung für CodeProject.com oder einer ähnlichen Umgebung zu bearbeiten. Schreibe mir.

(1) Bitte beachten Sie, dass es unmöglich ist, ein Unentschieden über das zu vermeiden, was sich dort befand. Für jeden Teil des Dialogs, der sich nicht geändert hat, kann der Benutzer nichts sehen (überhaupt kein Flimmern). Aber wo sich die Dinge geändert haben, ist eine Änderung für den Benutzer sichtbar - dies ist nicht zu vermeiden und eine 100% ige Lösung.

Antworten auf die Frage(8)

Ihre Antwort auf die Frage