2014-12-12 9 views
14

Quindi, mi sono imbattuto in un bug interessante con l'API di Windows e mi chiedo se qualcuno abbia qualche idea su come aggirare il problema. Sembra che persino Google abbia faticato con esso. Va notato che mentre risolvo questo problema nella sorgente Qt stessa, il problema riguarda la gestione dei messaggi predefinita di Windows, non Qt. Tutti i file che menzionerò possono essere trovati online in quanto sono tutte librerie open source. Qui di seguito è un po 'un problema complesso e proverò a dare più contesto possibile. Ho dedicato molto tempo e impegno a sistemarlo da solo, ma essendo che sono solo un ingegnere da circa 8 mesi, sono ancora abbastanza inesperto e potrei benissimo aver perso qualcosa di ovvio.Errore di rendering Aero di Windows

Il contesto:

Ho scritto un programma che utilizza Qt per la pelle mie finestre con skin personalizzate. Queste skin passano sugli skin dell'interfaccia utente non client di default del sistema. In altre parole, uso cornici dipinte su misura (supportate da Qt). Dal momento che il Qt5 ho riscontrato problemi con il mio programma quando viene eseguito su qualsiasi sistema operativo pre-Windows (meno di XP e maggiore di Vista con Windows Aero disabilitato). Sfortunatamente, gli sviluppatori di Qt hanno tutti confermato che non supportano realmente XP, quindi non mi affiderò a loro per correggere il bug.

The Bug:

Facendo clic in qualsiasi punto dell'area non-client durante l'esecuzione di una macchina con composizione disabilitato (Windows Aero disabilitato o non esistente) causerà di Windows di ridisegnare la propria interfaccia utente non-client di default del sistema in cima alla mia pelle personalizzata.

La mia ricerca

Un po 'di debugging e di indagine mi ha portato a qWindowsProc in qwindowscontext.cpp. Sono stato in grado di determinare che l'ultimo messaggio di Windows da gestire prima che la mia finestra fosse dipinta era WM_NCLBUTTONDOWN. Mi è sembrato strano, così ho preso gli internet.

Abbastanza sicuro, ho trovato un file chiamato hwnd_message_Handler.cc che proviene da Google Chrome Chromium Embedded Framework (CEF). In quel file ci sono molti commenti su come i vari messaggi di Windows, per qualche motivo insano, causano ritardi dei frame non client predefiniti di sistema su frame personalizzati. Quello che segue è uno di questi commenti.

// A scoping class that prevents a window from being able to redraw in response 
// to invalidations that may occur within it for the lifetime of the object. 
// 
// Why would we want such a thing? Well, it turns out Windows has some 
// "unorthodox" behavior when it comes to painting its non-client areas. 
// Occasionally, Windows will paint portions of the default non-client area 
// right over the top of the custom frame. This is not simply fixed by handling 
// WM_NCPAINT/WM_PAINT, with some investigation it turns out that this 
// rendering is being done *inside* the default implementation of some message 
// handlers and functions: 
// . **WM_SETTEXT** 
// . **WM_SETICON** 
// . **WM_NCLBUTTONDOWN** 
// . EnableMenuItem, called from our WM_INITMENU handler 
// The solution is to handle these messages and **call DefWindowProc ourselves**, 
// but prevent the window from being able to update itself for the duration of 
// the call. We do this with this class, which automatically calls its 
// associated Window's lock and unlock functions as it is created and destroyed. 
// See documentation in those methods for the technique used. 
// 
// The lock only has an effect if the window was visible upon lock creation, as 
// it doesn't guard against direct visiblility changes, and multiple locks may 
// exist simultaneously to handle certain nested Windows messages. 
// 
// IMPORTANT: Do not use this scoping object for large scopes or periods of 
//   time! IT WILL PREVENT THE WINDOW FROM BEING REDRAWN! (duh). 
// 
// I would love to hear Raymond Chen's explanation for all this. And maybe a 
// list of other messages that this applies to ;-) 

Anche in questo file esistono diversi gestori di messaggi personalizzati per evitare che questo errore si verifichi. Ad esempio, un altro messaggio che ho rilevato causa questo errore è WM_SETCURSOR. Abbastanza sicuro, hanno un gestore per ciò che, quando portato al mio programma, ha funzionato meravigliosamente.

Uno dei modi comuni in cui gestiscono questi messaggi è con ScopedRedrawLock. In sostanza, questo blocca semplicemente il ridisegno all'inizio della gestione predefinita del messaggio ostile (tramite DefWindowProc) e rimane bloccato per la durata della chiamata, sbloccandosi quando esce dall'ambito (quindi, Scoperto RedrawLock).Questo non funzionerà per WM_NCLBUTTONDOWN per il seguente motivo:

Varcata qWindowsWndProc durante la gestione predefinita di WM_NCLBUTTONDOWN, ho visto che WM_SYSCOMMAND viene gestito nello stesso stack di chiamate direttamente dopo WM_NCLBUTTONDOWN. Il wParam per questo particolare WM_SYSCOMMAND è 0xf012 - un altro valore ufficialmente non documentato **. Fortunatamente nella sezione delle osservazioni della pagina MSDN WM_SYSCOMMAND qualcuno ha commentato a riguardo. Risulta, è il codice SC_DRAGMOVE.

Per ragioni che possono sembrare ovvio, non possiamo semplicemente Bloccare ridisegnare per la movimentazione di WM_NCLBUTTONDOWN perché Windows presuppone automaticamente che l'utente sta cercando di trascinare la finestra se si fa clic su una zona non-client (in questo caso, HTCAPTION) . Il blocco qui causerà il mancato ridisegno della finestra per la durata del trascinamento, fino a quando Windows non riceverà un messaggio di pulsante (WM_NCLBUTTONUP o WM_LBUTTONUP).

