How can I detect whether my PC is in tablet mode?


Tablet Mode, introduced in Windows 10, is a blah blah blah blah.

Okay, enough with the introduction.

From a Store app, you detect whether you are in tablet mode by inspecting the User­Interaction­Mode for your view. Sample code for this is given in the UserInteractionMode sample, but the short version is that you do this:

UIViewSettings^ uiViewSettings = UIViewSettings::GetForCurrentView();
UserInteractionMode mode = uiViewSettings->UserInteractionMode;
switch (mode)
{
case UserInteractionMode::Touch:
  // PC is in tablet mode or other touch-first environment
  break;

case UserInteractionMode::Mouse:
  // PC is not in tablet mode or other mouse-first environment
  break;
}

The user interaction mode is a per-view property because the system may have multiple monitors, some of which are in tablet mode and some of which aren't. An app can detect when the user interaction mode of a view has changed by listening for the Size­Changed event.

This is a general convention for view properties: View properties that affect layout generally raise the Size­Changed event when they change. The idea behind this is that this gives you a single event to trigger the recalculation of your app's layout policy. If multiple things change at once, such as the window size, the user interaction mode, and the full-screen mode, then you run only one layout recalculation instead of three.

Okay, that's great for a Store app, but what about your classic desktop app? How does a classic desktop app learn whether a window is on a monitor that is in tablet mode?

You basically do exactly the same thing as the Store app: You get the UIView­Settings and ask it for the user interaction mode. The gotcha is that Get­For­Current­View doesn't make sense in a desktop app because desktop apps don't have have a Core­Application­View.

The answer is to use the interop interface IUIView­Settings­Interop. The general design pattern for the interop interface is that it each view-related static method has a corresponding method on the interop interface that takes a window handle instead of a view.

We'll see these as we go through the Little Program. (Remember, Little Programs do little to no error checking.) Start with the scratch program and make these changes:

#include <wrl/client.h>
#include <wrl/wrappers/corewrappers.h>
#include <windows.ui.viewmanagement.h>
#include <UIViewSettingsInterop.h>
#include <tchar.h> // Huh? Why are you still using ANSI?

namespace WRL = Microsoft::WRL;
namespace vm = ABI::Windows::UI::ViewManagement;

WRL::ComPtr<vm::IUIViewSettings> g_viewSettings;

vm::UserInteractionMode g_mode = vm::UserInteractionMode_Mouse;

So far, we're just declaring some global thingies. In a real program, these would probably be instance members of some C++ class, but I'm being lazy.

void CheckTabletMode(HWND hwnd)
{
  if (g_viewSettings)
  {
    vm::UserInteractionMode currentMode;
    g_viewSettings->get_UserInteractionMode(&currentMode);
    if (g_mode != currentMode)
    {
      g_mode = currentMode;
      // This sample just updates some text.
      InvalidateRect(hwnd, nullptr, true);
    }
  }
}

Okay, here's the part where we read the current user interaction mode from the IUIView­Settings, and if it changed, we do whatever we do when the user interact mode changes. In a real problem, we would do some relayout, but in this sample program, we're just going to update a string, so we invalidate our window so we can draw the new string.

BOOL
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
  WRL::ComPtr<IUIViewSettingsInterop> interop;
  Windows::Foundation::GetActivationFactory(WRL::Wrappers::HStringReference(
     RuntimeClass_Windows_UI_ViewManagement_UIViewSettings).Get(),
     &interop);

  interop->GetForWindow(hwnd, IID_PPV_ARGS(&g_viewSettings));

  CheckTabletMode(hwnd);
  return TRUE;
}

Okay, now things get exciting. To get the UIView­Settings for a window, you first get the activation factory (which is where all the static methods hang out) and ask for the IUIView­Settings­Interop interface. From there, call Get­For­Window, which is the window-based version of Get­For­Current­View.

That's the only wrinkle. Once you have the CODE>UIView­Settings, you can get its user interaction mode as usual.

void
OnDestroy(HWND hwnd)
{
  g_viewSettings.Reset();
  PostQuitMessage(0);
}

Naturally, we need to clean up when we're done.

void
PaintContent(HWND hwnd, PAINTSTRUCT *pps)
{
  PCTSTR message = TEXT("?");

  // adapt to the new mode! We just update our string.
  switch (g_mode)
  {
  case vm::UserInteractionMode_Mouse: message = TEXT("Mouse"); break;
  case vm::UserInteractionMode_Touch: message = TEXT("Touch"); break;
  }

  TextOut(pps->hdc, 0, 0, message, _tcslen(message));
}

Our Paint­Content function prints the current mode.

Wait, what about the Size­Changed event? Oh, right, for classic Win32, you can just use the WM_WINDOW­POS­CHANGED message, which will give us a chance to see if we moved to a monitor that is in a different tablet mode state from where we were before.

void
OnWindowPosChanged(HWND hwnd, LPWINDOWPOS lpwpos)
{
  CheckTabletMode(hwnd);
}

The last wrinkle is the case where the global tablet mode state changes.

void OnSettingsChange(HWND hwnd, LPCTSTR sectionName)
{
  if (sectionName &&
      lstrcmpi(sectionName, TEXT("UserInteractionMode")) == 0)
  {
    CheckTabletMode(hwnd);
  }
}

When the global tablet mode state changes, the shell broadcasts the "User­Interaction­Mode" setting change notification.

HANDLE_MSG(hwnd, WM_WINDOWPOSCHANGED, OnWindowPosChanged);
HANDLE_MSG(hwnd, WM_WININICHANGE, OnSettingsChange);

And finally, we hook up our message handlers.

There you go, a program that knows whether it is in tablet mode.

Comments (14)
  1. Yuri Khan says:

    I notice that you stopped announcing dice rolls on your table of COM smart pointers, but, evidently, the roll is still there.

    1. skSdnW says:

      No? He has used Microsoft::WRL::ComPtr in all WinRT examples IIRC.

    2. I know of only one COM smart pointer library which supports HSTRING, so there’s not much point rolling a one-sided die.

      1. EMB says:

        That’s the problem with the true random genera…. oohhhh…

  2. Nico says:

    And here I thought it would be:

    bool IsWindows10InTabletMode() {
    return true;
    }

    I’m kind of surprised there’s not a window message broadcast when the system changes between tablet and non-tablet modes (or maybe there is?)

    1. Nico says:

      Aw, my “snark” tags got eaten. This comment was very tongue-in-cheek.

      1. Nick says:

        Shouldn’t that be boolean to be compatible with the WRL ABI?

  3. Tihiy says:

    Nobody will use such stinky API when you can just read registry and listen for broadcast.

    1. Ray Koopa says:

      Reading registry? You mean like the sad story of the shell folders key?

    2. skSdnW says:

      How does a simple registry query work on something that is tied to a HWND? It might have a per-monitor “resolution” now but nothing is stopping MS from adding options to always force a specific mode for a particular window in the future…

      1. Dave Bacher says:

        On the Lumia 950 / 950XL, you can have apps displaying on the phone and on an attached wireless monitor with keyboard/mouse simultaneously. Presumably, the API would do the right thing for that case, I mean why else would you design it that way?

  4. Everytime I’ve used extended desktop multimonitor on my Surface Pro 2, tablet mode is disabled. Guessing this api is forward looking?

    1. Oliver says:

      Yep, that’s what I thought too. Or maybe it was a feature they dropped, but got left in the docs.

    2. Yup. Forward-looking.

Comments are closed.

Skip to main content