Accepting Signatures from Hand-Held Devices






3.22/5 (3 votes)
An article on drawing graphics on a hand-held device touch screen.
Introduction
This article is a great example of how code-share via sites like CodeProject could give rise to exciting results. While working on my first hand-held project (a Point of Sale application), I happened to go through leonardosalvatore's article on a GPS tracer application, which proved to be a big help for one of the challenges that I faced - how to take a customer's signature for invoicing? And, how to save and retrieve that signature data when needed?
How it works
There are three actions that a user can perform:
- Draw on the rectangular screen area (which I call 'canvas' in the code documentation) with the help of the stylus (or mouse in the case of the device emulator)
- Save a drawing (can be any weird one other than a signature) in a file for later retrieval
- Load a file containing the drawing (OK! From now on, I will use only one word - the signature) which was saved previously as a single-line string (of points to be drawn in a specific format)
Requirements
For this sample, I used a hand-held device with Windows CE .NET (4.2) and .NET Compact Framework 2.0. You can test it with the help of an emulator in Visual Studio 2005. I tested this application on PSION TECKLOGIX Workabout PRO.
Getting started
I have included a project which should have everything you need to get started. You can use this utility both as a standalone and as a part of your own device application to capture (and, of course, to show) signature data. The code-snippets for both the cases are also provided.
Using the code
The application is made up of two actors:
Plotter
: This is the main class that does all kinds of drawing jobs: draw, save, load the signatures.FrmSignPad
: It's the only Windows UI form of the application working as a client of the aforementioned class (Plotter.cs); it has an Options menu with Clear and Save actions; also, when the application detects a .sign file in the default \signs directory, it populates this menu with all those signature names so that the user can open and redraw the signature for display and/or edit.
The user is advised to clear the screen (i.e., click on Clear menu item) before writing the signature; it makes the drawing area (called canvas) look visible.
Note that I am not explaining the details of the main menu programming; I can add it to my future updates if people find it problematic.
Initializing things
It initializes the application; as you can see, it's able to run on 240px X 320px devices. Once the main actor m_SignPlotter
is created in the form constructor, it is ready for user actions for writing a signature; i.e., the FrmSignPad_MouseMove
and FrmSignPad_MouseUp
events:
public partial class FrmSignPad : Form
{
private Plotter m_SignPlotter;
...
#region BASIC DRAWING CODE
//load the Sign Pad passing this form's Graphics
//to be used for drawing tasks
public FrmSignPad()
{
InitializeComponent();
Graphics m_graphics= this.CreateGraphics();
//based on m_graphics, create & setup the plotter object
m_SignPlotter = new Plotter(m_graphics, 0, 30, 240, 300);
...
}
#endregion
But before we move on to examine these events, let's see how the Plotter
instance (m_SignPlotter
) gets initialized:
public class Plotter
{
#region STATIC/NON-STATIC VARIABLES
//list of points for the current sign-part
private List<Point> m_listPoints;
//comma-delimited x,y co-ordinates for all the points of signature
private StringBuilder m_strPoints = new StringBuilder();
//grafix canvas of the form which uses this plotter
private Graphics m_gfxCanvas;
//rectangular area which m_gfxCanvas uses for its clip region
private Rectangle m_rectClip;
//background colorizing tool for the m_gfxCanvas
static private Brush m_brushBg = new SolidBrush(Color.LightBlue);
//pen used to draw points as lines
static private Pen m_penLine = new Pen(Color.MediumBlue);
#endregion
#region BASIC DRAWING CODE
//the only constructor for the calling form
//params:
//Graphics gfx: the caller form's grafix
//int x, int y: the drawing canvas location co-ordinates
//int width, int height: the drawing canvas dimensions
public Plotter(Graphics gfx, int x, int y, int width, int height)
{
//initialize the plotter's members
m_listPoints = new List<Point>();
m_gfxCanvas = gfx;
m_rectClip = new Rectangle(x, y, width, height);
m_gfxCanvas.Clip = new Region(m_rectClip);
//draw the rectangular canvas as the plotter's signature pad
m_gfxCanvas.FillRectangle(m_brushBg, m_rectClip);
}
As you can see, I've tried to make the code look self-explanatory by sprinkling in-line comments all over; also, I have divided my code into three regions:
VARIABLES
: Static/non-static variables ofFrmSignPad
andPlotter
BASIC DRAWING CODE
: All you need to draw a signature on the form-screenADDITIONAL SAVE/LOAD FUNCTIONALITY
: A bit complex part of the program which deals with saving the signature data for reuse - as a .sign file, or a list ofPoint
objects, or a single-line string
Basic drawing code
It's better to look at these parts one by one; let's understand the basic drawing logic first:
- The
FrmSignPad
constructor calls thePlotter
constructor and creates a rectangular region for drawing (m_rectClip
) using theBrush
objectm_brushBg
- As the user moves the mouse or stylus on the screen, we collect its position co-ordinates, save them, and draw lines representing this move (called a SignPart)
Here's is how it goes:
//a user draws with the mouse or hand-held device stylus
private void FrmSignPad_MouseMove(object sender, MouseEventArgs e)
{
if (m_SignPlotter != null)//this hardly possible
{
//capture the mouse position co-ordinates;
//make up a point mapping that position
//and pass it on to the plotter to for drawing
Point aPoint = new Point();
aPoint.X = e.X;
aPoint.Y = e.Y;
m_SignPlotter.ReDraw(aPoint);
}
}
The plotter's ReDraw(Point aPoint)
method first adds the point of mouse position in the list of points (m_listPoints
), and calls Draw()
which does the main job of drawing the signature lines:
//the main plotter which adds up all the points sent by FrmSignPad_MouseMove;
//and draws all the currently held points in m_listPoints onto the canvas
public void ReDraw(Point aPoint){
m_listPoints.Add(aPoint);
Draw();
}
//called by ReDraw AND the load functionality given below
public void Draw()
{
float xTo = 0;
float xFrom = 0;
float yTo = 0;
float yFrom = 0;
if (m_listPoints.Count < 2)
//can't draw 1 point only; coz, only lines are drawn here
return;
for (int i = 1; i < m_listPoints.Count; i++)
{
//co-ordinates of starting point
xFrom = m_listPoints[i - 1].X;
yFrom = m_listPoints[i - 1].Y;
//co-ordinates of ending point
xTo = m_listPoints[i].X;
yTo = m_listPoints[i].Y;
//draw a line segment
m_gfxCanvas.DrawLine(m_penLine, (int)xTo,
(int)yTo, (int)xFrom, (int)yFrom);
}
}
As a SignPart is drawn, the user moves up the mouse (or stylus), which signals our application the end of drawing one SignPart so that the plotter clears the list of points because we keep only the current SignPart's points in m_listPoints
:
//save away the drawing data until now; and get ready
//for another sign-part (i.e. drawing via FrmSignPad_MouseMove)
private void FrmSignPad_MouseUp(object sender, MouseEventArgs e)
{
m_SignPlotter.SaveAndClearPoints();
}
//appends currently held points to the string of all points
//(i.e. m_strPoints) and clears the cashe (m_listPoints)
//called when mouse up OR when one sign-part is to be stored as string
public void SaveAndClearPoints()
{
...
//when done, clear the list and for the next points' addition
//(i.e. FrmSignPad_MouseMove results)
m_listPoints.Clear();
}
As far as drawing a signature is concerned, that's all there's to it. But surely, this won't be enough for our business applications because we would want to somehow save that signature data for record-keeping (say, in order to be able to further process a transaction). The SignPad application provides you with methods which either help you save away the signature data in a file or a database, or pass on to your device application as a continuous list of Point
objects so that it can be plotted with the help of some third party printing API for generating invoice print-outs with that signature (e.g., I used fieldsoftware.com's printing SDK for that very purpose).
Saving signature data for reuse
Each SignPart co-ordinates are maintained in two storage variables:
m_listPoint
: which holds only the current SignPartPoint
objectsm_strPoints
: which maintains all the SignParts' points in this format: x1,y1,x2,y2,x3,y3;x1,y1,x2,y2;x1,y1,x2,y2,x3 (each SignPart is delimited by a ';')
This is the string which we can save in a file or in a database. Also, to pass this points data on to any printing program, the Plotter
class has a read-only property (Points
) which makes up a list of all the points recorded during the signature writing; have a look at the SaveAndClearPoints()
method of Plotter
; every time FrmSignPad_MouseUp
is called, it appends the current SignPart points to m_strPoints
in this string format before it clears m_listPoint
for the next FrmSignPad_MouseMove
calls (i.e., next SignPart's points data collection).
//appends currently held points to the string of all points
//(i.e. m_strPoints) and clears the cashe (m_listPoints)
//called when mouse up OR when one sign-part is to be stored as string
public void SaveAndClearPoints()
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < m_listPoints.Count; i++)
{
sb.Append(m_listPoints[i].X + "," +
m_listPoints[i].Y + ",");
}
string strCoordinates = sb.ToString().Substring(0,
sb.ToString().Length - 1);//trim of last ','
//all sign-parts would be separated by ';',
//e.g. 12,334,13,34;122,23,34,45;etc...
this.m_strPoints.Append(strCoordinates + ";");
//when done, clear the list and for the next points' addition
//(i.e. FrmSignPad_MouseMove results)
m_listPoints.Clear();
}
Hence, to save a signature data, we just write that formatted string in a file by calling Plotter
's ToString()
method:
//persists the current drawing in a file as a single-line string
public void SaveSign(string file)
{
StreamWriter sw = new StreamWriter(file);
sw.WriteLine(this.ToString());
sw.Flush();
sw.Close();
}
Loading a signature data for reuse
Finally! You would want to retrieve a saved signature for display or printing purpose (from a disk file or a database).
Now! This part of the code is a bit tricky. Our saved signatures are single-line strings; but how to re-draw those SignParts when there is no FrmSignPad_MouseMove
and FrmSignPad_MouseUp
runtime environment present? For that, I have devised another version of the ReDraw
method which gets the signature string, splits it, and draws all the SignParts one by one looking as if it were being drawn by an invisible user:
//gets single-line string data from a .sign file
public void OpenSign(string file)
{
StreamReader sr = new StreamReader(file);
string signString="";
if(!sr.EndOfStream)
signString= sr.ReadLine();
if (signString != "")
ReDraw(signString);
sr.Close();
}
//a worker method for OpenSign(string file)
//draws the saved signature on the canvas
private void ReDraw(string signString)
{
this.Clear();
//sign pad must have no pending or old drawing
string[] signParts_asStr = signString.Split(';');
//collect all sign-parts as strings
foreach (string signPart in signParts_asStr)
{
//collect all x\y numbers as strings
string[] arrPoints = signPart.Split(',');
for (int i = 1; i < arrPoints.Length; )
{
string strX = arrPoints[i - 1];
string strY = arrPoints[i];
i = i + 2;
Point p = new Point(Convert.ToInt32(strX),
Convert.ToInt32(strY));
this.m_listPoints.Add(p);
}
// done with this signPart? draw it! store it!
// and clear the cashe for next signPart
Draw();
SaveAndClearPoints();
}// do the same for next signPart if any
//signature is re-drawn and ready for edit
}
Points of interest
I daresay this effort takes a lot of stuff from leonardosalvatore's article but I want to demonstrate that sharing your knowledge and expertise not only solves somebody's problems but also opens doors to new contributors (like myself) who further extend this knowledge-share to make software development so enticing for other developers. It's my first project on the great CodeProject website, so please contact me if you have any doubts or proposals. I'm always available for collaboration.