WintabをWPFで利用する方法①
2014年01月23日
今回から、Wintabの利用方法を紹介します。
Windowsはマウスを想定したインターフェースを持っていますが、Intuosシリーズなどのタブレット独自の筆圧や傾きの情報をアプリケーションに渡す機能を持っていません。
Wintabとは、上記のようなタブレット独自の機能をサポートできるように作られたライブラリです。
WPFでもInkCanvasが筆圧に対応していますが、情報を取得することはできませんので、独自に筆圧や傾きの情報を処理するにはWintabが必要となります。
WintabというとC++で実装する必要があり、敷居が高いというイメージがあるかと思いますが、C#からも利用することができます。
WPFからも利用することが可能ですので、WPFアプリケーションでWintabを利用する方法を紹介していきます。
まずは、環境の構築とタブレットの情報を取得する処理を実装します。
1.Wintabのインストール
Wintabは、ワコムのタブレットドライバをインストールすると、Wintabも一緒にインストールされます。
以下のサイトからドライバをダウンロードし、インストールしてください。
http://tablet.wacom.co.jp/download/
2.プロジェクトの作成
「WPFアプリケーション」でプロジェクトを作成します。
3.Wintabの関数定義
Wintabの関数定義用に「WintabFunctions」というクラスファイルを作成し、以下のようにWTInfoAを定義します。
WTInfoAは接続されているタブレットの情報などを取得するために利用する関数です。
/// <summary> /// Wintabの関数を定義するクラス /// </summary> class WintabFunctions { // タブレット情報の取得 [DllImport("Wintab32.dll", CharSet = CharSet.Auto)] public static extern UInt32 WTInfoA(UInt32 wCategory, UInt32 nIndex, IntPtr lpOutput); }
上記のように、C#からWintabの関数を利用するには、DllImportを利用します。
この際に、ポインタはIntPtrに変更し、構造体については独自に定義する必要があります。
4.定数・構造体の定義
Wintabで利用する定数や構造体を定義します。
今回は、必要な分のみを「WintabStructs」というクラスファイルに定義します。
定数は必須というわけではありませんが、分かりやすいよう定義しています。
/// <summary> /// WTInfoで取得するデータの種別 /// </summary> public enum EWTICategoryIndex { WTI_INTERFACE = 1, WTI_STATUS = 2, WTI_DEFCONTEXT = 3, WTI_DEFSYSCTX = 4, WTI_DEVICES = 100, WTI_CURSORS = 200, WTI_EXTENSIONS = 300, WTI_DDCTXS = 400, WTI_DSCTXS = 500 } /// <summary> /// WTI_DEVICESのインデックス /// </summary> public enum EWTIDevicesIndex { DVC_NAME = 1, DVC_HARDWARE = 2, DVC_NCSRTYPES = 3, DVC_FIRSTCSR = 4, DVC_PKTRATE = 5, DVC_PKTDATA = 6, DVC_PKTMODE = 7, DVC_CSRDATA = 8, DVC_XMARGIN = 9, DVC_YMARGIN = 10, DVC_ZMARGIN = 11, DVC_X = 12, DVC_Y = 13, DVC_Z = 14, DVC_NPRESSURE = 15, DVC_TPRESSURE = 16, DVC_ORIENTATION = 17, DVC_ROTATION = 18, DVC_PNPID = 19 } /// <summary> /// AXISデータ構造体 /// </summary> [StructLayout(LayoutKind.Sequential)] public struct WintabAxis { public Int32 axMin; public Int32 axMax; public UInt32 axUnits; public UInt32 axResolution; }
各列挙型・構造体の概要は以下の通りです。
・EWTICategoryIndex
WTInfoAで取得する情報のカテゴリ
・EWTIDevicesIndex
WTI_DEVICESを指定した場合の情報インデックス
・WintabAxis
情報の範囲などを定義する構造体です。
Wintabの関数と直接やり取りを行う構造体は、StructLayoutに「LayoutKind.Sequential」を指定する必要があります。
5.Wintabの状態取得
ここからは「WintabManager」というクラスファイルを作成し、処理を追加しています。
タブレットの情報を取得する前に、Wintabが動作しているかどうかの状態を取得するメソッドを実装します。
動作状態の取得もWTInfoAを利用することで取得することができます。
// Wintabの動作状態を取得 public static bool IsWintabAvailable() { try { uint result = WintabFunctions.WTInfoA(0, 0, IntPtr.Zero); if (result > 0) { // Wintabが動作している return true; } // Wintabが動作していない return false; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("WintabManager IsWintabAvailable : " + ex.Message); return false; } }
6.タブレット名の取得
タブレットの名前を取得する処理を実装します。
タブレットのモデル名などは取得できませんが、ワコム製のタブレットの場合は「WACOM Tablet」という名前が設定されています。
// 端末名の取得 public static String GetDeviceName() { string devName = null; IntPtr buf = WintabMemoryUtil.AllocUnmanagedBuffer(MAX_STRING_SIZE); try { uint sz = WintabFunctions.WTInfoA( (uint)EWTICategoryIndex.WTI_DEVICES, (uint)EWTIDevicesIndex.DVC_NAME, buf); if (sz == 0) { throw new Exception("GetDeviceName : Name is empty"); } devName = WintabMemoryUtil.MarshalUnmanagedString(buf, (int)sz - 1); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("GetDeviceName : " + ex.Message); throw ex; } finally { // メモリの開放 WintabMemoryUtil.FreeUnmanagedBuffer(buf); } return devName; }
この処理からWintabMemoryUtilというクラスを利用しています。
WTInfoAは、最後の引数の型が取得するデータによって変わるので、LPVOID型で指定されているため、C#ではIntPtr型になっています。
IntPtrに種別ごとのメモリを確保するために、WintabMemoryUtilではMarshalクラスを利用し、アンマネージドメモリの確保・破棄を行っています。
アンマネージドなので、処理が終わったら破棄を忘れずに行いましょう。
WintabMemoryUtilについては、記事内では触れませんが、サンプルソースに含まれていますので、興味のある方はソースを見てください。
7.筆圧・傾きの範囲取得
タブレット名以外にも、タブレットが筆圧や傾きに対応している場合、値の範囲を取得することができます。
// 筆圧の最大最小値取得 public static WintabAxis GetDeviceNPressure() { WintabAxis axis = new WintabAxis(); IntPtr pAxis = WintabMemoryUtil.AllocUnmanagedBuffer(axis); try { uint sz = WintabFunctions.WTInfoA( (uint)EWTICategoryIndex.WTI_DEVICES, (uint)EWTIDevicesIndex.DVC_NPRESSURE, pAxis); if (sz == 0) { throw new Exception("GetDeviceName : Pressure is empty"); } axis = WintabMemoryUtil.MarshalUnmanagedBuffer<WintabAxis>(pAxis, sz); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("GetNPressure : " + ex.Message); throw ex; } finally { // メモリの開放 WintabMemoryUtil.FreeUnmanagedBuffer(pAxis); } return axis; } // Azimuth,Atitude,Twistの範囲取得 public static WintabAxis[] GetDeviceOrientation() { WintabAxis[] axis = new WintabAxis[3]; IntPtr pAxis = WintabMemoryUtil.AllocUnmanagedBuffer(typeof(WintabAxis), 3); try { uint sz = WintabFunctions.WTInfoA( (uint)EWTICategoryIndex.WTI_DEVICES, (uint)EWTIDevicesIndex.DVC_ORIENTATION, pAxis); if (sz == 0) { throw new Exception("GetDeviceName : Orientation is empty"); } axis = WintabMemoryUtil.MarshalUnmanagedBuffer<WintabAxis>(pAxis, sz, 3); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("GetNPressure : " + ex.Message); throw ex; } finally { // メモリの開放 WintabMemoryUtil.FreeUnmanagedBuffer(pAxis); } return axis; }
8.表示処理の実装
WPFアプリケーション側に、取得した情報を表示する処理を実装します。
画面上には、TextBlockを設置します。
<Window x:Class="WpfWintabSample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <TextBlock x:Name="screenText"/> </Grid> </Window>
コード側では、Loadedイベント内でWintabが動作していれば、情報を取得して表示を行っています。
void MainWindow_Loaded(object sender, System.Windows.RoutedEventArgs e) { // Wintabの動作状態チェック if (WintabManager.IsWintabAvailable() == false) { MessageBox.Show("Wintabが動作していません"); return; } // 端末の名前取得 String name = ""; try { name = WintabManager.GetDeviceName(); } catch { } // 筆圧の最大・最小値取得 WintabAxis nPressure = new WintabAxis(); try { nPressure = WintabManager.GetDeviceNPressure(); } catch { } // Azimuth、Atitude、Twistの最大・最小値取得 WintabAxis[] orientations = null; try { orientations = WintabManager.GetDeviceOrientation(); } catch { } if (name == null) { screenText.Text = "端末が存在しません"; } else { screenText.Text = "Name : " + name + "\n"; screenText.Text += "Pressure : Max(" + nPressure.axMax + ") Min(" + nPressure.axMin + ")\n"; if (orientations != null) { screenText.Text += "Azumith : Max(" + orientations[0].axMax + ") Min(" + orientations[0].axMin + ")\n"; screenText.Text += "Atitude : Max(" + orientations[1].axMax + ") Min(" + orientations[1].axMin + ")\n"; screenText.Text += "Twist : Max(" + orientations[2].axMax + ") Min(" + orientations[2].axMin + ")\n"; } } }
以上で実装が完了しましたので、タブレットを接続し、アプリケーションを実行してみましょう。
タブレットの情報が、以下のように画面上に表示されるかと思います。
サンプルソースはこちらにアップしてありますので、興味のある方は実際に試して頂ければと思います。
次回は、ペンの位置や筆圧を取得する処理について紹介します。