簡體   English   中英

Windows Aero的渲染錯誤

[英]Windows Aero Rendering Bug

因此,我偶然發現了Windows API的一個有趣的錯誤,我想知道是否有人對如何解決它有所了解。 似乎甚至Google都在為此掙扎。 應該注意的是,盡管我將在Qt源本身中修復此問題 ,但問題出在Windows默認消息處理而不是Qt。 我將提到的所有文件都可以在網上找到,因為它們都是開源庫。 以下是一個復雜的問題,我將嘗試給出盡可能多的上下文。 我自己花了很多時間和精力來解決這個問題,但是由於我只擔任了大約8個月的工程師,所以我仍然缺乏經驗,很可能錯過了一些明顯的事情。

上下文:

我已經編寫了一個程序,該程序使用Qt用自定義皮膚為我的窗戶添加皮膚。 這些外觀遍歷系統默認的非客戶端UI外觀。 換句話說,我使用自定義的繪制框架(受Qt支持)。 從Qt5開始,我的程序在任何Windows Aero以前的操作系統 (小於XP且大於禁用Windows Aero的Vista)上運行時,一直遇到問題。 不幸的是,Qt開發人員幾乎已經確認他們不再真正支持XP,因此我不會依靠他們來修復該錯誤。

錯誤:

在運行禁用合成的計算機( 禁用 Windows Aero或不存在Windows Aero)的同時,在非客戶區域中單擊任何位置,將導致Windows在我的自定義皮膚上重新繪制其系統默認非客戶UI。

我的研究

調試和調查使我進入了qwindowscontext.cpp中的qWindowsProc。 我能夠確定在繪制窗口皮膚之前要處理的最后一個Windows消息是WM_NCLBUTTONDOWN 這似乎很奇怪,所以我上了互聯網。

果然,我找到了一個名為hwnd_message_Handler.cc的文件,該文件來自Google的Chromium嵌入式框架(CEF)。 在該文件中,有許多關於各種Windows消息如何由於某種瘋狂原因而導致在自定義框架上重繪系統默認非客戶端框架的注釋。 以下是這樣的評論之一。

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

該文件中還存在幾個自定義消息處理程序,以防止發生此錯誤。 例如,我發現導致此錯誤的另一條消息是WM_SETCURSOR 果然,他們有一個處理程序,將其移植到我的程序中后,效果非常好。

他們處理這些消息的常用方法之一是使用ScopedRedrawLock。 本質上,這只是在敵對消息的默認處理開始時(通過DefWindowProc)鎖定重繪,並在調用過程中保持鎖定,在超出范圍時會自行解鎖(因此,稱為Scoped RedrawLock)。 由於以下原因,這不適用於WM_NCLBUTTONDOWN

在WM_NCLBUTTONDOWN的默認處理過程中逐步執行qWindowsWndProc ,我看到WM_NCLBUTTONDOWN之后直接在同一調用堆棧中處理WM_SYSCOMMAND 此特定WM_SYSCOMMAND的wParam為0xf012-另一個正式未記錄的值**。 幸運的是,有人在MSDN WM_SYSCOMMAND頁面的備注部分對此進行了評論。 原來,這是SC_DRAGMOVE代碼。

由於看似顯而易見的原因,我們不能簡單地鎖定重繪來處理WM_NCLBUTTONDOWN,因為Windows會自動假定用戶單擊非客戶區域(在本例中為HTCAPTION)時正試圖拖動窗口。 鎖定此處將導致窗口在拖動過程中永不重繪,直到Windows收到一個按鍵消息(WM_NCLBUTTONUP或WM_LBUTTONUP)。

確實,我在他們的代碼中找到了此注釋,

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

這似乎使他們似乎遇到了相同的問題,但是並沒有完全解決這個問題。

CEF在與該問題相同的范圍內處理WM_NCLBUTTONDOWN的唯一其他位置在這里:

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

盡管該處理程序解決了類似的問題,但並不完全相同。

問題

所以在這一點上,我被困住了。 我不太確定在哪里看。 也許我讀錯了代碼? 也許CEF中存在答案,而我只是忽略了它? 鑒於TODO:注釋,CEF工程師似乎遇到了此問題,但尚未提出解決方案。 有人知道我還能做什么嗎? 我從這里去哪里? 不解決此錯誤不是一種選擇。 我願意進行更深入的研究,但在這一點上,我正在考慮實際上是自己處理Windows拖動事件,而不是讓DefWindowProc處理它。 但是,在用戶實際拖動窗口的情況下,這仍然可能導致錯誤。

鏈接

我列出了我在研究中一直使用的鏈接列表。 我個人下載了CEF源代碼,以便更好地瀏覽代碼。 如果您真的有興趣解決此問題,則可能需要這樣做。

WM_NCLBUTTONDOWN

WM_NCHITTEST

WM_SYSCOMMAND

DefWindowProc

hwnd_message_handler.cc

hwnd_message_handler.h

qwindowscontext.cpp

切線

只是為了驗證CEF的代碼,如果您查看hwnd_message_handler的標頭,您還將注意到有兩個未記錄的Windows消息,值分別為0xAE和0xAF。 在WM_SETICON的默認處理過程中,我看到了0xAE,這引起了問題,此代碼有助於確認我所看到的確實是真實的。

我發現頁面建議通過在調用DefWindowProc()之前立即刪除WS_VISIBLE隱藏窗口,然后在之后立即顯示它。 我還沒有嘗試過,但是值得一看。

因此,解決此問題的實際方法是在NC_LBUTTONDOWN期間刪除WS_CAPTION標志,並在NC_LBUTTONUP消息處理期間將其重新添加。 但是,由於Windows在渲染之前計算其大小的方式,由於它可能會刪除字幕區域,因此可能會計算錯誤。 因此,您將需要在處理WM_NCCALCSIZE消息時對此進行補償。

請記住,您需要偏移的像素數量將根據您所在的Windows主題或操作系統而有所不同。即Vista的主題與XP的主題不同。 因此,您需要確定比例因子以保持其清潔。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM