diff --git a/MahApps.Metro/Controls/Flyout.cs b/MahApps.Metro/Controls/Flyout.cs index e29893e892..05912f9c9f 100644 --- a/MahApps.Metro/Controls/Flyout.cs +++ b/MahApps.Metro/Controls/Flyout.cs @@ -1,7 +1,7 @@ using System; -using System.Linq; using System.Windows; using System.Windows.Controls; +using System.Windows.Controls.Primitives; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; @@ -15,6 +15,7 @@ namespace MahApps.Metro.Controls /// /// [TemplatePart(Name = "PART_BackButton", Type = typeof(Button))] + [TemplatePart(Name = "PART_WindowTitleThumb", Type = typeof(Thumb))] [TemplatePart(Name = "PART_Header", Type = typeof(ContentPresenter))] [TemplatePart(Name = "PART_Content", Type = typeof(ContentPresenter))] public class Flyout : ContentControl @@ -212,6 +213,13 @@ public Flyout() this.Loaded += (sender, args) => UpdateFlyoutTheme(); } + private MetroWindow parentWindow; + + private MetroWindow ParentWindow + { + get { return this.parentWindow ?? (this.parentWindow = this.TryFindParent()); } + } + private void UpdateFlyoutTheme() { var flyoutsControl = this.TryFindParent(); @@ -221,7 +229,7 @@ private void UpdateFlyoutTheme() this.Visibility = flyoutsControl != null ? Visibility.Collapsed : Visibility.Visible; } - var window = this.TryFindParent(); + var window = this.ParentWindow; if (window != null) { var windowTheme = DetectTheme(this); @@ -282,7 +290,7 @@ private static Tuple DetectTheme(Flyout flyout) return null; // first look for owner - var window = flyout.TryFindParent(); + var window = flyout.ParentWindow; var theme = window != null ? ThemeManager.DetectAppStyle(window) : null; if (theme != null && theme.Item2 != null) return theme; @@ -460,6 +468,7 @@ static Flyout() SplineDoubleKeyFrame fadeOutFrame; ContentPresenter PART_Header; ContentPresenter PART_Content; + Thumb windowTitleThumb; public override void OnApplyTemplate() { @@ -471,7 +480,19 @@ public override void OnApplyTemplate() PART_Header = (ContentPresenter)GetTemplateChild("PART_Header"); PART_Content = (ContentPresenter)GetTemplateChild("PART_Content"); - + this.windowTitleThumb = GetTemplateChild("PART_WindowTitleThumb") as Thumb; + + if (this.windowTitleThumb != null) + { + this.windowTitleThumb.DragDelta -= this.WindowTitleThumbMoveOnDragDelta; + this.windowTitleThumb.MouseDoubleClick -= this.WindowTitleThumbChangeWindowStateOnMouseDoubleClick; + this.windowTitleThumb.MouseRightButtonUp -= this.WindowTitleThumbSystemMenuOnMouseRightButtonUp; + + this.windowTitleThumb.DragDelta += this.WindowTitleThumbMoveOnDragDelta; + this.windowTitleThumb.MouseDoubleClick += this.WindowTitleThumbChangeWindowStateOnMouseDoubleClick; + this.windowTitleThumb.MouseRightButtonUp += this.WindowTitleThumbSystemMenuOnMouseRightButtonUp; + } + hideStoryboard = (Storyboard)GetTemplateChild("HideStoryboard"); hideFrame = (SplineDoubleKeyFrame)GetTemplateChild("hideFrame"); hideFrameY = (SplineDoubleKeyFrame)GetTemplateChild("hideFrameY"); @@ -485,6 +506,44 @@ public override void OnApplyTemplate() ApplyAnimation(Position, AnimateOpacity); } + protected internal void CleanUp(FlyoutsControl flyoutsControl) + { + if (this.windowTitleThumb != null) + { + this.windowTitleThumb.DragDelta -= this.WindowTitleThumbMoveOnDragDelta; + this.windowTitleThumb.MouseDoubleClick -= this.WindowTitleThumbChangeWindowStateOnMouseDoubleClick; + this.windowTitleThumb.MouseRightButtonUp -= this.WindowTitleThumbSystemMenuOnMouseRightButtonUp; + } + this.parentWindow = null; + } + + private void WindowTitleThumbMoveOnDragDelta(object sender, DragDeltaEventArgs dragDeltaEventArgs) + { + var window = this.ParentWindow; + if (window != null && this.Position != Position.Bottom) + { + MetroWindow.DoWindowTitleThumbMoveOnDragDelta(window, dragDeltaEventArgs); + } + } + + private void WindowTitleThumbChangeWindowStateOnMouseDoubleClick(object sender, MouseButtonEventArgs mouseButtonEventArgs) + { + var window = this.ParentWindow; + if (window != null && this.Position != Position.Bottom) + { + MetroWindow.DoWindowTitleThumbChangeWindowStateOnMouseDoubleClick(window, mouseButtonEventArgs); + } + } + + private void WindowTitleThumbSystemMenuOnMouseRightButtonUp(object sender, MouseButtonEventArgs e) + { + var window = this.ParentWindow; + if (window != null && this.Position != Position.Bottom) + { + MetroWindow.DoWindowTitleThumbSystemMenuOnMouseRightButtonUp(window, e); + } + } + internal void ApplyAnimation(Position position, bool animateOpacity, bool resetShowFrame = true) { if (root == null || hideFrame == null || showFrame == null || hideFrameY == null || showFrameY == null || fadeOutFrame == null) diff --git a/MahApps.Metro/Controls/FlyoutsControl.cs b/MahApps.Metro/Controls/FlyoutsControl.cs index ad6c7486ec..66a2ae226c 100644 --- a/MahApps.Metro/Controls/FlyoutsControl.cs +++ b/MahApps.Metro/Controls/FlyoutsControl.cs @@ -58,6 +58,12 @@ protected override void PrepareContainerForItemOverride(DependencyObject element this.AttachHandlers((Flyout)element); } + protected override void ClearContainerForItemOverride(DependencyObject element, object item) + { + ((Flyout) element).CleanUp(this); + base.ClearContainerForItemOverride(element, item); + } + private void AttachHandlers(Flyout flyout) { var isOpenNotifier = new PropertyChangeNotifier(flyout, Flyout.IsOpenProperty); diff --git a/MahApps.Metro/Controls/MetroWindow.cs b/MahApps.Metro/Controls/MetroWindow.cs index 82905574b9..bcd9a0436c 100644 --- a/MahApps.Metro/Controls/MetroWindow.cs +++ b/MahApps.Metro/Controls/MetroWindow.cs @@ -10,6 +10,7 @@ using MahApps.Metro.Native; using System.Windows.Shapes; using System.Collections.Generic; +using System.Windows.Controls.Primitives; namespace MahApps.Metro.Controls { @@ -19,6 +20,7 @@ namespace MahApps.Metro.Controls [TemplatePart(Name = PART_Icon, Type = typeof(UIElement))] [TemplatePart(Name = PART_TitleBar, Type = typeof(UIElement))] [TemplatePart(Name = PART_WindowTitleBackground, Type = typeof(UIElement))] + [TemplatePart(Name = PART_WindowTitleThumb, Type = typeof(Thumb))] [TemplatePart(Name = PART_LeftWindowCommands, Type = typeof(WindowCommands))] [TemplatePart(Name = PART_RightWindowCommands, Type = typeof(WindowCommands))] [TemplatePart(Name = PART_WindowButtonCommands, Type = typeof(WindowButtonCommands))] @@ -31,6 +33,7 @@ public class MetroWindow : Window private const string PART_Icon = "PART_Icon"; private const string PART_TitleBar = "PART_TitleBar"; private const string PART_WindowTitleBackground = "PART_WindowTitleBackground"; + private const string PART_WindowTitleThumb = "PART_WindowTitleThumb"; private const string PART_LeftWindowCommands = "PART_LeftWindowCommands"; private const string PART_RightWindowCommands = "PART_RightWindowCommands"; private const string PART_WindowButtonCommands = "PART_WindowButtonCommands"; @@ -99,6 +102,7 @@ public class MetroWindow : Window UIElement icon; UIElement titleBar; UIElement titleBarBackground; + Thumb windowTitleThumb; internal ContentPresenter LeftWindowCommandsPresenter; internal ContentPresenter RightWindowCommandsPresenter; internal WindowButtonCommands WindowButtonCommands; @@ -890,6 +894,7 @@ public override void OnApplyTemplate() icon = GetTemplateChild(PART_Icon) as UIElement; titleBar = GetTemplateChild(PART_TitleBar) as UIElement; titleBarBackground = GetTemplateChild(PART_WindowTitleBackground) as UIElement; + this.windowTitleThumb = GetTemplateChild(PART_WindowTitleThumb) as Thumb; this.SetVisibiltyForAllTitleElements(this.TitlebarHeight > 0); } @@ -897,22 +902,16 @@ public override void OnApplyTemplate() private void ClearWindowEvents() { // clear all event handlers first: - if (titleBarBackground != null) + if (this.windowTitleThumb != null) { - titleBarBackground.MouseDown -= TitleBarMouseDown; - titleBarBackground.MouseUp -= TitleBarMouseUp; - } - if (titleBar != null) - { - titleBar.MouseDown -= TitleBarMouseDown; - titleBar.MouseUp -= TitleBarMouseUp; + this.windowTitleThumb.DragDelta -= this.WindowTitleThumbMoveOnDragDelta; + this.windowTitleThumb.MouseDoubleClick -= this.WindowTitleThumbChangeWindowStateOnMouseDoubleClick; + this.windowTitleThumb.MouseRightButtonUp -= this.WindowTitleThumbSystemMenuOnMouseRightButtonUp; } if (icon != null) { icon.MouseDown -= IconMouseDown; } - MouseDown -= TitleBarMouseDown; - MouseUp -= TitleBarMouseUp; SizeChanged -= MetroWindow_SizeChanged; } @@ -927,30 +926,17 @@ private void SetWindowEvents() icon.MouseDown += IconMouseDown; } - // handle mouse events for PART_WindowTitleBackground -> MetroWindow - if (titleBarBackground != null && titleBarBackground.Visibility == Visibility.Visible) + if (this.windowTitleThumb != null) { - titleBarBackground.MouseDown += TitleBarMouseDown; - titleBarBackground.MouseUp += TitleBarMouseUp; + this.windowTitleThumb.DragDelta += this.WindowTitleThumbMoveOnDragDelta; + this.windowTitleThumb.MouseDoubleClick += this.WindowTitleThumbChangeWindowStateOnMouseDoubleClick; + this.windowTitleThumb.MouseRightButtonUp += this.WindowTitleThumbSystemMenuOnMouseRightButtonUp; } - // handle mouse events for PART_TitleBar -> MetroWindow - if (titleBar != null && titleBar.Visibility == Visibility.Visible) - { - titleBar.MouseDown += TitleBarMouseDown; - titleBar.MouseUp += TitleBarMouseUp; - - // special title resizing for centered title - if (titleBar.GetType() == typeof(Grid)) - { - SizeChanged += MetroWindow_SizeChanged; - } - } - else + // handle size if we have a Grid for the title (e.g. clean window have a centered title) + if (titleBar != null && titleBar.GetType() == typeof(Grid)) { - // handle mouse events for windows without PART_WindowTitleBackground or PART_TitleBar - MouseDown += TitleBarMouseDown; - MouseUp += TitleBarMouseUp; + SizeChanged += MetroWindow_SizeChanged; } } @@ -969,49 +955,82 @@ private void IconMouseDown(object sender, MouseButtonEventArgs e) } } - protected void TitleBarMouseDown(object sender, MouseButtonEventArgs e) + private void WindowTitleThumbMoveOnDragDelta(object sender, DragDeltaEventArgs dragDeltaEventArgs) + { + DoWindowTitleThumbMoveOnDragDelta(this, dragDeltaEventArgs); + } + + private void WindowTitleThumbChangeWindowStateOnMouseDoubleClick(object sender, MouseButtonEventArgs mouseButtonEventArgs) + { + DoWindowTitleThumbChangeWindowStateOnMouseDoubleClick(this, mouseButtonEventArgs); + } + + private void WindowTitleThumbSystemMenuOnMouseRightButtonUp(object sender, MouseButtonEventArgs e) { - // if UseNoneWindowStyle = true no movement, no maximize please - if (e.ChangedButton == MouseButton.Left && !this.UseNoneWindowStyle) + DoWindowTitleThumbSystemMenuOnMouseRightButtonUp(this, e); + } + + internal static void DoWindowTitleThumbMoveOnDragDelta(MetroWindow window, DragDeltaEventArgs dragDeltaEventArgs) + { + // drag only if IsWindowDraggable is set to true + if (!window.IsWindowDraggable || + (!(Math.Abs(dragDeltaEventArgs.HorizontalChange) > 2) && + !(Math.Abs(dragDeltaEventArgs.VerticalChange) > 2))) return; + + var windowHandle = new WindowInteropHelper(window).Handle; + var cursorPos = Standard.NativeMethods.GetCursorPos(); + + // if the window is maximized dragging is only allowed on title bar (also if not visible) + var windowIsMaximized = window.WindowState == WindowState.Maximized; + var isMouseOnTitlebar = cursorPos.y <= window.TitlebarHeight && window.TitlebarHeight > 0; + if (!isMouseOnTitlebar && windowIsMaximized) { - var mPoint = Mouse.GetPosition(this); + return; + } - if (IsWindowDraggable) - { - IntPtr windowHandle = new WindowInteropHelper(this).Handle; - UnsafeNativeMethods.ReleaseCapture(); - var wpfPoint = this.PointToScreen(mPoint); - var x = Convert.ToInt16(wpfPoint.X); - var y = Convert.ToInt16(wpfPoint.Y); - var lParam = (int) (uint) x | (y << 16); - UnsafeNativeMethods.SendMessage(windowHandle, Constants.WM_NCLBUTTONDOWN, Constants.HT_CAPTION, lParam); - } + if (windowIsMaximized) + { + window.Top = 2; + window.Left = Math.Max(cursorPos.x - window.RestoreBounds.Width / 2, 0); + } + var lParam = (int)(uint)cursorPos.x | (cursorPos.y << 16); + Standard.NativeMethods.SendMessage(windowHandle, Standard.WM.LBUTTONUP, (IntPtr)Standard.HT.CAPTION, (IntPtr)lParam); + Standard.NativeMethods.SendMessage(windowHandle, Standard.WM.SYSCOMMAND, (IntPtr)Standard.SC.MOUSEMOVE, IntPtr.Zero); + } - var canResize = this.ResizeMode == ResizeMode.CanResizeWithGrip || this.ResizeMode == ResizeMode.CanResize; + internal static void DoWindowTitleThumbChangeWindowStateOnMouseDoubleClick(MetroWindow window, MouseButtonEventArgs mouseButtonEventArgs) + { + // restore/maximize only with left button + if (mouseButtonEventArgs.ChangedButton == MouseButton.Left) + { // we can maximize or restore the window if the title bar height is set (also if title bar is hidden) - var isMouseOnTitlebar = mPoint.Y <= this.TitlebarHeight && this.TitlebarHeight > 0; - if (e.ClickCount == 2 && canResize && isMouseOnTitlebar) + var canResize = window.ResizeMode == ResizeMode.CanResizeWithGrip || window.ResizeMode == ResizeMode.CanResize; + var mousePos = Mouse.GetPosition(window); + var isMouseOnTitlebar = mousePos.Y <= window.TitlebarHeight && window.TitlebarHeight > 0; + if (canResize && isMouseOnTitlebar) { - if (this.WindowState == WindowState.Maximized) + if (window.WindowState == WindowState.Maximized) { - Microsoft.Windows.Shell.SystemCommands.RestoreWindow(this); + Microsoft.Windows.Shell.SystemCommands.RestoreWindow(window); } else { - Microsoft.Windows.Shell.SystemCommands.MaximizeWindow(this); + Microsoft.Windows.Shell.SystemCommands.MaximizeWindow(window); } + mouseButtonEventArgs.Handled = true; } } } - protected void TitleBarMouseUp(object sender, MouseButtonEventArgs e) + internal static void DoWindowTitleThumbSystemMenuOnMouseRightButtonUp(MetroWindow window, MouseButtonEventArgs e) { - if (ShowSystemMenuOnRightClick) + if (window.ShowSystemMenuOnRightClick) { - var mousePosition = e.GetPosition(this); - if (e.ChangedButton == MouseButton.Right && (UseNoneWindowStyle || mousePosition.Y <= TitlebarHeight)) + // show menu only if mouse pos is on title bar or if we have a window with none style and no title bar + var mousePos = e.GetPosition(window); + if ((mousePos.Y <= window.TitlebarHeight && window.TitlebarHeight > 0) || (window.UseNoneWindowStyle && window.TitlebarHeight <= 0)) { - ShowSystemMenuPhysicalCoordinates(this, PointToScreen(mousePosition)); + ShowSystemMenuPhysicalCoordinates(window, window.PointToScreen(mousePos)); } } } diff --git a/MahApps.Metro/MahApps.Metro.NET45.csproj b/MahApps.Metro/MahApps.Metro.NET45.csproj index cd3a328e33..184de5b9b1 100644 --- a/MahApps.Metro/MahApps.Metro.NET45.csproj +++ b/MahApps.Metro/MahApps.Metro.NET45.csproj @@ -642,6 +642,10 @@ MSBuild:Compile Designer + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/MahApps.Metro/MahApps.Metro.csproj b/MahApps.Metro/MahApps.Metro.csproj index 9ee1adad54..7b281af575 100644 --- a/MahApps.Metro/MahApps.Metro.csproj +++ b/MahApps.Metro/MahApps.Metro.csproj @@ -593,6 +593,10 @@ Designer MSBuild:Compile + + MSBuild:Compile + Designer + Designer MSBuild:Compile diff --git a/MahApps.Metro/Microsoft.Windows.Shell/Standard/NativeMethods.cs b/MahApps.Metro/Microsoft.Windows.Shell/Standard/NativeMethods.cs index c2a0a29c51..27f920948b 100644 --- a/MahApps.Metro/Microsoft.Windows.Shell/Standard/NativeMethods.cs +++ b/MahApps.Metro/Microsoft.Windows.Shell/Standard/NativeMethods.cs @@ -1160,6 +1160,7 @@ internal enum SC { SIZE = 0xF000, MOVE = 0xF010, + MOUSEMOVE = 0xF012, MINIMIZE = 0xF020, MAXIMIZE = 0xF030, NEXTWINDOW = 0xF040, diff --git a/MahApps.Metro/Themes/Flyout.xaml b/MahApps.Metro/Themes/Flyout.xaml index 78208adc23..c8d2879f3e 100644 --- a/MahApps.Metro/Themes/Flyout.xaml +++ b/MahApps.Metro/Themes/Flyout.xaml @@ -7,6 +7,7 @@ + + + @@ -92,6 +93,12 @@ + + + + + @@ -364,6 +374,12 @@ + + + + + diff --git a/MahApps.Metro/Themes/Thumb.xaml b/MahApps.Metro/Themes/Thumb.xaml new file mode 100644 index 0000000000..ec2c600abe --- /dev/null +++ b/MahApps.Metro/Themes/Thumb.xaml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file