2010-01-29 14 views
30

Quando l'utente acquisisce un angolo di una finestra ridimensionabile e quindi lo sposta, Windows sposta dapprima il contenuto della finestra, quindi emette un WM_SIZE sulla finestra da ridimensionare.Come faccio a forzare Windows a non ridisegnare nulla nella mia finestra di dialogo quando l'utente sta ridimensionando la mia finestra di dialogo?

Così, in una finestra di dialogo in cui voglio controllare il movimento di vari controlli figlio, e voglio eliminare lo sfarfallio, l'utente prima vede quale sistema operativo Windows pensa che la finestra avrà l'aspetto (perché, AFAICT, il sistema operativo usa un approccio bitblt allo spostamento di cose all'interno della finestra prima di inviare WM_SIZE) - e solo poi la mia finestra di dialogo può gestire lo spostamento dei suoi controlli figli, o ridimensionarli, ecc., dopo di che deve forzare le cose a ridisegnare, che ora causa sfarfallio (per lo meno).

La mia domanda principale è: C'è un modo per forzare Windows a non fare questa stupida cosa amara? Sicuramente sarà sbagliato nel caso di una finestra con controlli che si muovono man mano che la finestra viene ridimensionata, o che si ridimensionano come il loro genitore viene ridimensionato. In entrambi i casi, avendo il sistema operativo fare una pre-vernice, basta avvitare i lavori.

Ho pensato per un momento che potrebbe essere correlato ai flag di classe CS_HREDRAW e CSVREDRAW. Tuttavia, la realtà è che non voglio che il sistema operativo mi chieda di cancellare la finestra - voglio solo ridipingere me stesso senza che il sistema operativo modifichi prima il contenuto della mia finestra (cioè voglio che il display sia quello che era prima che l'utente iniziasse il ridimensionamento, senza alcun bitblit dal sistema operativo). E io non voglio che il sistema operativo di dire ogni controllo che ha bisogno di essere ridisegnato o (a meno che non è capitato di essere uno che è stato di fatto oscurato o rivelato dalla ridimensionamento

Quello che voglio veramente:.

  1. Per spostare & ridimensionamento bambini controlli prima nulla viene aggiornato sullo schermo.
  2. disegnare tutti del bambino spostati o ridimensionati controlla completamente in modo che appaiano senza artefatti alla loro nuova dimensione & posizione.
  3. Disegna gli spazi tra i controlli figlio senza influire sui controlli secondari.

NOTA: i passaggi 2 e 3 potrebbero essere invertiti.

Le tre cose sopra riportate si verificano correttamente quando utilizzo DeferSetWindowPos() in combinazione con la risorsa di dialogo contrassegnata come WS_CLIPCHILDREN.

Avrei un ulteriore piccolo vantaggio se potessi fare quanto sopra a un controller di memoria, e quindi fare solo un singolo bitblt alla fine del gestore WM_SIZE.

Ho giocato con questo per un po 'di tempo, e non posso scappare due cose:

  1. io sono ancora in grado di sopprimere Windows da fare un 'bitblt predittivo'. Risposta: vedere sotto per una soluzione che sovrascriva WM_NCCALCSIZE per disabilitare questo comportamento.

  2. Non riesco a vedere come si possa costruire una finestra di dialogo in cui il figlio controlla un doppio buffer. Risposta: Vedere la risposta di John (contrassegnata come risposta) di seguito per come chiedere al sistema operativo Windows di raddoppiare il buffer della finestra di dialogo (nota: questa operazione non consente l'uso di GetDC() tra le operazioni di disegno, in base ai documenti).


La mia soluzione finale (Grazie a tutti coloro che hanno contribuito, specialmente John K..):

Dopo tanto sudore e lacrime, ho trovato che la seguente tecnica funziona perfettamente, sia in Aero e in XP o con Aero disabilitato. Il flicking è inesistente (1).

  1. Aggancia la finestra di dialogo proc.
  2. Ignora WM_NCCALCSIZE per forzare Windows a convalidare l'intera area client e non bitblt.
  3. Ignora WM_SIZE per eseguire tutte le tue mosse & ridimensiona utilizzando BeginDeferWindowPos/DeferWindowPos/EndDeferWindowPos per tutte le finestre visibili.
  4. Assicurarsi che la finestra di dialogo abbia lo stile WS_CLIPCHILDREN.
  5. NON utilizzare CS_HREDRAW | CS_VREDRAW (le finestre di dialogo non lo fanno, quindi in genere non è un problema).

Il codice di layout è a vostra discrezione, è abbastanza facile trovare esempi su CodeGuru o CodeProject di gestori di layout o eseguire il rollover.

Ecco alcuni stralci di codice che dovrebbe ottenere la maggior parte del modo:

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

Il ridimensionamento è davvero fatto dal membro Resize(), in questo modo:

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

E forse il po 'complicato finale può essere visto in Riposizionare del ResizeAgent() gestore:

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

il 'ingannevole' essere che evitiamo di rovinare le finestre che sono state distrutte e non proviamo a rimandare un SetWindowPos contro una finestra che non è visibile (poiché ciò è documentato come "fallirà").

Ho testato quanto sopra in un progetto reale che nasconde alcuni controlli e utilizza layout abbastanza complessi con un eccellente successo. C'è zero sfarfallio (1) anche senza Aero, anche quando si ridimensiona usando l'angolo in alto a sinistra della finestra di dialogo (la maggior parte delle finestre ridimensionabili mostrerà il maggior sfarfallio e problemi quando si afferra quel manico - IE, FireFox, ecc.).

Se c'è interesse sufficiente, potrei essere persuaso a modificare i miei risultati con un'implementazione di esempio reale per CodeProject.com o qualcosa di simile. Scrivimi.

(1) Si noti che è impossibile evitare un pareggio sopra a quello che era solito essere lì. Per ogni parte della finestra di dialogo che non è stata modificata, l'utente non può vedere nulla (nessun sfarfallio di sorta). Ma dove le cose sono cambiate, c'è un cambiamento visibile all'utente - questo è impossibile da evitare, ed è una soluzione al 100%.

+1

Questa tecnica non funziona più con Win10 (e probabilmente non funziona con Win8). Triste. Siamo di nuovo a guardare i controlli "camminano" attraverso lo schermo fino a dove dovrebbero essere, invece di saltare istantaneamente nella loro posizione corretta. – Mordachai

+0

Dato che hai chiesto, certo, mi piacerebbe vedere un'implementazione di esempio reale. Questo è qualcosa che mi interessa, specialmente se si tratta di una soluzione completa winapi (cioè non MFC). Anche MFC sarebbe interessante da leggere. Eventuali aggiornamenti per Windows 10? – jrh

+1

@jrh Tristemente, Windows 8+ ha infranto questa tecnica, e non sono stato ispirato a capire come farlo funzionare di nuovo (se è anche possibile). Quanto sopra ha funzionato da XP, Vista, Win 7. Ma 8+ lo rende semplicemente stupido come lo era senza tutto questo sforzo. – Mordachai

risposta

13

non si può impedire la pittura durante il ridimensionamento, ma è possibile (con cura) prevenire riverniciatura che è dove sfarfallio proviene. prima, il bitblt.

Ci sono due modi per fermare la cosa bitblt.

Se si possiede la classe della finestra di livello superiore, è sufficiente registrarla con gli stili CS_HREDRAW | CS_VREDRAW. Ciò causerà un ridimensionamento della finestra per invalidare l'intera area client, piuttosto che cercare di indovinare quali bit non verranno modificati e bitblting.

Se non si possiede la classe, ma si ha la possibilità di controllare la gestione dei messaggi (vero per la maggior parte delle finestre di dialogo). L'elaborazione predefinita di WM_NCCALCSIZE è dove vengono gestiti gli stili di classe CS_HREDRAW e CS_VREDRAW, Il comportamento predefinito è di restituire WVR_HREDRAW | WVR_VREDRAW dall'elaborazione di WM_NCCALCSIZE quando la classe ha CS_HREDRAW | CS_VREDRAW.

Quindi, se è possibile intercettare WM_NCCALCSIZE, è possibile forzare il ritorno di questi valori dopo aver chiamato DefWindowProc per eseguire l'altra elaborazione normale.

È possibile ascoltare WM_ENTERSIZEMOVE e WM_EXITSIZEMOVE per sapere quando si avvia e si arresta il ridimensionamento della finestra e utilizzarlo per disattivare o modificare temporaneamente il modo in cui il disegno e/o il codice di layout funzionano per ridurre al minimo il lampeggiamento. Che cosa esattamente vuoi fare per modificare questo codice dipenderà da ciò che il tuo normale codice normalmente fa in WM_SIZEWM_PAINT e WM_ERASEBKGND.

Quando si dipinge lo sfondo della finestra di dialogo, è necessario non dipingere dietro a qualsiasi finestra secondaria. assicurandosi che la finestra di dialogo abbia WS_CLIPCHILDREN risolve questo problema, quindi è già stato gestito.

Quando si spostano le finestre secondarie, assicurarsi di utilizzare BeginDeferWindowPos/EndDefwindowPos in modo che tutto il ridisegno avvenga contemporaneamente. Altrimenti si otterrà un po 'di flashing in quanto ogni finestra ridisegna la loro area non client su ogni chiamata SetWindowPos.

+1

Esiste una tecnica per scegliere una classe Windows quando si crea una finestra di dialogo modale, invece di scegliere il sistema operativo standard? Posso certamente collegare il wndproc della finestra di dialogo, ma c'è un modo migliore? – Mordachai

+1

Ok, non sono sicuro che questo possa aiutare. Le opzioni a mia disposizione sembrano essere "preservare la finestra in modo corretto (ci sono diverse opzioni di allineamento), o ridisegnare tutto". Non voglio che Windows lo ridisegni completamente, ma non voglio che lo ridisegni necessariamente tutto - fammelo fare (forse posso convalidare le cose nel gestore WM_SIZE per contrastare il WVR_REDRAW. – Mordachai

+1

@Mordachai: Tu posso creare una classe di finestre e usare DefDialogProc invece di DefWindowProc, ma è qualcosa che nessuno fa mai, non sono sicuro del perché, ma sospetto che quando lo fai, le cose sottili vanno storte –

0

Se è possibile trovare un luogo per collegarlo, CWnd::LockWindowUpdates() impedirà l'esecuzione di qualsiasi disegno fino a dopo aver sbloccato gli aggiornamenti.

Ma tieni a mente che si tratta di un trucco, e piuttosto brutto. La tua finestra sembrerà terribile durante il ridimensionamento. Se il problema che si verifica è sfarfallio durante il ridimensionamento, la cosa migliore da fare è diagnosticare lo sfarfallio, piuttosto che nascondere lo sfarfallio bloccando le vernici.

Una cosa da cercare sono i comandi di ridisegno che vengono richiamati troppo spesso durante il ridimensionamento. Se i controlli della finestra r stanno chiamando RedrawWindow() con il flag RDW_UPDATENOW specificato, verrà ridisegnato allora e lì. Ma puoi togliere quel flag e specificare invece RDW_INVALIDATE, che dice al controllo di invalidare la finestra senza ridipingere. Si ridisegnerà al minimo, mantenendo il display fresco senza spazzare via.

+0

LockWindowUpdate aiuta contro le pitture ripetute dello stesso controllo, ma non si sovrappone a pitture di controlli diversi (tutte ricevono un WM_PAINT quando sbloccate), quindi aiuta in alcuni ma non in tutti gli scenari. Vedi anche qui: http://blogs.msdn.com/oldnewthing/archive/2007/02/19/1716211.aspx – peterchen

+0

Se I LockWindowUpdate() all'inizio del ridimensionamento/spostamento, e provare a sbloccarlo per ogni WM_SIZE iterazione: fa sì che sia impossibile vedere la finestra in movimento, né vedere nemmeno una singola iterazione di un ridimensionamento. Blocca l'intera finestra, inclusa la sua cornice, non solo il suo contenuto. quindi questo è effettivamente un vicolo cieco (per i miei scopi). – Mordachai

0

Ci sono vari approcci, ma ho trovato che l'unico che può essere utilizzato in genere è il doppio buffering: disegnare su un buffer fuori schermo, quindi miscelare l'intero buffer allo schermo.

Questo viene gratuitamente in Vista Aero e sopra, quindi il tuo dolore potrebbe essere di breve durata.

Non sono a conoscenza di un'implementazione generale doppio buffer per le finestre ei comandi del sistema sotto XP, tuttavia, qui ci sono alcune cose da esplorare:

Keith Rule's CMemDC per il doppio buffer qualsiasi cosa si disegna te stesso con GDI
WS_EX_COMPOSITED Window style (vedere la sezione Osservazioni, e qualcosa here on stackoverflow)

+0

Non sono chiaro su come una finestra genitore può forzare le sue finestre figlio a disegnarsi sul suo doppio buffer? – Mordachai

+0

@Mordachai: in realtà questo potrebbe funzionare. _Impre tutti i discendenti di una finestra nell'ordine di pittura dal basso verso l'alto usando discendenti double-buffering_ dovrebbero significare finestre secondarie. Non è necessario eseguire alcun doppio buffering esplicito, Windows lo fa per te. –

4

Se ho capito bene la domanda, è esattamente la domanda Raymond addressed today.

+0

Sfortunatamente, la tecnica descritta da Raymond presuppone che la finestra non abbia finestre secondarie. –

+0

@John: Non potrebbe essere applicato anche se ci sono finestre figlio? Windows non sa automaticamente come ridimensionare le finestre figlio. L'applicazione potrebbe scegliere di rinviare quelle ridimensionate. – jamesdlin

+0

@jamesdlin: La pittura di finestre figlio non è sotto controllo del genitore, quindi no, la tecnica non funziona se ci sono finestre figlio a meno che non si facciano cose complicate come nasconderle quando inizia il ridimensionamento. –

0

c'è solo un modo per diagnosticare efficacemente i problemi di riverniciatura - debug remoto.

Ottieni un secondo PC. Installa MSVSMON su di esso. Aggiungi un passaggio di post-produzione o un progetto di utilità che copia i tuoi prodotti di costruzione sul PC remoto.

Ora si dovrebbe essere in grado di posizionare i punti di interruzione nei gestori WM_PAINT, i gestori WM_SIZE e così via e effettivamente tracciare il codice di dialogo mentre esegue le dimensioni e il ridisegno. Se si scaricano simboli dai server dei simboli MS, sarà possibile visualizzare pile di chiamate complete.

Alcuni punti di interruzione posizionati correttamente nei gestori WM_PAINT, WM_ERAGEBKGND e si dovrebbe avere una buona idea del motivo per cui la finestra viene ridisegnata in modo sincrono durante il ciclo WM_SIZE.

Ci sono molte finestre nel sistema che consistono in una finestra genitore con controlli figlio sovrapposti: le finestre di explorer sono pesantemente complicate con listview, pannelli di anteprima di treeview ecc. Explorer non ha un problema di sfarfallio sul ridimensionamento, quindi è è possibile ottenere il ridimensionamento delle finestre principali senza sfarfallio: - ciò che devi fare è catturare i ridisegni, capire cosa li ha causati e, beh, assicurarsi che la causa venga rimossa.

+0

Avete trovato che il codice di disegno di debug è migliorato avendo una macchina remota semplicemente avendo un secondo monitor, ed eseguendo il debugger su un monitor, e l'app su un'altra? Li ho trovati per essere equivalenti. – Mordachai

+0

Inoltre, le mie osservazioni, che eseguono Windows 7 con Aero, sono che molte applicazioni eseguono il disegno come se tutti i controlli si muovessero, quindi si ridisegnano con loro nella posizione corretta. Windows Explorer è un'eccezione notevole, che mostra quasi nessun ritardo di ridisegno. – Mordachai

+1

Se si tenta di eseguire il debug del codice di pittura della finestra su un singolo PC, si ottengono effetti causati dall'attivazione del debugger ogni volta che si esegue un singolo passaggio. Questo ha avuto la tendenza a mandare in panico il window manager che avrebbe iniziato a disegnare le aree non client in particolare fuori processo perché pensava che il processo non rispondesse (non ingiustificato quando era single stepped). –

0

cosa sembra funzionare:

  1. Utilizzare il WS_CLIPCHILDREN nella finestra genitore (può essere impostato in WM_INITDIALOG)
  2. Durante WM_SIZE, ciclo attraverso i controlli figlio in movimento e il ridimensionamento utilizzando DeferSetWindowPos().

Questo è molto vicino alla perfezione, nei miei test sotto Windows 7 con Aero.

+0

Aero efficacemente fa un sacco di doppio buffering per te. Se spegni Aero o provi su XP, probabilmente avrai ancora un flicker. –

+0

Sì, è un po 'più luminoso con Aero disattivato. Riesco a vedere più del comportamento originariamente lamentato e vorrei vederlo eliminato. Quando trascino l'angolo di ridimensionamento della finestra di dialogo, posso vedere un ghosting del sistema operativo che cerca di prevedere dove appariranno le cose (quello che sembra più probabilmente un bitblt dei precedenti contenuti della mia finestra di dialogo), rapidamente sovrascritto dall'estrazione corretta durante il mio gestore WM_SIZE. Non è terribile, ma mi irrita ancora che non sembri avere un modo per forzare il sistema operativo a NON fare il bitter errante a mio nome. :( – Mordachai

1

Per alcuni controlli, è possibile utilizzare il messaggio WM_PRINT per trasformare il controllo in un DC. Ma questo non risolve il tuo problema principale, ovvero che vuoi che Windows NON disegni nulla durante il ridimensionamento, ma per lasciarti fare tutto.

E la risposta è che non puoi fare quello che vuoi finché hai finestre figlio.

Il modo in cui ho finito per risolverlo infine nel mio codice è passare all'utilizzo di Windowless Controls. Dal momento che non hanno una finestra propria, disegnano sempre nello stesso momento (e nello stesso DC) della loro finestra genitore. Ciò mi consente di utilizzare il doppio buffering doppio per rimuovere completamente lo sfarfallio. Posso anche sopprimere banalmente la pittura dei bambini quando ho bisogno solo di non chiamando la loro routine di disegno all'interno della routine di disegno del genitore.

Questo è l'unico modo che conosco per eliminare completamente lo sfarfallio e lo strappo durante le operazioni di ridimensionamento.

+0

Questo è sfortunato se vero, è un sacco di lavoro - e un carico di lavoro continuo, per provare a reimplementare ogni controllo in Windows. "F! @ # $ Ton" è un'etichetta ragionevole, IMO;) avere un fornitore di librerie di terze parti che fornisce una cosa del genere? IMX di programmazione e utilizzo delle prime implementazioni Java hanno succhiato. Difficile. E soprattutto perché i loro controlli senza finestre non andavano bene. Avevano un aspetto non Windows e si sono comportati in modo abbastanza strano da rappresentare un fastidio significativo per noi e per i nostri utenti. Ma mi viene in mente che il sistema operativo Windows non ha un flag di classe o finestra per "non fare il disegno predittivo". – Mordachai

+0

@Mordachai: Avevamo già implementato i nostri controlli per le cose che ci interessavano, quindi era solo questione di aggiungere una modalità senza finestra a loro. L'unico controllo che penso sia veramente _hard_ replicare è il controllo di modifica, ma si trattava ancora di un codice di controllo di anni di programmazione che avevamo già. Dai un'occhiata alla risposta a WS_EX_COMPOSITED. Questa bandiera non esisteva quando ho eseguito i controlli senza finestra, quindi non posso dire se risolverà il problema o no, ma promette. –

+0

Il più grande svantaggio di WS_EX_COMPOSITED di cui sono a conoscenza è che non è possibile disegnare direttamente sul controller di dominio della finestra (solo durante WM_PAINT). Inoltre, la soluzione che ho trovato usando il tuo altro suggerimento evita comunque di farlo a tutti gli effetti :) – Mordachai

Problemi correlati