Maintaining Form Position when closing and reopening in a multi-monitor environment

October 17th, 2008 | Tags:

I’ve been working on a small application recently and decided to include in the ability for the application to remember its position when closed and then when reloaded to appear is the same position.  I’ve run into a few problems with programs that do this is the past.  Specifically when resolution is changed or monitor configuration is changed.  For example my laptop may be connected to a secondary monitor at work, but not at home.  If I am running the application on the secondary monitor and close it, then when I’m at home and try to start the application it appears off the screen and I have to right click the task bar for the app, choose move, and then use my arrow keys to move it to a visible location.

I decided that I didn’t want my application to have this problem, but I still wanted to be able to save the position.  I searched the Internet for anything that would work and found many ways to save the position, but none of them seemed to take multiple monitors into account or more precisely an inconsistent monitor setup).  So after many tries I finally came up with a method that worked.  In this method I am storing the position in an INI file, but it could be stored in the registry or an XML file as easily.

I easily found code that would allow me to specify which screen to start on, but using that code, the coordinate reference assumes that 0,0 is the top left of the specified monitor.  When getting the coordinates of the form I could find nothing that would give me the coordinates based on the monitor in use.  Every method I found gave coordinates based on 0,0 being the top left of the primary monitor.  So if I positioned the form in the middle of my left monitor and exited I would store an X position of -600 (600 pixels to the left of the primary monitor), but when loading the form I need to tell it to move 424 pixes to the right from its origin on the secondary monitor.  All very confusing.

The key was to determine the bounds of the monitor used.  This uses the same coordinates referenced in the forms location so a 1024×768 monitor to the left of the primary monitor (also 1024×768) would have it’s upper left corner be -1024,0.  We can subtract the Bounds coordinates from the saved coordinates to find the coordinates relative to the top left corner of the monitor used.  I also check to verify that the position stored is within the bounds of the selected monitor, and if not display the form in the upper left corner of the selected monitor.  This way the form is never displayed in a position where it can not be seen.

Another problem with the code I found for specifying which screen to start on was that it did not verify the existence of that screen before trying to change to it.  So if a monitor was disconnected you would end up with an error.  Storing the selected monitor wound up being a bit more of a challenge than expected.  The property Screen.DisplayName pads the end of the string with a number of hidden characters.  Trimming off the extra characters solved my problems with this section, but it was annoying to track down.

I hope this helps anyone else who might be trying to achieve the same effect.

The following code goes in the Form_Load procedure
‘ Multi Monitor Aware – Restore Postition to last Used
Dim posScreen As Integer
Dim posX As Integer
Dim posY As Integer
Dim screen As Screen
‘ Read the previous screen used from the INI file
posScreen = Val(ReadIni(File, “Main”, “PosScreen”))
screen = screen.AllScreens(0)
‘ Verify the Specified screen exists, if so then use it
If screen.AllScreens.Length > posScreen + 1 Then
screen = screen.AllScreens(0)
Else
screen = screen.AllScreens(posScreen)
End If
‘ Read the Position from the INI File
posX = Val(ReadIni(File, “Main”, “PosX”))
posY = Val(ReadIni(File,
“Main”, “PosY”))
Dim pt As New Point(posX, posY)
If screen.Bounds().Contains(pt) Then
pt.X = posX – screen.Bounds().X
pt.Y = posY – screen.Bounds().Y
Else
pt.X = 0
pt.Y = 0
End If
Me.StartPosition = FormStartPosition.Manual
Me.Location = screen.Bounds.Location + pt

The following code goes in the Form_FormClosing procedure
‘ Save Screen Position
For i As Integer = 0 To
screen.AllScreens().Length – 1
If stringtoasc(screen.AllScreens(i).DeviceName.ToString) _
= stringtoasc(screen.FromControl(
Me).DeviceName.ToString) Then
WriteIni(File, “Main”, “PosScreen”, i)
End If
Next
WriteIni(File, “Main”, “PosX”, Me.Location.X)
WriteIni(File,
“Main”, “PosY”, Me.Location.Y)

*Note the ReadINI and WriteINI functions are in a module not shown here.  As they are not really a major part of the issue with positioning the form.  If anyone would like to see the related code I will be happy to post it, however there are several variations available by searching the Internet.

  1. It has come to my attention that I did not include one of the required functions for this code. In the FormClosing section I call a function “stringtoasc” which I did not include. The purpose of this function is to remove ASCII control characters and non-ASCII characters from the string. This is required to determine the correct screen that the form is on. So here’s the code for that function:

    ‘********************************************************************************************
    ‘ Return only normal ASCII Characters (characters on the keyboard) from a string
    ‘ Remove ASCII code 63 – causing problem in screen display name matching
    ‘********************************************************************************************
    Private Function stringtoasc(ByVal strVal As String) As String
    ‘ MsgBox(strVal.Length)
    stringtoasc = “”
    For i As Integer = 1 To strVal.Length
    If Asc(Mid(strVal, i, 1)) > 31 And Asc(Mid(strVal, i, 1)) < 127 And Asc(Mid(strVal, i, 1)) <> 63 Then
    stringtoasc = stringtoasc & Mid(strVal, i, 1)
    End If
    Next
    ‘ MsgBox(stringtoasc.Length)
    End Function