Friday, June 13, 2014

Detecting 2 in 1 state in WPF C# (Slate or Clamshell mode)

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 ("ConvertibleSlateMode"), (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

2in1

Happy Coding!