How can you ensure that a form stays completely on-screen?

stevex33

New member
Joined
Sep 10, 2010
Messages
3
Programming Experience
Beginner
Hi,
I'm trying to figure out how to ensure that a form stays on-screen completely and cannot be dragged outside the bounds of the window.

This is because the user needs to be able to completely see the form without any of it being cut off.

Does anyone know how this can be done?
 
Users are pretty much guaranteed to do whatever stupid things can possibly be done but, if they drag the form off the screen, can't they just drag it back on again?
 
Yes they could drag it back, but due to the concept of the application, the window must remain on top and within the bounds of the screen at all times, unless the user wants to close the program of course.
 
i agree, usually the user should have complete control over the placement, size, and position of a window. Don't impede on the users control over software or they'll find it constraining most of the time.


BUT

If you want it, yeah you can do it. There are properties of a window that you can set to move a window around screen, size it to the screen, maximize, minimize, etc. Just check out the System.Windows.Forms.Form class:

Form Class (System.Windows.Forms)
Be sure to check the "Form Members" link towards the bottom, that lists all the methods, properties, events, etc of the classes interface.

(I'm assuming you are doing a Windows Forms project, if you're using WPF then this link is useless and there is a completely different interface to your forms you need to learn).


Now all you'd have to do is update the position/size/other settings on whatever events you decide.

The "easiest" would probably just be to update on some interval using a timer (make sure it's a System.Windows.Forms.Timer, it remains thread safe with windows forms). Every once in a while you basically just set the window back to whatever "state" you want it in. (At the same time this is kind of costly as you're habitually updating the position/size/etc even when they don't change... but it's probably not that much overhead, hence my comment of "easiest").

Or you could get more technical reacting to user input events and the sort. But you could easily not react to some event and leave some loop hole open that can put the window into a faulty state that the user couldn't get out of.



For the latter just make sure you react only to the necessary events, and don't leave any out.

For instance if all you wanted to do was make sure the user didn't "Move off screen", then you just listen to the "Form.Move" event, and every time the user has moved the window, you move it back into view if it slid out. No more or less is necessary.

But if it's more complex then that and you start getting into events that are inter-dependent (especially focusing loops and the sort)... well then you got to get really tricky and the sort. And personally I'd advise against it... if your code is battling with events, then you probably are using the wrong events.




One last thing I want to bring up that may annoy some users!

