Introduction
This article demonstrates the following things:
- Interfacing a Digitizer Tablet (Wintab Compatible Digitizer) in C#.
- Handling events raised by the digitizer and capturing the data.
- Using the VBTablet COM vomponent in C# for interfacing a Digitizer.
The VBTablet component is used for interfacing digitizer devices; this component can be downloaded from: http://www.sourceforge.org/projects/vbtablet/. More details are available at: http://www.greenreaper.co.uk/ which is the developer's website.
The VBTablet is a COM component written in VB (most probably). The interface in VB.NET is available on the 'SourceForge' link, but C# based code is not available. I am right now working on the digitizer, hence I have tried this interface in C# and am posting here.
One thing you should note is that you must have a digitizer tablet to test this code. I am using WACOM Intuos4(4X3). You can get the details about this hardware at http://www.wacom.com/intuos/.
If you are developing a graphic designer application, this can be a starting step. This article also demonstrates the use of the COM component in C#.
Background
Install the device driver and connect the hardware. To understand this code, you should have a basic idea of COM and delegates. Here, we connect the digitizer to the PC, and the digitizer is interfaced as a VBTablet object in C# code. You need to add a reference to the VBTablet.dll file which contains all the definitions.
Using the code
We have to add a reference to this namespace:
using VBTablet;
Next, we create an object for the Digitizer; the class Tablet
is defined in the VBTablet
namespace.
public Tablet Digitizer;
The digitizer is connected in the main form load event. First, the digitizer is initialized. The Tablet
object (Digitizer) will probe the hardware and make the info available. We get the context, pressure, max X , max Y etc., here. Using this component, we can even read the Z coordinate of the pen tip (in 3D). Next, we define the delegate for handling the event for the Tablet
object (Digitizer).
Digitizer.PacketArrival += new Tablet.PacketArrivalEventHandler(PacketArrival);
This is the delegate which handles the packet arrival event. It occurs when the pen tip touches the surface of the digitizer.
private void MainFrm_Load(object sender, EventArgs e)
{
try
{
Digitizer = new Tablet();
sldGranularity.Value = 2;
Digitizer.UnavailableIsError = false;
prgX.Maximum = Digitizer.Context.OutputExtentX - Digitizer.Context.OutputOriginX;
prgY.Maximum = Digitizer.Context.OutputExtentY - Digitizer.Context.OutputOriginY;
prgZ.Maximum = 255;
prgPressure.Maximum = (int)Digitizer.Device.NormalPressure.get_Max(true);
prgTangentPressure.Maximum = (int)Digitizer.Device.TangentPressure.get_Max(true);
Digitizer.ContextClosed +=
new VBTablet.Tablet.ContextClosedEventHandler(ContextClosed);
Digitizer.ContextOpened +=
new VBTablet.Tablet.ContextOpenedEventHandler(ContextOpened);
Digitizer.ContextRepositioned +=
new Tablet.ContextRepositionedEventHandler(ContextRepositioned);
Digitizer.ContextUpdated += new Tablet.ContextUpdatedEventHandler(ContextUpdated);
Digitizer.CursorChange += new Tablet.CursorChangeEventHandler(CursorChange);
Digitizer.InfoChange += new Tablet.InfoChangeEventHandler(InfoChange);
Digitizer.ProximityChange += new Tablet.ProximityChangeEventHandler(ProximityChange);
Digitizer.PacketArrival += new Tablet.PacketArrivalEventHandler(PacketArrival);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + ". Please Connect the Digitizer Device First !!");
}
}
Before using the tablet, you have to enable it. This is done by first pressing the Connect button and then the Enable button. The Connect button will attach the context of the Digitizer to the picture box of the main form, through:
IntPtr Hwnd;
Hwnd = this.Handle;
private void cmdConnect_Click(object sender, EventArgs e)
{
IntPtr Hwnd;
bool IsDigitizingContext=false;
string ContextID = "FirstContext";
Connect.Enabled = false;
Disconnect.Enabled = true;
Enable.Enabled = true;
Disable.Enabled = false;
chkDigitise.Enabled = true;
Hwnd = this.Handle;
Digitizer.hWnd = Hwnd;
Digitizer.AddContext(ContextID, ref IsDigitizingContext);
Digitizer.SelectContext(ref ContextID);
Digitizer.Connected = true;
Digitizer.Context.QueueSize = 32;
Deviceinfo();
}
private void Enable_Click(object sender, EventArgs e)
{
Disable.Enabled = true;
Enable.Enabled = false;
InContext();
Digitizer.Context.TrackingMode = true;
Digitizer.Context.Enabled = true;
return;
}
The packet coming from the digitizer has X, Y, Z, Pressure, Azimuth, Altitude, and Cursor information. We get this info in the packet arrival event. The event handler updates the main form display; we are also using some GDI functions for displaying data in a picture box.
public void PacketArrival(ref IntPtr ContextHandle,
ref int Cursor_Renamed, ref int X , ref int Y , ref int Z,
ref int Buttons , ref int Pressure , ref int TangentPressure,
ref int Azimuth, ref int Altitude, ref int Twist, ref int Pitch,
ref int Roll , ref int Yaw,ref int PacketSerial, ref int PacketTim)
{
tmpl = System.Math.Abs(X);
if( tmpl != prgX.Value)
{
if( tmpl <= prgX.Maximum )
prgX.Value = tmpl;
}
tmpl = System.Math.Abs(Y);
if( tmpl != prgY.Value)
{
if( tmpl <= prgY.Maximum)
prgY.Value = tmpl;
}
tmpl = System.Math.Abs(Z);
if( tmpl != prgZ.Value)
{
if( tmpl <= prgZ.Maximum)
prgZ.Value = tmpl;
}
tmpl = System.Math.Abs(Pressure);
if( tmpl != prgPressure.Value)
prgPressure.Value = tmpl;
tmpl = System.Math.Abs(TangentPressure);
if( tmpl != prgTangentPressure.Value)
prgTangentPressure.Value = tmpl;
if( Convert.ToInt32(lblX.Text)!= X )
lblX.Text = X.ToString();
if( Convert.ToInt32(lblY.Text)!= Y )
lblY.Text = Y.ToString();
if( Convert.ToInt32(lblZ.Text)!= Z )
lblZ.Text = Z.ToString();
if( Convert.ToInt32(lblCursor.Text)!= Cursor_Renamed )
lblCursor.Text = Cursor_Renamed.ToString();
if( Convert.ToInt32(lblPressure.Text)!= Pressure )
lblPressure.Text = Pressure.ToString();
if( Convert.ToInt32(lblTangentPressure.Text)!= TangentPressure )
lblTangentPressure.Text = TangentPressure.ToString();
if( Convert.ToInt32(lblAzimuth.Text)!= Azimuth )
lblAzimuth.Text = Azimuth.ToString();
if( Convert.ToInt32(lblAltitude.Text)!= Altitude )
lblAltitude.Text = Altitude.ToString();
if( Convert.ToInt32(lblTwist.Text)!= Twist )
lblTwist.Text = Twist.ToString();
if( Convert.ToInt32(lblPitch.Text)!= Pitch )
lblPitch.Text = Twist.ToString();
if( Convert.ToInt32(lblRoll.Text)!= Roll )
lblRoll.Text = Roll.ToString();
if( Convert.ToInt32(lblYaw.Text)!= Yaw )
lblYaw.Text = Yaw.ToString();
Pen ppen=new Pen (Color.Red,1);
Graphics Gr;
if (Pressure > 0)
{
if (Digitizer.Context.CursorIsInverted)
{
ppen.Color= Color.Red;
picDraw.Refresh();
}
else
{
ppen.Color = Color.Blue;
}
tmpl = (int)Digitizer.Device.NormalPressure.get_Max(true);
RectWidth = (int)((Pressure / (float)tmpl) * 100);
if ((RectWidth >= 0) & (RectWidth <= 20))
ppen.Color = Color.LawnGreen;
else if ((RectWidth >= 21) & (RectWidth <= 40))
ppen.Color = Color.Blue;
else if((RectWidth >= 41) & (RectWidth <= 100))
ppen.Color = Color.Red;
try
{
Gr = picDraw.CreateGraphics();
Gr.DrawLine(ppen, X, picDraw.Height - Y,
Xold,picDraw.Height - Yold + 1);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message.ToString(), "Error",
MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}
Xold=X;
Yold=Y;
};
The device details are given by the following function:
private void Deviceinfo()
{
lblDeviceMaxPktRate.Text = "Maximal Update Packet Rate (Packets/sec): " +
Digitizer.Device.MaxPktRate.ToString();
lblDeviceMargins.Text = "Device Context Margins (x, y, z): " +
Digitizer.Device.Margins.X.ToString() + ", " +
Digitizer.Device.Margins.Y.ToString() + ", " +
Digitizer.Device.Margins.Z.ToString(); ;
lblDeviceNormalPressure.Text = "Normal Pressure (Min, Max, Resolution, Units): " +
Digitizer.Device.NormalPressure.get_Min(true).ToString() +
", " + Digitizer.Device.NormalPressure.get_Max(true).ToString() +
" , " + Digitizer.Device.NormalPressure.Resolution.ToString() +
" , " + Digitizer.Device.NormalPressure.Units;
lblDevicePnPID.Text = "Plug-and-Play device ID: " +
Digitizer.Device.PnPID.ToString();
lblDeviceX.Text = "X capabilities (Min, Max, Resolution, Units): " +
Digitizer.Device.X.get_Min(true).ToString()+ ", " +
Digitizer.Device.X.get_Max(true).ToString() +" ," +
Digitizer.Device.X.Resolution.ToString() + " ," + Digitizer.Device.X.Units;
lblDeviceY.Text = "Y capabilities (Min, Max, Resolution, Units): " +
Digitizer.Device.Y.get_Min(true).ToString() + ", " +
Digitizer.Device.Y.get_Max(true).ToString() + " ," +
Digitizer.Device.Y.Resolution.ToString() + " ," +
Digitizer.Device.Y.Units;
lblDeviceZ.Text = "Z capabilities (Min, Max, Resolution, Units): " +
Digitizer.Device.Z.get_Min(true).ToString() + ", " +
Digitizer.Device.Z.get_Max(true).ToString() + " ," +
Digitizer.Device.Z.Resolution.ToString() + " ," +
Digitizer.Device.Z.Units;
lblDeviceAzimuth.Text = "Azimuth capabilities (Min, Max, Resolution, Units): " +
Digitizer.Device.Azimuth.get_Min(true).ToString() + ", " +
Digitizer.Device.Azimuth.get_Max(true).ToString() + " ," +
Digitizer.Device.Azimuth.Resolution.ToString() + " ," +
Digitizer.Device.Azimuth.Units;
lblDevName.Text = " Device Name : " + Digitizer.Device.GetType().ToString();
lblStatusContexts.Text = "Number of Contexts open : " +
Digitizer.Status.OpenContexts.ToString();
lblStatusSysContexts.Text = "Number of System Contexts open : " +
Digitizer.Status.OpenSysContexts.ToString();
lblStatusMaxRate.Text = "Maximum Packet Rate in use (packets/sec): " +
Digitizer.Status.MaxCurrentPktRate.ToString();
lblStatusMgrHandles.Text = "Number of Manager Handles open : " +
Digitizer.Status.OpenMgrHandles.ToString();
lblExtensionTag.Text = "Extension ID: " + Digitizer.Extension.ID.ToString();
lblExtensionAbsSize.Text = "Size of Extension in a Packet (Absolute Mode): " +
Digitizer.Extension.AbsoluteSize.ToString();
lblExtensionRelSize.Text = "Size of Extension in a Packet (Relative Mode): " +
Digitizer.Extension.RelativeSize.ToString();
lblExtensionMask.Text = "Extension Or-Mask: " +
Digitizer.Extension.OrMask.MaskValue.ToString();
}
Points of interest
The VBTablet demo was in VB.NET, no C# version was available. This code demonstrates the C# version of implementation of VBTablet. This can be used with any Wintab compatible Digitizer. I have successfully tested it on a Wacom Intuos4.
Thanks
Thanks to 'L Laurence "GreenReaper" Parry', Developer of VBTablet.