Rick Strahl's FoxPro and Web Connection Web Log
White Papers | Products | Message Board | News |

Handling Multiple Screens in Visual FoxPro Desktop Applications

Sunday, May 15, 2011, 2:32:00 AM

When creating desktop applications one of the things I tend to do frequently is to capture last used window locations in some sort of configuration store when the application (or particular windows) are shut down, and then restore those window locations when the application or that particular window is reactivated. This is fairly common behavior for lots of Windows applications and nice for users who like to have their applications come up in the same place.

When running multiple monitors however, there’s a gotcha to this common operation: If you save a window location for a form that was last opened on a second monitor that is not attached when reloading the application, you can find yourself with a window invisible on the now disconnected monitor – off in non-visible virtual space. FWIW, that also is a common occurrence for many Windows applications …

Saving and Loading Screen Locations

Windows itself is actually pretty smart about screen size changes when they occur while windows are active. If you turn off a monitor while windows are active in your application, Windows will automatically move your windows onto the main monitor. It’ll try to keep the positioning of a second monitor intact if possible (not always possible if the second screen is larger) or center the window if won’t fit. So if your windows are actively running, Windows will do all the work for you which is cool.

Unfortunately if you save window positions on a second monitor, and then start up when the second monitor is gone and move your window, Windows can’t help you, because you are physically setting the window location.

You might have code that looks something like this to save settings in your form’s Destroy() code:

llIsConfig = VARTYPE(this.oConfig) = "O"
IF llIsConfig
   THISFORM.oConfig.cLastFile = THIS.oHelp.cFileName
   IF !ISNULL(THIS.oHelp.oTopic) AND THIS.oHelp.oTopic.Pk # "CONFIG"
      THISFORM.oConfig.cLastTopic = THIS.oHelp.oTopic.Pk

   IF THIS.WindowState = 0
          THISFORM.oConfig.nTop = THIS.Top
          THISFORM.oConfig.nLeft = THIS.Left
          THISFORM.oConfig.nHeight= THIS.Height
          THISFORM.oConfig.nWidth = THIS.Width
          THISFORM.oConfig.nSplitter = THIS.oSplitter.Left
   THISFORM.oConfig.nViewMode = THIS.nViewMode
   THISFORM.oConfig.cOutputPath = THIS.oHelp.cOutputPath

and some code like this to load settings in your form’s Init() code:

THIS.Top = THIS.oConfig.nTop
THIS.Left = THIS.oConfig.nLeft
THIS.oSplitter.Left = THIS.oConfig.nSplitter
THIS.nViewMode = THIS.oConfig.nViewMode

*** First Time Sizing
IF this.Top = 0 AND this.Left=0
   IF  SYSMETRIC(1) < 900
      this.Width = SYSMETRIC(1) - 10
      this.Height = SYSMETRIC(2) - 10
      this.Width = 840
      this.Height = 560
   this.AutoCenter = .t.
   THIS.Height = THIS.oConfig.nHeight
   THIS.Width = THIS.oConfig.nWidth

This code will work fine as long as you don’t save a position on a second monitor and that second monitor disappears before the next load. If it does your form will load into non-visible space which is not so cool.

Getting Screen Size Information

FoxPro includes the SYSMETRIC() function which is supposed to provide this sort of information. It does provide Screen Height and Width with parameter values of 1 and 2 respectively, but unfortunately it’s a bit dated and only returns information for the main screen.

Luckily Windows provides a simple API call – GetSystemMetrics - that picks up the slack and lets you retrieve the virtual screen size and number of monitors. Here’s a wrapper function that provides monitor statistics:


* GetMonitorStatistics
***  Function: Returns information about the desktop screen
***            Can be used to check for desktop width and size
***            and determine when a second monitor is disabled
***            and layout needs to be adjusted to keep the
***            window visible.
***      Pass:
***    Return:  Monitor Object
FUNCTION GetMonitorStatistics()


DECLARE INTEGER GetSystemMetrics IN user32 INTEGER nIndex

ADDProperty( loMonitor,"Monitors",GetSystemMetrics(SM_CMONITORS) )
ADDPROPERTY( loMonitor,"VirtualWidth",GetSystemMetrics(SM_CXVIRTUALSCREEN) )
ADDPROPERTY( loMonitor,"VirtualHeight",GetSystemMetrics(SM_CYVIRTUALSCREEN) )
ADDPROPERTY( loMonitor,"ScreenHeight",GetSystemMetrics(SM_CYFULLSCREEN) )
ADDPROPERTY( loMonitor,"ScreenWidth",GetSystemMetrics(SM_CXFULLSCREEN) )

RETURN loMonitor
*  wwAPI ::  GetMonitorStatistics

With this function you can easily check to see whether THISFORM.Left is located on a second screen when that screen no longer exists:

loMonitor = GetMonitorStatistics()

IF THISFORM.Left > loMonitor.VirtualWidth 
   thisform.Left = 1

In this snippet the code checks to see if the form’s location is off on a second monitor and if it is it’s moved back to the first monitor. You’d have to do a few more checks to really make this work right like checking for the width to ensure it’s not bigger than the main form since monitors can be of different sizes.

To help with this process I use another helper function called FixMonitorPosition which receives a form instance as a parameter:

