WindowChromeWorker.cs 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218
  1. /*************************************************************************************
  2. Extended WPF Toolkit
  3. Copyright (C) 2007-2013 Xceed Software Inc.
  4. This program is provided to you under the terms of the Microsoft Public
  5. License (Ms-PL) as published at http://wpftoolkit.codeplex.com/license
  6. For more features, controls, and fast professional support,
  7. pick up the Plus Edition at http://xceed.com/wpf_toolkit
  8. Stay informed: follow @datagrid on Twitter or Like http://facebook.com/datagrids
  9. ***********************************************************************************/
  10. /**************************************************************************\
  11. Copyright Microsoft Corporation. All Rights Reserved.
  12. \**************************************************************************/
  13. namespace Microsoft.Windows.Shell
  14. {
  15. using System;
  16. using System.Collections.Generic;
  17. using System.Diagnostics.CodeAnalysis;
  18. using System.Runtime.InteropServices;
  19. using System.Threading;
  20. using System.Windows;
  21. using System.Windows.Interop;
  22. using System.Windows.Media;
  23. using System.Windows.Threading;
  24. using Standard;
  25. using HANDLE_MESSAGE = System.Collections.Generic.KeyValuePair<Standard.WM, Standard.MessageHandler>;
  26. using System.Windows.Controls.Primitives;
  27. internal class WindowChromeWorker : DependencyObject
  28. {
  29. // Delegate signature used for Dispatcher.BeginInvoke.
  30. private delegate void _Action();
  31. #region Fields
  32. private const SWP _SwpFlags = SWP.FRAMECHANGED | SWP.NOSIZE | SWP.NOMOVE | SWP.NOZORDER | SWP.NOOWNERZORDER | SWP.NOACTIVATE;
  33. private readonly List<HANDLE_MESSAGE> _messageTable;
  34. /// <summary>The Window that's chrome is being modified.</summary>
  35. private Window _window;
  36. /// <summary>Underlying HWND for the _window.</summary>
  37. private IntPtr _hwnd;
  38. private HwndSource _hwndSource = null;
  39. private bool _isHooked = false;
  40. // These fields are for tracking workarounds for WPF 3.5SP1 behaviors.
  41. private bool _isFixedUp = false;
  42. private bool _isUserResizing = false;
  43. private bool _hasUserMovedWindow = false;
  44. private Point _windowPosAtStartOfUserMove = default(Point);
  45. // Field to track attempts to force off Device Bitmaps on Win7.
  46. private int _blackGlassFixupAttemptCount;
  47. /// <summary>Object that describes the current modifications being made to the chrome.</summary>
  48. private WindowChrome _chromeInfo;
  49. // Keep track of this so we can detect when we need to apply changes. Tracking these separately
  50. // as I've seen using just one cause things to get enough out of sync that occasionally the caption will redraw.
  51. private WindowState _lastRoundingState;
  52. private WindowState _lastMenuState;
  53. private bool _isGlassEnabled;
  54. #endregion
  55. public WindowChromeWorker()
  56. {
  57. _messageTable = new List<HANDLE_MESSAGE>
  58. {
  59. new HANDLE_MESSAGE(WM.SETTEXT, _HandleSetTextOrIcon),
  60. new HANDLE_MESSAGE(WM.SETICON, _HandleSetTextOrIcon),
  61. new HANDLE_MESSAGE(WM.NCACTIVATE, _HandleNCActivate),
  62. new HANDLE_MESSAGE(WM.NCCALCSIZE, _HandleNCCalcSize),
  63. new HANDLE_MESSAGE(WM.NCHITTEST, _HandleNCHitTest),
  64. new HANDLE_MESSAGE(WM.NCRBUTTONUP, _HandleNCRButtonUp),
  65. new HANDLE_MESSAGE(WM.SIZE, _HandleSize),
  66. new HANDLE_MESSAGE(WM.WINDOWPOSCHANGED, _HandleWindowPosChanged),
  67. new HANDLE_MESSAGE(WM.DWMCOMPOSITIONCHANGED, _HandleDwmCompositionChanged),
  68. };
  69. if (Utility.IsPresentationFrameworkVersionLessThan4)
  70. {
  71. _messageTable.AddRange(new[]
  72. {
  73. new HANDLE_MESSAGE(WM.SETTINGCHANGE, _HandleSettingChange),
  74. new HANDLE_MESSAGE(WM.ENTERSIZEMOVE, _HandleEnterSizeMove),
  75. new HANDLE_MESSAGE(WM.EXITSIZEMOVE, _HandleExitSizeMove),
  76. new HANDLE_MESSAGE(WM.MOVE, _HandleMove),
  77. });
  78. }
  79. }
  80. public void SetWindowChrome(WindowChrome newChrome)
  81. {
  82. VerifyAccess();
  83. Assert.IsNotNull(_window);
  84. if (newChrome == _chromeInfo)
  85. {
  86. // Nothing's changed.
  87. return;
  88. }
  89. if (_chromeInfo != null)
  90. {
  91. _chromeInfo.PropertyChangedThatRequiresRepaint -= _OnChromePropertyChangedThatRequiresRepaint;
  92. }
  93. _chromeInfo = newChrome;
  94. if (_chromeInfo != null)
  95. {
  96. _chromeInfo.PropertyChangedThatRequiresRepaint += _OnChromePropertyChangedThatRequiresRepaint;
  97. }
  98. _ApplyNewCustomChrome();
  99. }
  100. private void _OnChromePropertyChangedThatRequiresRepaint(object sender, EventArgs e)
  101. {
  102. _UpdateFrameState(true);
  103. }
  104. public static readonly DependencyProperty WindowChromeWorkerProperty = DependencyProperty.RegisterAttached(
  105. "WindowChromeWorker",
  106. typeof(WindowChromeWorker),
  107. typeof(WindowChromeWorker),
  108. new PropertyMetadata(null, _OnChromeWorkerChanged));
  109. private static void _OnChromeWorkerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  110. {
  111. var w = (Window)d;
  112. var cw = (WindowChromeWorker)e.NewValue;
  113. // The WindowChromeWorker object should only be set on the window once, and never to null.
  114. Assert.IsNotNull(w);
  115. Assert.IsNotNull(cw);
  116. Assert.IsNull(cw._window);
  117. cw._SetWindow(w);
  118. }
  119. private void _SetWindow(Window window)
  120. {
  121. Assert.IsNull(_window);
  122. Assert.IsNotNull(window);
  123. _window = window;
  124. // There are potentially a couple funny states here.
  125. // The window may have been shown and closed, in which case it's no longer usable.
  126. // We shouldn't add any hooks in that case, just exit early.
  127. // If the window hasn't yet been shown, then we need to make sure to remove hooks after it's closed.
  128. _hwnd = new WindowInteropHelper(_window).Handle;
  129. if (Utility.IsPresentationFrameworkVersionLessThan4)
  130. {
  131. // On older versions of the framework the client size of the window is incorrectly calculated.
  132. // We need to modify the template to fix this on behalf of the user.
  133. Utility.AddDependencyPropertyChangeListener(_window, Window.TemplateProperty, _OnWindowPropertyChangedThatRequiresTemplateFixup);
  134. Utility.AddDependencyPropertyChangeListener(_window, Window.FlowDirectionProperty, _OnWindowPropertyChangedThatRequiresTemplateFixup);
  135. }
  136. _window.Closed += _UnsetWindow;
  137. // Use whether we can get an HWND to determine if the Window has been loaded.
  138. if (IntPtr.Zero != _hwnd)
  139. {
  140. // We've seen that the HwndSource can't always be retrieved from the HWND, so cache it early.
  141. // Specifically it seems to sometimes disappear when the OS theme is changing.
  142. _hwndSource = HwndSource.FromHwnd(_hwnd);
  143. Assert.IsNotNull(_hwndSource);
  144. _window.ApplyTemplate();
  145. if (_chromeInfo != null)
  146. {
  147. _ApplyNewCustomChrome();
  148. }
  149. }
  150. else
  151. {
  152. _window.SourceInitialized += (sender, e) =>
  153. {
  154. _hwnd = new WindowInteropHelper(_window).Handle;
  155. Assert.IsNotDefault(_hwnd);
  156. _hwndSource = HwndSource.FromHwnd(_hwnd);
  157. Assert.IsNotNull(_hwndSource);
  158. if (_chromeInfo != null)
  159. {
  160. _ApplyNewCustomChrome();
  161. }
  162. };
  163. }
  164. }
  165. private void _UnsetWindow(object sender, EventArgs e)
  166. {
  167. if (Utility.IsPresentationFrameworkVersionLessThan4)
  168. {
  169. Utility.RemoveDependencyPropertyChangeListener(_window, Window.TemplateProperty, _OnWindowPropertyChangedThatRequiresTemplateFixup);
  170. Utility.RemoveDependencyPropertyChangeListener(_window, Window.FlowDirectionProperty, _OnWindowPropertyChangedThatRequiresTemplateFixup);
  171. }
  172. if (_chromeInfo != null)
  173. {
  174. _chromeInfo.PropertyChangedThatRequiresRepaint -= _OnChromePropertyChangedThatRequiresRepaint;
  175. }
  176. _RestoreStandardChromeState(true);
  177. }
  178. [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
  179. public static WindowChromeWorker GetWindowChromeWorker(Window window)
  180. {
  181. Verify.IsNotNull(window, "window");
  182. return (WindowChromeWorker)window.GetValue(WindowChromeWorkerProperty);
  183. }
  184. [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
  185. public static void SetWindowChromeWorker(Window window, WindowChromeWorker chrome)
  186. {
  187. Verify.IsNotNull(window, "window");
  188. window.SetValue(WindowChromeWorkerProperty, chrome);
  189. }
  190. private void _OnWindowPropertyChangedThatRequiresTemplateFixup(object sender, EventArgs e)
  191. {
  192. Assert.IsTrue(Utility.IsPresentationFrameworkVersionLessThan4);
  193. if (_chromeInfo != null && _hwnd != IntPtr.Zero)
  194. {
  195. // Assume that when the template changes it's going to be applied.
  196. // We don't have a good way to externally hook into the template
  197. // actually being applied, so we asynchronously post the fixup operation
  198. // at Loaded priority, so it's expected that the visual tree will be
  199. // updated before _FixupFrameworkIssues is called.
  200. _window.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, (_Action)_FixupFrameworkIssues);
  201. }
  202. }
  203. private void _ApplyNewCustomChrome()
  204. {
  205. if (_hwnd == IntPtr.Zero)
  206. {
  207. // Not yet hooked.
  208. return;
  209. }
  210. if (_chromeInfo == null)
  211. {
  212. _RestoreStandardChromeState(false);
  213. return;
  214. }
  215. if (!_isHooked)
  216. {
  217. _hwndSource.AddHook(_WndProc);
  218. _isHooked = true;
  219. }
  220. _FixupFrameworkIssues();
  221. // Force this the first time.
  222. _UpdateSystemMenu(_window.WindowState);
  223. _UpdateFrameState(true);
  224. NativeMethods.SetWindowPos(_hwnd, IntPtr.Zero, 0, 0, 0, 0, _SwpFlags);
  225. }
  226. private void _FixupFrameworkIssues()
  227. {
  228. Assert.IsNotNull(_chromeInfo);
  229. Assert.IsNotNull(_window);
  230. // This margin is only necessary if the client rect is going to be calculated incorrectly by WPF.
  231. // This bug was fixed in V4 of the framework.
  232. if (!Utility.IsPresentationFrameworkVersionLessThan4)
  233. {
  234. return;
  235. }
  236. if (_window.Template == null)
  237. {
  238. // Nothing to fixup yet. This will get called again when a template does get set.
  239. return;
  240. }
  241. // Guard against the visual tree being empty.
  242. if (VisualTreeHelper.GetChildrenCount(_window) == 0)
  243. {
  244. // The template isn't null, but we don't have a visual tree.
  245. // Hope that ApplyTemplate is in the queue and repost this, because there's not much we can do right now.
  246. _window.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, (_Action)_FixupFrameworkIssues);
  247. return;
  248. }
  249. var rootElement = (FrameworkElement)VisualTreeHelper.GetChild(_window, 0);
  250. RECT rcWindow = NativeMethods.GetWindowRect(_hwnd);
  251. RECT rcAdjustedClient = _GetAdjustedWindowRect(rcWindow);
  252. Rect rcLogicalWindow = DpiHelper.DeviceRectToLogical(new Rect(rcWindow.Left, rcWindow.Top, rcWindow.Width, rcWindow.Height));
  253. Rect rcLogicalClient = DpiHelper.DeviceRectToLogical(new Rect(rcAdjustedClient.Left, rcAdjustedClient.Top, rcAdjustedClient.Width, rcAdjustedClient.Height));
  254. Thickness nonClientThickness = new Thickness(
  255. rcLogicalWindow.Left - rcLogicalClient.Left,
  256. rcLogicalWindow.Top - rcLogicalClient.Top,
  257. rcLogicalClient.Right - rcLogicalWindow.Right,
  258. rcLogicalClient.Bottom - rcLogicalWindow.Bottom);
  259. rootElement.Margin = new Thickness(
  260. 0,
  261. 0,
  262. -(nonClientThickness.Left + nonClientThickness.Right),
  263. -(nonClientThickness.Top + nonClientThickness.Bottom));
  264. // The negative thickness on the margin doesn't properly get applied in RTL layouts.
  265. // The width is right, but there is a black bar on the right.
  266. // To fix this we just add an additional RenderTransform to the root element.
  267. // This works fine, but if the window is dynamically changing its FlowDirection then this can have really bizarre side effects.
  268. // This will mostly work if the FlowDirection is dynamically changed, but there aren't many real scenarios that would call for
  269. // that so I'm not addressing the rest of the quirkiness.
  270. if (_window.FlowDirection == FlowDirection.RightToLeft)
  271. {
  272. rootElement.RenderTransform = new MatrixTransform(1, 0, 0, 1, -(nonClientThickness.Left + nonClientThickness.Right), 0);
  273. }
  274. else
  275. {
  276. rootElement.RenderTransform = null;
  277. }
  278. if (!_isFixedUp)
  279. {
  280. _hasUserMovedWindow = false;
  281. _window.StateChanged += _FixupRestoreBounds;
  282. _isFixedUp = true;
  283. }
  284. }
  285. // There was a regression in DWM in Windows 7 with regard to handling WM_NCCALCSIZE to effect custom chrome.
  286. // When windows with glass are maximized on a multimonitor setup the glass frame tends to turn black.
  287. // Also when windows are resized they tend to flicker black, sometimes staying that way until resized again.
  288. //
  289. // This appears to be a bug in DWM related to device bitmap optimizations. At least on RTM Win7 we can
  290. // evoke a legacy code path that bypasses the bug by calling an esoteric DWM function. This doesn't affect
  291. // the system, just the application.
  292. // WPF also tends to call this function anyways during animations, so we're just forcing the issue
  293. // consistently and a bit earlier.
  294. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
  295. private void _FixupWindows7Issues()
  296. {
  297. if (_blackGlassFixupAttemptCount > 5)
  298. {
  299. // Don't keep trying if there's an endemic problem with this.
  300. return;
  301. }
  302. if (Utility.IsOSWindows7OrNewer && NativeMethods.DwmIsCompositionEnabled())
  303. {
  304. ++_blackGlassFixupAttemptCount;
  305. bool success = false;
  306. try
  307. {
  308. DWM_TIMING_INFO? dti = NativeMethods.DwmGetCompositionTimingInfo(_hwnd);
  309. success = dti != null;
  310. }
  311. catch (Exception)
  312. {
  313. // We aren't sure of all the reasons this could fail.
  314. // If we find new ones we should consider making the NativeMethod swallow them as well.
  315. // Since we have a limited number of retries and this method isn't actually critical, just repost.
  316. // Disabling this for the published code to reduce debug noise. This will get compiled away for retail binaries anyways.
  317. //Assert.Fail(e.Message);
  318. }
  319. // NativeMethods.DwmGetCompositionTimingInfo swallows E_PENDING.
  320. // If the call wasn't successful, try again later.
  321. if (!success)
  322. {
  323. Dispatcher.BeginInvoke(DispatcherPriority.Loaded, (_Action)_FixupWindows7Issues);
  324. }
  325. else
  326. {
  327. // Reset this. We will want to force this again if DWM composition changes.
  328. _blackGlassFixupAttemptCount = 0;
  329. }
  330. }
  331. }
  332. private void _FixupRestoreBounds(object sender, EventArgs e)
  333. {
  334. Assert.IsTrue(Utility.IsPresentationFrameworkVersionLessThan4);
  335. if (_window.WindowState == WindowState.Maximized || _window.WindowState == WindowState.Minimized)
  336. {
  337. // Old versions of WPF sometimes force their incorrect idea of the Window's location
  338. // on the Win32 restore bounds. If we have reason to think this is the case, then
  339. // try to undo what WPF did after it has done its thing.
  340. if (_hasUserMovedWindow)
  341. {
  342. _hasUserMovedWindow = false;
  343. WINDOWPLACEMENT wp = NativeMethods.GetWindowPlacement(_hwnd);
  344. RECT adjustedDeviceRc = _GetAdjustedWindowRect(new RECT { Bottom = 100, Right = 100 });
  345. Point adjustedTopLeft = DpiHelper.DevicePixelsToLogical(
  346. new Point(
  347. wp.rcNormalPosition.Left - adjustedDeviceRc.Left,
  348. wp.rcNormalPosition.Top - adjustedDeviceRc.Top));
  349. _window.Top = adjustedTopLeft.Y;
  350. _window.Left = adjustedTopLeft.X;
  351. }
  352. }
  353. }
  354. private RECT _GetAdjustedWindowRect(RECT rcWindow)
  355. {
  356. // This should only be used to work around issues in the Framework that were fixed in 4.0
  357. Assert.IsTrue(Utility.IsPresentationFrameworkVersionLessThan4);
  358. var style = (WS)NativeMethods.GetWindowLongPtr(_hwnd, GWL.STYLE);
  359. var exstyle = (WS_EX)NativeMethods.GetWindowLongPtr(_hwnd, GWL.EXSTYLE);
  360. return NativeMethods.AdjustWindowRectEx(rcWindow, style, false, exstyle);
  361. }
  362. // Windows tries hard to hide this state from applications.
  363. // Generally you can tell that the window is in a docked position because the restore bounds from GetWindowPlacement
  364. // don't match the current window location and it's not in a maximized or minimized state.
  365. // Because this isn't doced or supported, it's also not incredibly consistent. Sometimes some things get updated in
  366. // different orders, so this isn't absolutely reliable.
  367. private bool _IsWindowDocked
  368. {
  369. get
  370. {
  371. // We're only detecting this state to work around .Net 3.5 issues.
  372. // This logic won't work correctly when those issues are fixed.
  373. Assert.IsTrue(Utility.IsPresentationFrameworkVersionLessThan4);
  374. if (_window.WindowState != WindowState.Normal)
  375. {
  376. return false;
  377. }
  378. RECT adjustedOffset = _GetAdjustedWindowRect(new RECT { Bottom = 100, Right = 100 });
  379. Point windowTopLeft = new Point(_window.Left, _window.Top);
  380. windowTopLeft -= (Vector)DpiHelper.DevicePixelsToLogical(new Point(adjustedOffset.Left, adjustedOffset.Top));
  381. return _window.RestoreBounds.Location != windowTopLeft;
  382. }
  383. }
  384. #region WindowProc and Message Handlers
  385. private IntPtr _WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
  386. {
  387. // Only expecting messages for our cached HWND.
  388. Assert.AreEqual(hwnd, _hwnd);
  389. var message = (WM)msg;
  390. foreach (var handlePair in _messageTable)
  391. {
  392. if (handlePair.Key == message)
  393. {
  394. return handlePair.Value(message, wParam, lParam, out handled);
  395. }
  396. }
  397. return IntPtr.Zero;
  398. }
  399. private IntPtr _HandleSetTextOrIcon(WM uMsg, IntPtr wParam, IntPtr lParam, out bool handled)
  400. {
  401. bool modified = _ModifyStyle(WS.VISIBLE, 0);
  402. // Setting the caption text and icon cause Windows to redraw the caption.
  403. // Letting the default WndProc handle the message without the WS_VISIBLE
  404. // style applied bypasses the redraw.
  405. IntPtr lRet = NativeMethods.DefWindowProc(_hwnd, uMsg, wParam, lParam);
  406. // Put back the style we removed.
  407. if (modified)
  408. {
  409. _ModifyStyle(0, WS.VISIBLE);
  410. }
  411. handled = true;
  412. return lRet;
  413. }
  414. private IntPtr _HandleNCActivate(WM uMsg, IntPtr wParam, IntPtr lParam, out bool handled)
  415. {
  416. // Despite MSDN's documentation of lParam not being used,
  417. // calling DefWindowProc with lParam set to -1 causes Windows not to draw over the caption.
  418. // Directly call DefWindowProc with a custom parameter
  419. // which bypasses any other handling of the message.
  420. IntPtr lRet = NativeMethods.DefWindowProc(_hwnd, WM.NCACTIVATE, wParam, new IntPtr(-1));
  421. handled = true;
  422. return lRet;
  423. }
  424. private IntPtr _HandleNCCalcSize(WM uMsg, IntPtr wParam, IntPtr lParam, out bool handled)
  425. {
  426. // lParam is an [in, out] that can be either a RECT* (wParam == FALSE) or an NCCALCSIZE_PARAMS*.
  427. // Since the first field of NCCALCSIZE_PARAMS is a RECT and is the only field we care about
  428. // we can unconditionally treat it as a RECT.
  429. // Since we always want the client size to equal the window size, we can unconditionally handle it
  430. // without having to modify the parameters.
  431. handled = true;
  432. return new IntPtr((int)WVR.REDRAW);
  433. }
  434. private IntPtr _HandleNCHitTest(WM uMsg, IntPtr wParam, IntPtr lParam, out bool handled)
  435. {
  436. IntPtr lRet = IntPtr.Zero;
  437. handled = false;
  438. // Give DWM a chance at this first.
  439. if (Utility.IsOSVistaOrNewer && _chromeInfo.GlassFrameThickness != default(Thickness) && _isGlassEnabled)
  440. {
  441. // If we're on Vista, give the DWM a chance to handle the message first.
  442. handled = NativeMethods.DwmDefWindowProc(_hwnd, uMsg, wParam, lParam, out lRet);
  443. }
  444. // Handle letting the system know if we consider the mouse to be in our effective non-client area.
  445. // If DWM already handled this by way of DwmDefWindowProc, then respect their call.
  446. if (IntPtr.Zero == lRet)
  447. {
  448. var mousePosScreen = new Point(Utility.GET_X_LPARAM(lParam), Utility.GET_Y_LPARAM(lParam));
  449. Rect windowPosition = _GetWindowRect();
  450. HT ht = _HitTestNca(
  451. DpiHelper.DeviceRectToLogical(windowPosition),
  452. DpiHelper.DevicePixelsToLogical(mousePosScreen));
  453. // Don't blindly respect HTCAPTION.
  454. // We want UIElements in the caption area to be actionable so run through a hittest first.
  455. if (ht != HT.CLIENT)
  456. {
  457. Point mousePosWindow = mousePosScreen;
  458. mousePosWindow.Offset(-windowPosition.X, -windowPosition.Y);
  459. mousePosWindow = DpiHelper.DevicePixelsToLogical(mousePosWindow);
  460. IInputElement inputElement = _window.InputHitTest(mousePosWindow);
  461. if (inputElement != null && WindowChrome.GetIsHitTestVisibleInChrome(inputElement))
  462. {
  463. ht = HT.CLIENT;
  464. }
  465. }
  466. handled = true;
  467. lRet = new IntPtr((int)ht);
  468. }
  469. return lRet;
  470. }
  471. private IntPtr _HandleNCRButtonUp(WM uMsg, IntPtr wParam, IntPtr lParam, out bool handled)
  472. {
  473. // Emulate the system behavior of clicking the right mouse button over the caption area
  474. // to bring up the system menu.
  475. if (HT.CAPTION == (HT)wParam.ToInt32())
  476. {
  477. if (_window.ContextMenu != null)
  478. {
  479. _window.ContextMenu.Placement = PlacementMode.MousePoint;
  480. _window.ContextMenu.IsOpen = true;
  481. }
  482. else if (WindowChrome.GetWindowChrome(_window).ShowSystemMenu)
  483. SystemCommands.ShowSystemMenuPhysicalCoordinates(_window, new Point(Utility.GET_X_LPARAM(lParam), Utility.GET_Y_LPARAM(lParam)));
  484. }
  485. handled = false;
  486. return IntPtr.Zero;
  487. }
  488. private IntPtr _HandleSize(WM uMsg, IntPtr wParam, IntPtr lParam, out bool handled)
  489. {
  490. const int SIZE_MAXIMIZED = 2;
  491. // Force when maximized.
  492. // We can tell what's happening right now, but the Window doesn't yet know it's
  493. // maximized. Not forcing this update will eventually cause the
  494. // default caption to be drawn.
  495. WindowState? state = null;
  496. if (wParam.ToInt32() == SIZE_MAXIMIZED)
  497. {
  498. state = WindowState.Maximized;
  499. }
  500. _UpdateSystemMenu(state);
  501. // Still let the default WndProc handle this.
  502. handled = false;
  503. return IntPtr.Zero;
  504. }
  505. private IntPtr _HandleWindowPosChanged(WM uMsg, IntPtr wParam, IntPtr lParam, out bool handled)
  506. {
  507. // http://blogs.msdn.com/oldnewthing/archive/2008/01/15/7113860.aspx
  508. // The WM_WINDOWPOSCHANGED message is sent at the end of the window
  509. // state change process. It sort of combines the other state change
  510. // notifications, WM_MOVE, WM_SIZE, and WM_SHOWWINDOW. But it doesn't
  511. // suffer from the same limitations as WM_SHOWWINDOW, so you can
  512. // reliably use it to react to the window being shown or hidden.
  513. _UpdateSystemMenu(null);
  514. if (!_isGlassEnabled)
  515. {
  516. Assert.IsNotDefault(lParam);
  517. var wp = (WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(WINDOWPOS));
  518. _SetRoundingRegion(wp);
  519. }
  520. // Still want to pass this to DefWndProc
  521. handled = false;
  522. return IntPtr.Zero;
  523. }
  524. private IntPtr _HandleDwmCompositionChanged(WM uMsg, IntPtr wParam, IntPtr lParam, out bool handled)
  525. {
  526. _UpdateFrameState(false);
  527. handled = false;
  528. return IntPtr.Zero;
  529. }
  530. private IntPtr _HandleSettingChange(WM uMsg, IntPtr wParam, IntPtr lParam, out bool handled)
  531. {
  532. // There are several settings that can cause fixups for the template to become invalid when changed.
  533. // These shouldn't be required on the v4 framework.
  534. Assert.IsTrue(Utility.IsPresentationFrameworkVersionLessThan4);
  535. _FixupFrameworkIssues();
  536. handled = false;
  537. return IntPtr.Zero;
  538. }
  539. private IntPtr _HandleEnterSizeMove(WM uMsg, IntPtr wParam, IntPtr lParam, out bool handled)
  540. {
  541. // This is only intercepted to deal with bugs in Window in .Net 3.5 and below.
  542. Assert.IsTrue(Utility.IsPresentationFrameworkVersionLessThan4);
  543. _isUserResizing = true;
  544. // On Win7 if the user is dragging the window out of the maximized state then we don't want to use that location
  545. // as a restore point.
  546. Assert.Implies(_window.WindowState == WindowState.Maximized, Utility.IsOSWindows7OrNewer);
  547. if (_window.WindowState != WindowState.Maximized)
  548. {
  549. // Check for the docked window case. The window can still be restored when it's in this position so
  550. // try to account for that and not update the start position.
  551. if (!_IsWindowDocked)
  552. {
  553. _windowPosAtStartOfUserMove = new Point(_window.Left, _window.Top);
  554. }
  555. // Realistically we also don't want to update the start position when moving from one docked state to another (or to and from maximized),
  556. // but it's tricky to detect and this is already a workaround for a bug that's fixed in newer versions of the framework.
  557. // Not going to try to handle all cases.
  558. }
  559. handled = false;
  560. return IntPtr.Zero;
  561. }
  562. private IntPtr _HandleExitSizeMove(WM uMsg, IntPtr wParam, IntPtr lParam, out bool handled)
  563. {
  564. // This is only intercepted to deal with bugs in Window in .Net 3.5 and below.
  565. Assert.IsTrue(Utility.IsPresentationFrameworkVersionLessThan4);
  566. _isUserResizing = false;
  567. // On Win7 the user can change the Window's state by dragging the window to the top of the monitor.
  568. // If they did that, then we need to try to update the restore bounds or else WPF will put the window at the maximized location (e.g. (-8,-8)).
  569. if (_window.WindowState == WindowState.Maximized)
  570. {
  571. Assert.IsTrue(Utility.IsOSWindows7OrNewer);
  572. _window.Top = _windowPosAtStartOfUserMove.Y;
  573. _window.Left = _windowPosAtStartOfUserMove.X;
  574. }
  575. handled = false;
  576. return IntPtr.Zero;
  577. }
  578. private IntPtr _HandleMove(WM uMsg, IntPtr wParam, IntPtr lParam, out bool handled)
  579. {
  580. // This is only intercepted to deal with bugs in Window in .Net 3.5 and below.
  581. Assert.IsTrue(Utility.IsPresentationFrameworkVersionLessThan4);
  582. if (_isUserResizing)
  583. {
  584. _hasUserMovedWindow = true;
  585. }
  586. handled = false;
  587. return IntPtr.Zero;
  588. }
  589. #endregion
  590. /// <summary>Add and remove a native WindowStyle from the HWND.</summary>
  591. /// <param name="removeStyle">The styles to be removed. These can be bitwise combined.</param>
  592. /// <param name="addStyle">The styles to be added. These can be bitwise combined.</param>
  593. /// <returns>Whether the styles of the HWND were modified as a result of this call.</returns>
  594. private bool _ModifyStyle(WS removeStyle, WS addStyle)
  595. {
  596. Assert.IsNotDefault(_hwnd);
  597. var dwStyle = (WS)NativeMethods.GetWindowLongPtr(_hwnd, GWL.STYLE).ToInt32();
  598. var dwNewStyle = (dwStyle & ~removeStyle) | addStyle;
  599. if (dwStyle == dwNewStyle)
  600. {
  601. return false;
  602. }
  603. NativeMethods.SetWindowLongPtr(_hwnd, GWL.STYLE, new IntPtr((int)dwNewStyle));
  604. return true;
  605. }
  606. /// <summary>
  607. /// Get the WindowState as the native HWND knows it to be. This isn't necessarily the same as what Window thinks.
  608. /// </summary>
  609. private WindowState _GetHwndState()
  610. {
  611. var wpl = NativeMethods.GetWindowPlacement(_hwnd);
  612. switch (wpl.showCmd)
  613. {
  614. case SW.SHOWMINIMIZED: return WindowState.Minimized;
  615. case SW.SHOWMAXIMIZED: return WindowState.Maximized;
  616. }
  617. return WindowState.Normal;
  618. }
  619. /// <summary>
  620. /// Get the bounding rectangle for the window in physical coordinates.
  621. /// </summary>
  622. /// <returns>The bounding rectangle for the window.</returns>
  623. private Rect _GetWindowRect()
  624. {
  625. // Get the window rectangle.
  626. RECT windowPosition = NativeMethods.GetWindowRect(_hwnd);
  627. return new Rect(windowPosition.Left, windowPosition.Top, windowPosition.Width, windowPosition.Height);
  628. }
  629. /// <summary>
  630. /// Update the items in the system menu based on the current, or assumed, WindowState.
  631. /// </summary>
  632. /// <param name="assumeState">
  633. /// The state to assume that the Window is in. This can be null to query the Window's state.
  634. /// </param>
  635. /// <remarks>
  636. /// We want to update the menu while we have some control over whether the caption will be repainted.
  637. /// </remarks>
  638. private void _UpdateSystemMenu(WindowState? assumeState)
  639. {
  640. const MF mfEnabled = MF.ENABLED | MF.BYCOMMAND;
  641. const MF mfDisabled = MF.GRAYED | MF.DISABLED | MF.BYCOMMAND;
  642. WindowState state = assumeState ?? _GetHwndState();
  643. if (null != assumeState || _lastMenuState != state)
  644. {
  645. _lastMenuState = state;
  646. bool modified = _ModifyStyle(WS.VISIBLE, 0);
  647. IntPtr hmenu = NativeMethods.GetSystemMenu(_hwnd, false);
  648. if (IntPtr.Zero != hmenu)
  649. {
  650. var dwStyle = (WS)NativeMethods.GetWindowLongPtr(_hwnd, GWL.STYLE).ToInt32();
  651. bool canMinimize = Utility.IsFlagSet((int)dwStyle, (int)WS.MINIMIZEBOX);
  652. bool canMaximize = Utility.IsFlagSet((int)dwStyle, (int)WS.MAXIMIZEBOX);
  653. bool canSize = Utility.IsFlagSet((int)dwStyle, (int)WS.THICKFRAME);
  654. switch (state)
  655. {
  656. case WindowState.Maximized:
  657. NativeMethods.EnableMenuItem(hmenu, SC.RESTORE, mfEnabled);
  658. NativeMethods.EnableMenuItem(hmenu, SC.MOVE, mfDisabled);
  659. NativeMethods.EnableMenuItem(hmenu, SC.SIZE, mfDisabled);
  660. NativeMethods.EnableMenuItem(hmenu, SC.MINIMIZE, canMinimize ? mfEnabled : mfDisabled);
  661. NativeMethods.EnableMenuItem(hmenu, SC.MAXIMIZE, mfDisabled);
  662. break;
  663. case WindowState.Minimized:
  664. NativeMethods.EnableMenuItem(hmenu, SC.RESTORE, mfEnabled);
  665. NativeMethods.EnableMenuItem(hmenu, SC.MOVE, mfDisabled);
  666. NativeMethods.EnableMenuItem(hmenu, SC.SIZE, mfDisabled);
  667. NativeMethods.EnableMenuItem(hmenu, SC.MINIMIZE, mfDisabled);
  668. NativeMethods.EnableMenuItem(hmenu, SC.MAXIMIZE, canMaximize ? mfEnabled : mfDisabled);
  669. break;
  670. default:
  671. NativeMethods.EnableMenuItem(hmenu, SC.RESTORE, mfDisabled);
  672. NativeMethods.EnableMenuItem(hmenu, SC.MOVE, mfEnabled);
  673. NativeMethods.EnableMenuItem(hmenu, SC.SIZE, canSize ? mfEnabled : mfDisabled);
  674. NativeMethods.EnableMenuItem(hmenu, SC.MINIMIZE, canMinimize ? mfEnabled : mfDisabled);
  675. NativeMethods.EnableMenuItem(hmenu, SC.MAXIMIZE, canMaximize ? mfEnabled : mfDisabled);
  676. break;
  677. }
  678. }
  679. if (modified)
  680. {
  681. _ModifyStyle(0, WS.VISIBLE);
  682. }
  683. }
  684. }
  685. private void _UpdateFrameState(bool force)
  686. {
  687. if (IntPtr.Zero == _hwnd)
  688. {
  689. return;
  690. }
  691. // Don't rely on SystemParameters2 for this, just make the check ourselves.
  692. bool frameState = NativeMethods.DwmIsCompositionEnabled();
  693. if (force || frameState != _isGlassEnabled)
  694. {
  695. _isGlassEnabled = frameState && _chromeInfo.GlassFrameThickness != default(Thickness);
  696. if (!_isGlassEnabled)
  697. {
  698. _SetRoundingRegion(null);
  699. }
  700. else
  701. {
  702. _ClearRoundingRegion();
  703. _ExtendGlassFrame();
  704. _FixupWindows7Issues();
  705. }
  706. NativeMethods.SetWindowPos(_hwnd, IntPtr.Zero, 0, 0, 0, 0, _SwpFlags);
  707. }
  708. }
  709. private void _ClearRoundingRegion()
  710. {
  711. NativeMethods.SetWindowRgn(_hwnd, IntPtr.Zero, NativeMethods.IsWindowVisible(_hwnd));
  712. }
  713. private void _SetRoundingRegion(WINDOWPOS? wp)
  714. {
  715. const int MONITOR_DEFAULTTONEAREST = 0x00000002;
  716. // We're early - WPF hasn't necessarily updated the state of the window.
  717. // Need to query it ourselves.
  718. WINDOWPLACEMENT wpl = NativeMethods.GetWindowPlacement(_hwnd);
  719. if (wpl.showCmd == SW.SHOWMAXIMIZED)
  720. {
  721. int left;
  722. int top;
  723. if (wp.HasValue)
  724. {
  725. left = wp.Value.x;
  726. top = wp.Value.y;
  727. }
  728. else
  729. {
  730. Rect r = _GetWindowRect();
  731. left = (int)r.Left;
  732. top = (int)r.Top;
  733. }
  734. IntPtr hMon = NativeMethods.MonitorFromWindow(_hwnd, MONITOR_DEFAULTTONEAREST);
  735. MONITORINFO mi = NativeMethods.GetMonitorInfo(hMon);
  736. RECT rcMax = mi.rcWork;
  737. // The location of maximized window takes into account the border that Windows was
  738. // going to remove, so we also need to consider it.
  739. rcMax.Offset(-left, -top);
  740. IntPtr hrgn = IntPtr.Zero;
  741. try
  742. {
  743. hrgn = NativeMethods.CreateRectRgnIndirect(rcMax);
  744. NativeMethods.SetWindowRgn(_hwnd, hrgn, NativeMethods.IsWindowVisible(_hwnd));
  745. hrgn = IntPtr.Zero;
  746. }
  747. finally
  748. {
  749. Utility.SafeDeleteObject(ref hrgn);
  750. }
  751. }
  752. else
  753. {
  754. Size windowSize;
  755. // Use the size if it's specified.
  756. if (null != wp && !Utility.IsFlagSet(wp.Value.flags, (int)SWP.NOSIZE))
  757. {
  758. windowSize = new Size((double)wp.Value.cx, (double)wp.Value.cy);
  759. }
  760. else if (null != wp && (_lastRoundingState == _window.WindowState))
  761. {
  762. return;
  763. }
  764. else
  765. {
  766. windowSize = _GetWindowRect().Size;
  767. }
  768. _lastRoundingState = _window.WindowState;
  769. IntPtr hrgn = IntPtr.Zero;
  770. try
  771. {
  772. double shortestDimension = Math.Min(windowSize.Width, windowSize.Height);
  773. double topLeftRadius = DpiHelper.LogicalPixelsToDevice(new Point(_chromeInfo.CornerRadius.TopLeft, 0)).X;
  774. topLeftRadius = Math.Min(topLeftRadius, shortestDimension / 2);
  775. if (_IsUniform(_chromeInfo.CornerRadius))
  776. {
  777. // RoundedRect HRGNs require an additional pixel of padding.
  778. hrgn = _CreateRoundRectRgn(new Rect(windowSize), topLeftRadius);
  779. }
  780. else
  781. {
  782. // We need to combine HRGNs for each of the corners.
  783. // Create one for each quadrant, but let it overlap into the two adjacent ones
  784. // by the radius amount to ensure that there aren't corners etched into the middle
  785. // of the window.
  786. hrgn = _CreateRoundRectRgn(new Rect(0, 0, windowSize.Width / 2 + topLeftRadius, windowSize.Height / 2 + topLeftRadius), topLeftRadius);
  787. double topRightRadius = DpiHelper.LogicalPixelsToDevice(new Point(_chromeInfo.CornerRadius.TopRight, 0)).X;
  788. topRightRadius = Math.Min(topRightRadius, shortestDimension / 2);
  789. Rect topRightRegionRect = new Rect(0, 0, windowSize.Width / 2 + topRightRadius, windowSize.Height / 2 + topRightRadius);
  790. topRightRegionRect.Offset(windowSize.Width / 2 - topRightRadius, 0);
  791. Assert.AreEqual(topRightRegionRect.Right, windowSize.Width);
  792. _CreateAndCombineRoundRectRgn(hrgn, topRightRegionRect, topRightRadius);
  793. double bottomLeftRadius = DpiHelper.LogicalPixelsToDevice(new Point(_chromeInfo.CornerRadius.BottomLeft, 0)).X;
  794. bottomLeftRadius = Math.Min(bottomLeftRadius, shortestDimension / 2);
  795. Rect bottomLeftRegionRect = new Rect(0, 0, windowSize.Width / 2 + bottomLeftRadius, windowSize.Height / 2 + bottomLeftRadius);
  796. bottomLeftRegionRect.Offset(0, windowSize.Height / 2 - bottomLeftRadius);
  797. Assert.AreEqual(bottomLeftRegionRect.Bottom, windowSize.Height);
  798. _CreateAndCombineRoundRectRgn(hrgn, bottomLeftRegionRect, bottomLeftRadius);
  799. double bottomRightRadius = DpiHelper.LogicalPixelsToDevice(new Point(_chromeInfo.CornerRadius.BottomRight, 0)).X;
  800. bottomRightRadius = Math.Min(bottomRightRadius, shortestDimension / 2);
  801. Rect bottomRightRegionRect = new Rect(0, 0, windowSize.Width / 2 + bottomRightRadius, windowSize.Height / 2 + bottomRightRadius);
  802. bottomRightRegionRect.Offset(windowSize.Width / 2 - bottomRightRadius, windowSize.Height / 2 - bottomRightRadius);
  803. Assert.AreEqual(bottomRightRegionRect.Right, windowSize.Width);
  804. Assert.AreEqual(bottomRightRegionRect.Bottom, windowSize.Height);
  805. _CreateAndCombineRoundRectRgn(hrgn, bottomRightRegionRect, bottomRightRadius);
  806. }
  807. NativeMethods.SetWindowRgn(_hwnd, hrgn, NativeMethods.IsWindowVisible(_hwnd));
  808. hrgn = IntPtr.Zero;
  809. }
  810. finally
  811. {
  812. // Free the memory associated with the HRGN if it wasn't assigned to the HWND.
  813. Utility.SafeDeleteObject(ref hrgn);
  814. }
  815. }
  816. }
  817. private static IntPtr _CreateRoundRectRgn(Rect region, double radius)
  818. {
  819. // Round outwards.
  820. if (DoubleUtilities.AreClose(0, radius))
  821. {
  822. return NativeMethods.CreateRectRgn(
  823. (int)Math.Floor(region.Left),
  824. (int)Math.Floor(region.Top),
  825. (int)Math.Ceiling(region.Right),
  826. (int)Math.Ceiling(region.Bottom));
  827. }
  828. // RoundedRect HRGNs require an additional pixel of padding on the bottom right to look correct.
  829. return NativeMethods.CreateRoundRectRgn(
  830. (int)Math.Floor(region.Left),
  831. (int)Math.Floor(region.Top),
  832. (int)Math.Ceiling(region.Right) + 1,
  833. (int)Math.Ceiling(region.Bottom) + 1,
  834. (int)Math.Ceiling(radius),
  835. (int)Math.Ceiling(radius));
  836. }
  837. [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "HRGNs")]
  838. private static void _CreateAndCombineRoundRectRgn(IntPtr hrgnSource, Rect region, double radius)
  839. {
  840. IntPtr hrgn = IntPtr.Zero;
  841. try
  842. {
  843. hrgn = _CreateRoundRectRgn(region, radius);
  844. CombineRgnResult result = NativeMethods.CombineRgn(hrgnSource, hrgnSource, hrgn, RGN.OR);
  845. if (result == CombineRgnResult.ERROR)
  846. {
  847. throw new InvalidOperationException("Unable to combine two HRGNs.");
  848. }
  849. }
  850. catch
  851. {
  852. Utility.SafeDeleteObject(ref hrgn);
  853. throw;
  854. }
  855. }
  856. private static bool _IsUniform(CornerRadius cornerRadius)
  857. {
  858. if (!DoubleUtilities.AreClose(cornerRadius.BottomLeft, cornerRadius.BottomRight))
  859. {
  860. return false;
  861. }
  862. if (!DoubleUtilities.AreClose(cornerRadius.TopLeft, cornerRadius.TopRight))
  863. {
  864. return false;
  865. }
  866. if (!DoubleUtilities.AreClose(cornerRadius.BottomLeft, cornerRadius.TopRight))
  867. {
  868. return false;
  869. }
  870. return true;
  871. }
  872. private void _ExtendGlassFrame()
  873. {
  874. Assert.IsNotNull(_window);
  875. // Expect that this might be called on OSes other than Vista.
  876. if (!Utility.IsOSVistaOrNewer)
  877. {
  878. // Not an error. Just not on Vista so we're not going to get glass.
  879. return;
  880. }
  881. if (IntPtr.Zero == _hwnd)
  882. {
  883. // Can't do anything with this call until the Window has been shown.
  884. return;
  885. }
  886. // Ensure standard HWND background painting when DWM isn't enabled.
  887. if (!NativeMethods.DwmIsCompositionEnabled())
  888. {
  889. _hwndSource.CompositionTarget.BackgroundColor = SystemColors.WindowColor;
  890. }
  891. else
  892. {
  893. // This makes the glass visible at a Win32 level so long as nothing else is covering it.
  894. // The Window's Background needs to be changed independent of this.
  895. // Apply the transparent background to the HWND
  896. _hwndSource.CompositionTarget.BackgroundColor = Colors.Transparent;
  897. // Thickness is going to be DIPs, need to convert to system coordinates.
  898. Point deviceTopLeft = DpiHelper.LogicalPixelsToDevice(new Point(_chromeInfo.GlassFrameThickness.Left, _chromeInfo.GlassFrameThickness.Top));
  899. Point deviceBottomRight = DpiHelper.LogicalPixelsToDevice(new Point(_chromeInfo.GlassFrameThickness.Right, _chromeInfo.GlassFrameThickness.Bottom));
  900. var dwmMargin = new MARGINS
  901. {
  902. // err on the side of pushing in glass an extra pixel.
  903. cxLeftWidth = (int)Math.Ceiling(deviceTopLeft.X),
  904. cxRightWidth = (int)Math.Ceiling(deviceBottomRight.X),
  905. cyTopHeight = (int)Math.Ceiling(deviceTopLeft.Y),
  906. cyBottomHeight = (int)Math.Ceiling(deviceBottomRight.Y),
  907. };
  908. NativeMethods.DwmExtendFrameIntoClientArea(_hwnd, ref dwmMargin);
  909. }
  910. }
  911. /// <summary>
  912. /// Matrix of the HT values to return when responding to NC window messages.
  913. /// </summary>
  914. [SuppressMessage("Microsoft.Performance", "CA1814:PreferJaggedArraysOverMultidimensional", MessageId = "Member")]
  915. private static readonly HT[,] _HitTestBorders = new[,]
  916. {
  917. { HT.TOPLEFT, HT.TOP, HT.TOPRIGHT },
  918. { HT.LEFT, HT.CLIENT, HT.RIGHT },
  919. { HT.BOTTOMLEFT, HT.BOTTOM, HT.BOTTOMRIGHT },
  920. };
  921. private HT _HitTestNca(Rect windowPosition, Point mousePosition)
  922. {
  923. // Determine if hit test is for resizing, default middle (1,1).
  924. int uRow = 1;
  925. int uCol = 1;
  926. bool onResizeBorder = false;
  927. // Determine if the point is at the top or bottom of the window.
  928. if (mousePosition.Y >= windowPosition.Top && mousePosition.Y < windowPosition.Top + _chromeInfo.ResizeBorderThickness.Top + _chromeInfo.CaptionHeight)
  929. {
  930. onResizeBorder = (mousePosition.Y < (windowPosition.Top + _chromeInfo.ResizeBorderThickness.Top));
  931. uRow = 0; // top (caption or resize border)
  932. }
  933. else if (mousePosition.Y < windowPosition.Bottom && mousePosition.Y >= windowPosition.Bottom - (int)_chromeInfo.ResizeBorderThickness.Bottom)
  934. {
  935. uRow = 2; // bottom
  936. }
  937. // Determine if the point is at the left or right of the window.
  938. if (mousePosition.X >= windowPosition.Left && mousePosition.X < windowPosition.Left + (int)_chromeInfo.ResizeBorderThickness.Left)
  939. {
  940. uCol = 0; // left side
  941. }
  942. else if (mousePosition.X < windowPosition.Right && mousePosition.X >= windowPosition.Right - _chromeInfo.ResizeBorderThickness.Right)
  943. {
  944. uCol = 2; // right side
  945. }
  946. // If the cursor is in one of the top edges by the caption bar, but below the top resize border,
  947. // then resize left-right rather than diagonally.
  948. if (uRow == 0 && uCol != 1 && !onResizeBorder)
  949. {
  950. uRow = 1;
  951. }
  952. HT ht = _HitTestBorders[uRow, uCol];
  953. if (ht == HT.TOP && !onResizeBorder)
  954. {
  955. ht = HT.CAPTION;
  956. }
  957. return ht;
  958. }
  959. #region Remove Custom Chrome Methods
  960. private void _RestoreStandardChromeState(bool isClosing)
  961. {
  962. VerifyAccess();
  963. _UnhookCustomChrome();
  964. if (!isClosing)
  965. {
  966. _RestoreFrameworkIssueFixups();
  967. _RestoreGlassFrame();
  968. _RestoreHrgn();
  969. _window.InvalidateMeasure();
  970. }
  971. }
  972. private void _UnhookCustomChrome()
  973. {
  974. Assert.IsNotDefault(_hwnd);
  975. Assert.IsNotNull(_window);
  976. if (_isHooked)
  977. {
  978. _hwndSource.RemoveHook(_WndProc);
  979. _isHooked = false;
  980. }
  981. }
  982. private void _RestoreFrameworkIssueFixups()
  983. {
  984. // This margin is only necessary if the client rect is going to be calculated incorrectly by WPF.
  985. // This bug was fixed in V4 of the framework.
  986. if (Utility.IsPresentationFrameworkVersionLessThan4)
  987. {
  988. Assert.IsTrue(_isFixedUp);
  989. var rootElement = (FrameworkElement)VisualTreeHelper.GetChild(_window, 0);
  990. // Undo anything that was done before.
  991. rootElement.Margin = new Thickness();
  992. _window.StateChanged -= _FixupRestoreBounds;
  993. _isFixedUp = false;
  994. }
  995. }
  996. private void _RestoreGlassFrame()
  997. {
  998. Assert.IsNull(_chromeInfo);
  999. Assert.IsNotNull(_window);
  1000. // Expect that this might be called on OSes other than Vista
  1001. // and if the window hasn't yet been shown, then we don't need to undo anything.
  1002. if (!Utility.IsOSVistaOrNewer || _hwnd == IntPtr.Zero)
  1003. {
  1004. return;
  1005. }
  1006. _hwndSource.CompositionTarget.BackgroundColor = SystemColors.WindowColor;
  1007. if (NativeMethods.DwmIsCompositionEnabled())
  1008. {
  1009. // If glass is enabled, push it back to the normal bounds.
  1010. var dwmMargin = new MARGINS();
  1011. NativeMethods.DwmExtendFrameIntoClientArea(_hwnd, ref dwmMargin);
  1012. }
  1013. }
  1014. private void _RestoreHrgn()
  1015. {
  1016. _ClearRoundingRegion();
  1017. NativeMethods.SetWindowPos(_hwnd, IntPtr.Zero, 0, 0, 0, 0, _SwpFlags);
  1018. }
  1019. #endregion
  1020. }
  1021. }