I would like to implement a feature in my application, the problem is that I don't know where to search because I don't know the name/term of this feature.
In some applications, when you move the Form near a border/corner of the screen, the application automatically self adhering to that border until you drag the form far the border to un-adherit.
I don't have any application example to show using this feature, sorry about that.
Someone could explain me which is the name/term of this feature, and where I can find a source to examine the techniques used to implement this feature (in WinForms)?.
That feature is sometimes called 'snapping', 'sticky' or 'magnetic' windows, like used in WinAmp. An example implementation can be found at CodeProject: A .NET Snap To Screen Form.
The C# version comes down to this:
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPOS
{
public IntPtr hwnd;
public IntPtr hwndInsertAfter;
public int x;
public int y;
public int cx;
public int cy;
public int flags;
}
public partial class Form1 : Form
{
private const int SnapOffset = 35;
private const int WM_WINDOWPOSCHANGING = 70;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_WINDOWPOSCHANGING)
{
SnapToDesktopBorder(this, m.LParam, 0);
}
base.WndProc(ref m);
}
private void SnapToDesktopBorder(Form clientForm, IntPtr intPtr, int widthAdjustment)
{
var newPosition = new WINDOWPOS();
newPosition = (WINDOWPOS)System.Runtime.InteropServices.Marshal.PtrToStructure(intPtr, typeof(WINDOWPOS));
if (newPosition.y == 0 || newPosition.x == 0)
{
return;
// Nothing to do!
}
// Adjust the client size for borders and caption bar
Rectangle ClientRect = clientForm.RectangleToScreen(clientForm.ClientRectangle);
ClientRect.Width += (SystemInformation.FrameBorderSize.Width * 2) - widthAdjustment;
ClientRect.Height += (SystemInformation.FrameBorderSize.Height * 2) + SystemInformation.CaptionHeight;
// Now get the screen working area (without taskbar)
Rectangle WorkingRect = Screen.FromControl(clientForm).WorkingArea;
// Left border
if (newPosition.x >= WorkingRect.X - SnapOffset && newPosition.x <= WorkingRect.X + SnapOffset)
{
newPosition.x = WorkingRect.X;
}
// Get screen bounds and taskbar height
// (when taskbar is horizontal)
Rectangle ScreenRect = Screen.FromControl(clientForm).Bounds;
int TaskbarHeight = ScreenRect.Height - WorkingRect.Height;
// Top border (check if taskbar is on top
// or bottom via WorkingRect.Y)
if (newPosition.y >= -SnapOffset && (WorkingRect.Y > 0 && newPosition.y <= (TaskbarHeight + SnapOffset)) || (WorkingRect.Y <= 0 && newPosition.y <= (SnapOffset)))
{
if (TaskbarHeight > 0)
{
newPosition.y = WorkingRect.Y;
// Horizontal Taskbar
}
else
{
newPosition.y = 0;
// Vertical Taskbar
}
}
// Right border
if (newPosition.x + ClientRect.Width <= WorkingRect.Right + SnapOffset && newPosition.x + ClientRect.Width >= WorkingRect.Right - SnapOffset)
{
newPosition.x = WorkingRect.Right - (ClientRect.Width + SystemInformation.FrameBorderSize.Width);
}
// Bottom border
if (newPosition.y + ClientRect.Height <= WorkingRect.Bottom + SnapOffset && newPosition.y + ClientRect.Height >= WorkingRect.Bottom - SnapOffset)
{
newPosition.y = WorkingRect.Bottom - (ClientRect.Height + SystemInformation.FrameBorderSize.Height);
}
// Marshal it back
System.Runtime.InteropServices.Marshal.StructureToPtr(newPosition, intPtr, true);
}
}
But the code seems to be a bit bloated, I think it can be greatly simplified. It also only works on desktop borders, not other windows.
See also Anyone familiar with a good “sticky windows” library for Winforms?, both answers linking to other CodeProject solutions: SnapFormExtender - a magnet for your MDI child forms (2004) and Sticky Windows - How to make your (top-level) forms to stick one to the other or to the screen, also from 2004.
Related
So I have a resizable control which I'm using WM_NCHITTEST to resize. I have double buffered the control and I'm drawing a border. However, when resizing, I get medium flickering issues.
Here's the code for drawing:
if (_cachedPen == null)
{
_cachedPen = new Pen(BorderColor, VisibleBorderWidth);
}
Rectangle clientRectangle = ClientRectangle;
if ((BorderSides & BorderSides.Left) == BorderSides.Left)
{
e.Graphics.DrawLine(_cachedPen, clientRectangle.X, clientRectangle.Y, clientRectangle.X, clientRectangle.Height);
}
if ((BorderSides & BorderSides.Right) == BorderSides.Right)
{
e.Graphics.DrawLine(_cachedPen, clientRectangle.Width - VisibleBorderWidth, clientRectangle.Y, clientRectangle.Width - VisibleBorderWidth, clientRectangle.Height);
}
if ((BorderSides & BorderSides.Bottom) == BorderSides.Bottom)
{
e.Graphics.DrawLine(_cachedPen, clientRectangle.X, clientRectangle.Height - VisibleBorderWidth, clientRectangle.Width, clientRectangle.Height - VisibleBorderWidth);
}
if ((BorderSides & BorderSides.Top) == BorderSides.Top)
{
e.Graphics.DrawLine(_cachedPen, clientRectangle.X, clientRectangle.Y, clientRectangle.Width, clientRectangle.Y);
}
And for resizing:
case WM_NCHITTEST:
{
Point pos = PointToClient(new Point(m.LParam.ToInt32()));
if ((BorderSides & BorderSides.Left) == BorderSides.Left && pos.X < PhysicalBorderWidth)
{
m.Result = new IntPtr(HTLEFT);
}
if ((BorderSides & BorderSides.Bottom) == BorderSides.Bottom && pos.Y > Height - PhysicalBorderWidth)
{
m.Result = new IntPtr(HTBOTTOM);
}
if ((BorderSides & BorderSides.Right) == BorderSides.Right && pos.X > Width - PhysicalBorderWidth)
{
m.Result = new IntPtr(HTRIGHT);
}
if ((BorderSides & BorderSides.Top) == BorderSides.Top && pos.Y < PhysicalBorderWidth)
{
m.Result = new IntPtr(HTTOP);
}
return;
}
case 0x0014:
return;
default:
base.WndProc(ref m);
break;
Note that SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.ResizeRedraw, true); is on. However, when resizing, I get light-medium flickering. Is there any way to solve this?
Just to note, it happens even when I don't cache the Pen objects.
The painting is just called from OnPaint. The custom control just inherits from Control.
The definition of BorderSides is as follows:
[Flags]
internal enum BorderSides
{
None = 0,
Left = 1,
Right = 2,
Top = 4,
Bottom = 8
}
And here are the constants:
private const int WM_NCHITTEST = 0x0084;
private const int HTBOTTOM = 15;
private const int HTLEFT = 10;
private const int HTRIGHT = 11;
private const int HTTOP = 12;
This is for a docking control. It doesn't have a graphic background, and I intend to inherit from it. It will host controls. I'm testing all 4 border sides, and all have slight flickering issues.
Thanks
I'm new to Xamarin Android and currently working on a floating action button, I implemented View.IOnTouchListener and normal button click event (faButton.Click += floatButtonPressed;) to carry out my actions. But for the case MotionEventActions.Move, it doesn't work as I wanted. Moving left and right it works fine but for top and bottom it will move downwards a little bit whenever I start moving it. Besides, when I move the button to screen border it will be able to exceed the screen. Hence, I tried detect screen size and restrict it but it still not good enough, is there any other available solution or settings?
public bool OnTouch(View v, MotionEvent e)
{
switch (e.Action)
{
case MotionEventActions.Down:
oldXvalue = e.GetX();
oldYvalue = e.GetY();
if (oldXvalue == e.GetX() && oldYvalue == e.GetY())
{
return false;
}
break;
case MotionEventActions.Up:
if (oldXvalue == e.GetX() && oldYvalue == e.GetY())
{
return false;
}
break;
case MotionEventActions.Move:
var xleft = (int)(e.RawX - oldXvalue);
var xright = xleft + v.Width;
var ytop = (int)(e.RawY - oldYvalue);
var ybtm = (ytop + v.Height);
if (xleft + v.Width >= intWidth)
{
break;
}
if (xleft <= 0)
{
break;
}
if (ytop + v.Height >= intHeight)
{
break;
}
if (ytop <= 0)
{
break;
}
v.Layout(xleft, ytop, xright, ybtm);
break;
}
return true;
}
You can get the screen height and screen width first. And when the button exceed the screen you need to reset the button position.
Try the following code:
public class MainActivity : Activity, IOnTouchListener
{
Button dragAbleBt;
int screenWidth = 0;
int screenHeight = 0;
int lastX = 0, lastY = 0;
public bool OnTouch(View v, MotionEvent e)
{
MotionEventActions ea = e.Action;
switch (ea) {
case MotionEventActions.Down:
lastX = (int)e.RawX;
lastY = (int)e.RawY;
break;
case MotionEventActions.Move:
int dx = (int)e.RawX - lastX;
int dy = (int)e.RawY - lastY;
int left = v.Left + dx;
int right = v.Right + dx;
int top = v.Top + dy;
int bottom = v.Bottom + dy;
if (left < 0)
{
left = 0;
right = left + v.Width;
}
if (right > screenWidth)
{
right = screenWidth;
left = right - v.Width;
}
if (top < 0)
{
top = 0;
bottom = top + v.Height;
}
if (bottom > screenHeight)
{
bottom = screenHeight;
top = bottom - v.Height;
}
v.Layout(left, top, right, bottom);
lastX = (int) e.RawX;
lastY = (int) e.RawY;
v.PostInvalidate();
break;
case MotionEventActions.Up:
break;
}
return false;
}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView (Resource.Layout.Main);
//DisplayMetrics dm = Resources.DisplayMetrics;
//screenWidth = dm.WidthPixels;
//screenHeight = dm.HeightPixels;
dragAbleBt = FindViewById<Button>(Resource.Id.button1);
dragAbleBt.SetOnTouchListener(this);
}
public override void OnWindowFocusChanged(bool hasFocus)
{
base.OnWindowFocusChanged(hasFocus);
if (hasFocus)
{
Rect outRect = new Rect();
this.Window.FindViewById(Window.IdAndroidContent).GetDrawingRect(outRect);
screenWidth = outRect.Width();
screenHeight = outRect.Height();
}
}
}
I tried detect screen size and restrict it but it still not good enough, is there any other available solution or settings?
You may got the whole screen width and height, the button will exceed the screen height, In this kind of situation you need get the view drawing area by this.Window.FindViewById(Window.IdAndroidContent).GetDrawingRect(outRect)
Screen Shot:
Note: I am using the emulator that is slow, If you are using the real device it will be faster.
The Windows command console only allows you to resize the window to a multiple of the character size. This "snapping" of the window size is instantaneous and does not flicker. In native code, this is done by processing the WM_SIZING message and modifying the RECT structure accordingly.
In C#, I tried overriding the OnResize method, computing the "snapped" size, and setting the Form's ClientSize property accordingly. Unfortunately, the size keeps jumping between the snapped size and whatever size the cursor currently dictates.
protected override void OnResize(EventArgs e)
{
int tgtCols = (ClientSize.Width + 4) / 8;
int tgtLines = (ClientSize.Height + 8) / 15;
if (cols != tgtCols || lines != tgtLines)
{
cols = tgtCols;
lines = tgtLines;
int tgtWidth = cols * 8;
int tgtHeight = lines * 15;
//ClientSize = new Size(tgtWidth, tgtHeight);
Size = new Size(tgtWidth + exWidth, tgtHeight + exHeight);
}
base.OnResize(e);
}
As you can see, I've tried using both the Size and ClientSize properties, but both yield the same effect. Is there a better way of constraining the size? Or do I need to manually intercept the WM_SIZING message?
EDIT: I also tried manually intercepting WM_SIZING, but I get the same result:
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_SIZING:
DoSizeSnap(ref m);
m.Result = new IntPtr(1);
break;
default:
base.WndProc(ref m);
break;
}
}
private unsafe void DoSizeSnap(ref Message m)
{
int edge = m.WParam.ToInt32();
RECT *pRect = (RECT *)m.LParam.ToPointer();
int tgtCols = (pRect->right - pRect->left - exWidth + 4) / 8;
int tgtLines = (pRect->bottom - pRect->top - exHeight + 8) / 15;
if (cols != tgtCols || lines != tgtLines)
{
cols = tgtCols;
lines = tgtLines;
int tgtWidth = cols * 8;
int tgtHeight = lines * 15;
// TODO: handle edge
pRect->right = pRect->left + tgtWidth + exWidth;
pRect->bottom = pRect->top + tgtHeight + exHeight;
}
}
Subscribing to the Resize event doesn't work either.
The problem is the statement if (cols != tgtCols || lines != tgtLines). This causes the size snapping to occur only when the target lines/columns changes, not when the window width/height changes. This was to prevent infinite recursion, but the Resize event is not fired if the size does not actually change, so the if statement is unnecessary.
I'd like the user to resize a borderless window on bottom right corner like I can resize the autocomplete window of the combobox control.
I cannot find the properties to configure a form that way.
Maybe someone could help me on the problem.
An image could be found here:
Here's the code corresponding to Franci's explanations, I was writing it but he answered meanwhile so vote up his explanation which is good if this code suits your needs.
protected override void WndProc(ref Message m) {
const int wmNcHitTest = 0x84;
const int htBottomLeft = 16;
const int htBottomRight = 17;
if (m.Msg == wmNcHitTest) {
int x = (int) (m.LParam.ToInt64() & 0xFFFF);
int y = (int) ((m.LParam.ToInt64() & 0xFFFF0000) >> 16);
Point pt = PointToClient(new Point(x, y));
Size clientSize = ClientSize;
if (pt.X >= clientSize.Width - 16 && pt.Y >= clientSize.Height - 16 && clientSize.Height >= 16) {
m.Result = (IntPtr) (IsMirrored ? htBottomLeft : htBottomRight);
return;
}
}
base.WndProc(ref m);
}
Edit: to write the gripper, you can initialize a new VisualStyleRenderer(VisualStyleElement.Status.Gripper.Normal) and use its PaintBackground() method.
Thanks so much for posting this great sample and explanation. I've added some additions below that others might be interested in. Some of the code here came from other stackoverflow postings, but to be able to see it in one code block might be helpful to others. I wanted to be able to resize the form on ALL borders, not just the lower right corner. I also wanted to be able to drag the form around. Lastly, I wanted a drop-shadow.
//***********************************************************
//This gives us the ability to resize the borderless from any borders instead of just the lower right corner
protected override void WndProc(ref Message m)
{
const int wmNcHitTest = 0x84;
const int htLeft = 10;
const int htRight = 11;
const int htTop = 12;
const int htTopLeft = 13;
const int htTopRight = 14;
const int htBottom = 15;
const int htBottomLeft = 16;
const int htBottomRight = 17;
if (m.Msg == wmNcHitTest)
{
int x = (int)(m.LParam.ToInt64() & 0xFFFF);
int y = (int)((m.LParam.ToInt64() & 0xFFFF0000) >> 16);
Point pt = PointToClient(new Point(x, y));
Size clientSize = ClientSize;
///allow resize on the lower right corner
if (pt.X >= clientSize.Width - 16 && pt.Y >= clientSize.Height - 16 && clientSize.Height >= 16)
{
m.Result = (IntPtr)(IsMirrored ? htBottomLeft : htBottomRight);
return;
}
///allow resize on the lower left corner
if (pt.X <= 16 && pt.Y >= clientSize.Height - 16 && clientSize.Height >= 16)
{
m.Result = (IntPtr)(IsMirrored ? htBottomRight : htBottomLeft);
return;
}
///allow resize on the upper right corner
if (pt.X <= 16 && pt.Y <= 16 && clientSize.Height >= 16)
{
m.Result = (IntPtr)(IsMirrored ? htTopRight : htTopLeft);
return;
}
///allow resize on the upper left corner
if (pt.X >= clientSize.Width - 16 && pt.Y <= 16 && clientSize.Height >= 16)
{
m.Result = (IntPtr)(IsMirrored ? htTopLeft : htTopRight);
return;
}
///allow resize on the top border
if (pt.Y <= 16 && clientSize.Height >= 16)
{
m.Result = (IntPtr)(htTop);
return;
}
///allow resize on the bottom border
if (pt.Y >= clientSize.Height - 16 && clientSize.Height >= 16)
{
m.Result = (IntPtr)(htBottom);
return;
}
///allow resize on the left border
if (pt.X <= 16 && clientSize.Height >= 16)
{
m.Result = (IntPtr)(htLeft);
return;
}
///allow resize on the right border
if (pt.X >= clientSize.Width - 16 && clientSize.Height >= 16)
{
m.Result = (IntPtr)(htRight);
return;
}
}
base.WndProc(ref m);
}
//***********************************************************
//***********************************************************
//This gives us the ability to drag the borderless form to a new location
public const int WM_NCLBUTTONDOWN = 0xA1;
public const int HT_CAPTION = 0x2;
[DllImportAttribute("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
[DllImportAttribute("user32.dll")]
public static extern bool ReleaseCapture();
private void YOURCONTROL_MouseDown(object sender, MouseEventArgs e)
{
//ctrl-leftclick anywhere on the control to drag the form to a new location
if (e.Button == MouseButtons.Left && Control.ModifierKeys == Keys.Control)
{
ReleaseCapture();
SendMessage(Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0);
}
}
//***********************************************************
//***********************************************************
//This gives us the drop shadow behind the borderless form
private const int CS_DROPSHADOW = 0x20000;
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ClassStyle |= CS_DROPSHADOW;
return cp;
}
}
//***********************************************************
The proper way to achieve this would be to add a message proc handler (by overriding Form.WndProc for example) to your form and handle the WM_NCHITTEST message. (You can find the C# definition of that message on PInvoke.net) In particular, when you receive the message, calculate if the hit test is for a point in the region you've designated for resize and if it is, return HTBOTTOMRIGHT. The default window proc will do the rest for you, as it will assume that the user has clicked on the bottom right corner of the window border, even though your window has no border.
This aproach requires a teensy bit of Win32 interop, but it'll make your resize look exactly like any other window resize.
The easy way would be to do as #benPearce said and put a panel in the corner and adjust the form size using Width/Height. It's going to work, but the resize is not going to be smooth, especially on Vista and Win7 Basic, where full redraw is disabled on standard move and resize, while is going to attempt redraw on every step.
Update: In both approaches you will have to figure out also how to paint the gripper. You can put a bitmap of the standard gripper, for example. Though, given that your form has no title and border so you are not necessarily stuck with the standard Windows visuals, you might opt in for something snazzier.
Update 2: If you have a control that covers the whole window, it will eat the form mouse messages. You have to somehow clip the place you want to use for resizing out of that control. You have several options to deal with this:
Resize the control to make some space for the resizing grip.
Tweak the control region (throug the Region property) to exclude the resizing grip.
Cover the resizing grip a panel, listen to its MouseEnter message and set the form Capture property to true, which will cause all further mouse messages to go to it. Note: you will have to release the capture once the mouse leaves that region after the resize is finished.
I would recommend to go for option 1 as the simplest. Option 3 is the most complex and would require intimate details on how mouse input works in Windows, so I wouldn't recommend it. Option 2 is a good alternative to option 1, but you'll have to give it a try to see how the ListView control would react to its region being tweaked.
Put a panel or some other control in the corner, using the MouseDown and MouseMove events of the panel, adjust the forms size appropriately.
In MouseDown, i would record the coordinates, then in the MouseMove you can calculate the difference from the original position to adjust the forms size.
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 Windows Form written in C#, using VS08. I am looking to add this functionality to a custom user control, but I figured more people would benefit from having it described for the application and its main form.
Thank you.
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;
}
}
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:
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.
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!
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