2014-12-12 9 views
14

Ich bin also auf einen interessanten Fehler mit der Windows-API gestoßen und frage mich, ob irgendjemand etwas darüber weiß, wie man damit umgehen kann. Es scheint, dass Google selbst damit zu kämpfen hat. Es sollte beachtet werden, dass, während ich dies in Qt-Quelle selbst beheben wird, ist das Problem mit Windows-Standardnachrichtenbehandlung, nicht Qt. Alle Dateien, die ich erwähnen werde, können online gefunden werden, da sie alle Open-Source-Bibliotheken sind. Unten ist etwas von einem komplexen Problem und ich werde versuchen, so viel Kontext wie möglich zu geben. Ich habe viel Zeit und Mühe investiert, um das selbst zu beheben, aber da ich erst seit etwa 8 Monaten Ingenieur bin, bin ich noch ziemlich unerfahren und hätte sehr wahrscheinlich etwas Offensichtliches übersehen.Windows Aero Rendering Bug

Der Kontext:

ich ein Programm geschrieben habe, die Qt verwendet, um meine Fenster mit benutzerdefiniertem Skin Haut. Diese Skins gehen über die standardmäßigen Nicht-Client-UI-Skins des Systems. Mit anderen Worten verwende ich benutzerdefinierte gemalte Rahmen (unterstützt von Qt). Seit Qt5 habe ich Probleme mit meinem Programm, wenn es auf irgendein Pre-Windows Aero OS ausgeführt wird (weniger als XP und größer als Vista mit Windows Aero deaktiviert). Unglücklicherweise haben Qt-Entwickler bestätigt, dass sie XP nicht mehr wirklich unterstützen, daher werde ich mich nicht darauf verlassen, dass sie den Fehler beheben.

Der Bug:

überall in dem Nicht-Client-Bereich Klicken auf, während eine Maschine mit Zusammensetzung ausgeführt wird, deaktiviert (bestehende Windows Aero deaktiviert oder nicht) wird Windows veranlassen, seine System-Standard-nicht-Client-Benutzeroberfläche neu streichen auf meiner benutzerdefinierten Haut.

Meine Forschung

Ein bisschen Debuggen und Untersuchung qWindowsProc in qwindowscontext.cpp führte mich. Ich konnte feststellen, dass die letzte Windows-Nachricht, die bearbeitet wurde, bevor die Haut meines Fensters übermalt wurde, WM_NCLBUTTONDOWN war. Das schien seltsam, also ging ich ins Internet.

Sicher genug, fand ich eine Datei hwnd_message_Handler.cc genannt, die von Googles Chromium Embedded-Framework kommt (CEF). In dieser Datei gibt es viele Kommentare darüber, wie verschiedene Windows-Nachrichten aus irgendeinem Grund Repaints der System-Nicht-Client-Standardframes über benutzerdefinierte Frames verursachen. Das Folgende ist ein solcher Kommentar.

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

Auch in dieser Datei existiert mehrere Handler benutzerdefinierte Nachricht, diesen Fehler zu verhindern. Zum Beispiel, eine andere Nachricht, die ich gefunden habe, die diesen Fehler verursacht, ist WM_SETCURSOR. Sicher genug, sie haben einen Handler für das, was, wenn sie in mein Programm portiert wurde, wunderbar funktioniert hat.

Einer der häufigsten Wege sie diese Nachrichten verarbeiten ist mit einem ScopedRedrawLock. Im Wesentlichen sperrt dies das Neuzeichnen zu Beginn der Standardbehandlung der feindlichen Nachricht (über DefWindowProc) und bleibt für die Dauer des Aufrufs gesperrt, wobei es sich selbst entriegelt, wenn es außerhalb des Geltungsbereichs liegt (daher Scoped RedrawLock).Diese wird nicht funktionieren für WM_NCLBUTTONDOWN aus dem folgenden Grund:

Stepping durch qWindowsWndProc während der Standardbehandlung von WM_NCLBUTTONDOWN, sah ich, dass WM_SYSCOMMAND im gleichen Call-Stack behandelt wird direkt nach WM_NCLBUTTONDOWN. Der wParam für dieses bestimmte WM_SYSCOMMAND ist 0xf012 - ein weiterer offiziell undokumentierter Wert **. Glücklicherweise hat jemand im Kommentarbereich der MSDN WM_SYSCOMMAND Seite darüber gesprochen. Stellt sich heraus, es ist der SC_DRAGMOVE Code.

