从手持设备接收签名






3.22/5 (3投票s)
一篇关于在手持设备触摸屏上绘制图形的文章。
引言
这篇文章很好地展示了像CodeProject这样的网站如何通过代码共享产生令人兴奋的结果。在我第一个手持设备项目(一个POS应用程序)工作时,我恰好阅读了leonardosalvatore关于GPS跟踪器应用程序的文章,这对我面临的一个挑战非常有帮助——如何为发票获取客户签名?以及如何在他需要时保存和检索该签名数据?
工作原理
用户可以执行三种操作
- 在矩形屏幕区域(在代码文档中我称之为“画布”)上用触笔(或在设备模拟器中使用鼠标)进行绘图
- 将绘制的图形(可以是任何奇怪的图形,不一定是签名)保存到文件中,以便以后检索
- 加载一个包含之前保存的签名(好的!从现在开始,我将只使用一个词——签名)的文件,该文件保存为单行字符串(包含以特定格式绘制的点)
要求
对于这个示例,我使用了一台运行Windows CE .NET (4.2) 和 .NET Compact Framework 2.0 的手持设备。您可以使用Visual Studio 2005中的模拟器进行测试。我在PSION TECKLOGIX Workabout PRO上测试了这个应用程序。
入门
我提供了一个项目,其中包含您开始所需的一切。您可以将此实用程序用作独立应用程序,也可以作为您自己设备应用程序的一部分来捕获(当然,也包括显示)签名数据。还提供了两种情况的代码片段。
使用代码
该应用程序由两个组件组成
Plotter
:这是执行所有绘图工作的主要类:绘制、保存、加载签名。FrmSignPad
:它是应用程序中唯一的Windows UI窗体,作为上述类(Plotter.cs)的客户端;它有一个带有“清除”和“保存”操作的“选项”菜单;此外,当应用程序在默认的\signs目录中检测到.sign文件时,它会用所有这些签名名称填充该菜单,以便用户可以打开并重绘签名以供显示和/或编辑。
建议用户在书写签名之前清除屏幕(即单击“清除”菜单项);这会使绘图区域(称为画布)看起来更清晰。
请注意,我并未详细解释主菜单编程的细节;如果人们觉得有问题,我可以在将来的更新中添加。
初始化
它负责应用程序的初始化;正如您所看到的,它能够运行在240px X 320px的设备上。在窗体构造函数中创建主组件m_SignPlotter
后,它就可以响应用户的签名书写操作了;即FrmSignPad_MouseMove
和FrmSignPad_MouseUp
事件。
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
但在我们继续检查这些事件之前,让我们看看Plotter
实例(m_SignPlotter
)是如何初始化的。
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);
}
正如您所看到的,我试图通过添加内联注释使代码更具自解释性;此外,我还将代码分成了三个区域:
VARIABLES
:FrmSignPad
和Plotter
的静态/非静态变量。BASIC DRAWING CODE
:在窗体屏幕上绘制签名所需的所有内容。ADDITIONAL SAVE/LOAD FUNCTIONALITY
:程序中比较复杂的部分,负责将签名数据保存以供重用——以.sign文件、点列表或单行字符串的形式。
基本绘图代码
最好逐一查看这些部分;让我们先理解基本绘图逻辑。
FrmSignPad
构造函数调用Plotter
构造函数,并使用Brush
对象m_brushBg
创建一个用于绘图的矩形区域(m_rectClip
)。- 当用户在屏幕上移动鼠标或触笔时,我们收集其位置坐标,保存它们,并绘制代表此移动的线条(称为SignPart)。
具体过程如下:
//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);
}
}
Plotter的ReDraw(Point aPoint)
方法首先将鼠标位置点添加到点列表(m_listPoints
)中,然后调用Draw()
,后者执行绘制签名线条的主要工作。
//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);
}
}
当一个SignPart绘制完成后,用户抬起鼠标(或触笔),这标志着应用程序结束了一个SignPart的绘制,然后Plotter会清除点列表,因为我们只将当前SignPart的点保留在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();
}
就绘制签名而言,这就是全部。但这肯定不足以满足我们的业务应用程序的需求,因为我们会希望以某种方式保存签名数据以供记录(例如,以便进一步处理交易)。SignPad应用程序提供了方法,这些方法可以帮助您将签名数据保存到文件或数据库中,或者将其作为连续的点列表传递给您的设备应用程序,以便可以使用某些第三方打印API进行打印以生成带有签名的发票(例如,我为此目的使用了fieldsoftware.com的打印SDK)。
保存签名数据以供重用
每个SignPart的坐标都维护在两个存储变量中:
m_listPoint
:仅包含当前SignPart的Point
对象。m_strPoints
:维护所有SignParts的点,格式如下:x1,y1,x2,y2,x3,y3;x1,y1,x2,y2;x1,y1,x2,y2,x3(每个SignPart由“;”分隔)。
这就是我们可以保存到文件或数据库中的字符串。此外,为了将这些点数据传递给任何打印程序,Plotter
类有一个只读属性(Points
),它构成在签名书写过程中记录的所有点的列表;请查看Plotter
的SaveAndClearPoints()
方法;每次调用FrmSignPad_MouseUp
时,它都会以这种字符串格式将当前SignPart的点追加到m_strPoints
中,然后清除m_listPoint
以备下一个FrmSignPad_MouseMove
调用(即收集下一个SignPart的点数据)。
//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();
}
因此,要保存签名数据,我们只需通过调用Plotter
的ToString()
方法将该格式化字符串写入文件即可。
//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();
}
加载签名数据以供重用
最后!您可能希望检索已保存的签名以供显示或打印(从磁盘文件或数据库中)。
现在!这部分代码有点棘手。我们保存的签名是单行字符串;但是,当没有FrmSignPad_MouseMove
和FrmSignPad_MouseUp
运行时环境时,如何重新绘制这些SignParts?为此,我设计了ReDraw
方法的另一个版本,它获取签名字符串,将其拆分,然后逐个绘制所有SignParts,看起来就像是由一个看不见的用户绘制的。
//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
}
关注点
我敢说这项努力借鉴了leonardosalvatore文章中的许多内容,但我希望证明分享您的知识和专业知识不仅能解决别人的问题,还能为新的贡献者(比如我)打开大门,他们会进一步扩展这种知识分享,使软件开发对其他开发者更具吸引力。这是我在CodeProject这个很棒的网站上的第一个项目,所以如果您有任何疑问或建议,请联系我。我随时可以合作。