' ***********************************************************************
' Author : Elektro
' Last Modified On : 09-19-2014
' ***********************************************************************
' <copyright file="WindowSticker.vb" company="Elektro Studios">
' Copyright (c) Elektro Studios. All rights reserved.
' </copyright>
' ***********************************************************************
#Region " Usage Examples "
' Private WindowSticker As New WindowSticker(ClientForm:=Me) With {.SnapMargin = 35}
'Private Sub Form1_Load() Handles MyBase.Shown
' WindowSticker.Dispose()
' WindowSticker = New WindowSticker(Form2)
' WindowSticker.ClientForm.Show()
'End Sub
#End Region
#Region " Imports "
Imports System.ComponentModel
Imports System.Runtime.InteropServices
#End Region
#Region " WindowSticker "
''' <summary>
''' Sticks a Form to a Desktop border (if the Form is near).
''' </summary>
Public Class WindowSticker : Inherits NativeWindow : Implements IDisposable
#Region " Properties "
#Region " Public "
''' <summary>
''' Gets the client form used to stick its borders.
''' </summary>
''' <value>The client form used to stick its borders.</value>
Public ReadOnly Property ClientForm As Form
Get
Return Me._ClientForm
End Get
End Property
Private WithEvents _ClientForm As Form = Nothing
''' <summary>
''' Gets or sets the snap margin (offset), in pixels.
''' (Default value is: 30))
''' </summary>
''' <value>The snap margin (offset), in pixels.</value>
Public Property SnapMargin As Integer
Get
Return Me._SnapMargin
End Get
Set(ByVal value As Integer)
Me.DisposedCheck()
Me._SnapMargin = value
End Set
End Property
Private _SnapMargin As Integer = 30I
#End Region
#Region " Private "
''' <summary>
''' Gets rectangle that contains the size of the current screen.
''' </summary>
''' <value>The rectangle that contains the size of the current screen.</value>
Private ReadOnly Property ScreenRect As Rectangle
Get
Return Screen.FromControl(Me._ClientForm).Bounds
End Get
End Property
''' <summary>
''' Gets the working area of the current screen.
''' </summary>
''' <value>The working area of the current screen.</value>
Private ReadOnly Property WorkingArea As Rectangle
Get
Return Screen.FromControl(Me._ClientForm).WorkingArea
End Get
End Property
''' <summary>
''' Gets the desktop taskbar height (when thet taskbar is horizontal).
''' </summary>
''' <value>The desktop taskbar height (when thet taskbar is horizontal).</value>
Private ReadOnly Property TaskbarHeight As Integer
Get
Return Me.ScreenRect.Height - Me.WorkingArea.Height
End Get
End Property
#End Region
#End Region
#Region " Enumerations "
''' <summary>
''' Windows Message Identifiers.
''' </summary>
<Description("Messages to process in WndProc")>
Public Enum WindowsMessages As Integer
''' <summary>
''' Sent to a window whose size, position, or place in the Z order is about to change.
''' MSDN Documentation: http://msdn.microsoft.com/en-us/library/windows/desktop/ms632653%28v=vs.85%29.aspx
''' </summary>
WM_WINDOWPOSCHANGING = &H46I
End Enum
#End Region
#Region " Structures "
''' <summary>
''' Contains information about the size and position of a window.
''' MSDN Documentation: http://msdn.microsoft.com/en-us/library/windows/desktop/ms632612%28v=vs.85%29.aspx
''' </summary>
<StructLayout(LayoutKind.Sequential)>
Public Structure WINDOWPOS
''' <summary>
''' A handle to the window.
''' </summary>
Public hwnd As IntPtr
''' <summary>
''' The position of the window in Z order (front-to-back position).
''' This member can be a handle to the window behind which this window is placed,
''' or can be one of the special values listed with the 'SetWindowPos' function.
''' </summary>
Public hwndInsertAfter As IntPtr
''' <summary>
''' The position of the left edge of the window.
''' </summary>
Public x As Integer
''' <summary>
''' The position of the top edge of the window.
''' </summary>
Public y As Integer
''' <summary>
''' The window width, in pixels.
''' </summary>
Public width As Integer
''' <summary>
''' The window height, in pixels.
''' </summary>
Public height As Integer
''' <summary>
''' Flag containing the window position.
''' </summary>
Public flags As Integer
End Structure
#End Region
#Region " Constructor "
''' <summary>
''' Initializes a new instance of WindowSticker class.
''' </summary>
''' <param name="ClientForm">The client form to assign this NativeWindow.</param>
Public Sub New(ByVal ClientForm As Form)
' Assign the Formulary.
Me._ClientForm = ClientForm
End Sub
''' <summary>
''' Prevents a default instance of the <see cref="WindowSticker"/> class from being created.
''' </summary>
Private Sub New()
End Sub
#End Region
#Region " Event Handlers "
''' <summary>
''' Assign the handle of the target Form to this NativeWindow,
''' necessary to override target Form's WndProc.
''' </summary>
Private Sub SetFormHandle() Handles _ClientForm.HandleCreated, _ClientForm.Load, _ClientForm.Shown
If (Me._ClientForm IsNot Nothing) AndAlso (Not MyBase.Handle.Equals(Me._ClientForm.Handle)) Then
MyBase.AssignHandle(Me._ClientForm.Handle)
End If
End Sub
''' <summary>
''' Releases the Handle.
''' </summary>
Private Sub OnHandleDestroyed() Handles _ClientForm.HandleDestroyed
MyBase.ReleaseHandle()
End Sub
#End Region
#Region " WndProc "
''' <summary>
''' Invokes the default window procedure associated with this window to process messages.
''' </summary>
''' <param name="m">
''' A <see cref="T:System.Windows.Forms.Message" /> that is associated with the current Windows message.
''' </param>
Protected Overrides Sub WndProc(ByRef m As Message)
If (Me._ClientForm IsNot Nothing) AndAlso (m.Msg = WindowsMessages.WM_WINDOWPOSCHANGING) Then
Me.SnapToDesktopBorder(ClientForm:=Me._ClientForm, Handle:=m.LParam, widthAdjustment:=0)
End If
MyBase.WndProc(m)
End Sub
#End Region
#Region " Private Methods "
''' <summary>
''' Sticks a Form to a desktop border (it its near).
''' </summary>
''' <param name="ClientForm">The client form used to stick its borders.</param>
''' <param name="Handle">A pointer to a 'WINDOWPOS' structure that contains information about the window's new size and position.</param>
''' <param name="widthAdjustment">The border width adjustment.</param>
Private Sub SnapToDesktopBorder(ByVal ClientForm As Form,
ByVal Handle As IntPtr,
Optional ByVal widthAdjustment As Integer = 0I)
Dim newPosition As WINDOWPOS = CType(Marshal.PtrToStructure(Handle, GetType(WINDOWPOS)), WINDOWPOS)
If (newPosition.y = 0) OrElse (newPosition.x = 0) Then
' Nothing to do.
Exit Sub
End If
' Top border (check if taskbar is on top or bottom via WorkingRect.Y)
If (newPosition.y >= -SnapMargin AndAlso (Me.WorkingArea.Y > 0 AndAlso newPosition.y <= (Me.TaskbarHeight + Me.SnapMargin))) _
OrElse (Me.WorkingArea.Y <= 0 AndAlso newPosition.y <= (SnapMargin)) Then
If Me.TaskbarHeight > 0 Then
' Horizontal Taskbar
newPosition.y = Me.WorkingArea.Y
Else
' Vertical Taskbar
newPosition.y = 0
End If
End If
' Left border
If (newPosition.x >= Me.WorkingArea.X - Me.SnapMargin) _
AndAlso (newPosition.x <= Me.WorkingArea.X + Me.SnapMargin) Then
newPosition.x = Me.WorkingArea.X
End If
' Right border.
If (newPosition.x + Me._ClientForm.Width <= Me.WorkingArea.Right + Me.SnapMargin) _
AndAlso (newPosition.x + Me._ClientForm.Width >= Me.WorkingArea.Right - Me.SnapMargin) Then
newPosition.x = (Me.WorkingArea.Right - Me._ClientForm.Width)
End If
' Bottom border.
If (newPosition.y + Me._ClientForm.Height <= Me.WorkingArea.Bottom + Me.SnapMargin) _
AndAlso (newPosition.y + Me._ClientForm.Height >= Me.WorkingArea.Bottom - Me.SnapMargin) Then
newPosition.y = (Me.WorkingArea.Bottom - Me._ClientForm.Height)
End If
' Marshal it back.
Marshal.StructureToPtr([structure]:=newPosition, ptr:=Handle, fDeleteOld:=True)
End Sub
#End Region
#Region " Hidden Methods "
''' <summary>
''' Determines whether the specified System.Object instances are the same instance.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Private Shadows Sub ReferenceEquals()
End Sub
''' <summary>
''' Assigns a handle to this window.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub AssignHandle()
End Sub
''' <summary>
''' Creates a window and its handle with the specified creation parameters.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub CreateHandle()
End Sub
''' <summary>
''' Destroys the window and its handle.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub DestroyHandle()
End Sub
''' <summary>
''' Releases the handle associated with this window.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub ReleaseHandle()
End Sub
''' <summary>
''' Retrieves the window associated with the specified handle.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Private Shadows Sub FromHandle()
End Sub
''' <summary>
''' Serves as a hash function for a particular type.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub GetHashCode()
End Sub
''' <summary>
''' Retrieves the current lifetime service object that controls the lifetime policy for this instance.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Function GetLifeTimeService()
Return Nothing
End Function
''' <summary>
''' Obtains a lifetime service object to control the lifetime policy for this instance.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Function InitializeLifeTimeService()
Return Nothing
End Function
''' <summary>
''' Creates an object that contains all the relevant information required to generate a proxy used to communicate with a remote object.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Function CreateObjRef()
Return Nothing
End Function
''' <summary>
''' Determines whether the specified System.Object instances are considered equal.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub Equals()
End Sub
''' <summary>
''' Returns a String that represents the current object.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub ToString()
End Sub
''' <summary>
''' Invokes the default window procedure associated with this window.
''' </summary>
<EditorBrowsable(EditorBrowsableState.Never)>
Public Shadows Sub DefWndProc()
End Sub
#End Region
#Region " IDisposable "
''' <summary>
''' To detect redundant calls when disposing.
''' </summary>
Private IsDisposed As Boolean = False
''' <summary>
''' Prevent calls to methods after disposing.
''' </summary>
''' <exception cref="System.ObjectDisposedException"></exception>
Private Sub DisposedCheck()
If Me.IsDisposed Then
Throw New ObjectDisposedException(Me.GetType().FullName)
End If
End Sub
''' <summary>
''' Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
''' </summary>
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
''' <summary>
''' Releases unmanaged and - optionally - managed resources.
''' </summary>
''' <param name="IsDisposing">
''' <c>true</c> to release both managed and unmanaged resources;
''' <c>false</c> to release only unmanaged resources.
''' </param>
Protected Sub Dispose(ByVal IsDisposing As Boolean)
If Not Me.IsDisposed Then
If IsDisposing Then
Me._ClientForm = Nothing
MyBase.ReleaseHandle()
MyBase.DestroyHandle()
End If
End If
Me.IsDisposed = True
End Sub
#End Region
End Class
#End Region