Handling Multiple Screens in Visual FoxPro Desktop Applications
May 15, 2011 •
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.
Mike James
May 26, 2011