Aus Gründen, die offensichtlich erscheinen mögen, können wir das Neuzeichnen für die Behandlung von WM_NCLBUTTONDOWN nicht einfach sperren, da Windows automatisch annimmt, dass der Benutzer versucht, das Fenster zu ziehen, wenn er auf einen Nicht-Clientbereich klickt (in diesem Fall HTCAPTION). . Wenn Sie hier klicken, wird das Fenster für die Dauer des Ziehens nie neu gezeichnet, bis Windows eine Schaltflächenmeldung (WM_NCLBUTTONUP oder WM_LBUTTONUP) empfängt.

Und sicher genug, finde ich diesen Kommentar in ihrem Code,

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

Dies macht es scheint, als ob sie das gleiche Problem hatten, aber nicht ganz, um es zu lösen bekommen.

Der einzige CEF anderer Ort behandelt WM_NCLBUTTONDOWN im gleichen Umfang wie dieses Problem ist hier:

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

Und während das Handler behandelt ein ähnliches Problem, es ist nicht ganz dasselbe.

Die Frage

an dieser Stelle also bin ich stecken. Ich bin mir nicht sicher, wo ich hinschauen soll. Vielleicht lese ich den Code falsch?Vielleicht gibt es die Antwort in CEF und ich übersehe es gerade? Es scheint, als hätten CEF-Ingenieure dieses Problem erkannt und müssen angesichts des TODO-Kommentars noch die Lösung finden. Hat jemand eine Idee, was ich noch tun könnte?Wohin gehe ich von hier? Diesen Fehler nicht zu beheben ist keine Option. Ich bin bereit, tiefer zu graben, aber an dieser Stelle überlege ich eigentlich, Windows-Drag-Ereignisse selbst zu behandeln, anstatt dass DefWindowProc damit umgehen muss. Dies kann jedoch immer noch den Fehler verursachen, wenn der Benutzer das Fenster tatsächlich zieht.

Verbindungen

Ich habe eine Liste von Links enthalten, die ich in meiner Forschung verwendet haben. Persönlich habe ich selbst die CEF-Quelle heruntergeladen, damit ich besser durch den Code navigieren konnte. Wenn Sie wirklich daran interessiert sind, dieses Problem zu lösen, müssen Sie möglicherweise dasselbe tun.

WM_NCLBUTTONDOWN

WM_NCHITTEST

WM_SYSCOMMAND

DefWindowProc

hwnd_message_handler.cc

hwnd_message_handler.h

qwindowscontext.cpp

Tangent

Nur Validierung CEF Code zu bringen, wenn Sie in der Kopfzeile hwnd_message_handler betrachten, werden Sie auch feststellen, dass es zwei undokumentierte Windows-Nachrichten von Wert 0xAE und 0xAF. Ich sah 0xAE während der Standardbehandlung von WM_SETICON, die Probleme verursachte, und dieser Code half zu bestätigen, dass das, was ich sah, tatsächlich echt war.

+2

Aber was ist Ihre Frage? – vines

+1

Ja, ich musste dieses Problem mit WM_NCUAHDRAWCAPTION und WM_NCUAHDRAWFRAME selbst lösen. Es ist auch im Quellcode von Chrome. Aber was ist deine Frage? – sashoalm

+0

Bearbeitet, um Frage –

Antwort

0

Die tatsächliche Art und Weise, wie dieser Fix erreicht wurde, bestand darin, das WS_CAPTION-Flag während NC_LBUTTONDOWN zu entfernen und es während NC_LBUTTONUP-Nachrichtenbehandlung hinzuzufügen. Aufgrund der Art und Weise, wie Windows vor dem Rendern seine Größe berechnet, kann es sich jedoch falsch berechnen, da der Beschriftungsbereich aus der Betrachtung entfernt wird. Daher müssen Sie dies während der Behandlung der WM_NCCALCSIZE-Nachricht ausgleichen.

Beachten Sie, dass die Anzahl der Pixel, die Sie zum Versetzen benötigen, davon abhängt, in welchem ​​Windows-Design oder Betriebssystem Sie sich befinden. Das heißt, Vista hat ein anderes Thema als XP. Sie müssen sich also für einen Skalierungsfaktor entscheiden, um ihn sauber zu halten.

0

Ich fand this Seite, die schlägt vor, Ihr Fenster zu verbergen, indem Sie WS_VISIBLE sofort vor dem Anruf DefWindowProc() entfernen, dann zeigt es sofort nach. Ich habe es nicht ausprobiert, aber es ist etwas zum Anschauen.

Verwandte Themen