E infatti, trovo questo commento nel loro codice,

if (!handled && message == WM_NCLBUTTONDOWN && w_param != HTSYSMENU && 
     delegate_->IsUsingCustomFrame()) { 
    // TODO(msw): Eliminate undesired painting, or re-evaluate this workaround. 
    // DefWindowProc for WM_NCLBUTTONDOWN does weird non-client painting, so we 
    // need to call it inside a ScopedRedrawLock. This may cause other negative 
    // side-effects (ex/ stifling non-client mouse releases). 
    DefWindowProcWithRedrawLock(message, w_param, l_param); 
    handled = true; 
    } 

Questo lo rende sembrare come se avessero lo stesso problema, ma non abbastanza andare in giro a risolverlo.

L'unico altro posto CEF gestisce WM_NCLBUTTONDOWN nello stesso ambito come questo problema è qui:

else if (message == WM_NCLBUTTONDOWN && delegate_->IsUsingCustomFrame()) { 
    switch (w_param) { 
     case HTCLOSE: 
     case HTMINBUTTON: 
     case HTMAXBUTTON: { 
     // When the mouse is pressed down in these specific non-client areas, 
     // we need to tell the RootView to send the mouse pressed event (which 
     // sets capture, allowing subsequent WM_LBUTTONUP (note, _not_ 
     // WM_NCLBUTTONUP) to fire so that the appropriate WM_SYSCOMMAND can be 
     // sent by the applicable button's ButtonListener. We _have_ to do this 
     // way rather than letting Windows just send the syscommand itself (as 
     // would happen if we never did this dance) because for some insane 
     // reason DefWindowProc for WM_NCLBUTTONDOWN also renders the pressed 
     // window control button appearance, in the Windows classic style, over 
     // our view! Ick! By handling this message we prevent Windows from 
     // doing this undesirable thing, but that means we need to roll the 
     // sys-command handling ourselves. 
     // Combine |w_param| with common key state message flags. 
     w_param |= base::win::IsCtrlPressed() ? MK_CONTROL : 0; 
     w_param |= base::win::IsShiftPressed() ? MK_SHIFT : 0; 
     } 
    } 

E mentre questo gestore affronta un problema simile, non il suo proprio la stessa cosa.

La questione

Quindi a questo punto mi sono bloccato. Non sono abbastanza sicuro di dove guardare. Forse sto leggendo il codice in modo errato?Forse la risposta c'è in CEF e la sto solo ignorando? Sembra che gli ingegneri CEF abbiano riscontrato questo problema e non abbiano ancora trovato la soluzione, dato il TODO: commento. Qualcuno ha idea di cos'altro potrei fare?Dove vado da qui? Non risolvere questo bug non è un'opzione. Sono disposto a scavare più a fondo, ma a questo punto sto pensando di gestire effettivamente gli eventi di trascinamento di Windows da solo, piuttosto che avere il DefWindowProc gestirlo. Tuttavia, questo potrebbe ancora causare il bug nel caso in cui l'utente stia effettivamente trascinando la finestra.

Link

ho incluso un elenco di link che ho utilizzato nella mia ricerca. Personalmente, ho scaricato io stesso la fonte CEF in modo da poter navigare meglio nel codice. Se sei veramente interessato a risolvere questo problema, potresti dover fare lo stesso.

WM_NCLBUTTONDOWN

WM_NCHITTEST

WM_SYSCOMMAND

DefWindowProc

hwnd_message_handler.cc

hwnd_message_handler.h

qwindowscontext.cpp

tangente

Solo per portare la convalida al codice del CEF, se si guarda nell'intestazione del hwnd_message_handler, noterete anche che ci sono due finestre senza documenti messaggi di valore 0xAE e 0xAF. Stavo vedendo 0xAE durante la gestione predefinita di WM_SETICON che stava causando problemi e questo codice mi ha aiutato a confermare che quello che stavo vedendo era davvero reale.

+2

Ma qual è la tua domanda? – vines

+1

Sì, ho dovuto risolvere questo problema con WM_NCUAHDRAWCAPTION e WM_NCUAHDRAWFRAME personalmente. È anche nel codice sorgente di Chrome. Ma qual è la tua domanda? – sashoalm

+0

Modificato per evidenziare la domanda –

risposta

0

Quindi, il modo effettivo con cui questa correzione è stata raggiunta rimuovendo il flag WS_CAPTION durante NC_LBUTTONDOWN e aggiungendolo nuovamente durante la gestione del messaggio NC_LBUTTONUP. Tuttavia, a causa del modo in cui Windows calcola le sue dimensioni prima del rendering, può essere erroneamente calcolato poiché rimuove l'area didascalia dalla considerazione. Pertanto, dovrai compensare questo problema mentre gestisci il messaggio WM_NCCALCSIZE.

Ricorda che la quantità di pixel che dovrai compensare varia a seconda del tema di Windows o del sistema operativo in cui ti trovi. Ad esempio, Vista ha un tema diverso rispetto a XP. Quindi dovrai decidere su un fattore di scala per tenerlo pulito.

0

Ho trovato la pagina this che suggerisce di nascondere la finestra rimuovendo WS_VISIBLE immediatamente prima di chiamare DefWindowProc(), quindi mostrarlo immediatamente dopo. Non l'ho provato, ma è qualcosa da guardare.