Now that I have posted the PressureLevels utility I thought that I should give some insight into how it works - in case any real developers want to look into something similar. :)
While it is trivial to collect ink on a tablet using the controls provided by the Tablet SDK if you want more direct, low level access to stylus information then you need to use the Real Time Stylus API. This is a pretty bare bones API that provides a stream of packets as the stylus moves around. As a developer you can create a plug in that basically filters this information to just what you are interested and executes code every time on of those "events" fires.
I found this CoDe Magazine article by Marcus Egger to be very well written and extremely useful.
I have a class that contains this plug in that looks roughly like this:
Imports Microsoft.StylusInput
Public Class SimpleStylus
Implements IStylusSyncPlugin
Dim iLastPressure As Integer = 0
Public ReadOnly Property DataInterest() _
As DataInterestMask _
Implements IStylusSyncPlugin.DataInterest
Get
Return DataInterestMask.Packets _
Or DataInterestMask.StylusDown _
Or DataInterestMask.StylusUp
End Get
End Property
Private attachedControl As Form
Public Sub New(ByVal form As Form)
Me.attachedControl = form
End Sub
Public Sub Packets(ByVal s As RealTimeStylus, _
ByVal data As PluginData.PacketsData) _
Implements IStylusSyncPlugin.Packets
Dim g As Graphics = _
Me.attachedControl.CreateGraphics()
Dim packetCounter As Integer
For packetCounter = 0 To _
data.Count - data.PacketPropertyCount _
Step data.PacketPropertyCount
Dim iX As Integer
Dim iY As Integer
Dim iPressure As Integer = 10
iX = g.DpiX * data(packetCounter) / 2540
iY = g.DpiY * data(packetCounter + 1) / 2540
If data.PacketPropertyCount > 2 Then
iPressure = data(packetCounter + 2)
End If
Console.WriteLine("P: " & iPressure & ", LP: " & iLastPressure)
If iPressure < iLastPressure Then
g.Clear(Form.DefaultBackColor)
End If
g.FillEllipse(Brushes.Black, _
iX - iPressure, iY - iPressure, _
iPressure * 2, iPressure * 2)
iLastPressure = iPressure
Next
End Sub
Public Sub StylusDown(ByVal sender As RealTimeStylus, _
ByVal data As PluginData.StylusDownData) _
Implements IStylusSyncPlugin.StylusDown
Console.WriteLine("Stylus Down")
End Sub
Public Sub StylusUp(ByVal sender As RealTimeStylus, _
ByVal data As PluginData.StylusUpData) _
Implements IStylusSyncPlugin.StylusUp
Console.WriteLine("Stylus Up")
Dim g As Graphics = _
Me.attachedControl.CreateGraphics()
g.Clear(Form.DefaultBackColor)
End Sub
Public Sub CustomStylusDataAdded( _
ByVal sender As RealTimeStylus, _
ByVal data As PluginData.CustomStylusData) _
Implements IStylusSyncPlugin.CustomStylusDataAdded
End Sub
Public Sub [Error](ByVal sender As RealTimeStylus, _
ByVal data As PluginData.ErrorData) _
Implements IStylusSyncPlugin.Error
End Sub
Public Sub InAirPackets(ByVal sender As RealTimeStylus, _
ByVal data As PluginData.InAirPacketsData) _
Implements IStylusSyncPlugin.InAirPackets
End Sub
Public Sub RealTimeStylusDisabled( _
ByVal sender As RealTimeStylus, _
ByVal data As PluginData.RealTimeStylusDisabledData) _
Implements IStylusSyncPlugin.RealTimeStylusDisabled
End Sub
Public Sub RealTimeStylusEnabled( _
ByVal sender As RealTimeStylus, _
ByVal data As PluginData.RealTimeStylusEnabledData) _
Implements IStylusSyncPlugin.RealTimeStylusEnabled
End Sub
Public Sub StylusButtonDown( _
ByVal sender As RealTimeStylus, _
ByVal data As PluginData.StylusButtonDownData) _
Implements IStylusSyncPlugin.StylusButtonDown
End Sub
Public Sub StylusButtonUp(ByVal sender As RealTimeStylus, _
ByVal data As PluginData.StylusButtonUpData) _
Implements IStylusSyncPlugin.StylusButtonUp
End Sub
Public Sub StylusInRange(ByVal sender As RealTimeStylus, _
ByVal data As PluginData.StylusInRangeData) _
Implements IStylusSyncPlugin.StylusInRange
End Sub
Public Sub StylusOutOfRange(ByVal sender As RealTimeStylus, _
ByVal data As PluginData.StylusOutOfRangeData) _
Implements IStylusSyncPlugin.StylusOutOfRange
End Sub
Public Sub SystemGesture(ByVal sender As RealTimeStylus, _
ByVal data As PluginData.SystemGestureData) _
Implements IStylusSyncPlugin.SystemGesture
End Sub
Public Sub TabletAdded(ByVal sender As RealTimeStylus, _
ByVal data As PluginData.TabletAddedData) _
Implements IStylusSyncPlugin.TabletAdded
End Sub
Public Sub TabletRemoved(ByVal sender As RealTimeStylus, _
ByVal data As PluginData.TabletRemovedData) _
Implements IStylusSyncPlugin.TabletRemoved
End Sub
End Class
In the data interest mask I state that I am interested in StylusDown, StylusUp and Packets events. This means that every time the stylus comes into contact with the screen or is lifted from the screen the StylusDown and StylusUp subroutines respectively will run. I use the Stylus up event to clear the drawing so that when you lift the pen you get a clean slate ('scuse the pun).
The workhorse is the packets subroutine. A packet is generated every time there is new data to send. This will include at least an x and y coordinate and may include a pressure level. What this routine does is convert the X and Y coordinates to screen coordinates (the digitizer has a much higher resolution than the display), and if there is a pressure level it converts that to the radius and draws a circle on the attached control (which is the main form you see when you run the app.
The other subroutines are just stubs that could be used if you edited the data interest mask.
On the main form I just use the load event to instantiate a RealTimeStylus object then add the SimpleStylus plugin in the class above and pass it the form itself as the attached control. Here's the code for the main form.
Imports Microsoft.StylusInput
Public Class Form1
Inherits System.Windows.Forms.Form
Private rts As RealTimeStylus
Private Sub Form1_Load _
(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles MyBase.Load
Me.rts = New RealTimeStylus(Me)
Dim plugIn As New SimpleStylus(Me)
Me.rts.SyncPluginCollection.Add(plugIn)
Me.rts.Enabled = True
End Sub
End Class
And that is really all there is to it. Pretty cool that you can get access to such low-level information so fast, eh?