Click here to Skip to main content
Click here to Skip to main content
Go to top

Digitizer interface in C# using VBTablet

, 4 Dec 2009
Rate this:
Please Sign up or sign in to vote.
Digitzer device interface using VBTablet in C#.

Introduction

This article demonstrates the following things:

  1. Interfacing a Digitizer Tablet (Wintab Compatible Digitizer) in C#.
  2. Handling events raised by the digitizer and capturing the data.
  3. 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
    {
        // On Error GoTo errorload
        Digitizer = new Tablet(); //Actually create the tablet object
        sldGranularity.Value = 2; //Set packet granularity value
        //that uses a tablet attribute. Remember not everyone has _your_ tablet.
        Digitizer.UnavailableIsError = false;

        //Tablet.hWnd = frmMain.DefInstance.Handle
        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);

        // The Delegates for Digitizer Event Handling
        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;//Enable Digitize Mode
    Hwnd = this.Handle;
    Digitizer.hWnd = Hwnd;
    Digitizer.AddContext(ContextID, ref IsDigitizingContext);
    Digitizer.SelectContext(ref ContextID);
    Digitizer.Connected = true;
    Digitizer.Context.QueueSize = 32;//Set queue size to a reasonable value

    Deviceinfo();
}

private void Enable_Click(object sender, EventArgs e)
{
    Disable.Enabled = true;
    Enable.Enabled = false;
    InContext();//Call Incontext
    //Check Following Code
   
    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) 
{
    //Show current stats. Note that it's a good idea not to update
    //if not necessary - 100+ updates a second can really hurt performance
    
    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) // 'catch normalpressure and button 1
    {
        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);
            // Gr.DrawEllipse(ppen, X, picDraw.Height - Y, 1, 1);
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message.ToString(), "Error", 
                   MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
        }
    }
    //   Application.DoEvents(); // Poor Data Capture & Rendering
    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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author


Comments and Discussions

 
Questiondigitizer interface PinmemberMember 1042452925-Jul-14 1:41 
Questionsave picture PinmemberMember 826372018-Mar-13 0:16 
AnswerRe: save picture Pinmemberkidoucorp3-May-13 15:39 
QuestionDigitizer interface PinmemberMember 826372016-Mar-13 23:54 
QuestionCursor PinmemberMember 172834714-Oct-10 22:13 
GeneralProblema con Windows 7 (64 bits) Pinmembernixsus20-May-10 12:43 
GeneralRe: Problema con Windows 7 (64 bits) PinmemberVinayak Bharadi1-Jul-10 2:29 
GeneralGreat work! Pinmemberrahulphilips18-Apr-10 19:39 
GeneralNice Work PingroupMd. Marufuzzaman17-Dec-09 18:38 
GeneralYEAH!!! X| PinmemberDIEGO FELDNER17-Dec-09 8:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140916.1 | Last Updated 5 Dec 2009
Article Copyright 2009 by Dr. Vinayak Ashok Bharadi
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid