Detecting a 2in1 convertable's state using C#

Detecting a 2in1 convertable's state using C#

Ritratto di Adrian Stevens

I thought I'd add this here since Intel already has great docs up on how to do this using C++.  I wanted to be able to detect the state change in a WPF application.  Fortunately it's not overly difficult but just technical enough that it might not be obvious.

Below is a copy/paste from my blog: http://www.themethodology.net/2014/05/detecting-2-in-1-state-using-wpf-c... 

 

I’ve been wanting to experiment with changing UIs in my code between “tablet” mode and the “laptop” mode on my new Dell XPS 12 (or any other Ultrabook 2 in 1 that happened across my desk).  There’s already a really great article on how us developers can detect the state change on Intel’s developer portal; but the example code is in C++ and not my preferred C#.

Before we go any further, I recommend heading over to software.intel.com and reading Detecting Slate/Clamshell Mode & Screen Orientation in a 2 in 1.

The code we’re looking to implement is basically this (done in a WndProc):

   1: case WM_SETTINGCHANGE:
   2:     if (wcscmp ( TEXT ("ConvertableSlateMode"), (TCHAR*)lParam ) == 0)
   3:         NotifySlateModeChange ();
   4:    else if wcscmp ( TEXT ("SystemDockMode"), (TCHAR*)lParam ) == 0)
   5:         NotifyDockingModeChange ();
Simply stated, we need to monitor WM_SETTINGCHANGE or equivalent, detect which state we’re in, and respond accordingly.
Now that we know what our goal is, let’s make it work in a modern WPF C# application.  I looked a few approaches before settling on my solution, and hopefully it will make sense why I’ve done it this way as we work through the code.
Our first step is to import user32.dll so we can define and use the GetSystemMetrics method.

 

 

   1: public partial class MainWindow : Window
   2: {
   3:     [DllImport("user32.dll")]
   4:     static extern int GetSystemMetrics(SystemMetric smIndex);
   5:     ...

 

 

 

To do that we’ll need to reference the InteropServices:

using System.Runtime.InteropServices;
At this point you may have noticed that we don’t have the SystemMetric enum defined.  We could create our own with just the two metrics we need, but we might as well jump over to www.pinvoke.net and get a fairly complete enum, and just copy that into our window class or somewhere else convenient (this might just be useful for other purposes as well):
http://www.pinvoke.net/default.aspx/Enums/SystemMetric.html 
Unfortunately this list doesn’t contain the two metrics we want to check against as we saw in the Intel doc, namely
SM_CONVERTABLESLATEMODE and SM_SYSTEMDOCKED
so we’ll add these to the end of the enum ourselves:

 

 

   1: public enum SystemMetric
   2: {   
   3:    ...    
   4:    SM_CONVERTABLESLATEMODE = 0x2003,    
   5:    SM_SYSTEMDOCKED = 0x2004
   6: }

 

(* after writing this post I decided I should update the pinvoke.net page so the enum should now be up to date)
We’re almost ready to watch for the 2 in 1 state change.  In C++ we would just capture the WM_SETTINGSCHANGED message.
We could do this in C# by overriding OnSourceInitialized() and hooking into the WndProc.  Truth be told this was my first approach.  But on my machine I wasn’t receiving “SystemDockMode” from the lParam – it would always give me “ConvertableSlateMode” regardless of the state.  
So I decided to do it the “C# way” and just watch UserPreferenceChanging.  To try it out just add 

 

 

   1: SystemEvents.UserPreferenceChanging += SystemEvents_UserPreferenceChanging; 
to the constructor and create a method with the correct signature.  You’ll need to include “using Microsoft.Win32;” for SystemEvents.
We’ll get a UserPreferenceChanging event every time the device changes state, and it will come in under the General Category of UserPreferenceCategory. This is passed in with the UserPreferenceChagingEventArgs.  Unfortunately we don’t get any additional information, and we don’t have access to the lParam as we would if we were handling the message directly.
And that’s where the GetSystemMetrics() method comes in.  We’ll simply query for the state every time we change comes in under the General category.  Uou’ll likely want to add some logic to remember your current state and only update your UI if it changes.
See the code below: 

 

 

   1: void SystemEvents_UserPreferenceChanging(object sender, UserPreferenceChangingEventArgs e)
   2: {    
   3:     if(e.Category == UserPreferenceCategory.General)    
   4:     {        
   5:         if (GetSystemMetrics(SystemMetric.SM_CONVERTABLESLATEMODE) == 0)        
   6:         {            
   7:             Debug.WriteLine("detected slate mode");        
   8:         }        
   9:         else if (GetSystemMetrics(SystemMetric.SM_SYSTEMDOCKED) == 0)        
  10:         {            
  11:             Debug.WriteLine("detected docked mode");        
  12:         }    
  13:     }
  14: }

And that should give you everything you need to get started making 2 in 1 C# apps in WPF. 

 

Finally, there’s a sample project with all of the above code on github here:

https://github.com/adrianstevens/WPF-CSharp-2in1-mode-detection

 

http://www.themethodology.net http://www.vancouvermobile.net
6 post / 0 nuovi
Ultimo contenuto
Per informazioni complete sulle ottimizzazioni del compilatore, consultare l'Avviso sull'ottimizzazione
Ritratto di chang-li

I spend several hours to figure out a typo bug of this article, to benefit others I list it. At first I do not know what happened because of no action of slate mode switch.

 1: case WM_SETTINGCHANGE:
   2:     if (wcscmp ( TEXT ("ConvertableSlateMode"), (TCHAR*)lParam ) == 0)
   3:         NotifySlateModeChange ();

 

"ConvertableSlateMode" should be "ConvertibleSlateMode"
Ritratto di Adrian Stevens

My apologies - thank you Chang-li

I'm not able to edit the post above but it's now fixed on my blog.  Fortunately it doesn't effect the c# code but it does if you're implementing it in c++ ;)

http://www.themethodology.net http://www.vancouvermobile.net
Ritratto di chang-li

Adrian you are welcome. I just copy & past your code clip for my C++ program unfortunately :-)

Do you find there are two NotifySlateModeChange (); in your samples?

I met a weird problem below:

  1. Slate / clamshell mode indicator device driver (Compatible ID PNP0C60.)

In my DELL XPS12,  there are two drivers with ID PNP0C60. I guess there are two mode indicators.  

And I found there are two WM_SETTINGCHANGE messages with “ConvertibleSlateMode”  mode generated. 

Is this an error or in expected?

Ritratto di Adrian Stevens

Interesting - I just checked my system (XPS 12 as well)

Device Manager -> Human Interface Devices -> GPIO Button Driver -> Properties -> Details Tab -> Compatible Ids

Mine shows:

ACPI\PNP0C40

PNP0C40

So I'm seeing PNP0C40 not PNP0C60 as the Intel doc states but mine is working correctly - and when I was trapping WM_SETTINGSCHANGE I'm fairly certain I only received one message.

I suppose as long as your lParam is consistent you can always check the state and only update when it changes.

I've currently only tried it in C# but I can put together a quick C++ project if you want to me to test anything on my system.

http://www.themethodology.net http://www.vancouvermobile.net
Ritratto di Adrian Stevens

I just realized the Intel doc is a little off - of course the driver to check is "GPIO Laptop or Slate Indicator Driver" and yes I am seeing two listed under "Human Interface Devices" in the Device Manager.  Both with the correct ID - PNP0C60. 

I am however only getting one event per state change.

 

 

http://www.themethodology.net http://www.vancouvermobile.net

Accedere per lasciare un commento.