How to make my Windows Form app snap to screen edges?

前端 未结 5 2047
迷失自我
迷失自我 2020-11-29 01:23

Anyone out there know how to make your .net windows form app sticky/snappy like Winamp so it snaps to the edges of the screen?

The target framework would be .NET 2.0

相关标签:
5条回答
  • 2020-11-29 01:44

    https://github.com/stax76/staxrip

    Protected Overrides Sub WndProc(ByRef m As Message)
        Snap(m)
        MyBase.WndProc(m)
    End Sub
    
    Private IsResizing As Boolean
    
    Sub Snap(ByRef m As Message)
        Select Case m.Msg
            Case &H214 'WM_SIZING
                IsResizing = True
            Case &H232 'WM_EXITSIZEMOVE
                IsResizing = False
            Case &H46 'WM_WINDOWPOSCHANGING
                If Not IsResizing Then Snap(m.LParam)
        End Select
    End Sub
    
    Sub Snap(handle As IntPtr)
        Dim workingArea = Screen.FromControl(Me).WorkingArea
        Dim newPos = DirectCast(Marshal.PtrToStructure(handle, GetType(WindowPos)), WindowPos)
        Dim snapMargin = Control.DefaultFont.Height
        Dim border As Integer
        If OSVersion.Current >= OSVersion.Windows8 Then border = (Width - ClientSize.Width) \ 2 - 1
    
        If newPos.Y <> 0 Then
            If Math.Abs(newPos.Y - workingArea.Y) < snapMargin AndAlso Top > newPos.Y Then
                newPos.Y = workingArea.Y
            ElseIf Math.Abs(newPos.Y + Height - (workingArea.Bottom + border)) < snapMargin AndAlso Top < newPos.Y Then
                newPos.Y = (workingArea.Bottom + border) - Height
            End If
        End If
    
        If newPos.X <> 0 Then
            If Math.Abs(newPos.X - (workingArea.X - border)) < snapMargin AndAlso Left > newPos.X Then
                newPos.X = workingArea.X - border
            ElseIf Math.Abs(newPos.X + Width - (workingArea.Right + border)) < snapMargin AndAlso Left < newPos.X Then
                newPos.X = (workingArea.Right + border) - Width
            End If
        End If
    
        Marshal.StructureToPtr(newPos, handle, True)
    End Sub
    
    0 讨论(0)
  • 2020-11-29 01:47

    The accepted answer only snaps the window after finishing the drag, whereas I wanted the form to continuously snap to the screen edges while dragging. Here's my solution, loosely based off the Paint.NET source code:

    using System;
    using System.ComponentModel;
    using System.Drawing;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    
    namespace Whatever
    {
        /// <summary>
        /// Managed equivalent of the Win32 <code>RECT</code> structure.
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        public struct LtrbRectangle
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
    
            public LtrbRectangle(int left, int top, int right, int bottom)
            {
                Left = left;
                Top = top;
                Right = right;
                Bottom = bottom;
            }
    
            public Rectangle ToRectangle()
            {
                return Rectangle.FromLTRB(Left, Top, Right, Bottom);
            }
    
            public static LtrbRectangle FromRectangle(Rectangle rect)
            {
                return new LtrbRectangle(rect.X, rect.Y, rect.X + rect.Width, rect.Y + rect.Height);
            }
    
            public override string ToString()
            {
                return "{Left=" + Left + ",Top=" + Top + ",Right=" + Right + ",Bottom=" + Bottom + "}";
            }
        }
    
        /// <summary>
        /// A form that "snaps" to screen edges when moving.
        /// </summary>
        public class AnchoredForm : Form
        {
            private const int WmEnterSizeMove = 0x0231;
            private const int WmMoving = 0x0216;
            private const int WmSize = 0x0005;
    
            private SnapLocation _snapAnchor;
            private int _dragOffsetX;
            private int _dragOffsetY;
    
            /// <summary>
            /// Flags specifying which edges to anchor the form at.
            /// </summary>
            [Flags]
            public enum SnapLocation
            {
                None = 0,
                Left = 1 << 0,
                Top = 1 << 1,
                Right = 1 << 2,
                Bottom = 1 << 3,
                All = Left | Top | Right | Bottom
            }
    
            /// <summary>
            /// How far from the screen edge to anchor the form.
            /// </summary>
            [Browsable(true)]
            [DefaultValue(10)]
            [Description("The distance from the screen edge to anchor the form.")]
            public virtual int AnchorDistance { get; set; } = 10;
    
            /// <summary>
            /// Gets or sets how close the form must be to the
            /// anchor point to snap to it. A higher value gives
            /// a more noticable "snap" effect.
            /// </summary>
            [Browsable(true)]
            [DefaultValue(20)]
            [Description("The maximum form snapping distance.")]
            public virtual int SnapDistance { get; set; } = 20;
    
            /// <summary>
            /// Re-snaps the control to its current anchor points.
            /// This can be useful for re-positioning the form after
            /// the screen resolution changes.
            /// </summary>
            public void ReSnap()
            {
                SnapTo(_snapAnchor);
            }
    
            /// <summary>
            /// Forces the control to snap to the specified edges.
            /// </summary>
            /// <param name="anchor">The screen edges to snap to.</param>
            public void SnapTo(SnapLocation anchor)
            {
                Screen currentScreen = Screen.FromPoint(Location);
                Rectangle workingArea = currentScreen.WorkingArea;
                if ((anchor & SnapLocation.Left) != 0)
                {
                    Left = workingArea.Left + AnchorDistance;
                }
                else if ((anchor & SnapLocation.Right) != 0)
                {
                    Left = workingArea.Right - AnchorDistance - Width;
                }
                if ((anchor & SnapLocation.Top) != 0)
                {
                    Top = workingArea.Top + AnchorDistance;
                }
                else if ((anchor & SnapLocation.Bottom) != 0)
                {
                    Top = workingArea.Bottom - AnchorDistance - Height;
                }
                _snapAnchor = anchor;
            }
    
            private bool InSnapRange(int a, int b)
            {
                return Math.Abs(a - b) < SnapDistance;
            }
    
            private SnapLocation FindSnap(ref Rectangle effectiveBounds)
            {
                Screen currentScreen = Screen.FromPoint(effectiveBounds.Location);
                Rectangle workingArea = currentScreen.WorkingArea;
                SnapLocation anchor = SnapLocation.None;
                if (InSnapRange(effectiveBounds.Left, workingArea.Left + AnchorDistance))
                {
                    effectiveBounds.X = workingArea.Left + AnchorDistance;
                    anchor |= SnapLocation.Left;
                }
                else if (InSnapRange(effectiveBounds.Right, workingArea.Right - AnchorDistance))
                {
                    effectiveBounds.X = workingArea.Right - AnchorDistance - effectiveBounds.Width;
                    anchor |= SnapLocation.Right;
                }
                if (InSnapRange(effectiveBounds.Top, workingArea.Top + AnchorDistance))
                {
                    effectiveBounds.Y = workingArea.Top + AnchorDistance;
                    anchor |= SnapLocation.Top;
                }
                else if (InSnapRange(effectiveBounds.Bottom, workingArea.Bottom - AnchorDistance))
                {
                    effectiveBounds.Y = workingArea.Bottom - AnchorDistance - effectiveBounds.Height;
                    anchor |= SnapLocation.Bottom;
                }
                return anchor;
            }
    
            protected override void WndProc(ref Message m)
            {
                switch (m.Msg)
                {
                    case WmEnterSizeMove:
                    case WmSize:
                        // Need to handle window size changed as well when
                        // un-maximizing the form by dragging the title bar.
                        _dragOffsetX = Cursor.Position.X - Left;
                        _dragOffsetY = Cursor.Position.Y - Top;
                        break;
                    case WmMoving:
                        LtrbRectangle boundsLtrb = Marshal.PtrToStructure<LtrbRectangle>(m.LParam);
                        Rectangle bounds = boundsLtrb.ToRectangle();
                        // This is where the window _would_ be located if snapping
                        // had not occurred. This prevents the cursor from sliding
                        // off the title bar if the snap distance is too large.
                        Rectangle effectiveBounds = new Rectangle(
                            Cursor.Position.X - _dragOffsetX,
                            Cursor.Position.Y - _dragOffsetY,
                            bounds.Width,
                            bounds.Height);
                        _snapAnchor = FindSnap(ref effectiveBounds);
                        LtrbRectangle newLtrb = LtrbRectangle.FromRectangle(effectiveBounds);
                        Marshal.StructureToPtr(newLtrb, m.LParam, false);
                        m.Result = new IntPtr(1);
                        break;
                }
                base.WndProc(ref m);
            }
        }
    }
    

    And here's what it looks like:

    0 讨论(0)
  • 2020-11-29 01:50

    I don't know if you found your solution, but I created a small component for just that: http://www.formsnapper.net - it snaps accross the process boundaries!

    0 讨论(0)
  • 2020-11-29 01:52

    This worked pretty well, works on multiple monitors, observes the taskbar:

      public partial class Form1 : Form {
        public Form1() {
          InitializeComponent();
        }
        private const int SnapDist = 100;
        private bool DoSnap(int pos, int edge) {
          int delta = pos - edge;
          return delta > 0 && delta <= SnapDist;
        }
        protected override void  OnResizeEnd(EventArgs e) {
          base.OnResizeEnd(e);
          Screen scn = Screen.FromPoint(this.Location);
          if (DoSnap(this.Left, scn.WorkingArea.Left)) this.Left= scn.WorkingArea.Left;
          if (DoSnap(this.Top, scn.WorkingArea.Top)) this.Top = scn.WorkingArea.Top;
          if (DoSnap(scn.WorkingArea.Right, this.Right)) this.Left = scn.WorkingArea.Right - this.Width;
          if (DoSnap(scn.WorkingArea.Bottom, this.Bottom)) this.Top = scn.WorkingArea.Bottom - this.Height;
        }
      }
    
    0 讨论(0)
  • 2020-11-29 02:02

    Just retrieve the current pixel height/width of the monitor you're on...

    How to determine active monitor of the current cursor location

    ... and process the location changed/moved events for the form. When you get within, say 25 pixels or so of an edge (your main form's Location.Left + form width) or height (your main form's Location.Top + form height), then go ahead and set the .Left and .Top properties so that your application "docks" in the corners.

    Edit: One other note - when you actually do the "snapping" you may also want to move the cursor position the relative distance to make it stay on the same point on the window bar. Otherwise your form may become a giant ping pong ball between the cursor position and your "snappy" functionality as the MouseMove and form location changed events fight against each other.

    0 讨论(0)
提交回复
热议问题