Hi everybody,
a previous discussion in the thread http://forums.microsoft.com/MSDN/ShowPost.aspx PostID=800439&SiteID=1 has lead me to a more general question/suggestion concering a hardware environment with an external Tablet device, like the Wacom Graphire4.
The question: Will it be possible to temporarily "bind" a whole TabletDevice to a certain control, e.g. an InkCanvas I.e. the full size and resolution of the tablet device is mapped to the target control's client area (instead of to the whole screen.) This is useful for CAD UserControls or e.g. when a 1:1 relationship between physical drawing size on the tablet and the display size on the screen is desired.
I know, that one can already setup a mapping to a static rectangle of the screen manually using Wacom's Pen Tablet driver settings. However, this is not of much use, because windows tend to be moved and resized... and remapping to a virtual client area (e.g. of a control enclosed in a ScrollViewer) is impossible. As far as I know, at the moment the only way to do this programmatically is with the old VC++6.0 based WinTab API, but then you would loose the WPF features for Stylus and Ink.
Therefore my suggestion: Create a .BindTo(UIElement target) method for the TabletDevice class with the following behaviour:
- The tablet's stylus input is remapped to the target's client area or to a rectangle part of it, i.e. StylusEvents are directly created on the target control's node in the logical tree after the corresponding coordinate transformation. (The target itself may be the content of a ScrollViewer, so that stylus events may also occur on currently invisible parts of the client area, but of course they can be scrolled into view automatically when input is recieved there.)
- The mouse continues to work normally (no binding/remapping).
- An application Window can only have one binding per tablet device at a time and only to an ancestor UIElement (or more general: to one in the same process.)
- If the application loses the focus, the binding ends automatically and the tablet will be mapped to the whole screen again. Of course, as soon as the window gets the focus again, the binding should be reestablished.
(I have tried my luck with a 99% transparent maximized topmost "remapper window", which was supposed to to the thing. The problem was, that I could not make MouseEvents to "tunnel" through the transparent window layer and I could not reraise the captured stylus events after the coordinate transformation successfully on the target control. I finally understood that I was working against security measures to prevent mouse spoofing. Even if it had worked, it would have been quite inefficient... so there is my suggestion.)
Thanks for your interest,
Michael.

CAD with WPF: "Capture" External TabletDevice
AmitKa
This is awesome, Michael! Thanks for sharing this in the forum!
I did receive your eMail and I will reply shortly.
Thanks, Stefan Wick
bachie
Hi Stefan,
thanks for your code example. First of all / @Stefan: Did you receive my eMail
Back to topic. To use a transparent above-all InkCanvas and a remap plugin was my first idea too, but this approach has some disadvantages:
Anyway, I have found a practical solution in the meantime. It has the following characteristics:
The most significant construction steps are:
if
(Stylus.Captured != this) Stylus.Capture(this, CaptureMode.Element);This is the important step. It will "bind" the stylus input to your InkCanvas, even if the stylus cursor leaves its bounds. However, it does not work, if the cursor goes beyond the application window. So, in order to make use of the full size and resolution of your graphics tablet, you will need to make your app window maximized.if
(Stylus.Captured == this) Stylus.Capture(this, CaptureMode.None);OnStylusInAirMove, OnStylusDown: Enable the remap plugin. (Later you could add some logic for not remapping the eraser and e.g. to set the .EditingMode.)
optional: OnLostFocus, OnStylusOutOfRange: If it is desired, you can prevent mouse inking by this.EditingMode = InkCanvasEditingMode.Select in these methods (use .Select also as the inital value for the EditingMode of the InkCanvas).
OnStylusInAirMove: Show and update the remapped stylus cursor: Use an Ellipse child element of Width=Height=2 (XAML defined). Position it, where the ink will flow after remapping. To calculate the coordinates, I am using a public StylusPointCollection calculateRemapping(StylusPointCollection stylusPoints) method in the remap plugin (it just makes the for loop of Stefan's code public). To move the ellipse (here named "remappedStylusCursor") you can use
remappedStylusCursor.RenderTransform = new TranslateTransform(p.X - remappedStylusCursor.ActualWidth / 2, p.Y - remappedStylusCursor.ActualHeight / 2);
It is also advisable to create a new implematation for the .Enabled property in the RemapPlugin, which allows you to centralize the show/hide logic for the real and the remapped cursor. (You can hide the real cursor, by _target.Cursor = Cursors.None; _target.ForceCursor = true; where _target is your InkCanvas (transported into the RemapPlugin as constructor parameter).
That's it. I hope, it helps others with the same scenario. Any questions
Michael.
B. Ritter
Hi Michael,
thank you for your suggestion - I'll add this to our list of features to consider for one of the next releases. In the current version, the focus of the stylus and Tablet features has been on integrated digitizers (e.g. Tablet PC scenarios). With integrated digitizers the input is always mapped to the whole screen.
In the current version we actually do allow re-mapping stylus input within a given UIElement. To accomplish this you can add your own System.Windows.Input.StylusPlugin object to your UIElement's StylusPlugins collection. In that plug-in you can remap the x,y data (see code sample below). As an example, you could use a transparent, custom InkCanvas in a Window that sits on top of your application. That InkCanvas could remap the stylus input to a specified target region - allowing you to draw on a small area of the app wile using the full area on your digitizer pad.
Thanks, Stefan Wick
public class RemapPlugin : StylusPlugIn
{
public RemapPlugin()
{
_matrix = new Matrix();
_matrix.Scale(0.5d, 0.5d);
_matrix.Translate(200d, 200d);
}
void OnRawStylusInput(RawStylusInput args)
{
StylusPointCollection stylusPoints = args.GetStylusPoints();
for (int x = 0; x < stylusPoints.Count; x++)
{
StylusPoint sp = stylusPoints[x];
Point pt = (Point)sp;
pt *= _matrix;
sp.X = pt.X;
sp.Y = pt.Y;
stylusPoints[x] = sp;
}
args.SetStylusPoints(stylusPoints);
}
protected override void OnStylusDown(RawStylusInput rawStylusInput)
{
OnRawStylusInput(rawStylusInput);
base.OnStylusDown(rawStylusInput);
}
protected override void OnStylusMove(RawStylusInput rawStylusInput)
{
OnRawStylusInput(rawStylusInput);
base.OnStylusMove(rawStylusInput);
}
protected override void OnStylusUp(RawStylusInput rawStylusInput)
{
OnRawStylusInput(rawStylusInput);
base.OnStylusUp(rawStylusInput);
}
private Matrix _matrix;
}
class RemapInkCanvas : InkCanvas
{
public RemapInkCanvas()
{
this.StylusPlugIns.Insert(0, new RemapPlugin());
}
}