mirror of
https://github.com/ckaczor/Common.Wpf.git
synced 2026-01-14 17:22:59 -05:00
329 lines
12 KiB
C#
329 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.InteropServices;
|
|
using System.Windows;
|
|
using System.Windows.Interop;
|
|
|
|
using Common.Native;
|
|
|
|
using Rectangle = System.Drawing.Rectangle;
|
|
using Screen = System.Windows.Forms.Screen;
|
|
|
|
namespace Common.Wpf.Windows
|
|
{
|
|
public class SnappingWindow : Window
|
|
{
|
|
#region Member variables
|
|
|
|
private HwndSource _hwndSource;
|
|
private Structures.WindowPosition _lastWindowPosition;
|
|
private List<WindowInformation> _otherWindows;
|
|
|
|
#endregion
|
|
|
|
#region Enumerations
|
|
|
|
private enum SnapMode
|
|
{
|
|
Move,
|
|
Resize
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
protected virtual int SnapDistance
|
|
{
|
|
get { return 20; }
|
|
}
|
|
|
|
protected virtual List<WindowInformation> OtherWindows
|
|
{
|
|
get { return null; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Window overrides
|
|
|
|
protected override void OnSourceInitialized(EventArgs e)
|
|
{
|
|
base.OnSourceInitialized(e);
|
|
|
|
// Store the window handle
|
|
_hwndSource = PresentationSource.FromVisual(this) as HwndSource;
|
|
|
|
// If we failed to get the hwnd then don't bother
|
|
if (_hwndSource == null)
|
|
return;
|
|
|
|
// Add a hook
|
|
_hwndSource.AddHook(WndProc);
|
|
}
|
|
|
|
protected override void OnContentRendered(EventArgs e)
|
|
{
|
|
base.OnContentRendered(e);
|
|
|
|
// Initialize the last window position
|
|
_lastWindowPosition = new Structures.WindowPosition
|
|
{
|
|
Left = (int) Left,
|
|
Width = (int) Width,
|
|
Height = (int) Height,
|
|
Top = (int) Top
|
|
};
|
|
}
|
|
|
|
protected override void OnClosed(EventArgs e)
|
|
{
|
|
base.OnClosed(e);
|
|
|
|
// Unhook the window procedure
|
|
_hwndSource.RemoveHook(WndProc);
|
|
_hwndSource.Dispose();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Window procedure
|
|
|
|
protected virtual IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
|
{
|
|
if (msg == (int) Constants.WindowMessage.WindowPositionChanging)
|
|
return OnWindowPositionChanging(lParam, ref handled);
|
|
|
|
if (msg == (int) Constants.WindowMessage.EnterSizeMove)
|
|
_otherWindows = OtherWindows;
|
|
|
|
return IntPtr.Zero;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Snapping
|
|
|
|
private IntPtr OnWindowPositionChanging(IntPtr lParam, ref bool handled)
|
|
{
|
|
int snapDistance = SnapDistance;
|
|
|
|
// Initialize whether we've updated the position
|
|
bool updated = false;
|
|
|
|
// Convert the lParam into the current structure
|
|
var windowPosition = (Structures.WindowPosition) Marshal.PtrToStructure(lParam, typeof(Structures.WindowPosition));
|
|
|
|
// If the window flags indicate no movement then do nothing
|
|
if ((windowPosition.Flags & Constants.WindowPositionFlags.NoMove) != 0)
|
|
return IntPtr.Zero;
|
|
|
|
// If nothing changed then do nothing
|
|
if (_lastWindowPosition.IsSameLocationAndSize(windowPosition))
|
|
return IntPtr.Zero;
|
|
|
|
// Figure out if the window is being moved or resized
|
|
SnapMode snapMode = (_lastWindowPosition.IsSameSize(windowPosition) ? SnapMode.Move : SnapMode.Resize);
|
|
|
|
// Get the screen the cursor is currently on
|
|
Screen screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position);
|
|
|
|
// Create a rectangle based on the current working area of the screen
|
|
Rectangle snapToBorder = screen.WorkingArea;
|
|
|
|
// Deflate the rectangle based on the snap distance
|
|
snapToBorder.Inflate(-snapDistance, -snapDistance);
|
|
|
|
if (snapMode == SnapMode.Resize)
|
|
{
|
|
// See if we need to snap on the left
|
|
if (windowPosition.Left < snapToBorder.Left)
|
|
{
|
|
windowPosition.Width += windowPosition.Left - screen.WorkingArea.Left;
|
|
windowPosition.Left = screen.WorkingArea.Left;
|
|
|
|
updated = true;
|
|
}
|
|
|
|
// See if we need to snap on the right
|
|
if (windowPosition.Right > snapToBorder.Right)
|
|
{
|
|
windowPosition.Width += (screen.WorkingArea.Right - windowPosition.Right);
|
|
|
|
updated = true;
|
|
}
|
|
|
|
// See if we need to snap to the top
|
|
if (windowPosition.Top < snapToBorder.Top)
|
|
{
|
|
windowPosition.Height += windowPosition.Top - screen.WorkingArea.Top;
|
|
windowPosition.Top = screen.WorkingArea.Top;
|
|
|
|
updated = true;
|
|
}
|
|
|
|
// See if we need to snap to the bottom
|
|
if (windowPosition.Bottom > snapToBorder.Bottom)
|
|
{
|
|
windowPosition.Height += (screen.WorkingArea.Bottom - windowPosition.Bottom);
|
|
|
|
updated = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// See if we need to snap on the left
|
|
if (windowPosition.Left < snapToBorder.Left)
|
|
{
|
|
windowPosition.Left = screen.WorkingArea.Left;
|
|
updated = true;
|
|
}
|
|
|
|
// See if we need to snap on the top
|
|
if (windowPosition.Top < snapToBorder.Top)
|
|
{
|
|
windowPosition.Top = screen.WorkingArea.Top;
|
|
updated = true;
|
|
}
|
|
|
|
// See if we need to snap on the right
|
|
if (windowPosition.Right > snapToBorder.Right)
|
|
{
|
|
windowPosition.Left = (screen.WorkingArea.Right - windowPosition.Width);
|
|
updated = true;
|
|
}
|
|
|
|
// See if we need to snap on the bottom
|
|
if (windowPosition.Bottom > snapToBorder.Bottom)
|
|
{
|
|
windowPosition.Top = (screen.WorkingArea.Bottom - windowPosition.Height);
|
|
updated = true;
|
|
}
|
|
}
|
|
|
|
var otherWindows = _otherWindows;
|
|
|
|
if (otherWindows != null && otherWindows.Count > 0)
|
|
{
|
|
// Loop over all other windows looking to see if we should stick
|
|
foreach (var otherWindow in otherWindows)
|
|
{
|
|
// Get a rectangle with the bounds of the other window
|
|
var otherWindowRect = otherWindow.Location;
|
|
|
|
// Check the current window left against the other window right
|
|
var otherWindowSnapBorder = new Rectangle(otherWindowRect.Right, otherWindowRect.Top, snapDistance, otherWindowRect.Height);
|
|
var thisWindowSnapBorder = new Rectangle(windowPosition.Left, windowPosition.Top, 1, windowPosition.Height);
|
|
|
|
if (thisWindowSnapBorder.IntersectsWith(otherWindowSnapBorder))
|
|
{
|
|
windowPosition.Left = otherWindowRect.Right;
|
|
CheckSnapTopAndBottom(ref windowPosition, otherWindowRect, snapMode);
|
|
updated = true;
|
|
}
|
|
|
|
// Check the current window right against the other window left
|
|
otherWindowSnapBorder = new Rectangle(otherWindowRect.Left - snapDistance + 1, otherWindowRect.Top, snapDistance, otherWindowRect.Height);
|
|
thisWindowSnapBorder = new Rectangle(windowPosition.Right, windowPosition.Top, 1, windowPosition.Height);
|
|
|
|
if (thisWindowSnapBorder.IntersectsWith(otherWindowSnapBorder))
|
|
{
|
|
windowPosition.Left = otherWindowRect.Left - windowPosition.Width;
|
|
CheckSnapTopAndBottom(ref windowPosition, otherWindowRect, snapMode);
|
|
updated = true;
|
|
}
|
|
|
|
// Check the current window bottom against the other window top
|
|
otherWindowSnapBorder = new Rectangle(otherWindowRect.Left, otherWindowRect.Top - snapDistance + 1, otherWindowRect.Width, snapDistance);
|
|
thisWindowSnapBorder = new Rectangle(windowPosition.Left, windowPosition.Bottom, windowPosition.Width, 1);
|
|
|
|
if (thisWindowSnapBorder.IntersectsWith(otherWindowSnapBorder))
|
|
{
|
|
windowPosition.Top = otherWindowRect.Top - windowPosition.Height;
|
|
CheckSnapLeftAndRight(ref windowPosition, otherWindowRect, snapMode);
|
|
updated = true;
|
|
}
|
|
|
|
// Check the current window top against the other window bottom
|
|
otherWindowSnapBorder = new Rectangle(otherWindowRect.Left, otherWindowRect.Bottom, otherWindowRect.Width, snapDistance);
|
|
thisWindowSnapBorder = new Rectangle(windowPosition.Left, windowPosition.Top, windowPosition.Width, 1);
|
|
|
|
if (thisWindowSnapBorder.IntersectsWith(otherWindowSnapBorder))
|
|
{
|
|
windowPosition.Top = otherWindowRect.Bottom;
|
|
CheckSnapLeftAndRight(ref windowPosition, otherWindowRect, snapMode);
|
|
updated = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the last window position
|
|
_lastWindowPosition = windowPosition;
|
|
|
|
if (updated)
|
|
{
|
|
Marshal.StructureToPtr(windowPosition, lParam, true);
|
|
handled = true;
|
|
}
|
|
|
|
return IntPtr.Zero;
|
|
}
|
|
|
|
private void CheckSnapTopAndBottom(ref Structures.WindowPosition windowPosition, Rectangle otherWindowRect, SnapMode snapMode)
|
|
{
|
|
int snapDistance = SnapDistance;
|
|
|
|
switch (snapMode)
|
|
{
|
|
case SnapMode.Move:
|
|
if (Math.Abs(windowPosition.Top - otherWindowRect.Top) <= snapDistance)
|
|
windowPosition.Top = otherWindowRect.Top;
|
|
else if (Math.Abs(windowPosition.Bottom - otherWindowRect.Bottom) <= snapDistance)
|
|
windowPosition.Top = otherWindowRect.Bottom - windowPosition.Height;
|
|
|
|
break;
|
|
case SnapMode.Resize:
|
|
if (Math.Abs(windowPosition.Top - otherWindowRect.Top) <= snapDistance)
|
|
{
|
|
windowPosition.Height += (windowPosition.Top - otherWindowRect.Top);
|
|
windowPosition.Top = otherWindowRect.Top;
|
|
}
|
|
else
|
|
if (Math.Abs(windowPosition.Bottom - otherWindowRect.Bottom) <= snapDistance)
|
|
windowPosition.Height = otherWindowRect.Bottom - windowPosition.Top;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void CheckSnapLeftAndRight(ref Structures.WindowPosition windowPosition, Rectangle otherWindowRect, SnapMode snapMode)
|
|
{
|
|
int snapDistance = SnapDistance;
|
|
|
|
switch (snapMode)
|
|
{
|
|
case SnapMode.Move:
|
|
if (Math.Abs(windowPosition.Left - otherWindowRect.Left) <= snapDistance)
|
|
windowPosition.Left = otherWindowRect.Left;
|
|
else if (Math.Abs(windowPosition.Right - otherWindowRect.Right) <= snapDistance)
|
|
windowPosition.Left = otherWindowRect.Right - windowPosition.Width;
|
|
|
|
break;
|
|
case SnapMode.Resize:
|
|
if (Math.Abs(windowPosition.Left - otherWindowRect.Left) <= snapDistance)
|
|
{
|
|
windowPosition.Width += (windowPosition.Left - otherWindowRect.Left);
|
|
windowPosition.Left = otherWindowRect.Left;
|
|
}
|
|
else
|
|
if (Math.Abs(windowPosition.Right - otherWindowRect.Right) <= snapDistance)
|
|
windowPosition.Width = otherWindowRect.Right - windowPosition.Left;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|