Introduction
This article demonstrates how to add a snap-to-screen feature to your application. This feature allows you to drag your window near the screen border and have the window automatically get pulled towards that border like a magnet. There is already a nice unmanaged C++ implementation here.
Background
At TrayGames, we have a client that delivers chat and games to your computer. Our client is written completely in .NET 2.0, using a combination of C# and VB.NET. We wanted our client to have the snap-to-screen functionality that other popular applications such as Winamp and Skype have.
Using the Code
To add snap-to-screen functionality to your application, you have to override the Windows.Forms.Form.WndProc
method and handle the WM_WINDOWPOSCHANGING
message. Your handler will call my custom SnapToDesktopBorder
method:
Imports system.Runtime.InteropServices
Public Class Form1
Private Const mSnapOffset As Integer = 35
Private Const WM_WINDOWPOSCHANGING As Integer = &H46
<StructLayout(LayoutKind.Sequential)> _
Public Structure WINDOWPOS
Public hwnd As IntPtr
Public hwndInsertAfter As IntPtr
Public x As Integer
Public y As Integer
Public cx As Integer
Public cy As Integer
Public flags As Integer
End Structure
Protected Overrides Sub WndProc(ByRef m As Message)
Select Case m.Msg
Case WM_WINDOWPOSCHANGING
SnapToDesktopBorder(Me, m.LParam, 0)
End Select
MyBase.WndProc(m)
End Sub
The heart of the functionality is in the implmentation of the SnapToDesktopBorder
method. It calculates the distance to the desktop screen edges, and repositions the window as necessary:
Public Shared Sub SnapToDesktopBorder(ByVal clientForm _
As Form, ByVal LParam As IntPtr, ByVal widthAdjustment As Integer)
If clientForm Is Nothing Then
Throw New ArgumentNullException("clientForm")
End If
Try
Dim NewPosition As New WINDOWPOS
NewPosition = CType(Runtime.InteropServices.Marshal.PtrToStructure( _
LParam, GetType(WINDOWPOS)), WINDOWPOS)
If NewPosition.y = 0 OrElse NewPosition.x = 0 Then
Return
End If
Dim ClientRect As Rectangle = _
clientForm.RectangleToScreen(clientForm.ClientRectangle)
ClientRect.Width += _
SystemInformation.FrameBorderSize.Width - widthAdjustment
ClientRect.Height += (SystemInformation.FrameBorderSize.Height + _
SystemInformation.CaptionHeight)
Dim WorkingRect As Rectangle = _
Screen.GetWorkingArea(clientForm.ClientRectangle)
If NewPosition.x >= WorkingRect.X - mSnapOffset AndAlso _
NewPosition.x <= WorkingRect.X + mSnapOffset Then
NewPosition.x = WorkingRect.X
End If
Dim ScreenRect As Rectangle = _
Screen.GetBounds(Screen.PrimaryScreen.Bounds)
Dim TaskbarHeight As Integer = _
ScreenRect.Height - WorkingRect.Height
If NewPosition.y >= -mSnapOffset AndAlso _
(WorkingRect.Y > 0 AndAlso NewPosition.y <= _
(TaskbarHeight + mSnapOffset)) OrElse _
(WorkingRect.Y <= 0 AndAlso NewPosition.y <= _
(mSnapOffset)) Then
If TaskbarHeight > 0 Then
NewPosition.y = WorkingRect.Y
Else
NewPosition.y = 0
End If
End If
If NewPosition.x + ClientRect.Width <= _
WorkingRect.Right + mSnapOffset AndAlso _
NewPosition.x + ClientRect.Width >= _
WorkingRect.Right - mSnapOffset Then
NewPosition.x = WorkingRect.Right - (ClientRect.Width + _
SystemInformation.FrameBorderSize.Width)
End If
If NewPosition.y + ClientRect.Height <= _
WorkingRect.Bottom + mSnapOffset AndAlso _
NewPosition.y + ClientRect.Height >= _
WorkingRect.Bottom - mSnapOffset Then
NewPosition.y = WorkingRect.Bottom - (ClientRect.Height + _
SystemInformation.FrameBorderSize.Height)
End If
Runtime.InteropServices.Marshal.StructureToPtr(NewPosition, _
LParam, True)
Catch ex As ArgumentException
End Try
End Sub
End Class
You can adjust the mSnapOffset
member variable to adjust the distance between your window and the screen border where the snap will happen. Note that the widthAdjustment
parameter is available as a fudge factor in case your client has special width requirements. That's all there is to it!
Points of Interest
This article is interesting if you want to learn some creative ways to position Windows Forms. Although this wasn't the most complicated topic, hopefully, I've saved someone a little time figuring this out! If you are interested in creating your own multi-player online games, you should check out the TGSDK which is downloadable from the TrayGames web site.