I'm writing the basics of a VFP class to work with tablets. There is already some code out there to handle a few relevant tablet features, like ink input (
https://msdn.microsoft.com/en-us/library/ms965060.aspx), and some interesting threads in this and other forums, especially covering application design.
For the moment, the Tablet class is already answering some questions: is this a tablet? is the device working in desktop or tablet mode? how is the screen oriented, in landscape or portrait? did a change in these settings occurred (for instance, did the user rotate the device, or changed to tablet mode)?
Now, as
something for your weekend, I am asking for tests and comments because there are a few things that I'm not able to try, or because maybe I'm not taking the right approach to address the different issues. I never executed some of this code, but only assumed it would work based on info I gathered online.
So, if you have a Windows 8 tablet, for example, it would be great to see if the class is holding or, on the contrary, if it fails. But you don't have to have a tablet to contribute to the testing: a Windows 10 desktop, for instance, can operate in tablet mode and have its screen rotated.
The code of the class is not very big, for the moment, so I'll post it here inline. At the bottom, there is a tester program that can be ran to perform the actual tests, so that you won't need to code anything to run the tests: just Copy + Paste the two programs somewhere, and run the second.
Any help with this will be highly appreciated and properly acknowledged.
I hope that this sparks your interest, and that it will engage a fruitful discussion.
1.
Tablet.prg
#DEFINE WM_SETTINGCHANGE 0x001A
#DEFINE WM_DISPLAYCHANGE 0x007E
#DEFINE HKEY_CURRENT_USER 0x80000001
#DEFINE RRF_RT_DWORD 0x00000018
#DEFINE SM_CONVERTIBLESLATEMODE 0x2003
#DEFINE SM_TABLETPC 86
#DEFINE SM_CXFULLSCREEN 16
#DEFINE SM_CYFULLSCREEN 17
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
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
m.WMIService = GETOBJECT("winmgmts:\\.\root\cimv2")
m.OperatingSystem = m.WMIService.InstancesOf("Win32_OperatingSystem").ItemIndex(0)
This.OSVersion = VAL(m.OperatingSystem.Version)
TRY
This.IsTablet = GetSystemMetrics(SM_TABLETPC) != 0
CATCH
This.IsTablet = .F.
ENDTRY
This.Orientation = IIF(GetSystemMetrics(SM_CXFULLSCREEN) > GetSystemMetrics(SM_CYFULLSCREEN), "L", "P")
This.IsTabletModeOn()
BINDEVENT(_VFP.HWnd, WM_SETTINGCHANGE, This, "SystemChanged", 5)
BINDEVENT(_VFP.HWnd, WM_DISPLAYCHANGE, This, "SystemChanged", 5)
ENDFUNC
FUNCTION Destroy
UNBINDEVENTS(This)
ENDFUNC
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
This.Orientation = IIF(m.Param2 % 0x10000 > INT(m.Param2 / 0x10000), "L", "P")
IF m.Previous != This.Orientation
RAISEEVENT(This, "OnOrientationChange")
ENDIF
CASE m.WindowsMessage = WM_SETTINGCHANGE
TRY
m.P2 = STREXTRACT(SYS(2600, m.Param2, 255), "", CHR(0), 1, 2)
CATCH
m.P2 = ""
ENDTRY
DO CASE
CASE m.P2 == "ConvertibleSlateMode"
IF This.OSVersion < 10
m.Previous = This.TabletMode
This.IsTabletModeOn()
IF m.Previous != This.TabletMode
RAISEEVENT(This, "OnTabletModeChange")
ENDIF
ENDIF
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
Sleep(m.Waiting)
m.Waiting = m.Waiting * 2
This.IsTabletModeOn()
ENDDO
IF m.Previous != This.TabletMode
RAISEEVENT(This, "OnTabletModeChange")
ENDIF
ENDIF
ENDCASE
ENDCASE
ENDPROC
FUNCTION IsTabletModeOn
IF This.OSVersion < 10
LOCAL SlateMode
TRY
m.SlateMode = GetSystemMetrics(SM_CONVERTIBLESLATEMODE)
CATCH
m.SlateMode = 1
ENDTRY
This.TabletMode = EMPTY(m.SlateMode)
ELSE
LOCAL BufferData AS String
LOCAL BufferLen AS Integer
LOCAL Zerro AS String
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
PROCEDURE OnTabletModeChange
ENDPROC
PROCEDURE OnOrientationChange
ENDPROC
ENDDEFINE
2.
tester.prgSET DEFAULT TO (JUSTPATH(SYS(16)))
DO Tablet.prg
ON ERROR DO ShowError WITH PROGRAM(), LINENO(), MESSAGE(1), MESSAGE(), ERROR()
LOCAL tester AS TestForm
LOCAL Controller AS TabletController
m.Controller = CREATEOBJECT("TabletController")
m.tester = CREATEOBJECT("TestForm")
m.Controller.OutputForm = m.tester
m.Controller.OnOrientationChange()
m.Controller.OnTabletModeChange()
IF m.Controller.IsTablet
m.tester.Caption = "Testing in a tablet"
m.tester.cmdChangeDisplayOrientation.Enabled = .F.
ELSE
m.tester.Caption = "Testing in a desktop / laptop"
ENDIF
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 = ""
InitialOrientation = _Screen.DisplayOrientation
PROCEDURE cmdChangeDisplayOrientation.Click
_Screen.DisplayOrientation = IIF(_Screen.DisplayOrientation = 3,0,_Screen.DisplayOrientation + 1)
ENDPROC
PROCEDURE Destroy
IF This.cmdChangeDisplayOrientation.Enabled AND _Screen.DisplayOrientation != This.InitialOrientation
_Screen.DisplayOrientation = This.InitialOrientation
ENDIF
CLEAR EVENTS
ENDPROC
ENDDEFINE
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
----------------------------------
António Tavares Lopes