123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379 |
- using System;
- using System.ComponentModel;
- using System.Runtime.InteropServices;
- using System.Windows;
- using System.Windows.Controls;
- using System.Windows.Forms;
- using System.Windows.Input;
- using System.Windows.Interop;
- namespace CustomControls
- {
- [DesignTimeVisible(false)]
- public partial class ChromeWindow : Window, IForm
- {
- private IntPtr handle;
- public System.Windows.Controls.ContentControl ContentControl => this.AppArea;
- private bool extendedAppArea;
- public bool ExtendedAppArea
- {
- get => extendedAppArea;
- set
- {
- extendedAppArea = value;
- Grid.SetRow(this.AppArea, value ? 0 : 1);
- Grid.SetRowSpan(this.AppArea, value ? 2 : 1);
- }
- }
- private double extendedAppAreaSize;
- public double ExtendedAppAreaSize
- {
- get => extendedAppAreaSize;
- set
- {
- extendedAppAreaSize = value;
- this.TitleArea.Margin = new Thickness(value, 0, 0, 0);
- this.TitleDock.Margin = new Thickness(-value, 0, 0, 0);
- this.TitleText.Margin = new Thickness(value + 8, 0, 140, 0);
- }
- }
- public System.Drawing.Size ClientSizeDelta
- {
- get
- {
- double scale = Helper.GetDpiScale(this);
- return new System.Drawing.Size(0, (int)(scale * (ExtendedAppArea ? 0 : TitleArea.Height)));
- }
- }
- private FormBorderStyle formBorderStyle;
- public FormBorderStyle FormBorderStyle
- {
- get => formBorderStyle;
- set
- {
- formBorderStyle = value;
- MinimizeBox = MaximizeBox = value == FormBorderStyle.Sizable || value == FormBorderStyle.SizableToolWindow;
- ShowIcon = value == FormBorderStyle.Sizable;
- }
- }
- private bool maximizeBox;
- public bool MaximizeBox
- {
- get => maximizeBox;
- set
- {
- maximizeBox = value;
- UpdateMaximizeRestoreState();
- }
- }
- private bool minimizeBox;
- public bool MinimizeBox
- {
- get => minimizeBox;
- set
- {
- minimizeBox = value;
- MinimizeButton.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
- }
- }
- public bool ShowIcon
- {
- get => TitleIcon.Visibility == Visibility.Visible;
- set => TitleIcon.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
- }
- public ChromeWindow()
- {
- InitializeComponent();
- StateChanged += (s, e) => UpdateMaximizeRestoreState();
- SourceInitialized += (s, e) =>
- {
- handle = new WindowInteropHelper(this).Handle;
- HwndSource.FromHwnd(handle).AddHook(WindowProc);
- UpdateMaximizeRestoreState();
- };
- }
- private IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
- {
- const int WM_GETMINMAXINFO = 0x24;
- const int WM_NCCALCSIZE = 0x83;
- const int WM_NCHITTEST = 0x84;
- const int HT_CLIENT = 1;
- // 1. in order to use snap features, we have to set the ResizeMode to CanResize
- // 2. setting WindowStyle to SingleBorderWindow is required to get maximize/restore animations
- // 3. use WM_NCCALCSIZE (handle and return 0) to remove non-client area
- // 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
- // 5. update WM_NCCALCSIZE to return smaller client area when maximized
- // 6. now we have popping effect when maximized window is shown first time (due to changed nc size)
- // 7. handle WM_GETMINMAXINFO to return smaller maximized bounds to cheat Windows
- // 8. the only side effect we have is a single pixel gap at the bottom of maximized window
- switch (msg)
- {
- case WM_GETMINMAXINFO:
- WmGetMinMaxInfo(hwnd, lParam);
- handled = true;
- break;
- case WM_NCCALCSIZE:
- if (NativeMethods.IsWindowMaximized(hwnd))
- {
- var rc = Marshal.PtrToStructure<NativeMethods.RECT>(lParam);
- var screen = Screen.FromHandle(hwnd);
- rc.left = screen.WorkingArea.Left;
- rc.top = screen.WorkingArea.Top;
- rc.right = screen.WorkingArea.Right;
- rc.bottom = screen.WorkingArea.Bottom - 1; // -1 to allow auto-hide taskbar (at the bottom)
- Marshal.StructureToPtr(rc, lParam, true);
- }
- else if (wParam.ToInt32() != 0)
- {
- var rc = Marshal.PtrToStructure<NativeMethods.RECT>(lParam);
- // cheat Windows to force a flicker free resize
- rc.bottom += 1;
- Marshal.StructureToPtr(rc, lParam, true);
- }
- var retVal = IntPtr.Zero;
- if (wParam.ToInt32() != 0)
- {
- // returning VALIDRECTS | REDRAW gives the smoothest resize behavior
- retVal = new IntPtr(0x0400 | 0x0100 | 0x0200);
- }
- handled = true;
- return retVal;
- case WM_NCHITTEST:
- // required for Win7 (caption buttons issue)
- handled = true;
- return new IntPtr(HT_CLIENT);
- }
- return (IntPtr)0;
- }
- private void WmGetMinMaxInfo(IntPtr hwnd, IntPtr lParam)
- {
- var mmi = Marshal.PtrToStructure<NativeMethods.MINMAXINFO>(lParam);
- var screen = Screen.FromHandle(hwnd);
- mmi.ptMaxPosition.X = screen.WorkingArea.Left - screen.Bounds.Left;
- mmi.ptMaxPosition.Y = screen.WorkingArea.Top - screen.Bounds.Top;
- mmi.ptMaxSize.X = screen.WorkingArea.Width;
- mmi.ptMaxSize.Y = screen.WorkingArea.Height - 1; // -1 is a cheat to remove popping effect when maximize window on startup
- mmi.ptMinTrackSize.X = 50;
- mmi.ptMinTrackSize.Y = 50;
- Marshal.StructureToPtr(mmi, lParam, true);
- }
- private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
- {
- e.CanExecute = true;
- }
- private void CommandBinding_Executed_Minimize(object sender, ExecutedRoutedEventArgs e)
- {
- SystemCommands.MinimizeWindow(this);
- }
- private void CommandBinding_Executed_Maximize(object sender, ExecutedRoutedEventArgs e)
- {
- WindowState = WindowState.Maximized;// SystemCommands.MaximizeWindow(this); // issue with dragging maximized window
- }
- private void CommandBinding_Executed_Restore(object sender, ExecutedRoutedEventArgs e)
- {
- SystemCommands.RestoreWindow(this);
- }
- private void CommandBinding_Executed_Close(object sender, ExecutedRoutedEventArgs e)
- {
- SystemCommands.CloseWindow(this);
- }
- private void UpdateMaximizeRestoreState()
- {
- if (WindowState == WindowState.Maximized)
- {
- RestoreButton.Visibility = MaximizeBox ? Visibility.Visible : Visibility.Collapsed;
- MaximizeButton.Visibility = Visibility.Collapsed;
- WindowBorder.Visibility = Visibility.Collapsed;
- ResizingBorder.Visibility = Visibility.Collapsed;
- NativeMethods.DropShadow(handle, false);
- }
- else
- {
- RestoreButton.Visibility = Visibility.Collapsed;
- MaximizeButton.Visibility = MaximizeBox ? Visibility.Visible : Visibility.Collapsed;
- WindowBorder.Visibility = Visibility.Visible;
- ResizingBorder.Visibility = Visibility.Visible;
- NativeMethods.DropShadow(handle, WindowState == WindowState.Normal);
- }
- }
- private SizingAction sizingAction;
- private void UpdateCursor(FrameworkElement c, SizingAction mode)
- {
- var cursor = System.Windows.Input.Cursors.Arrow;
- switch (mode)
- {
- case SizingAction.North:
- case SizingAction.South:
- cursor = System.Windows.Input.Cursors.SizeNS;
- break;
- case SizingAction.West:
- case SizingAction.East:
- cursor = System.Windows.Input.Cursors.SizeWE;
- break;
- case SizingAction.NorthWest:
- case SizingAction.SouthEast:
- cursor = System.Windows.Input.Cursors.SizeNWSE;
- break;
- case SizingAction.NorthEast:
- case SizingAction.SouthWest:
- cursor = System.Windows.Input.Cursors.SizeNESW;
- break;
- }
- c.Cursor = cursor;
- }
- private void Border_MouseDown(object sender, MouseButtonEventArgs e)
- {
- if (e.LeftButton == MouseButtonState.Pressed && sizingAction != SizingAction.None)
- {
- NativeMethods.DragSize(handle, sizingAction);
- e.Handled = true;
- }
- }
- private void Border_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
- {
- var border = sender as Border;
- var pos = e.GetPosition(border);
- sizingAction = SizingAction.None;
- if (pos.X == 0 && pos.Y == 0)
- return;
- bool canResize = FormBorderStyle == FormBorderStyle.Sizable || FormBorderStyle == FormBorderStyle.SizableToolWindow;
- if (canResize && e.LeftButton != MouseButtonState.Pressed)
- {
- var width = border.ActualWidth;
- var height = border.ActualHeight;
- if (pos.Y <= 4) // top
- {
- if (pos.X <= 4)
- sizingAction = SizingAction.NorthWest;
- else if (pos.X >= width - 4)
- sizingAction = SizingAction.NorthEast;
- else
- sizingAction = SizingAction.North;
- }
- else if (pos.Y >= height - 4) // bottom
- {
- if (pos.X <= 4)
- sizingAction = SizingAction.SouthWest;
- else if (pos.X >= width - 4)
- sizingAction = SizingAction.SouthEast;
- else
- sizingAction = SizingAction.South;
- }
- else // middle
- {
- if (pos.X <= 4)
- sizingAction = SizingAction.West;
- else if (pos.X >= width - 4)
- sizingAction = SizingAction.East;
- }
- UpdateCursor(border, sizingAction);
- }
- }
- private void Border_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
- {
- var border = sender as Border;
- UpdateCursor(border, SizingAction.None);
- }
- private bool restoreForDragMove;
- private void TitleArea_MouseDown(object sender, MouseButtonEventArgs e)
- {
- if (e.ClickCount > 1)
- {
- if (this.WindowState == WindowState.Maximized)
- this.WindowState = WindowState.Normal;
- else if (this.WindowState == WindowState.Normal)
- this.WindowState = WindowState.Maximized;
- return;
- }
- restoreForDragMove = WindowState == WindowState.Maximized;
- DragMove();
- e.Handled = true;
- }
- private void TitleArea_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
- {
- if (restoreForDragMove)
- {
- restoreForDragMove = false;
- var dpiScale = Helper.GetDpiScale(this);
- var winPoint = e.MouseDevice.GetPosition(this);
- var scrPoint = PointToScreen(winPoint);
- scrPoint.X /= dpiScale;
- scrPoint.Y /= dpiScale;
- var half = RestoreBounds.Width / 2;
- if (winPoint.X < half)
- Left = scrPoint.X - winPoint.X;
- else if (Width - winPoint.X < half)
- Left = scrPoint.X - RestoreBounds.Width + (Width - winPoint.X);
- else
- Left = scrPoint.X - half;
- Top = scrPoint.Y - winPoint.Y;
- WindowState = WindowState.Normal;
- DragMove();
- }
- }
- private void TitleArea_MouseUp(object sender, MouseButtonEventArgs e)
- {
- restoreForDragMove = false;
- }
- private void TitleIcon_MouseDown(object sender, MouseButtonEventArgs e)
- {
- if (e.ClickCount > 1)
- {
- SystemCommands.CloseWindow(this);
- e.Handled = true;
- }
- else
- {
- var pt = this.PointToScreen(new Point(0, TitleArea.ActualHeight));
- var scale = Helper.GetDpiScale(this);
- SystemCommands.ShowSystemMenu(this, new Point(pt.X / scale, pt.Y / scale));
- e.Handled = true;
- }
- }
- }
- }
|