Laptop oder Tablet? Erkennen und richtig Handeln!

Durch Windows 8 ist eine ganz neue Form von „mobilen“ Anwendungen auf den Markt gekommen. Die Windows 8 Apps. Ebenso kamen Systeme dazu, die sowohl Tablet als auch Laptop sind und ebenso welche, die beides sein und sich zur Laufzeit ändern können. Passend dazu kamen auch relativ zeitnah die ersten Anwendungen, die auf dieses Verhalten entsprechend reagieren und sich mit jeweils einer anderen angepassten Oberfläche zur Schau stellen.

Die hierfür notwendige API gibt es leider bei Intel nur für C++. Aber mithilfe von P/Invoke kann dies auch ganz einfach mit C# und WPF verwendet werden.

Als Erstes muss hierzu eine entsprechende WPF Anwendung erstellt werden oder vorhanden sein. Wenn dies der Fall ist, muss die „user32.dll“ aus der Win32-API entsprechend referenziert werden. Um den Zugriff hier aus dem managed Code heraus zu ermöglichen, wird eine Methode mit den „DllImport“-Attribut versehen.

[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern int GetSystemMetrics(DeviceMode deviceMode);

Nun stehen uns über diese Methode derartige Zugriffe zur Verfügung. Jedoch dürfte an dieser Stelle noch der Typ „DeviceMode“ fehlen und dem Kompiler unbekannt sein. Hierbei handelt es sich um ein typisches Enum, das wir uns noch definieren, um den Zugriff zu vereinfachen. Welcher Integer-Wert jedoch für was steht, kann der MSDN-Dokumentation auf der MSDN-Webseite entnommen werden. In unserem Fall sind die beiden Werte 0x2003 sowie 0x2004 relevant. Diese können wir nun in unserem Enum einpflegen:

public enum DeviceMode
{
	SM_CONVERTIBLESLATEMODE = 0x2003,
	SM_SYSTEMDOCKED = 0x2004
}

Nun können wir auf die externen Funktionalitäten aus der „user32.dll“ zugreifen. Dies tun wir am Besten zunächst einmal im Loaded-Event unseres Hauptfensters. Dies würde z.B. so aussehen:

void OnLoaded(object sender, RoutedEventArgs e)
{
	if (Convert.ToBoolean(GetSystemMetrics(DeviceMode.SM_CONVERTIBLESLATEMODE)))
		MessageBox.Show("Application in Laptop mode.");
	else
		MessageBox.Show("Application in Tablet mode.");
}

Natürlich müssen wir jetzt auch noch darauf reagieren, wenn sich dieser Zustand im laufenden Betrieb verändert. Dies bedeutet, das wir auf die Systemevents der Win32-API reagieren und entsprechend handeln müssen. Hierbei hilft uns die „HwndSource“-Klasse mit der wir auf die Win32-Instanz unserer WPF-Anwendung zugreifen können. Diese sollte noch der „OnLoaded“-Implementierung hinzugefügt werden.

	HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
	source.AddHook(new HwndSourceHook(WndProc));

Nun benötigen wir noch die Methode, die aufgerufen wird, sobald dieses Event ausgelöst wird. Diese könnte z. B. wie folgt aufgebaut sein:

private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
	if (msg == 26)
	{
		if (Convert.ToBoolean(GetSystemMetrics(DeviceMode.SM_CONVERTIBLESLATEMODE)))
			MessageBox.Show("Application in Laptop mode.");
		else
			MessageBox.Show("Application in Tablet mode.");

		if (Convert.ToBoolean(GetSystemMetrics(DeviceMode.SM_SYSTEMDOCKED)))
			MessageBox.Show("System is docked.");
	}

	return IntPtr.Zero;
}

Somit ergibt sich für uns folgendes Gesamtbild:

public partial class MainWindow : Window
{
	public MainWindow()
	{
		InitializeComponent();
		Loaded += OnLoaded;
	}
	  
	void OnLoaded(object sender, RoutedEventArgs e)
	{
		if (Convert.ToBoolean(GetSystemMetrics(DeviceMode.SM_CONVERTIBLESLATEMODE)))
			MessageBox.Show("Application in Laptop mode.");
		else
			MessageBox.Show("Application in Tablet mode.");
			
		HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
		source.AddHook(new HwndSourceHook(WndProc));
	}

	private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
	{
		if (msg == 26)
		{
			if (Convert.ToBoolean(GetSystemMetrics(DeviceMode.SM_CONVERTIBLESLATEMODE)))
				MessageBox.Show("Application in Laptop mode.");
			else
				MessageBox.Show("Application in Tablet mode.");

			if (Convert.ToBoolean(GetSystemMetrics(DeviceMode.SM_SYSTEMDOCKED)))
				MessageBox.Show("System is docked.");
		}

		return IntPtr.Zero;
	}

	public enum DeviceMode
	{
		SM_CONVERTIBLESLATEMODE = 0x2003,
		SM_SYSTEMDOCKED = 0x2004
	}

	[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
	public static extern int GetSystemMetrics(DeviceMode deviceMode);
}

Nützlich oder nicht? Ich denke, dass das jeder für sich entscheiden muss. Anwendungen auf möglichst viele Fälle anzupassen, halte ich persönlich jedoch für sinnvoll.