Level Extreme platform
Subscription
Corporate profile
Products & Services
Support
Legal
Français
Tablet class (Request For Tests)
Message
From
04/02/2017 11:29:57
 
 
To
All
General information
Forum:
Visual FoxPro
Category:
Tablet PC
Title:
Tablet class (Request For Tests)
Environment versions
Visual FoxPro:
VFP 9 SP2
Miscellaneous
Thread ID:
01647482
Message ID:
01647482
Views:
123
Likes (1)
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
*
* 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

ENDDEFINE
2. 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
----------------------------------
António Tavares Lopes
Next
Reply
Map
View

Click here to load this message in the networking platform