【LibUIDK界面庫系列文章】使用雙窗口製作陰影邊框時的激活問題

作者:劉樹偉

在使用LibUIDK界面庫爲客戶製作一個類似桌面版微信界面的時候,採用了雙窗口來製作邊框陰影。在關閉了登錄對話框後,彈出的主界面未被激活到z-order的最前端。下面的場景及解決方案。

當使用雙窗口來製作陰影窗口時,可以先創建陰影窗口,再由陰影窗口DoModal出工作窗口。常見的例子是:先創建一個陰影窗口,然後陰影窗口DoModal出登錄對話框。當點擊登錄按鈕登錄成功後,登錄對話框返回,接着彈出主界面。爲了讓陰影窗口和工作窗口同步移動、隱藏、顯示、TopMost等,封裝成了下面的接口:

// 當工作窗口位置改變後,讓陰影窗口的位置同步改變
// 本函數在工作窗口的WM_WINDOWPOSCHANGED消息中調用。
// lprcMargin: 工作窗口相對於陰影窗口的邊距
// lpsizeRoundRect: 如果工作窗口有圓角,設置圓角大小
int LibUIDK::SyncHostWindow(CUIWnd *pWorkWnd, UINT uWorkWndFlags, LPCRECT lprcMargin, LPSIZE lpsizeRoundRect)
{
 if (pWorkWnd == NULL || lprcMargin == NULL)
 {
  ASSERT(FALSE);
  return -1;
 }

 CWnd *pParent = pWorkWnd->GetParent();
 if (pParent == NULL)
 {
  return 1;
 }

 // 陰影窗口同步隱藏顯示
 if (IsIncludeFlag(uWorkWndFlags, SWP_HIDEWINDOW))
 {
  pParent->ShowWindow(SW_HIDE);
 }
 else if (IsIncludeFlag(uWorkWndFlags, SWP_SHOWWINDOW))
 {
  pParent->ShowWindow(SW_SHOW);
 }

 // 雖然通過把工作窗口當成陰影窗口的模式對話框,可以解決第三方窗口嵌入問題。
 // 但當工作窗口爲topmost時,仍然會導致第三方窗口嵌入,解決方案就是讓陰影窗口也變成topmost。
 // 如果爲工作窗口設置了Topmost,那麼同時也爲陰影窗口設置Topmost。
 // 當陰影窗口爲Topmost時,即使沒有爲工作窗口設置Topmost,系統也會自動爲工作窗口加上的。
 // 所以在爲陰影窗口設置Topmost時,沒必要清除工作窗口的WS_EX_TOPMOST屬性。
 LONG lExStyle = GetWindowLong(pWorkWnd->GetSafeHwnd(), GWL_EXSTYLE);
 if ((lExStyle & WS_EX_TOPMOST) == WS_EX_TOPMOST)
 {
  LONG lParentExStyle = GetWindowLong(pWorkWnd->GetParent()->GetSafeHwnd(), GWL_EXSTYLE);
  if ((lParentExStyle & WS_EX_TOPMOST) == 0)
  {
   pWorkWnd->GetParent()->SetWindowPos(&CWnd::wndTopMost,
    0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
  }
 }

 // 陰影窗口同步跟隨代碼
 CRect rcWnd;
 pWorkWnd->GetWindowRect(rcWnd);
 rcWnd.InflateRect(lprcMargin);

 pParent->MoveWindow(rcWnd);

 // 如果有圓角,設置rgn
 if (lpsizeRoundRect != NULL && lpsizeRoundRect->cx != 0 && lpsizeRoundRect->cy != 0)
 {
  CRect rcClient;
  pWorkWnd->GetClientRect(rcClient);
  CRgn rgn;
  rgn.CreateRoundRectRgn(0, 0, rcClient.right + 1, rcClient.bottom + 1, 4, 4);
  pWorkWnd->SetWindowRgn((HRGN)rgn.GetSafeHandle(), TRUE);
  rgn.DeleteObject();
 }

 return 0;
}

但是,在工作窗口的WM_WINDOWPOSCHANGED消息中調用。在工作窗口的DoModal內部,在RunModalLoop後,會調用如下代碼讓工作窗口先隱藏掉:
 SetWindowPos(NULL, 0, 0, 0, 0, SWP_HIDEWINDOW |
  SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER);
如果這時,隱藏陰影窗口。會導致接下來彈出的窗口,未激活到最前端。 針對上面的例子,主界面在顯示時,可能被另一個窗口擋住。甚至調用BringWindowToTop、SetActiveWindow 都無法激活。

解決方案是:
在pParent->ShowWindow(SW_HIDE);隱藏陰影窗口之前,先調用pParent->SetActiveWindow();或pParent->SetForegroundWindow()把陰影窗口激活(推薦使用SetForegroundWindow,因爲托盤圖標右鍵菜單也是類似問題,微軟官方使用SetForegroundWindow)。

經測試:pParent->SetActiveWindow()與pParent->ShowWindow(SW_HIDE)或者pParent->SetActiveWindow()與pParent->SetWindowPos(NULL, 0, 0, 0, 0, SWP_HIDEWINDOW | SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER)都是可以的。
但pParent->SetActiveWindow();與
 LONG_PTR lStyle = IUIGetWindowLong(pParent->GetSafeHwnd(), GWL_STYLE);
 lStyle &= ~WS_VISIBLE;
 IUISetWindowLong(pParent->GetSafeHwnd(), GWL_STYLE, lStyle);
不行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章