Jak wymusić, aby okna nie rysowały niczego w moim oknie dialogowym, gdy użytkownik zmienia rozmiar mojego okna?

Gdy użytkownik chwyta narożnik okna, który można zmienić, a następnie go przesuwa, windows najpierw przenosi zawartość okna, a następnie wysyła WM_SIZE do okna, które jest zmieniane.

Tak więc, w oknie dialogowym, w którym chcę kontrolować ruch różnych kontrolek potomnych i chcę wyeliminować migotanie, użytkownik najpierw widzi, jak System Windows myśli, że okno będzie wyglądać (ponieważ, AFAICT, system operacyjny używa podejścia bitblt do przenoszenia rzeczy wewnątrz okna przed wysłaniem WM_SIZE) - i tylko wtedy {[7] } moje okno dialogowe może obsługiwać przesuwanie kontrolek potomnych wokół, lub zmieniać ich rozmiar, itp., po czym musi zmusić rzeczy do przemalowania, co teraz powoduje migotanie(przynajmniej).

Moje główne pytanie brzmi: czy istnieje sposób, aby zmusić windows, aby nie robił tego głupiego bitblt? to zdecydowanie będzie źle w przypadku okna z kontrolkami, które poruszają się w miarę zmiany rozmiaru okna, lub które zmieniają rozmiar w miarę zmiany rozmiaru ich rodzica. Tak czy siak, posiadanie systemu operacyjnego do wstępnego malowania wystarczy wkręcić prace.

Przez pewien czas myślałem, że może to być związane z flagami klas CS_HREDRAW i CSVREDRAW. Jednak rzeczywistość jest taka, że nie chcę, aby system operacyjny poprosił mnie o wymazanie okna - chcę tylko zrobić przemalowanie samodzielnie bez uprzedniej zmiany zawartości mojego okna przez system operacyjny (tj. chcę, aby wyświetlacz był taki, jaki był, zanim użytkownik zaczął zmieniać rozmiar - bez bitblit ' in z systemu operacyjnego). I nie chcę, żeby system operacyjny mówił każdej kontroli, że to trzeba też przeformułować (chyba, że stało się to takie, które w rzeczywistości zostało zasłonięte lub ujawnione przez zmianę rozmiaru.

What I really want:

  1. aby przenieść & zmienić rozmiar kontrolek potomnych przed wszystko jest aktualizowane na ekranie.
  2. narysuj całkowicie wszystkie elementy sterujące przeniesione lub zmienione, aby wyglądały bez artefaktów w nowym rozmiarze i lokalizacji.
  3. rysowanie spacji pomiędzy kontrolkami potomnymi bez wpływu na kontrolki potomne siebie.

Uwaga: kroki 2 i 3 można odwrócić.

Powyższe trzy rzeczy wydają się dziać poprawnie, gdy używam defersetwindowpos () w połączeniu z zasobem dialogowym oznaczonym jako WS_CLIPCHILDREN.

Otrzymałbym dodatkową małą korzyść, gdybym mógł wykonać powyższe dla pamięci DC, a następnie wykonać tylko jeden bitblt na końcu obsługi WM_SIZE.

Grałem z tym już od jakiegoś czasu i nie mogę uciec od dwóch rzeczy:

  1. I still nie jestem w stanie powstrzymać Windows od robienia "predykcyjnego bitblt". odpowiedź: zobacz poniżej rozwiązanie, które nadpisuje WM_NCCALCSIZE, aby wyłączyć to zachowanie.

  2. Nie widzę, jak można zbudować okno dialogowe, w którym jego kontrolki potomne rysują się w podwójnym buforze. odpowiedź: Zobacz odpowiedź Johna (oznaczoną jako odpowiedź) poniżej, jak poprosić SYSTEM OPERACYJNY Windows o dwukrotne buforowanie okna dialogowego (uwaga: to wyklucza jakiekolwiek operacje GetDC () pomiędzy farbą, zgodnie z docs).


Moje ostateczne rozwiązanie (dziękuję wszystkim, którzy przyczynili się, esp. John K.):

Po długim pocie i łzach odkryłem, że poniższa technika działa bez zarzutu, zarówno w Aero, jak i w XP lub z aero wyłączonym. Flicking nie istnieje (1).

  1. Hook The dialog proc.
  2. nadpisuje WM_NCCALCSIZE, aby wymusić, aby System Windows walidował cały obszar klienta, a nie cokolwiek bitblt.
  3. Override WM_SIZE to do all of your moves & zmienia rozmiar za pomocą BeginDeferWindowPos/DeferWindowPos/EndDeferWindowPos dla wszystkich widocznych okien.
  4. upewnij się, że okno dialogowe ma styl WS_CLIPCHILDREN.
  5. nie używaj CS_HREDRAW / CS_VREDRAW(okna dialogowe nie, więc generalnie nie jest to problem).

Kod układu zależy od ciebie - łatwo jest znaleźć przykłady na CodeGuru lub CodeProject menedżerów układu, lub samodzielnie je zwijać.

Oto kilka fragmentów kodu, które powinny dać ci większość sposób:

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

Zmiana rozmiaru jest naprawdę wykonywana przez członka Resize (), w ten sposób:

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

I być może ostatni tricky bit można zobaczyć w obsłudze repozycji ResizeAgent:

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

"tricky" polega na tym, że unikamy prób namieszania wszystkich zniszczonych okien i nie staramy się odkładać SetWindowPos na okno, które nie jest widoczne (jak to jest udokumentowane jako "zawiedzie".

Przetestowałem powyższe w prawdziwym projekcie, który ukrywa niektóre kontrolki i korzysta z dość złożonych układów z doskonałym powodzeniem. Nie ma migotania(1) nawet bez Aero, nawet przy zmianie rozmiaru za pomocą lewego górnego rogu okna dialogowego (większość okien z możliwością zmiany rozmiaru wyświetli najwięcej migotania i problemów podczas chwytania tego uchwytu - IE, FireFox, itp.).

Jeśli jest wystarczająco interesujące, można mnie przekonać do edycji moich ustaleń z prawdziwym przykładem realizacji dla CodeProject.com albo gdzieś podobnego. Wyślij mi wiadomość.

(1) Należy pamiętać, że nie da się uniknąć jednego remisu na szczycie tego, co kiedyś tam było. Dla każdej części okna dialogowego, która się nie zmieniła, użytkownik nie widzi nic (nie ma migotania). Ale tam, gdzie rzeczy się zmieniły, jest zmiana widoczna dla użytkownika - jest to niemożliwe do uniknięcia i jest rozwiązaniem w 100%.

Author: Mordachai, 2010-01-30

7 answers

Nie możesz zapobiec malowaniu podczas zmiany rozmiaru, ale możesz (ostrożnie)zapobiec przemalowaniu , z którego pochodzi migotanie. najpierw bitblt.

Są dwa sposoby na powstrzymanie bitblt.

Jeśli posiadasz klasę okna najwyższego poziomu, po prostu zarejestruj ją za pomocą stylów CS_HREDRAW | CS_VREDRAW. Spowoduje to zmianę rozmiaru okna, aby unieważnić cały obszar klienta, zamiast próbować odgadnąć, które bity się nie zmienią i bitblting.

Jeśli nie jesteś właścicielem klasy, ale masz możliwość kontrolowania obsługi wiadomości(prawda dla większości okien dialogowych). Domyślne przetwarzanie WM_NCCALCSIZE jest tam, gdzie obsługiwane są Style klas CS_HREDRAW i CS_VREDRAW, domyślnym zachowaniem jest zwracanie WVR_HREDRAW | WVR_VREDRAW z przetwarzania WM_NCCALCSIZE, gdy klasa ma CS_HREDRAW | CS_VREDRAW.

Więc jeśli możesz przechwycić WM_NCCALCSIZE, możesz wymusić zwrot tych wartości po wywołaniu DefWindowProc, aby wykonać inne normalne przetwarzanie.

Możesz posłuchać WM_ENTERSIZEMOVE i WM_EXITSIZEMOVE aby dowiedzieć się, kiedy zaczyna się i zatrzymuje zmiana rozmiaru okna, i użyj tego, aby tymczasowo wyłączyć lub zmodyfikować sposób działania rysunku i / lub kodu układu, aby zminimalizować miganie. To, co dokładnie chcesz zrobić, aby zmodyfikować ten kod, zależy od tego, co twój normalny kod normalnie robi w WM_SIZE WM_PAINT i WM_ERASEBKGND.

Kiedy malujesz tło okna dialogowego, musisz , a nie malować za którymkolwiek z okien potomnych. upewnienie się, że okno dialogowe ma WS_CLIPCHILDREN rozwiązuje to, więc zajmijcie się tym.

Gdy przesuniesz okna potomne, upewnij się, że używasz BeginDeferWindowPos / EndDefwindowPos, aby wszystkie przemalowania miały miejsce na raz. W przeciwnym razie otrzymasz kilka migających, gdy każde okno ponownie rysuje swój obszar nonclient na każdym wywołaniu SetWindowPos.

 13
Author: John Knoeller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-01 20:31:17

Jeśli dobrze zrozumiałem pytanie, to jest to dokładnie Pytanie Raymond odpowiedział dzisiaj .

 4
Author: GSerg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-30 00:58:49

Dla niektórych kontrolek, można użyć wiadomości WM_PRINT, aby wprowadzić kontrolkę do DC. Ale to tak naprawdę nie rozwiązuje Twojego głównego problemu, którym jest to, że chcesz, aby System Windows nie rysował niczego podczas zmiany rozmiaru, ale pozwalał ci to wszystko zrobić.

A odpowiedź jest taka, że nie możesz robić tego, co chcesz, dopóki masz okna potomne.

Sposób, w jaki ostatecznie rozwiązałem to w moim własnym kodzie, polega na przełączeniu się na użycie Kontroli bez okien . Ponieważ nie mają okna swojego własne, zawsze rysują w tym samym czasie (i w tym samym DC) co ich okno nadrzędne. Pozwala mi to na użycie prostego podwójnego buforowania, aby całkowicie usunąć migotanie. Mogę nawet trywialnie stłumić malowanie dzieci, gdy muszę po prostu przez , a nie wywoływanie ich procedury rysowania wewnątrz procedury rysowania rodziców.

To jedyny znany mi sposób na całkowite pozbycie się migotania i rozdarcia podczas operacji zmiany rozmiaru.

 1
Author: John Knoeller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-02 22:33:46

Jeśli znajdziesz miejsce do podłączenia, CWnd::LockWindowUpdates() zapobiega powstawaniu rysunków do momentu odblokowania aktualizacji.

Ale pamiętaj, że to jest hack, i to dość brzydki. Twoje okno będzie wyglądać okropnie podczas zmiany rozmiaru. Jeśli problem, który masz jest migotanie podczas zmiany rozmiaru, to najlepszą rzeczą do zrobienia jest zdiagnozowanie migotania, zamiast ukrywania migotania przez blokowanie farb.

Jedną rzeczą, której należy szukać, są polecenia przerysowywania, które są wywoływane zbyt często podczas zmiany rozmiaru. Jeśli kontrolki okna r wywołują RedrawWindow() z podaną flagą RDW_UPDATENOW, Zostanie ona przemalowana wtedy i tam. Można jednak usunąć tę flagę i zamiast tego podać RDW_INVALIDATE, co nakazuje kontrolce unieważnienie okna bez ponownego malowania. Będzie przemalowywać w czasie bezczynności, zachowując świeżość wyświetlacza bez spazzingu.

 0
Author: John Dibling,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-29 23:06:32

Istnieją różne podejścia, ale znalazłem jedyną, która może być używana ogólnie jest podwójne buforowanie: narysuj do bufora poza ekranem, a następnie przerzuć cały bufor na ekran.

To jest za darmo w Vista Aero i wyżej, więc ból może być krótkotrwały.

Nie znam ogólnej implementacji podwójnego buforowania dla windows i kontroli systemu pod XP, jednak oto kilka rzeczy do zbadania:

Keith Rule ' s CMemDC do podwójnego buforowania wszystkiego, co narysuj się z GDI
WS_EX_COMPOSITED Window style (patrz sekcja uwagi, i coś tutaj na stackoverflow )

 0
Author: peterchen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:35

Jest tylko jeden sposób, aby skutecznie diagnozować problemy z malowaniem-zdalne debugowanie.

Pobierz drugi komputer. Zainstaluj na nim MSVSMON. Dodaj krok po kompilacji lub projekt narzędzia, który kopiuje produkty kompilacji na zdalnym komputerze.

Teraz powinieneś być w stanie umieścić punkty przerwania w programach obsługi WM_PAINT, programach obsługi WM_SIZE i tak dalej i faktycznie prześledzić kod okna dialogowego podczas wykonywania rozmiaru i przerysowania. Po pobraniu symboli z serwerów MS symbol będziesz mógł zobaczyć pełną zadzwoń do stacks ' a.

Kilka dobrze umieszczonych punktów przerwania-w programach obsługi WM_PAINT, WM_ERAGEBKGND i powinieneś mieć dobry pomysł, dlaczego twoje okno jest synchronicznie przemalowywane na początku cyklu WM_SIZE.

Istnieje wiele okien w systemie, które składają się z okna nadrzędnego z warstwowymi kontrolkami potomnymi - okna Eksploratora są ogromnie skomplikowane z panelami podglądu listviews, treeviews itp. Explorer nie ma problemu z migotaniem przy zmianie rozmiaru, więc można uzyskaj wolną od migotania zmianę rozmiaru okien rodzicielskich: - to, co musisz zrobić, to złapać przemalowania, dowiedzieć się, co je spowodowało, i, cóż, upewnij się, że przyczyna została usunięta.

 0
Author: Chris Becke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-30 11:34:05

Co wydaje się działać:

  1. Użyj WS_CLIPCHILDREN w oknie dialogowym rodzica (można ustawić w WM_INITDIALOG)
  2. podczas WM_SIZE, Pętla przez kontrolki potomne poruszające się i zmieniające ich rozmiar za pomocą DeferSetWindowPos ().

To jest bardzo blisko ideału, w moich testach pod Windows 7 z Aero.

 0
Author: Mordachai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-01 20:07:31