Reputation: 1575
There are two problems with WPF windows when the WindowStyle=None option is used.
How can these problems be corrected? Preferably without using Windows.Forms.
Upvotes: 16
Views: 18484
Reputation: 693
I didn't go through all the comments & answers, but according to question this is what I usually do in all my WPF app projects:
1.) XAML - Check Window.Resources, you can ofcourse use that button style in your App.xaml registered styles. Check all other XAML too, be aware of line "WindowChrome.IsHitTestVisibleInChrome="True"".
<Window x:Class="WpfTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
WindowStyle="None"
ResizeMode="CanResizeWithGrip"
TextOptions.TextFormattingMode="Ideal"
TextOptions.TextRenderingMode="ClearType"
UseLayoutRounding="True"
Background="Transparent"
Height="500"
Width="1000">
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="30" GlassFrameThickness="2" ResizeBorderThickness="5"/>
</WindowChrome.WindowChrome>
<Window.Resources>
<Style TargetType="{x:Type Button}" x:Key="WindowButton">
<Setter Property="Foreground" Value="Black" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Padding" Value="1" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid x:Name="_background">
<Grid Width="30" Height="25" Background="Transparent" >
<Path Name="_path" Data="" VerticalAlignment="Center" HorizontalAlignment="Center"
Fill="{TemplateBinding Foreground}" Stroke="White" StrokeThickness=".1"
Stretch="Fill"/>
</Grid>
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Tag, RelativeSource={RelativeSource Self}}" Value="Close">
<Setter Property="Data" TargetName="_path" Value="M10 8.586L2.929 1.515 1.515 2.929 8.586
10l-7.071 7.071 1.414 1.414L10 11.414l7.071 7.071 1.414-1.414L11.414 10l7.071-7.071-1.414-1.414L10 8.586z"/>
<Setter Property="Height" TargetName="_path" Value="10"/>
<Setter Property="Width" TargetName="_path" Value="10"/>
<Setter Property="Command" Value="{Binding Source={x:Static SystemCommands.CloseWindowCommand}}"/>
<Setter Property="ToolTip" Value="Close"/>
</DataTrigger>
<DataTrigger Binding="{Binding Tag, RelativeSource={RelativeSource Self}}" Value="Minimize">
<Setter Property="Data" TargetName="_path" Value="M128 512h768v-128h-768v128z"/>
<Setter Property="Height" TargetName="_path" Value="1"/>
<Setter Property="Width" TargetName="_path" Value="10"/>
<Setter Property="Command" Value="{Binding Source={x:Static SystemCommands.MinimizeWindowCommand}}"/>
<Setter Property="ToolTip" Value="Minimize"/>
</DataTrigger>
<DataTrigger Binding="{Binding Tag, RelativeSource={RelativeSource Self}}" Value="Maximize">
<Setter Property="Data" TargetName="_path" Value="M187.5 812.5V187.5H812.5V812.5H187.5zM750 250H250V750H750V250z"/>
<Setter Property="Height" TargetName="_path" Value="10"/>
<Setter Property="Width" TargetName="_path" Value="10"/>
<Setter Property="Command" Value="{Binding Source={x:Static SystemCommands.MaximizeWindowCommand}}"/>
<Setter Property="ToolTip" Value="Maximize"/>
</DataTrigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" TargetName="_background" Value="#FF5896AB" />
<Setter Property="Fill" TargetName="_path" Value="White" />
<Setter Property="Stroke" TargetName="_path" Value="White" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<Border BorderBrush="Silver" Background="White" BorderThickness="1">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding WindowState,RelativeSource={RelativeSource FindAncestor,AncestorType=Window}}" Value="Maximized">
<Setter Property="Padding" Value="8" />
</DataTrigger>
<DataTrigger Binding="{Binding WindowState,RelativeSource={RelativeSource FindAncestor,AncestorType=Window}}" Value="Normal">
<Setter Property="Padding" Value="0" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Right" WindowChrome.IsHitTestVisibleInChrome="True">
<Button Style="{StaticResource WindowButton}" Tag="Minimize" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Command="{Binding Source={x:Static SystemCommands.RestoreWindowCommand}}">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Padding" Value="1" />
<Setter Property="ToolTip" Value="Restore"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid x:Name="_background">
<Grid Width="30" Height="25" Background="Transparent" UseLayoutRounding="True">
<Rectangle x:Name="_rect1" Width="9" Height="9" Fill="White"
StrokeThickness="1" Stroke="Black" Margin="3,0,0,3"/>
<Rectangle x:Name="_rect2" Width="9" Height="9" Fill="White"
StrokeThickness="1" Stroke="Black" Margin="0,3,3,0"/>
</Grid>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" TargetName="_background" Value="#FF5896AB" />
<Setter Property="Fill" TargetName="_rect1" Value="#FF5896AB" />
<Setter Property="Fill" TargetName="_rect2" Value="#FF5896AB" />
<Setter Property="Stroke" TargetName="_rect1" Value="White" />
<Setter Property="Stroke" TargetName="_rect2" Value="White" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding WindowState,RelativeSource={RelativeSource FindAncestor,AncestorType=Window}}" Value="Maximized">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding WindowState,RelativeSource={RelativeSource FindAncestor,AncestorType=Window}}" Value="Normal">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button x:Name="_maximize" Grid.Column="1" Tag="Maximize">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource WindowButton}">
<Style.Triggers>
<DataTrigger Binding="{Binding WindowState,RelativeSource={RelativeSource FindAncestor,AncestorType=Window}}" Value="Maximized">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding WindowState,RelativeSource={RelativeSource FindAncestor,AncestorType=Window}}" Value="Normal">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
<Button Style="{StaticResource WindowButton}" Tag="Close" />
</StackPanel>
<TextBlock Grid.Row="1" FontSize="30" Text="ADD CONTENT OF WINDOW HERE - THIS IS JUST EXAMPLE" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</Border>
</Grid>
2.) Code behind:
using System.Windows;
using System.Windows.Input;
namespace WpfTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MaxHeight = SystemParameters.MaximizedPrimaryScreenHeight;
MaxWidth = SystemParameters.MaximizedPrimaryScreenWidth;
CommandBindings.Add(new CommandBinding(SystemCommands.CloseWindowCommand, OnCloseWindow));
CommandBindings.Add(new CommandBinding(SystemCommands.MaximizeWindowCommand, OnMaximizeWindow, OnCanResizeWindow));
CommandBindings.Add(new CommandBinding(SystemCommands.MinimizeWindowCommand, OnMinimizeWindow, OnCanMinimizeWindow));
CommandBindings.Add(new CommandBinding(SystemCommands.RestoreWindowCommand, OnRestoreWindow, OnCanResizeWindow));
}
#region Events
private void OnCanResizeWindow(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = ResizeMode == ResizeMode.CanResize || ResizeMode == ResizeMode.CanResizeWithGrip;
}
private void OnCanMinimizeWindow(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = ResizeMode != ResizeMode.NoResize;
}
private void OnCloseWindow(object target, ExecutedRoutedEventArgs e)
{
SystemCommands.CloseWindow(this);
}
private void OnMaximizeWindow(object target, ExecutedRoutedEventArgs e)
{
SystemCommands.MaximizeWindow(this);
}
private void OnMinimizeWindow(object target, ExecutedRoutedEventArgs e)
{
SystemCommands.MinimizeWindow(this);
}
private void OnRestoreWindow(object target, ExecutedRoutedEventArgs e)
{
SystemCommands.RestoreWindow(this);
}
#endregion
}
}
This does not solve issues with secondary monitor, but It provides a simple solution for all WPF projects - once done, you can just copy & paste all to a new project. For me It's like a template...
Another note - If you double click on region I defined in XAML(WindowChrome CaptionHeight="30") you can restore Window without Restore button too - just as normal Window does. So basically all Window styling completely depends on you :)
Hope It helps to someone.
Upvotes: 0
Reputation: 1
XAML:
<Window
x:Class="WpfAppMaximize.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfAppMaximize"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="450"
AllowsTransparency="True"
ResizeMode="CanResize"
StateChanged="Window_StateChanged"
WindowStartupLocation="CenterScreen"
WindowStyle="None"
mc:Ignorable="d">
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="0" ResizeBorderThickness="5" />
</WindowChrome.WindowChrome>
<Border BorderBrush="Blue" BorderThickness="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid
x:Name="GridTitleBar"
Background="Black"
PreviewMouseLeftButtonDown="GridTitleBar_PreviewMouseLeftButtonDown"
PreviewMouseMove="GridTitleBar_PreviewMouseMove">
<StackPanel
HorizontalAlignment="Left"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock
x:Name="TextBlockTitle"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Bold" />
</StackPanel>
<StackPanel
x:Name="Header"
Grid.Row="0"
Grid.Column="0"
HorizontalAlignment="Right"
Orientation="Horizontal">
<Button
x:Name="ButtonMinimize"
Width="30"
Content="_"
PreviewMouseLeftButtonDown="ButtonMinimize_PreviewMouseLeftButtonDown"
ToolTip="Minimize"
WindowChrome.IsHitTestVisibleInChrome="True" />
<Button
x:Name="ButtonRestore"
Width="30"
Content="▯"
PreviewMouseLeftButtonDown="ButtonRestore_PreviewMouseLeftButtonDown"
ToolTip="Restore"
Visibility="Collapsed"
WindowChrome.IsHitTestVisibleInChrome="True" />
<Button
x:Name="ButtonMaximize"
Width="30"
Content="🗖"
PreviewMouseLeftButtonDown="ButtonMaximize_PreviewMouseLeftButtonDown"
ToolTip="Maximize"
Visibility="Visible"
WindowChrome.IsHitTestVisibleInChrome="True" />
<Button
x:Name="ButtonClose"
Width="30"
Content="X"
PreviewMouseLeftButtonDown="ButtonClose_PreviewMouseLeftButtonDown"
ToolTip="Close"
WindowChrome.IsHitTestVisibleInChrome="True" />
</StackPanel>
</Grid>
<UserControl
x:Name="UserControlContent"
Grid.Row="1"
Background="Gray" />
</Grid>
</Border>
</Window>
Code-Behind:
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
namespace WpfAppMaximize
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private double _restoreLeft;
private double _restoreTop;
private double _restoreWidth;
private double _restoreHeight;
private bool _isSimulatedMaximized = false;
public MainWindow()
{
InitializeComponent();
}
private void Window_StateChanged(object sender, EventArgs e)
{
if (this.WindowState == WindowState.Maximized)
{
MaximizeSimulated();
}
}
private void MaximizeSimulated()
{
_isSimulatedMaximized = true;
_restoreLeft = this.Left;
_restoreTop = this.Top;
_restoreWidth = this.Width;
_restoreHeight = this.Height;
ResizeMode = ResizeMode.NoResize;
WindowState = WindowState.Normal;
var screen = Screen.FromHandle(new WindowInteropHelper(this).Handle);
var workingArea = screen.WorkingArea;
this.Left = workingArea.Left;
this.Top = workingArea.Top;
this.Width = workingArea.Width;
this.Height = workingArea.Height;
ToggleMaximizeRestoreButtons(true);
}
private void ToggleMaximizeRestoreButtons(bool isMaximized)
{
ButtonRestore.Visibility = isMaximized ? Visibility.Visible : Visibility.Collapsed;
ButtonMaximize.Visibility = isMaximized ? Visibility.Collapsed : Visibility.Visible;
}
private void RestoreWindow(double left, double top)
{
_isSimulatedMaximized = false;
ResizeMode = ResizeMode.CanResize;
WindowState = WindowState.Normal;
this.Left = left;
this.Top = top;
this.Width = _restoreWidth;
this.Height = _restoreHeight;
ToggleMaximizeRestoreButtons(false);
}
private void GridTitleBar_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount == 2)
{
SwitchWindowState();
}
}
private void SwitchWindowState()
{
if (_isSimulatedMaximized)
{
RestoreWindow(_restoreLeft, _restoreTop);
}
else
{
MaximizeSimulated();
}
}
private void GridTitleBar_PreviewMouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
if (_isSimulatedMaximized)
{
var mousePos = System.Windows.Forms.Cursor.Position;
RestoreWindow(mousePos.X - (_restoreWidth / 2), mousePos.Y);
}
if (e.LeftButton == MouseButtonState.Pressed)
{
DragMove();
}
}
}
private void ButtonMinimize_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
SystemCommands.MinimizeWindow(this);
e.Handled = true;
}
private void ButtonRestore_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
RestoreWindow(_restoreLeft, _restoreTop);
}
private void ButtonMaximize_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
SystemCommands.MaximizeWindow(this);
}
private void ButtonClose_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
SystemCommands.CloseWindow(this);
}
}
}
Upvotes: 0
Reputation: 71
Such a nice code leebickmtu!
I had a little issue with multiple monitors, in windows 10 : since there is a taskBar on each screen, if you maximize your window on a secondary screen his taskbar become hidden.
I just modify a bit this method, in order to have relative position from any screen :
private static void WmGetMinMaxInfo(System.IntPtr hwnd, System.IntPtr lParam)
{
POINT lMousePosition;
GetCursorPos(out lMousePosition);
IntPtr lCurrentScreen = MonitorFromPoint(lMousePosition, MonitorOptions.MONITOR_DEFAULTTONEAREST);
MINMAXINFO lMmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));
MONITORINFO lCurrentScreenInfo = new MONITORINFO();
if (GetMonitorInfo(lCurrentScreen, lCurrentScreenInfo) == false)
{
return;
}
//Position relative pour notre fenêtre
lMmi.ptMaxPosition.X = lCurrentScreenInfo.rcWork.Left - lCurrentScreenInfo.rcMonitor.Left;
lMmi.ptMaxPosition.Y = lCurrentScreenInfo.rcWork.Top - lCurrentScreenInfo.rcMonitor.Top;
lMmi.ptMaxSize.X = lCurrentScreenInfo.rcWork.Right - lCurrentScreenInfo.rcWork.Left;
lMmi.ptMaxSize.Y = lCurrentScreenInfo.rcWork.Bottom - lCurrentScreenInfo.rcWork.Top;
Marshal.StructureToPtr(lMmi, lParam, true);
}
Hope this help...
Upvotes: 7
Reputation: 105
Building on Dennis' excellent solution:
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
HandleWinMaximized();
StateChanged += MainWindow_StateChanged;
}
private void MainWindow_StateChanged(object sender, EventArgs e)
{
HandleWinMaximized();
}
private void HandleWinMaximized()
{
if (WindowState == WindowState.Maximized)
{
WindowStyle = WindowStyle.SingleBorderWindow;
WindowStyle = WindowStyle.None;
}
}
Upvotes: 1
Reputation: 15413
leebickmtu's answer is basically correct, but has some extraneous code and picks the monitor based one where the cursor is, rather than where the window is. That would have the wrong behavior if the mouse is on a different monitor and the user presses Win
+Up
to maximize. We should be using MonitorFromWindow
to identify the monitor to maximize to.
Put the following in your window codebehind:
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
((HwndSource)PresentationSource.FromVisual(this)).AddHook(HookProc);
}
public static IntPtr HookProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_GETMINMAXINFO)
{
// We need to tell the system what our size should be when maximized. Otherwise it will
// cover the whole screen, including the task bar.
MINMAXINFO mmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));
// Adjust the maximized size and position to fit the work area of the correct monitor
IntPtr monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if (monitor != IntPtr.Zero)
{
MONITORINFO monitorInfo = new MONITORINFO();
monitorInfo.cbSize = Marshal.SizeOf(typeof(MONITORINFO));
GetMonitorInfo(monitor, ref monitorInfo);
RECT rcWorkArea = monitorInfo.rcWork;
RECT rcMonitorArea = monitorInfo.rcMonitor;
mmi.ptMaxPosition.X = Math.Abs(rcWorkArea.Left - rcMonitorArea.Left);
mmi.ptMaxPosition.Y = Math.Abs(rcWorkArea.Top - rcMonitorArea.Top);
mmi.ptMaxSize.X = Math.Abs(rcWorkArea.Right - rcWorkArea.Left);
mmi.ptMaxSize.Y = Math.Abs(rcWorkArea.Bottom - rcWorkArea.Top);
}
Marshal.StructureToPtr(mmi, lParam, true);
}
return IntPtr.Zero;
}
private const int WM_GETMINMAXINFO = 0x0024;
private const uint MONITOR_DEFAULTTONEAREST = 0x00000002;
[DllImport("user32.dll")]
private static extern IntPtr MonitorFromWindow(IntPtr handle, uint flags);
[DllImport("user32.dll")]
private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi);
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public RECT(int left, int top, int right, int bottom)
{
this.Left = left;
this.Top = top;
this.Right = right;
this.Bottom = bottom;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct MONITORINFO
{
public int cbSize;
public RECT rcMonitor;
public RECT rcWork;
public uint dwFlags;
}
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct MINMAXINFO
{
public POINT ptReserved;
public POINT ptMaxSize;
public POINT ptMaxPosition;
public POINT ptMinTrackSize;
public POINT ptMaxTrackSize;
}
Upvotes: 1
Reputation: 196
If only one monitor is used another simple approach is to set the maximum height of the window. The System.Windows.SystemParameters class provides some usefull values e.g. PrimaryScreenHeight or MaximizedPrimaryScreenHeight.
In my sample code i use MaximizedPrimaryScreenHeight and subtract the ResizeBorderThickness i set in WindowChrome.
using System.Windows;
using System.Windows.Shell;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Thickness resizeBorderThickness = WindowChrome.GetWindowChrome(this).ResizeBorderThickness;
this.MaxHeight = SystemParameters.MaximizedPrimaryScreenHeight - resizeBorderThickness.Top - resizeBorderThickness.Bottom;
}
}
Upvotes: 1
Reputation: 151
I have a nice quick and dirty solution. Try following code when maximize your none bordered window:
if (WindowState == WindowState.Normal)
{
WindowStyle = WindowStyle.SingleBorderWindow;
WindowState = WindowState.Maximized;
WindowStyle = WindowStyle.None;
}
The trick is to set the WindowStyle
to SingleBorderWindow
then maximize the window and set it back to None
.
Upvotes: 15
Reputation: 1575
There are other answers to these problems online. However none of them take into acount how the solution will perform on setups with multiple monitors. Especially if the primary monitor is not the left-most in the setup.
I designed this code taking into account single and multiple monitors setups.
This solution also does not bring in Windows.Forms as a reference, it uses unmanagaged calls.
XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Background="AliceBlue" WindowStyle="None" Height="350" Width="525" SourceInitialized="Window_SourceInitialized">
<Grid>
<Rectangle Name="rctHeader" Height="40" VerticalAlignment="Top" Fill="CadetBlue" PreviewMouseLeftButtonDown="rctHeader_PreviewMouseLeftButtonDown" PreviewMouseLeftButtonUp="rctHeader_PreviewMouseLeftButtonUp" PreviewMouseMove="rctHeader_PreviewMouseMove"/>
</Grid>
</Window>
Code Behind
using System.Runtime.InteropServices;
using System.Windows.Interop;
private bool mRestoreIfMove = false;
public MainWindow()
{
InitializeComponent();
}
void Window_SourceInitialized(object sender, EventArgs e)
{
IntPtr mWindowHandle = (new WindowInteropHelper(this)).Handle;
HwndSource.FromHwnd(mWindowHandle).AddHook(new HwndSourceHook(WindowProc));
}
private static System.IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case 0x0024:
WmGetMinMaxInfo(hwnd, lParam);
break;
}
return IntPtr.Zero;
}
private static void WmGetMinMaxInfo(System.IntPtr hwnd, System.IntPtr lParam)
{
POINT lMousePosition;
GetCursorPos(out lMousePosition);
IntPtr lPrimaryScreen = MonitorFromPoint(new POINT(0, 0), MonitorOptions.MONITOR_DEFAULTTOPRIMARY);
MONITORINFO lPrimaryScreenInfo = new MONITORINFO();
if (GetMonitorInfo(lPrimaryScreen, lPrimaryScreenInfo) == false)
{
return;
}
IntPtr lCurrentScreen = MonitorFromPoint(lMousePosition, MonitorOptions.MONITOR_DEFAULTTONEAREST);
MINMAXINFO lMmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));
if (lPrimaryScreen.Equals(lCurrentScreen) == true)
{
lMmi.ptMaxPosition.X = lPrimaryScreenInfo.rcWork.Left;
lMmi.ptMaxPosition.Y = lPrimaryScreenInfo.rcWork.Top;
lMmi.ptMaxSize.X = lPrimaryScreenInfo.rcWork.Right - lPrimaryScreenInfo.rcWork.Left;
lMmi.ptMaxSize.Y = lPrimaryScreenInfo.rcWork.Bottom - lPrimaryScreenInfo.rcWork.Top;
}
else
{
lMmi.ptMaxPosition.X = lPrimaryScreenInfo.rcMonitor.Left;
lMmi.ptMaxPosition.Y = lPrimaryScreenInfo.rcMonitor.Top;
lMmi.ptMaxSize.X = lPrimaryScreenInfo.rcMonitor.Right - lPrimaryScreenInfo.rcMonitor.Left;
lMmi.ptMaxSize.Y = lPrimaryScreenInfo.rcMonitor.Bottom - lPrimaryScreenInfo.rcMonitor.Top;
}
Marshal.StructureToPtr(lMmi, lParam, true);
}
private void SwitchWindowState()
{
switch (WindowState)
{
case WindowState.Normal:
{
WindowState = WindowState.Maximized;
break;
}
case WindowState.Maximized:
{
WindowState = WindowState.Normal;
break;
}
}
}
private void rctHeader_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount == 2)
{
if ((ResizeMode == ResizeMode.CanResize) || (ResizeMode == ResizeMode.CanResizeWithGrip))
{
SwitchWindowState();
}
return;
}
else if (WindowState == WindowState.Maximized)
{
mRestoreIfMove = true;
return;
}
DragMove();
}
private void rctHeader_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
mRestoreIfMove = false;
}
private void rctHeader_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (mRestoreIfMove)
{
mRestoreIfMove = false;
double percentHorizontal = e.GetPosition(this).X / ActualWidth;
double targetHorizontal = RestoreBounds.Width * percentHorizontal;
double percentVertical = e.GetPosition(this).Y / ActualHeight;
double targetVertical = RestoreBounds.Height * percentVertical;
WindowState = WindowState.Normal;
POINT lMousePosition;
GetCursorPos(out lMousePosition);
Left = lMousePosition.X - targetHorizontal;
Top = lMousePosition.Y - targetVertical;
DragMove();
}
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetCursorPos(out POINT lpPoint);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr MonitorFromPoint(POINT pt, MonitorOptions dwFlags);
enum MonitorOptions : uint
{
MONITOR_DEFAULTTONULL = 0x00000000,
MONITOR_DEFAULTTOPRIMARY = 0x00000001,
MONITOR_DEFAULTTONEAREST = 0x00000002
}
[DllImport("user32.dll")]
static extern bool GetMonitorInfo(IntPtr hMonitor, MONITORINFO lpmi);
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct MINMAXINFO
{
public POINT ptReserved;
public POINT ptMaxSize;
public POINT ptMaxPosition;
public POINT ptMinTrackSize;
public POINT ptMaxTrackSize;
};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class MONITORINFO
{
public int cbSize = Marshal.SizeOf(typeof(MONITORINFO));
public RECT rcMonitor = new RECT();
public RECT rcWork = new RECT();
public int dwFlags = 0;
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left, Top, Right, Bottom;
public RECT(int left, int top, int right, int bottom)
{
this.Left = left;
this.Top = top;
this.Right = right;
this.Bottom = bottom;
}
}
Upvotes: 40