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
   ENDIF

   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
   ENDIF
    
   THISFORM.oConfig.nViewMode = THIS.nViewMode
   THISFORM.oConfig.cOutputPath = THIS.oHelp.cOutputPath
ENDIF

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
   ELSE
      this.Width = 840
      this.Height = 560
   ENDIF
   this.AutoCenter = .t.
ELSE
   THIS.Height = THIS.oConfig.nHeight
   THIS.Width = THIS.oConfig.nWidth
ENDIF

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()

#DEFINE SM_XVIRTUALSCREEN 76
#DEFINE SM_YVIRTUALSCREEN 77
#DEFINE SM_CXVIRTUALSCREEN 78
#DEFINE SM_CYVIRTUALSCREEN 79
#DEFINE SM_CMONITORS 80
#DEFINE SM_CXFULLSCREEN 16
#DEFINE SM_CYFULLSCREEN 17


DECLARE INTEGER GetSystemMetrics IN user32 INTEGER nIndex

loMonitor = CREATEOBJECT("EMPTY")
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
ENDFUNC
*  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
ENDIF

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
ENDIF 
IF EMPTY(lnHeight)
   lnHeight = loMonitor.VirtualHeight - 10
ENDIF   

*** 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
ENDIF
IF loForm.Top > loMonitor.VirtualHeight - 50
    loForm.Top = 5
ENDIF

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

ENDFUNC
*   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
  THISFORM.Top=wwt_cfg.ypos
  THISFORM.Left=wwt_cfg.xpos
  THISFORM.Height=wwt_cfg.height
  THISFORM.Width=wwt_cfg.width
  IF !THISFORM.lVerticalSplitter
     THISFORM.shpSplitter.Left = wwt_cfg.SliderLeft
  ELSE
     THISFORM.shpSplitter.Top = wwt_cfg.SliderLeft
  ENDIF
ENDIF

*** Check and fix for multi-monitor position
FixMonitorPosition(thisform)

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



Paul
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.

#DEFINE SM_XVIRTUALSCREEN 76
#DEFINE SM_YVIRTUALSCREEN 77
#DEFINE SM_CXVIRTUALSCREEN 78
#DEFINE SM_CYVIRTUALSCREEN 79

clear
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)

*Test
lnLeft = _Screen.Left
lnTop = _screen.Top

?"left"
?_Screen.Left
?"SM_CYVIRTUALSCREEN"
?GetSystemMetrics(SM_XVIRTUALSCREEN)
?"lnVirtualMaxRight "
?lnVirtualMaxRight
?"lnVirtualMaxLeft "
?lnVirtualMaxLeft

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

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

re: Handling Multiple Screens in Visual FoxPro Desktop Applications



BC
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.

_Screen.left=5
_Screen.top=5

re: Handling Multiple Screens in Visual FoxPro Desktop Applications



P.C.
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