Here is an example of how this can be done:
using System; using System.Runtime.InteropServices; using System.Windows.Interop; namespace DeleteMeWPF { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow { public MainWindow() { InitializeComponent(); } protected override void OnSourceInitialized(EventArgs e) { base.OnSourceInitialized(e); IntPtr handle = new WindowInteropHelper(this).Handle; HwndSource.FromHwnd(handle).AddHook(new HwndSourceHook(this.WindowProc)); } [StructLayout(LayoutKind.Sequential)] private struct RECT { public int left; public int top; public int right; public int bottom; } private const int WM_SIZING = 0x0214; private const int WMSZ_BOTTOM = 6; private const int WMSZ_BOTTOMLEFT = 7; private const int WMSZ_BOTTOMRIGHT = 8; private const int WMSZ_LEFT = 1; private const int WMSZ_RIGHT = 2; private const int WMSZ_TOP = 3; private const int WMSZ_TOPLEFT = 4; private const int WMSZ_TOPRIGHT = 5; private const int SnappingIncrement = 100; private const int SnappingThresholdWidth = 300; private const int SnappingThresholdHeight = 400; private IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { switch (msg) { case WM_SIZING: RECT bounds = (RECT)Marshal.PtrToStructure(lParam, typeof(RECT)); int width = bounds.right - bounds.left; int height = bounds.bottom - bounds.top; switch (wParam.ToInt32()) { case WMSZ_BOTTOM: if (height > SnappingThresholdHeight) bounds.bottom = bounds.top + ((int)((double)height / (double)SnappingIncrement) * SnappingIncrement); break; case WMSZ_BOTTOMLEFT: if (height > SnappingThresholdHeight) bounds.bottom = bounds.top + ((int)((double)height / (double)SnappingIncrement) * SnappingIncrement); if (width > SnappingThresholdWidth) bounds.left = bounds.right - ((int)((double)width / (double)SnappingIncrement) * SnappingIncrement); break; case WMSZ_BOTTOMRIGHT: if (height > SnappingThresholdHeight) bounds.bottom = bounds.top + ((int)((double)height / (double)SnappingIncrement) * SnappingIncrement); if (width > SnappingThresholdWidth) bounds.right = bounds.left + ((int)((double)width / (double)SnappingIncrement) * SnappingIncrement); break; case WMSZ_LEFT: if (width > SnappingThresholdWidth) bounds.left = bounds.right - ((int)((double)width / (double)SnappingIncrement) * SnappingIncrement); break; case WMSZ_RIGHT: if (width > SnappingThresholdWidth) bounds.right = bounds.left + ((int)((double)width / (double)SnappingIncrement) * SnappingIncrement); break; case WMSZ_TOP: if (height > SnappingThresholdHeight) bounds.top = bounds.bottom - ((int)((double)height / (double)SnappingIncrement) * SnappingIncrement); break; case WMSZ_TOPLEFT: if (width > SnappingThresholdWidth) bounds.left = bounds.right - ((int)((double)width / (double)SnappingIncrement) * SnappingIncrement); if (height > SnappingThresholdHeight) bounds.top = bounds.bottom - ((int)((double)height / (double)SnappingIncrement) * SnappingIncrement); break; case WMSZ_TOPRIGHT: if (width > SnappingThresholdWidth) bounds.right = bounds.left + ((int)((double)width / (double)SnappingIncrement) * SnappingIncrement); if (height > SnappingThresholdHeight) bounds.top = bounds.bottom - ((int)((double)height / (double)SnappingIncrement) * SnappingIncrement); break; } Marshal.StructureToPtr(bounds, lParam, false); break; } return IntPtr.Zero; } } }
Here, step 100 is used to truly illustrate the βsnapβ effect. In addition, you can configure snap thresholds that ensure that snap only works when the size exceeds a given width / height.
source share