I work on a multi-monitor display (3 monitors of varying sizes). And I've used several pieces of software (some designed by MS themselves) that restrict the placement of the window or the mouse with respect to the window and as a result can get me stuck on one of my three monitors until I close that software! Sometimes worse I can get stuck with my mouse on one screen and the software on another, requiring the user to resort to the task manager or keyboard shortcuts. Neither of which is good... most users don't know to use keyboard shortcuts. How many windows users do you know (that aren't techy) that know what "Alt+Tab" does or "Ctrl+F5"?


Give you an example, Windows Media Center by Microsoft themselves. One of my monitors is a 50 inch 1080p HDTV, I use it for watching movies and playing videogames (it's got its own dedicated video card). Most of my software will let me play over on my other two monitors while things are fullscreened on the 50 inch. For instance VLC lets me remain on the other screens when it's fullscreened, so my girlfriend can watch movies on the 50inch while I'm coding on the 30inch, and I have MSDN open in firefox on the 20 inch for reference. BUT Windows Media Center doesn't. My mouse gets locked on that screen, AND that window randomly gets locked on that screen when I take it out of fullscreen back into window mode (not sure why either).
 
Last edited:
Thanks for your post, it's very helpful.
As for it annoying the user, I'm sure they'll be fine with it, as being able to hide the window out of sight, or cover it in any way would defeat the purpose of using it, or to put it another way, it would allow them to "cheat" the system, which I cannot allow.

I imagine that I would run a timer that on each 1 second tick would move the window back where it's supposed to be.
 
Here's the "proper" way to do it:
VB.NET:
Imports System.Runtime.InteropServices

Public Class Form1

    ''' <summary>
    ''' Represents a the four sides of a rectangle.
    ''' </summary>
    ''' <remarks>
    ''' A managed implementation of the structure used by the WM_MOVING message.
    ''' </remarks>
    Private Structure RECT
        Public Left As Integer
        Public Top As Integer
        Public Right As Integer
        Public Bottom As Integer

        Public Overrides Function ToString() As String
            Return String.Format("{{Left={0},Top={1},Right={2},Bottom={3}}}",
                                 Me.Left,
                                 Me.Top,
                                 Me.Right,
                                 Me.Bottom)
        End Function
    End Structure


    ''' <summary>
    ''' The message received when the form is about to move.
    ''' </summary>
    Private Const WM_MOVING As Integer = &H216

    Private Const [TRUE] As Integer = 1


    ''' <summary>
    ''' Processes messages received from the OS.
    ''' </summary>
    ''' <param name="m">
    ''' The message received.
    ''' </param>
    ''' <remarks>
    ''' Processes the WM_MOVING message to prevent the form moving off-screen.
    ''' </remarks>
    Protected Overrides Sub WndProc(ByRef m As Message)
        If m.Msg = WM_MOVING Then
            'The current bounds of the form.
            Dim currentFormBounds As Rectangle = Me.Bounds

            'The proposed new bounds of the form after the move.
            Dim newFormBounds = DirectCast(Marshal.PtrToStructure(m.LParam, GetType(RECT)), RECT)

            'The bounds of the primary monitor.
            Dim screenBounds As Rectangle = Screen.PrimaryScreen.WorkingArea

            'Don't let the form move beyond the bounds of the screen in a horizontal direction.
            If newFormBounds.Left < screenBounds.Left OrElse
               newFormBounds.Right > screenBounds.Right Then
                newFormBounds.Left = currentFormBounds.Left
                newFormBounds.Right = currentFormBounds.Right
            End If

            'Don't let the form move beyond the bounds of the screen in a vertical direction.
            If newFormBounds.Top < screenBounds.Top OrElse
               newFormBounds.Bottom > screenBounds.Bottom Then
                newFormBounds.Top = currentFormBounds.Top
                newFormBounds.Bottom = currentFormBounds.Bottom
            End If

            'Replace the proposed bounds with the calculated bounds.
            Marshal.StructureToPtr(newFormBounds, _
                                   m.LParam, _
                                   False)
            m.Result = New IntPtr([TRUE])
        End If

        'Pass the message on.
        MyBase.WndProc(m)
    End Sub

End Class
Here's a reusable class that you can use to restrict any form:
VB.NET:
Imports System.Runtime.InteropServices

Public Class FormBoundsRestricter
    Inherits NativeWindow

    Private Structure RECT
        Public Left As Integer
        Public Top As Integer
        Public Right As Integer
        Public Bottom As Integer

        Public Overrides Function ToString() As String
            Return String.Format("{{Left={0},Top={1},Right={2},Bottom={3}}}",
                                 Me.Left,
                                 Me.Top,
                                 Me.Right,
                                 Me.Bottom)
        End Function
    End Structure

    Private Const WM_MOVING As Integer = &H216
    Private Const [TRUE] As Integer = 1

    Private WithEvents target As Form

    Public Sub New(ByVal target As Form)
        Me.target = target
    End Sub

    Private Sub target_HandleCreated(ByVal sender As Object, _
                                     ByVal e As EventArgs) Handles target.HandleCreated
        Me.AssignHandle(Me.target.Handle)
    End Sub

    Private Sub target_HandleDestroyed(ByVal sender As Object, _
                                       ByVal e As EventArgs) Handles target.HandleDestroyed
        Me.ReleaseHandle()
    End Sub

    Protected Overrides Sub WndProc(ByRef m As Message)
        If m.Msg = WM_MOVING Then
            Dim currentFormBounds As Rectangle = Me.target.Bounds
            Dim newFormBounds = DirectCast(Marshal.PtrToStructure(m.LParam, GetType(RECT)), RECT)
            Dim screenBounds As Rectangle = Screen.PrimaryScreen.WorkingArea

            'Don't let the form move beyond the bounds of the screen in a horizontal direction.
            If newFormBounds.Left < screenBounds.Left OrElse
               newFormBounds.Right > screenBounds.Right Then
                newFormBounds.Left = currentFormBounds.Left
                newFormBounds.Right = currentFormBounds.Right
            End If

            'Don't let the form move beyond the bounds of the screen in a vertical direction.
            If newFormBounds.Top < screenBounds.Top OrElse
               newFormBounds.Bottom > screenBounds.Bottom Then
                newFormBounds.Top = currentFormBounds.Top
                newFormBounds.Bottom = currentFormBounds.Bottom
            End If

            Marshal.StructureToPtr(newFormBounds, _
                                   m.LParam, _
                                   False)
            m.Result = New IntPtr([TRUE])
        End If

        MyBase.WndProc(m)
    End Sub

End Class
which you would use like this:
VB.NET:
Public Class Form2

    Private restricter As New FormBoundsRestricter(Me)

End Class
 
Back
Top