framelessmainwindowwin.cpp 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. #include "framelessmainwindowwin.h"
  2. #ifdef Q_OS_WIN
  3. #include <QTimer>
  4. #include <QEvent>
  5. #include <windows.h>
  6. #include <windowsx.h>
  7. #include <dwmapi.h>
  8. #pragma comment (lib,"dwmapi.lib")
  9. #pragma comment (lib, "user32.lib")
  10. using namespace vnotex;
  11. FramelessMainWindowWin::FramelessMainWindowWin(bool p_frameless, QWidget *p_parent)
  12. : FramelessMainWindow(p_frameless, p_parent)
  13. {
  14. if (m_frameless) {
  15. m_resizeAreaWidth *= devicePixelRatio();
  16. m_redrawTimer = new QTimer(this);
  17. m_redrawTimer->setSingleShot(true);
  18. m_redrawTimer->setInterval(500);
  19. connect(m_redrawTimer, &QTimer::timeout,
  20. this, &FramelessMainWindowWin::forceRedraw);
  21. connect(this, &FramelessMainWindow::windowStateChanged,
  22. this, &FramelessMainWindowWin::updateMargins);
  23. // Enable some window effects on Win, such as snap and maximizing.
  24. // It will activate the title bar again. Need to remove it in WM_NCCALCSIZE msg.
  25. HWND hwnd = reinterpret_cast<HWND>(winId());
  26. DWORD style = ::GetWindowLong(hwnd, GWL_STYLE);
  27. ::SetWindowLong(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION);
  28. // Leave 1 pixel width of border so OS will draw a window shadow.
  29. const MARGINS shadow = {1, 1, 1, 1};
  30. DwmExtendFrameIntoClientArea(hwnd, &shadow);
  31. }
  32. }
  33. #if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
  34. bool FramelessMainWindowWin::nativeEvent(const QByteArray &p_eventType, void *p_message, qintptr *p_result)
  35. #else
  36. bool FramelessMainWindowWin::nativeEvent(const QByteArray &p_eventType, void *p_message, long *p_result)
  37. #endif
  38. {
  39. if (!m_frameless) {
  40. return FramelessMainWindow::nativeEvent(p_eventType, p_message, p_result);
  41. }
  42. if (p_eventType == QStringLiteral("windows_generic_MSG")) {
  43. MSG *msg = static_cast<MSG *>(p_message);
  44. switch (msg->message) {
  45. case WM_NCCALCSIZE:
  46. *p_result = 0;
  47. return true;
  48. case WM_NCHITTEST:
  49. {
  50. if (m_windowStates & Qt::WindowFullScreen) {
  51. *p_result = HTCLIENT;
  52. return true;
  53. }
  54. RECT windowRect;
  55. ::GetWindowRect(msg->hwnd, &windowRect);
  56. // x and y could not be compared with width() and height() in hidpi case.
  57. const int x = static_cast<int>(GET_X_LPARAM(msg->lParam) - windowRect.left);
  58. const int y = static_cast<int>(GET_Y_LPARAM(msg->lParam) - windowRect.top);
  59. *p_result = 0;
  60. if (m_resizable) {
  61. if (x < m_resizeAreaWidth) {
  62. // Left.
  63. if (y < m_resizeAreaWidth) {
  64. // Top.
  65. *p_result = HTTOPLEFT;
  66. } else if (y > windowRect.bottom - windowRect.top - m_resizeAreaWidth) {
  67. // Bottom.
  68. *p_result = HTBOTTOMLEFT;
  69. } else {
  70. *p_result = HTLEFT;
  71. }
  72. } else if (x > windowRect.right - windowRect.left - m_resizeAreaWidth) {
  73. // Right.
  74. if (y < m_resizeAreaWidth) {
  75. // Top.
  76. *p_result = HTTOPRIGHT;
  77. } else if (y > windowRect.bottom - windowRect.top - m_resizeAreaWidth) {
  78. // Bottom.
  79. *p_result = HTBOTTOMRIGHT;
  80. } else {
  81. *p_result = HTRIGHT;
  82. }
  83. } else if (y < m_resizeAreaWidth) {
  84. *p_result = HTTOP;
  85. } else if (y > windowRect.bottom - windowRect.top - m_resizeAreaWidth) {
  86. *p_result = HTBOTTOM;
  87. }
  88. }
  89. if (0 != *p_result) {
  90. return true;
  91. }
  92. if (m_titleBar) {
  93. if (m_titleBarHeight == 0) {
  94. m_titleBarHeight = m_titleBar->height() * devicePixelRatio();
  95. }
  96. if (y < m_titleBarHeight) {
  97. QWidget *child = m_titleBar->childAt(m_titleBar->mapFromGlobal(QCursor::pos()));
  98. if (!child) {
  99. *p_result = HTCAPTION;
  100. if (::GetAsyncKeyState(VK_LBUTTON) < 0 || ::GetAsyncKeyState(VK_RBUTTON) < 0) {
  101. m_sizeBeforeMove = size();
  102. }
  103. return true;
  104. }
  105. }
  106. }
  107. break;
  108. }
  109. case WM_POWERBROADCAST:
  110. {
  111. if (msg->wParam == PBT_APMSUSPEND) {
  112. // Minimize when system is going to sleep to avoid bugs.
  113. showMinimized();
  114. }
  115. break;
  116. }
  117. case WM_GETMINMAXINFO:
  118. {
  119. // When maximized, OS will expand the content area. To avoid missing the real contents, set extra margins.
  120. if (::IsZoomed(msg->hwnd)) {
  121. RECT frame = {0, 0, 0, 0};
  122. ::AdjustWindowRectEx(&frame, WS_OVERLAPPEDWINDOW, false, 0);
  123. const int dpiScale = devicePixelRatio();
  124. // Use bottom as top.
  125. QMargins newMargins(qAbs(frame.left) / dpiScale,
  126. qAbs(frame.bottom) / dpiScale,
  127. frame.right / dpiScale,
  128. frame.bottom / dpiScale);
  129. if (newMargins != m_maximizedMargins) {
  130. m_maximizedMargins = newMargins;
  131. updateMargins();
  132. }
  133. }
  134. break;
  135. }
  136. default:
  137. break;
  138. }
  139. }
  140. return FramelessMainWindow::nativeEvent(p_eventType, p_message, p_result);
  141. }
  142. void FramelessMainWindowWin::moveEvent(QMoveEvent *p_event)
  143. {
  144. FramelessMainWindow::moveEvent(p_event);
  145. if (m_frameless) {
  146. if (m_windowStates & Qt::WindowMaximized) {
  147. m_redrawTimer->stop();
  148. } else {
  149. m_redrawTimer->start();
  150. }
  151. }
  152. }
  153. void FramelessMainWindowWin::updateMargins()
  154. {
  155. if (!m_frameless) {
  156. return;
  157. }
  158. int topMargin = 0;
  159. if (isMaximized()) {
  160. setContentsMargins(m_maximizedMargins);
  161. topMargin = m_maximizedMargins.top();
  162. } else {
  163. setContentsMargins(0, 0, 0, 0);
  164. }
  165. if (m_titleBar) {
  166. m_titleBarHeight = (m_titleBar->height() + topMargin) * devicePixelRatio();
  167. }
  168. }
  169. void FramelessMainWindowWin::forceRedraw()
  170. {
  171. Q_ASSERT(m_frameless);
  172. if (m_windowStates & Qt::WindowMaximized) {
  173. return;
  174. }
  175. const QSize sz = size();
  176. RECT frame;
  177. ::GetWindowRect((HWND)winId(), &frame);
  178. const int clientWidth = (frame.right - frame.left) / devicePixelRatio();
  179. const int clientHeight = (frame.bottom - frame.top) / devicePixelRatio();
  180. if (clientWidth != sz.width() || clientHeight != sz.height()) {
  181. // resize() may result to "unable to set geometry" warning.
  182. // adjustsize() or resize() to another size before could solve this.
  183. resize(sz.width() + 1, sz.height() + 1);
  184. if (m_sizeBeforeMove.isEmpty()) {
  185. resize(clientWidth, clientHeight);
  186. } else {
  187. resize(m_sizeBeforeMove);
  188. }
  189. }
  190. }
  191. void FramelessMainWindowWin::setWindowFlagsOnUpdate()
  192. {
  193. if (m_frameless) {
  194. // We need to re-set the window flags again in some cases, such as after StayOnTop.
  195. HWND hwnd = reinterpret_cast<HWND>(winId());
  196. DWORD style = ::GetWindowLong(hwnd, GWL_STYLE);
  197. ::SetWindowLong(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION);
  198. }
  199. }
  200. #endif