前にWindowsXPでWinTab.NETを使うサンプルを書きました。
今回はそれと同じことをwpfでやってみました。 後者の「ペンタブを縦置き...」と同等のコードです。 環境は、
- Windows7 Home Premium 64bit
- Visual C# 2010 Express
- WinTab.NET 1.6.1
xamlのコードは、
<Window x:Class="WinTabTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="640"
Width="360"
ResizeMode="NoResize"
SourceInitialized="OnSourceInitialized"
Activated="OnActivated"
Deactivated="OnDeactivated">
<Canvas Name="mainCanvas">
<Ellipse Name="penCursor"
Width="16"
Height="16"
StrokeThickness="2"
Stroke="Black"/>
</Canvas>
</Window>
csのコードは、
// MainWindow.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using WinTabDotnet;
namespace WinTabTest
{
public partial class MainWindow : Window
{
private const int WINTAB_RANGE = 65536;
private WinTabMessenger _wtMessenger;
private WinTabContext _wtContext;
private int _cx;
private int _cy;
private int _tx;
private int _ty;
private int _pressure;
private System.Windows.Forms.Message _wndProcMessage;
public MainWindow()
{
InitializeComponent();
_wndProcMessage = new System.Windows.Forms.Message();
}
private void OnSourceInitialized(object sender, EventArgs e)
{
if (!WinTab.LoadWinTab())
{
MessageBox.Show("ペンタブレットが見つかりません(WinTab32.dllが見つかりません)。", "WinTabTest");
throw new WinTabException("WinTab.NETの初期化に失敗しました。");
}
_wtMessenger = new WinTabMessenger();
_wtContext = new WinTabContext();
_wtMessenger.CursorMove += OnTabletCursorMove;
_wtMessenger.NPressureChange += OnTabletNPressureChange;
HwndSource source = (HwndSource)HwndSource.FromVisual(this);
_wtContext.Open(
source.Handle,
true,
0,
0,
WINTAB_RANGE,
WINTAB_RANGE,
ContextOption.OFFMODE | ContextOption.SYSTEM,
RelativeField.None);
source.AddHook(new HwndSourceHook(WndProc));
}
private void OnTabletCursorMove(PacketEventArgs e)
{
_tx = e.pkts.pkX;
_ty = e.pkts.pkY;
_cx = TabY2ClientX(_ty);
_cy = TabX2ClientY(_tx);
UpdateTitle();
Canvas.SetLeft(penCursor, _cx - penCursor.Width / 2);
Canvas.SetTop(penCursor, _cy - penCursor.Height / 2);
}
private void OnTabletNPressureChange(PacketEventArgs e)
{
_pressure = e.pkts.pkNormalPressure;
UpdateTitle();
penCursor.Width = 16 + _pressure / 8;
penCursor.Height = 16 + _pressure / 8;
}
private void OnActivated(object sender, System.EventArgs e)
{
_wtContext.Overlap(true);
}
private void OnDeactivated(object sender, System.EventArgs e)
{
_wtContext.Overlap(false);
}
private int TabX2ClientY(int x)
{
return (int)((WINTAB_RANGE - x) * mainCanvas.ActualHeight / WINTAB_RANGE);
}
private int TabY2ClientX(int y)
{
return (int)(y * mainCanvas.ActualWidth / WINTAB_RANGE);
}
private void UpdateTitle()
{
this.Title =
"(" + _tx + ", " + _ty + ") → " +
"(" + _cx + ", " + _cy + ") " +
_pressure;
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
_wndProcMessage.HWnd = hwnd;
_wndProcMessage.Msg = msg;
_wndProcMessage.WParam = wParam;
_wndProcMessage.LParam = lParam;
_wndProcMessage.Result = IntPtr.Zero;
_wtMessenger.WndProc(ref _wndProcMessage);
handled = false;
return IntPtr.Zero;
}
}
}
変わったところは、まずはWinTabの初期化のタイミングですね。 wpfではhwndをWindowクラスのコンストラクタで取得できません。 なので、WinTabの初期化はWindow.SourceInitializedイベントで行っています。 hwndを得るには、コードの通りHwndSourceを使います。
WndProcの扱いも変わっています。 overrideではなく、HwndSource.AddHookで登録しなければなりません。 base.WndProcにあたるものは呼ばなくてもいいようです。 handled = falseを設定するのはその代わりなのかな?
WinTabMessenger.WndProcは引数にSystem.Windows.Forms.Message構造体をとります。 これは参照の追加をしなければ使用できません。 参照の追加ダイアログ → .NETタグでSystem.Windows.Formsのコンポーネントを追加しましょう。 ただし、このコンポーネントにはwpfとクラス名がかぶっているものが多いです。 Message構造体以外に使う要素もないでしょうし、usingを書かずにフルパスで構造体を指定しましょう。
このサンプルではコンストラクタでMessage構造体をnewして使いまわしています。 「イベントのたびにnewすると重いかも?」と思ってのコーディングですけど、微々たる重さなので分かりやすいコードにした方がよかったかも?
あと、カーソルをxamlのEllipseに変更しています。 筆圧で大きさが変わるようにもしてるんですが、ちょっとチラつきますね。 ちゃんとしたアプリケーションではこういう方法はダメっぽいです。
ペンタブのカーソル座標はmainCanvasの幅と高さを元に計算しています。 しかし、mainCanvasのサイズはxamlで明記していません。 こういう場合、ActualWidthとActualHeightでサイズを調べます。

