* * Tablet * * A utility class to assist in VFP tablet programming * * Properties: * IsTablet - .T. if PC is a tablet * TabletMode - .T. if running in tablet mode * Orientation - "L" for landscape, "P" for portrait * * Methods: * IsTabletModeOn() - .T. if running in tablet mode * * Events: * OnTabletModeChange() - when user changes modes * OnOrientationChange() - when user changes orientation * * Windows message identifiers #DEFINE WM_SETTINGCHANGE 0x001A #DEFINE WM_DISPLAYCHANGE 0x007E * For registry inspection #DEFINE HKEY_CURRENT_USER 0x80000001 #DEFINE RRF_RT_DWORD 0x00000018 * System metrics keys #DEFINE SM_CONVERTIBLESLATEMODE 0x2003 #DEFINE SM_TABLETPC 86 #DEFINE SM_CXFULLSCREEN 16 #DEFINE SM_CYFULLSCREEN 17 * load the class definition IF !SYS(16)$SET("Procedure") SET PROCEDURE TO (SYS(16)) ADDITIVE ENDIF DEFINE CLASS Tablet AS Custom HIDDEN OSVersion IsTablet = .F. TabletMode = .F. Orientation = "" OSVersion = 0 _memberdata = '<VFPData>' + ; '<memberdata name="istablet" type="property" display="IsTablet"/>' + ; '<memberdata name="istabletmodeon" type="method" display="IsTabletModeOn"/>' + ; '<memberdata name="onorientationchange" type="method" display="OnOrientationChange"/>' + ; '<memberdata name="ontabletmodechange" type="method" display="OnTabletModeChange"/>' + ; '<memberdata name="orientation" type="property" display="Orientation"/>' + ; '<memberdata name="tabletmode" type="property" display="TabletMode"/>' + ; '</VFPData>' FUNCTION Init LOCAL WMIService, OperatingSystem * declarations, we'll need these functions DECLARE INTEGER GetSystemMetrics IN WIN32API INTEGER Metric DECLARE INTEGER RegGetValue IN WIN32API ; INTEGER hkey, STRING lpSubKey, STRING lpValue, INTEGER dwFlags, INTEGER @ dwType, STRING @ pvData, INTEGER @ pcbData DECLARE Sleep IN WIN32API INTEGER Milliseconds * get the major OS version m.WMIService = GETOBJECT("winmgmts:\\.\root\cimv2") m.OperatingSystem = m.WMIService.InstancesOf("Win32_OperatingSystem").ItemIndex(0) This.OSVersion = VAL(m.OperatingSystem.Version) * check if PC is a tablet TRY This.IsTablet = GetSystemMetrics(SM_TABLETPC) != 0 CATCH This.IsTablet = .F. ENDTRY * set screen orientation This.Orientation = IIF(GetSystemMetrics(SM_CXFULLSCREEN) > GetSystemMetrics(SM_CYFULLSCREEN), "L", "P") * set the tablet mode This.IsTabletModeOn() * trap changes that may be broadcasted by Windows BINDEVENT(_VFP.HWnd, WM_SETTINGCHANGE, This, "SystemChanged", 5) BINDEVENT(_VFP.HWnd, WM_DISPLAYCHANGE, This, "SystemChanged", 5) ENDFUNC FUNCTION Destroy * forget about all our event bindings UNBINDEVENTS(This) ENDFUNC * when relevant display settings are changed, Windows broadcasts messages to all [top-form] windows * messages are trapped by this method that, in turn, fires the public event HIDDEN PROCEDURE SystemChanged (hWnd AS Integer, WindowsMessage AS Integer, Param1 AS Integer, Param2 AS Integer) LOCAL P2 AS String LOCAL Previous LOCAL Waiting AS Integer DO CASE CASE m.WindowsMessage = WM_DISPLAYCHANGE m.Previous = This.Orientation * new values for Screen WxH comes in Param2 This.Orientation = IIF(m.Param2 % 0x10000 > INT(m.Param2 / 0x10000), "L", "P") * changed? IF m.Previous != This.Orientation RAISEEVENT(This, "OnOrientationChange") ENDIF CASE m.WindowsMessage = WM_SETTINGCHANGE * try to fetch a string from Param2 TRY m.P2 = STREXTRACT(SYS(2600, m.Param2, 255), "", CHR(0), 1, 2) CATCH m.P2 = "" ENDTRY DO CASE * pre-Windows 10 message for Tablet mode CASE m.P2 == "ConvertibleSlateMode" IF This.OSVersion < 10 m.Previous = This.TabletMode * check tablet mode This.IsTabletModeOn() IF m.Previous != This.TabletMode RAISEEVENT(This, "OnTabletModeChange") ENDIF ENDIF * Windows 10 message for Tablet mode CASE m.P2 == "UserInteractionMode" IF This.OSVersion >= 10 m.Previous = This.TabletMode This.IsTabletModeOn() m.Waiting = 250 DO WHILE m.Previous = This.TabletMode AND m.Waiting <= 2000 * give some time to the system to do its work Sleep(m.Waiting) m.Waiting = m.Waiting * 2 * check again This.IsTabletModeOn() ENDDO IF m.Previous != This.TabletMode RAISEEVENT(This, "OnTabletModeChange") ENDIF ENDIF ENDCASE ENDCASE ENDPROC * verify if the laptop/device is in tablet mode FUNCTION IsTabletModeOn * before Windows 10, get a system metric that holds the slate mode IF This.OSVersion < 10 LOCAL SlateMode TRY m.SlateMode = GetSystemMetrics(SM_CONVERTIBLESLATEMODE) CATCH m.SlateMode = 1 ENDTRY This.TabletMode = EMPTY(m.SlateMode) * after Windows 10, inspect the registry ELSE LOCAL BufferData AS String LOCAL BufferLen AS Integer LOCAL Zerro AS String * check for the registry value m.BufferLen = 4 m.Zero = REPLICATE(CHR(0), m.BufferLen) m.BufferData = m.Zero RegGetValue(HKEY_CURRENT_USER, ; "SOFTWARE\Microsoft\Windows\CurrentVersion\ImmersiveShell", ; "TabletMode", ; RRF_RT_DWORD, ; 0, ; @m.BufferData, ; @m.BufferLen) This.TabletMode = m.BufferData != m.Zero ENDIF RETURN This.TabletMode ENDPROC * events PROCEDURE OnTabletModeChange ENDPROC PROCEDURE OnOrientationChange ENDPROC ENDDEFINE2. tester.prg
SET DEFAULT TO (JUSTPATH(SYS(16))) * put the class definition in scope DO Tablet.prg * a simple error handler, just to help us debug this ON ERROR DO ShowError WITH PROGRAM(), LINENO(), MESSAGE(1), MESSAGE(), ERROR() * the testers: a form and an extended instance of the tablet class, just to output something when there are events LOCAL tester AS TestForm LOCAL Controller AS TabletController m.Controller = CREATEOBJECT("TabletController") m.tester = CREATEOBJECT("TestForm") * set a reference to the form in a new property of the tablet class m.Controller.OutputForm = m.tester * display the initial status m.Controller.OnOrientationChange() m.Controller.OnTabletModeChange() IF m.Controller.IsTablet m.tester.Caption = "Testing in a tablet" * if it is a tablet, let the user handle orientation change m.tester.cmdChangeDisplayOrientation.Enabled = .F. ELSE m.tester.Caption = "Testing in a desktop / laptop" ENDIF * show things as they happen m.tester.Show() READ EVENTS ON ERROR DEFINE CLASS TestForm AS Form ADD OBJECT cmdChangeDisplayOrientation AS CommandButton WITH Top = 10, Left = 10, Caption = "Change Orientation", AutoSize = .T. ADD OBJECT lblTabletMode AS Label WITH Top = 40, Left = 10, AutoSize = .T., Caption = "" ADD OBJECT lblDisplayOrientation AS Label WITH Top = 70, Left = 10, AutoSize = .T., Caption = "" * how was the display orientated when we started InitialOrientation = _Screen.DisplayOrientation * cycle throuh the different orientations (pressing the space bar will be ok) PROCEDURE cmdChangeDisplayOrientation.Click _Screen.DisplayOrientation = IIF(_Screen.DisplayOrientation = 3,0,_Screen.DisplayOrientation + 1) ENDPROC PROCEDURE Destroy * reset to the initial orientation IF This.cmdChangeDisplayOrientation.Enabled AND _Screen.DisplayOrientation != This.InitialOrientation _Screen.DisplayOrientation = This.InitialOrientation ENDIF CLEAR EVENTS ENDPROC ENDDEFINE * extend the class tablet, to react to events (just display the different states) DEFINE CLASS TabletController AS tablet OutputForm = .NULL. FUNCTION OnOrientationChange IF !ISNULL(This.OutputForm) This.OutputForm.lblDisplayOrientation.Caption = ; STREXTRACT("L:Landscape:P:Portrait", This.Orientation + ":",":",1,2) ENDIF ENDFUNC FUNCTION OnTabletModeChange IF !ISNULL(This.OutputForm) This.OutputForm.lblTabletMode.Caption = IIF(This.TabletMode,"Tablet mode","Desktop mode") ENDIF ENDFUNC ENDDEFINE PROCEDURE ShowError (ProgramName, LineNumber, Source, ErrorText, ErrorNumber) MESSAGEBOX(m.ProgramName + ":" + TRANSFORM(m.LineNumber) + CHR(13) + ; m.Source + CHR(13) + CHR(13) + ; TRANSFORM(m.ErrorNumber) + ">" + m.ErrorText) RETURN ENDPROC