Как сделать так, чтобы приложение Windows Form было привязано к краям экрана?

Кто-нибудь знает, как сделать ваше приложение формы Windows .net липким / быстрым, как Winamp, чтобы оно привязалось к краям экрана?

Целевой платформой будет .NET 2.0 Windows Form, написанный на C# с использованием VS08. Я хочу добавить эту функциональность в настраиваемый пользовательский элемент управления, но я полагал, что больше людей выиграют от того, чтобы описать ее для приложения и его основной формы.

Спасибо.

Ответов (5)

Решение

Это сработало очень хорошо, работает на нескольких мониторах, наблюдает за панелью задач:

  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;
    }
  }

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

Принятый ответ привязывает окно только после завершения перетаскивания, тогда как я хотел, чтобы форма непрерывно привязывалась к краям экрана во время перетаскивания. Вот мое решение, основанное на исходном коде Paint.NET :

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);
        }
    }
}

А вот как это выглядит:

Скриншот

Просто получите текущую высоту / ширину в пикселях монитора, на котором вы находитесь ...

Как определить активный монитор текущего положения курсора

... и обработать события изменения / перемещения местоположения для формы. Когда вы окажетесь внутри, скажем, 25 пикселей или около того края (Location.Left + ширина формы вашей основной формы) или высоты (Location.Top + form height вашей основной формы), затем установите свойства .Left и .Top. так что ваше приложение "стыкуется" по углам.

Изменить: еще одно замечание - когда вы на самом деле делаете «привязку», вы также можете переместить курсор на относительное расстояние, чтобы он оставался в той же точке на панели окна. В противном случае ваша форма может стать гигантским мячом для пинг-понга между положением курсора и вашей «быстрой» функциональностью, поскольку события MouseMove и изменения местоположения формы борются друг с другом.

Не знаю, нашли ли вы свое решение, но я создал для этого небольшой компонент: http://www.formsnapper.net - он пересекает границы процесса!