*  FixMonitorPosition
***  Function: Fixes a FoxPro form to fit on the screen and become
***            visible on activation even if the location is on
***            no longer visible screen
***            This function is useful if you store screen positions
***            in configuration files and have a screen on a second
***            monitor that is no longer available
***    Assume:
***      Pass: loForm    -  The form to fix
***    Return: nothing
FUNCTION FixMonitorPosition(loForm,lnWidth, lnHeight)
LOCAL loMonitor

*** Retrieve statistics about virtual and active screen
loMonitor = GetMonitorStatistics()

IF EMPTY(lnWidth)
   lnWidth = loMonitor.VirtualWidth - 10
IF EMPTY(lnHeight)
   lnHeight = loMonitor.VirtualHeight - 10

*** If the monitor is on a non-visible screen move it over
*** to the current screen

*** Fix top and left first - on another screen most likely
IF loForm.Left > loMonitor.VirtualWidth - 10
    loForm.Left = 5
IF loForm.Top > loMonitor.VirtualHeight - 50
    loForm.Top = 5

*** Now fix the width if larger than screen
IF loForm.Width > loMonitor.VirtualWidth - 10
   loForm.Width = lnWidth -  10
   loForm.Left = 5
IF loForm.Height > loMonitor.VirtualHeight - 10
   loForm.Height = lnHeight - 10
   loForm.Top = 5

*   FixMonitorPosition

This function does all the work of figuring out whether the form is visible, moving it to the primary screen if it’s not and resizing it to fit onto the screen.

With this function in place you can now simply use your normal sizing routines and then call FixMonitorPosition(THISFORM) afterwards to ensure that the form displays. Here’s an example I use in my Message Reader application:

*** Try to resize the window to saved pos
IF wwt_cfg.ypos # 0
  IF !THISFORM.lVerticalSplitter
     THISFORM.shpSplitter.Left = wwt_cfg.SliderLeft
     THISFORM.shpSplitter.Top = wwt_cfg.SliderLeft

*** Check and fix for multi-monitor position

And that’s all there’s to it.

Don’t be a Pest – Handle Multiple Monitors

It’s surprising how many applications get screwed up when you disconnect from a second monitor. I dread having to move windows onto my main monitor manually. BTW, in case you didn’t know how to do this:

  • Click on the window in the task bar
  • On pre-Windows 7 windows right click and choose Move Window
  • On Windows 7, hover over the task icon until the preview pops up, then right click over the preview and click Move Window
  • Use the arrow keys to move the window into the main window and grab it with the mouse when it becomes visible
  • Move where desired

Don’t let your application be one of those applications that require these steps. Be pre-emptive and automatically move your windows using these simple helpers I’ve described above.

Posted in:

Feedback for this Weblog Entry

re: Handling Multiple Screens in Visual FoxPro Desktop Applications

Thanks for the aside on how to move a window back onto the primary monitor under Windows 7 - great tip, apply to FoxPro itself, which is the only app I know that doesn't know how to re-set itself.

re: Handling Multiple Screens in Visual FoxPro Desktop Applications

Metin Emre
Thank you,

This works better than I found at the WEB... Clear, simple and works perfectly... :)

re: Handling Multiple Screens in Visual FoxPro Desktop Applications

This code assumes that the primary monitor is the left most and or top most monitor. If the main monitor was in the middle then you would need to check the range between SM_XVIRTUALSCREEN and (SM_CXVIRTUALSCREEN - SM_XVIRTUALSCREEN) to ensure that there is a valid location.


LOCAL lnVirtualWidth, lnVirtualHeight

DECLARE INTEGER GetSystemMetrics IN user32 INTEGER nIndex

*get max right position, need to subtract if main monitor is not the left most monitor
lnVirtualMaxRight = GetSystemMetrics(SM_CXVIRTUALSCREEN) - ABS(getsystemmetrics(SM_XVIRTUALSCREEN)) - 10
*get min left position, if main monitor is not left most monitor then this is a negative value
lnVirtualMaxLeft = getsystemmetrics(SM_XVIRTUALSCREEN)

*get max top position, need to substract if main monitor is not top most monitor
lnVirtualMaxTop = GetSystemMetrics(SM_CYVIRTUALSCREEN) - ABS(getSystemMetrics(SM_YVIRTUALSCREEN)) - 5
*get min top position, if main monitor is not top most monitor then this is a negative value
lnVirtualMaxBottom= getsystemmetrics(SM_YVIRTUALSCREEN)

lnLeft = _Screen.Left
lnTop = _screen.Top

?"lnVirtualMaxRight "
?"lnVirtualMaxLeft "

*Only set the position if the location is avaible
IF BETWEEN(lnLeft, lnVirtualMaxLeft, lnVirtualMaxRight)
*Set left location

IF BETWEEN(lnTop, lnVirtualMaxTop , lnVirtualMaxBottom)
*Set left location

re: Handling Multiple Screens in Visual FoxPro Desktop Applications

My Foxpro disappeared as you had explained but I don't even have a second monitor. Somehow, when I double clicked, it ran with just the icon at the bottom. No matter how I clicked on it, the screen didn't appear then I saw this post. I used Notepad to create a prg file with the following codes and then dragged this prg file to the VFP icon to run. My screen popped back. Wonderful.


re: Handling Multiple Screens in Visual FoxPro Desktop Applications

Yes, the _screen.top and _screen.left can obtain value -32000 which is hard to find on any monitor...

This can happen when the main FoxPro window is minimized.


© Rick Strahl, West Wind Technologies, 2003 - 2018