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
public FrmSignPad()
{
InitializeComponent();
Graphics m_graphics= this.CreateGraphics();
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
private List<Point> m_listPoints;
private StringBuilder m_strPoints = new StringBuilder();
private Graphics m_gfxCanvas;
private Rectangle m_rectClip;
static private Brush m_brushBg = new SolidBrush(Color.LightBlue);
static private Pen m_penLine = new Pen(Color.MediumBlue);
#endregion
#region BASIC DRAWING CODE
public Plotter(Graphics gfx, int x, int y, int width, int height)
{
m_listPoints = new List<Point>();
m_gfxCanvas = gfx;
m_rectClip = new Rectangle(x, y, width, height);
m_gfxCanvas.Clip = new Region(m_rectClip);
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 of FrmSignPad
and Plotter
BASIC DRAWING CODE
: All you need to draw a signature on the form-screen
ADDITIONAL 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 of Point
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 the Plotter
constructor and creates a rectangular region for drawing (m_rectClip
) using the Brush
object m_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:
private void FrmSignPad_MouseMove(object sender, MouseEventArgs e)
{
if (m_SignPlotter != null) {
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:
public void ReDraw(Point aPoint){
m_listPoints.Add(aPoint);
Draw();
}
public void Draw()
{
float xTo = 0;
float xFrom = 0;
float yTo = 0;
float yFrom = 0;
if (m_listPoints.Count < 2)
return;
for (int i = 1; i < m_listPoints.Count; i++)
{
xFrom = m_listPoints[i - 1].X;
yFrom = m_listPoints[i - 1].Y;
xTo = m_listPoints[i].X;
yTo = m_listPoints[i].Y;
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
:
private void FrmSignPad_MouseUp(object sender, MouseEventArgs e)
{
m_SignPlotter.SaveAndClearPoints();
}
public void SaveAndClearPoints()
{
...
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 SignPart Point
objects
m_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).
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); this.m_strPoints.Append(strCoordinates + ";");
m_listPoints.Clear();
}
Hence, to save a signature data, we just write that formatted string in a file by calling Plotter
's ToString()
method:
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:
public void OpenSign(string file)
{
StreamReader sr = new StreamReader(file);
string signString="";
if(!sr.EndOfStream)
signString= sr.ReadLine();
if (signString != "")
ReDraw(signString);
sr.Close();
}
private void ReDraw(string signString)
{
this.Clear();
string[] signParts_asStr = signString.Split(';');
foreach (string signPart in signParts_asStr)
{
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);
}
Draw();
SaveAndClearPoints();
} }
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.