ChromeWindow.xaml.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. using System;
  2. using System.ComponentModel;
  3. using System.Runtime.InteropServices;
  4. using System.Windows;
  5. using System.Windows.Controls;
  6. using System.Windows.Forms;
  7. using System.Windows.Input;
  8. using System.Windows.Interop;
  9. namespace CustomControls
  10. {
  11. [DesignTimeVisible(false)]
  12. public partial class ChromeWindow : Window, IForm
  13. {
  14. private IntPtr handle;
  15. public System.Windows.Controls.ContentControl ContentControl => this.AppArea;
  16. private bool extendedAppArea;
  17. public bool ExtendedAppArea
  18. {
  19. get => extendedAppArea;
  20. set
  21. {
  22. extendedAppArea = value;
  23. Grid.SetRow(this.AppArea, value ? 0 : 1);
  24. Grid.SetRowSpan(this.AppArea, value ? 2 : 1);
  25. }
  26. }
  27. private double extendedAppAreaSize;
  28. public double ExtendedAppAreaSize
  29. {
  30. get => extendedAppAreaSize;
  31. set
  32. {
  33. extendedAppAreaSize = value;
  34. this.TitleArea.Margin = new Thickness(value, 0, 0, 0);
  35. this.TitleDock.Margin = new Thickness(-value, 0, 0, 0);
  36. this.TitleText.Margin = new Thickness(value + 8, 0, 140, 0);
  37. }
  38. }
  39. public System.Drawing.Size ClientSizeDelta
  40. {
  41. get
  42. {
  43. double scale = Helper.GetDpiScale(this);
  44. return new System.Drawing.Size(0, (int)(scale * (ExtendedAppArea ? 0 : TitleArea.Height)));
  45. }
  46. }
  47. private FormBorderStyle formBorderStyle;
  48. public FormBorderStyle FormBorderStyle
  49. {
  50. get => formBorderStyle;
  51. set
  52. {
  53. formBorderStyle = value;
  54. MinimizeBox = MaximizeBox = value == FormBorderStyle.Sizable || value == FormBorderStyle.SizableToolWindow;
  55. ShowIcon = value == FormBorderStyle.Sizable;
  56. }
  57. }
  58. private bool maximizeBox;
  59. public bool MaximizeBox
  60. {
  61. get => maximizeBox;
  62. set
  63. {
  64. maximizeBox = value;
  65. UpdateMaximizeRestoreState();
  66. }
  67. }
  68. private bool minimizeBox;
  69. public bool MinimizeBox
  70. {
  71. get => minimizeBox;
  72. set
  73. {
  74. minimizeBox = value;
  75. MinimizeButton.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
  76. }
  77. }
  78. public bool ShowIcon
  79. {
  80. get => TitleIcon.Visibility == Visibility.Visible;
  81. set => TitleIcon.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
  82. }
  83. public ChromeWindow()
  84. {
  85. InitializeComponent();
  86. StateChanged += (s, e) => UpdateMaximizeRestoreState();
  87. SourceInitialized += (s, e) =>
  88. {
  89. handle = new WindowInteropHelper(this).Handle;
  90. HwndSource.FromHwnd(handle).AddHook(WindowProc);
  91. UpdateMaximizeRestoreState();
  92. };
  93. }
  94. private IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
  95. {
  96. const int WM_GETMINMAXINFO = 0x24;
  97. const int WM_NCCALCSIZE = 0x83;
  98. const int WM_NCHITTEST = 0x84;
  99. const int HT_CLIENT = 1;
  100. // 1. in order to use snap features, we have to set the ResizeMode to CanResize
  101. // 2. setting WindowStyle to SingleBorderWindow is required to get maximize/restore animations
  102. // 3. use WM_NCCALCSIZE (handle and return 0) to remove non-client area
  103. // 4. the maximized window's client area is off now. Don't try to reduce it using wpf margins: on a multi-monitor system this will degrade rendering performance
  104. // 5. update WM_NCCALCSIZE to return smaller client area when maximized
  105. // 6. now we have popping effect when maximized window is shown first time (due to changed nc size)
  106. // 7. handle WM_GETMINMAXINFO to return smaller maximized bounds to cheat Windows
  107. // 8. the only side effect we have is a single pixel gap at the bottom of maximized window
  108. switch (msg)
  109. {
  110. case WM_GETMINMAXINFO:
  111. WmGetMinMaxInfo(hwnd, lParam);
  112. handled = true;
  113. break;
  114. case WM_NCCALCSIZE:
  115. if (NativeMethods.IsWindowMaximized(hwnd))
  116. {
  117. var rc = Marshal.PtrToStructure<NativeMethods.RECT>(lParam);
  118. var screen = Screen.FromHandle(hwnd);
  119. rc.left = screen.WorkingArea.Left;
  120. rc.top = screen.WorkingArea.Top;
  121. rc.right = screen.WorkingArea.Right;
  122. rc.bottom = screen.WorkingArea.Bottom - 1; // -1 to allow auto-hide taskbar (at the bottom)
  123. Marshal.StructureToPtr(rc, lParam, true);
  124. }
  125. else if (wParam.ToInt32() != 0)
  126. {
  127. var rc = Marshal.PtrToStructure<NativeMethods.RECT>(lParam);
  128. // cheat Windows to force a flicker free resize
  129. rc.bottom += 1;
  130. Marshal.StructureToPtr(rc, lParam, true);
  131. }
  132. var retVal = IntPtr.Zero;
  133. if (wParam.ToInt32() != 0)
  134. {
  135. // returning VALIDRECTS | REDRAW gives the smoothest resize behavior
  136. retVal = new IntPtr(0x0400 | 0x0100 | 0x0200);
  137. }
  138. handled = true;
  139. return retVal;
  140. case WM_NCHITTEST:
  141. // required for Win7 (caption buttons issue)
  142. handled = true;
  143. return new IntPtr(HT_CLIENT);
  144. }
  145. return (IntPtr)0;
  146. }
  147. private void WmGetMinMaxInfo(IntPtr hwnd, IntPtr lParam)
  148. {
  149. var mmi = Marshal.PtrToStructure<NativeMethods.MINMAXINFO>(lParam);
  150. var screen = Screen.FromHandle(hwnd);
  151. mmi.ptMaxPosition.X = screen.WorkingArea.Left - screen.Bounds.Left;
  152. mmi.ptMaxPosition.Y = screen.WorkingArea.Top - screen.Bounds.Top;
  153. mmi.ptMaxSize.X = screen.WorkingArea.Width;
  154. mmi.ptMaxSize.Y = screen.WorkingArea.Height - 1; // -1 is a cheat to remove popping effect when maximize window on startup
  155. mmi.ptMinTrackSize.X = 50;
  156. mmi.ptMinTrackSize.Y = 50;
  157. Marshal.StructureToPtr(mmi, lParam, true);
  158. }
  159. private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
  160. {
  161. e.CanExecute = true;
  162. }
  163. private void CommandBinding_Executed_Minimize(object sender, ExecutedRoutedEventArgs e)
  164. {
  165. SystemCommands.MinimizeWindow(this);
  166. }
  167. private void CommandBinding_Executed_Maximize(object sender, ExecutedRoutedEventArgs e)
  168. {
  169. WindowState = WindowState.Maximized;// SystemCommands.MaximizeWindow(this); // issue with dragging maximized window
  170. }
  171. private void CommandBinding_Executed_Restore(object sender, ExecutedRoutedEventArgs e)
  172. {
  173. SystemCommands.RestoreWindow(this);
  174. }
  175. private void CommandBinding_Executed_Close(object sender, ExecutedRoutedEventArgs e)
  176. {
  177. SystemCommands.CloseWindow(this);
  178. }
  179. private void UpdateMaximizeRestoreState()
  180. {
  181. if (WindowState == WindowState.Maximized)
  182. {
  183. RestoreButton.Visibility = MaximizeBox ? Visibility.Visible : Visibility.Collapsed;
  184. MaximizeButton.Visibility = Visibility.Collapsed;
  185. WindowBorder.Visibility = Visibility.Collapsed;
  186. ResizingBorder.Visibility = Visibility.Collapsed;
  187. NativeMethods.DropShadow(handle, false);
  188. }
  189. else
  190. {
  191. RestoreButton.Visibility = Visibility.Collapsed;
  192. MaximizeButton.Visibility = MaximizeBox ? Visibility.Visible : Visibility.Collapsed;
  193. WindowBorder.Visibility = Visibility.Visible;
  194. ResizingBorder.Visibility = Visibility.Visible;
  195. NativeMethods.DropShadow(handle, WindowState == WindowState.Normal);
  196. }
  197. }
  198. private SizingAction sizingAction;
  199. private void UpdateCursor(FrameworkElement c, SizingAction mode)
  200. {
  201. var cursor = System.Windows.Input.Cursors.Arrow;
  202. switch (mode)
  203. {
  204. case SizingAction.North:
  205. case SizingAction.South:
  206. cursor = System.Windows.Input.Cursors.SizeNS;
  207. break;
  208. case SizingAction.West:
  209. case SizingAction.East:
  210. cursor = System.Windows.Input.Cursors.SizeWE;
  211. break;
  212. case SizingAction.NorthWest:
  213. case SizingAction.SouthEast:
  214. cursor = System.Windows.Input.Cursors.SizeNWSE;
  215. break;
  216. case SizingAction.NorthEast:
  217. case SizingAction.SouthWest:
  218. cursor = System.Windows.Input.Cursors.SizeNESW;
  219. break;
  220. }
  221. c.Cursor = cursor;
  222. }
  223. private void Border_MouseDown(object sender, MouseButtonEventArgs e)
  224. {
  225. if (e.LeftButton == MouseButtonState.Pressed && sizingAction != SizingAction.None)
  226. {
  227. NativeMethods.DragSize(handle, sizingAction);
  228. e.Handled = true;
  229. }
  230. }
  231. private void Border_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
  232. {
  233. var border = sender as Border;
  234. var pos = e.GetPosition(border);
  235. sizingAction = SizingAction.None;
  236. if (pos.X == 0 && pos.Y == 0)
  237. return;
  238. bool canResize = FormBorderStyle == FormBorderStyle.Sizable || FormBorderStyle == FormBorderStyle.SizableToolWindow;
  239. if (canResize && e.LeftButton != MouseButtonState.Pressed)
  240. {
  241. var width = border.ActualWidth;
  242. var height = border.ActualHeight;
  243. if (pos.Y <= 4) // top
  244. {
  245. if (pos.X <= 4)
  246. sizingAction = SizingAction.NorthWest;
  247. else if (pos.X >= width - 4)
  248. sizingAction = SizingAction.NorthEast;
  249. else
  250. sizingAction = SizingAction.North;
  251. }
  252. else if (pos.Y >= height - 4) // bottom
  253. {
  254. if (pos.X <= 4)
  255. sizingAction = SizingAction.SouthWest;
  256. else if (pos.X >= width - 4)
  257. sizingAction = SizingAction.SouthEast;
  258. else
  259. sizingAction = SizingAction.South;
  260. }
  261. else // middle
  262. {
  263. if (pos.X <= 4)
  264. sizingAction = SizingAction.West;
  265. else if (pos.X >= width - 4)
  266. sizingAction = SizingAction.East;
  267. }
  268. UpdateCursor(border, sizingAction);
  269. }
  270. }
  271. private void Border_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
  272. {
  273. var border = sender as Border;
  274. UpdateCursor(border, SizingAction.None);
  275. }
  276. private bool restoreForDragMove;
  277. private void TitleArea_MouseDown(object sender, MouseButtonEventArgs e)
  278. {
  279. if (e.ClickCount > 1)
  280. {
  281. if (this.WindowState == WindowState.Maximized)
  282. this.WindowState = WindowState.Normal;
  283. else if (this.WindowState == WindowState.Normal)
  284. this.WindowState = WindowState.Maximized;
  285. return;
  286. }
  287. restoreForDragMove = WindowState == WindowState.Maximized;
  288. DragMove();
  289. e.Handled = true;
  290. }
  291. private void TitleArea_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
  292. {
  293. if (restoreForDragMove)
  294. {
  295. restoreForDragMove = false;
  296. var dpiScale = Helper.GetDpiScale(this);
  297. var winPoint = e.MouseDevice.GetPosition(this);
  298. var scrPoint = PointToScreen(winPoint);
  299. scrPoint.X /= dpiScale;
  300. scrPoint.Y /= dpiScale;
  301. var half = RestoreBounds.Width / 2;
  302. if (winPoint.X < half)
  303. Left = scrPoint.X - winPoint.X;
  304. else if (Width - winPoint.X < half)
  305. Left = scrPoint.X - RestoreBounds.Width + (Width - winPoint.X);
  306. else
  307. Left = scrPoint.X - half;
  308. Top = scrPoint.Y - winPoint.Y;
  309. WindowState = WindowState.Normal;
  310. DragMove();
  311. }
  312. }
  313. private void TitleArea_MouseUp(object sender, MouseButtonEventArgs e)
  314. {
  315. restoreForDragMove = false;
  316. }
  317. private void TitleIcon_MouseDown(object sender, MouseButtonEventArgs e)
  318. {
  319. if (e.ClickCount > 1)
  320. {
  321. SystemCommands.CloseWindow(this);
  322. e.Handled = true;
  323. }
  324. else
  325. {
  326. var pt = this.PointToScreen(new Point(0, TitleArea.ActualHeight));
  327. var scale = Helper.GetDpiScale(this);
  328. SystemCommands.ShowSystemMenu(this, new Point(pt.X / scale, pt.Y / scale));
  329. e.Handled = true;
  330. }
  331. }
  332. }
  333. }