またまた Windows デスクトップ (WPF) のお話です。
先日の めとべや東京勉強会 にて、デスクトップ アプリのタッチ エクスペリエンス的な話を少しだけさせて頂きましたが、タッチ デバイスでの操作のためにタッチ向けの UI を用意するのが理想です (Office のタッチ モードのようなやつ)。
そこで、実行中のデバイスがタッチ デバイスかどうかを調べたい場合があります。タッチ デバイスであれば、それ専用の UI に切り替える、といったことができたり。
デスクトップ アプリの場合は user32.dll の GetSystemMetrics 関数 で、引数に SM_DIGITIZER を指定すると取得できます。この関数の .NET ラッパーである SystemParameters クラス から取ってこられないものかと期待したのですが、現時点では SM_DIGITIZER にマップされるプロパティは提供されていないようです。
ちなみにストア アプリの場合は、TouchCapabilities クラス で 楽に取得 できるようなのですが、生憎デスクトップからは使用できない API ですのでこの方法も使えず。
つまり… P/Invoke です!
これ以外の取得方法をご存じの方いらっしゃいましたら教えて頂きたく。。。
というわけで、GetSystemMetrics 関数を C# から使用するためのコードを以下のように記述します。
SM_* 定数は他にもたくさんありますが、今回はこの 2 つしか使用しません。
static class NativeMethods { [DllImport("user32.dll")] public static extern int GetSystemMetrics(int nIndex); } enum SystemMetric { SM_DIGITIZER = 94, SM_MAXIMUMTOUCHES = 95, }
MSDN より抜粋。
入力デジタイザーの機能を照会するには、GetSystemMetrics 関数を使用して、SM_DIGITIZER の nIndex 値を渡します。GetSystemMetrics は、デバイスの準備ができているかどうか、デバイスがペンまたはタッチをサポートしているかどうか、入力デバイスが統合デバイスと外付けデバイスのどちらであるか、およびデバイスが複数入力 (Windows タッチ) をサポートしているかどうかを示すビット フィールドを返します。
関数の戻り値としてデバイスのデジタイザー対応状況を表すビットフィールドを得られます。そこで、次のような列挙型を定義しました。
////// デバイスのデジタイザー対応状況を示す識別子を定義します。 /// public enum DigitizerType { ////// 入力デジタイザーにタッチ機能がありません。 /// NotSupported = 0x00000000, ////// 統合型のタッチ デジタイザーが入力に使用されています。 /// IntegratedTouch = 0x00000001, ////// 外付けのタッチ デジタイザーが入力に使用されています。 /// ExternalTouch = 0x00000002, ////// 統合型のペン デジタイザーが入力に使用されています。 /// IntegratedPen = 0x00000004, ////// 外付けのペン デジタイザーが入力に使用されています。 /// ExternalPen = 0x00000008, ////// 複数入力がサポートされた入力デジタイザーが入力に使用されています。 /// MultiInput = 0x00000040, ////// 入力デジタイザーで入力の準備ができています。この値が設定されていない場合、タブレット サービスが停止されているか、デジタイザーがサポートされていないか、デジタイザー ドライバーがインストールされていない可能性があります。 /// Ready = 0x00000080, }
これらを利用し、以下のような Digitizer クラスを用意します。
GetDigitizer メソッドで、Digitizer クラスのインスタンスを生成すると共にデジタイザー対応状況を照会する感じ。nIndex に SM_DIGITIZER を指定してデジタイザーの対応状況を取得し、対応していれば更に SM_MAXIMUMTOUCHES を指定してタッチの点数を取得しています。
/// <summary> /// 入力デバイスのデジタイザー機能のサポート状況を表します。 /// </summary> public class Digitizer { /// <summary> /// 入力デバイスがデジタイザーをサポートしているかどうかを示す値を取得します。 /// </summary> public bool Supported { get; private set; } /// <summary> /// 入力デバイスが統合デバイスと外付けデバイスのどちらであるか、およびデバイスが複数入力をサポートしているかどうかを示すビット フィールドを取得します。 /// </summary> public DigitizerType Type { get; private set; } /// <summary> /// 入力デバイスのタッチの最大数を取得します。 /// </summary> public int MaxTouches { get; private set; } private Digitizer() { } /// <summary> /// 入力デバイスのデジタイザー機能を照会し、<see cref="Digitizer"> オブジェクトを返します。 /// </see></summary> public static Digitizer GetDigitizer() { var result = (DigitizerType)NativeMethods.GetSystemMetrics((int)SystemMetric.SM_DIGITIZER); var digitizer = new Digitizer { Supported = result != DigitizerType.NotSupported, Type = result, }; if (digitizer.Supported) { var max = NativeMethods.GetSystemMetrics((int)SystemMetric.SM_MAXIMUMTOUCHES); digitizer.MaxTouches = max; } return digitizer; } }
こんな感じで使えます。べんり! (熱い自画自賛)
var digitizer = Digitizer.GetDigitizer(); if (digitizer.Supported) { // タッチ デバイス向けの処理 }
なお、GetSystemMetrics 関数で SM_DIGITIZER と SM_MAXIMUMTOUCHES がサポートされているのは、Windows 7 または Windows Server 2008 R2 以降となります。それ以前の古の OS でこの API を叩いても 0 (NotSupported) しか返ってきませんのでご注意ください。当然 Windows 8 ではちゃんと動きました。
上記コードをコピペすればすぐにチェックできるようになると思いますが、WPF で サンプル アプリ も作りました (GitHub で公開済み)。
以下は、タッチ デバイスのないデスクトップ PC で実行したときのもの。
そして、以下がタッチ デバイス (Acer ICONIA W7) で実行したときのもの。
10 点タッチの情報がしっかり取得できています。