Created
September 29, 2017 11:21
-
-
Save C0BR4cH/8b2e6418980308f67ce34d2981b477a8 to your computer and use it in GitHub Desktop.
C# WPF Borderless Window with custom Titlebar
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// Source initialization | |
/// </summary> | |
/// <param name="sender">sender</param> | |
/// <param name="e">args</param> | |
void Window_SourceInitialized(object sender, System.EventArgs e) | |
{ | |
// Get handler for WPF window and hook into it | |
IntPtr windowHandle = (new WindowInteropHelper(this)).Handle; | |
HwndSource.FromHwnd(windowHandle).AddHook(new HwndSourceHook(WindowProc)); | |
} | |
/// <summary> | |
/// Used to catch the message for WM_GETMINMAXINFO and handle the correct sizes. | |
/// </summary> | |
/// <param name="hwnd">The window handle</param> | |
/// <param name="msg">The message ID</param> | |
/// <param name="wParam">The message's wParam value</param> | |
/// <param name="lParam">The message's lParam value</param> | |
/// <param name="handled">A value that indicates whether the message was handled. Set the value to <code>true</code> if the message was handled; otherwise, <code>false</code></param> | |
/// <returns>In this case only used to handle messages. Will only return <code>IntPtr.Zero</code></returns> | |
private static IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) | |
{ | |
switch (msg) | |
{ | |
// Message for WM_GETMINMAXINFO | |
case 0x0024: | |
WmGetMinMaxInfo(hwnd, lParam); | |
break; | |
} | |
return IntPtr.Zero; | |
} | |
private static void WmGetMinMaxInfo(IntPtr hwnd, IntPtr lParam) | |
{ | |
// Get cursor position | |
POINT lMousePosition; | |
GetCursorPos(out lMousePosition); | |
// Get pointer of primary screen | |
IntPtr lPrimaryScreen = MonitorFromPoint(new POINT(0, 0), MonitorOptions.MONITOR_DEFAULTTOPRIMARY); | |
// Check for Monitor availablility | |
MONITORINFO lPrimaryScreenInfo = new MONITORINFO(); | |
if (!GetMonitorInfo(lPrimaryScreen, lPrimaryScreenInfo)) | |
{ | |
return; | |
} | |
// Get pointer of current screen | |
IntPtr lCurrentScreen = MonitorFromPoint(lMousePosition, MonitorOptions.MONITOR_DEFAULTTONEAREST); | |
// Get MinMaxInfo | |
MINMAXINFO lMmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO)); | |
// Set correct position and sizes of screen / monitor | |
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; | |
} | |
// Set the values to memory | |
Marshal.StructureToPtr(lMmi, lParam, true); | |
} | |
/// <summary> | |
/// Switches between window stats Maximized and Normal | |
/// </summary> | |
private void SwitchWindowState() | |
{ | |
switch (WindowState) | |
{ | |
case WindowState.Normal: | |
{ | |
WindowState = WindowState.Maximized; | |
break; | |
} | |
case WindowState.Maximized: | |
{ | |
WindowState = WindowState.Normal; | |
break; | |
} | |
} | |
} | |
/// <summary> | |
/// Event on left mousebutton down on titlebar | |
/// </summary> | |
/// <param name="sender">sender</param> | |
/// <param name="e">args</param> | |
private void rctHeader_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) | |
{ | |
// On doubleclick | |
if (e.ClickCount == 2) | |
{ | |
// If allowed, switch window state | |
if ((ResizeMode == ResizeMode.CanResize) || (ResizeMode == ResizeMode.CanResizeWithGrip)) | |
{ | |
SwitchWindowState(); | |
} | |
return; | |
} | |
// Allow restore on move if maximized | |
else if (WindowState == WindowState.Maximized) | |
{ | |
doRestoreIfMove = true; | |
return; | |
} | |
// In every other case enable draging the window around | |
DragMove(); | |
} | |
/// <summary> | |
/// Event on left mousebutton up on titlebar | |
/// </summary> | |
/// <param name="sender">sender</param> | |
/// <param name="e">args</param> | |
private void rctHeader_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e) | |
{ | |
// Disable restore on move | |
doRestoreIfMove = false; | |
} | |
/// <summary> | |
/// Event on moving the mouse | |
/// </summary> | |
/// <param name="sender">sender</param> | |
/// <param name="e">args</param> | |
private void rctHeader_PreviewMouseMove(object sender, MouseEventArgs e) | |
{ | |
// Only do, if restore on move is allowed | |
if (doRestoreIfMove) | |
{ | |
doRestoreIfMove = false; | |
// Calculate values | |
double percentHorizontal = e.GetPosition(this).X / ActualWidth; | |
double targetHorizontal = RestoreBounds.Width * percentHorizontal; | |
double percentVertical = e.GetPosition(this).Y / ActualHeight; | |
double targetVertical = RestoreBounds.Height * percentVertical; | |
// Force set window state | |
WindowState = WindowState.Normal; | |
// Get current mouse position | |
POINT lMousePosition; | |
GetCursorPos(out lMousePosition); | |
// Set window location relative to current mouse cursor. This simulates the moving of the window | |
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) | |
{ | |
X = x; | |
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) | |
{ | |
Left = left; | |
Top = top; | |
Right = right; | |
Bottom = bottom; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment