65.9K
CodeProject 正在变化。 阅读更多。
Home

从手持设备接收签名

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.22/5 (3投票s)

2007年12月26日

CPOL

6分钟阅读

viewsIcon

38453

downloadIcon

781

一篇关于在手持设备触摸屏上绘制图形的文章。

引言

这篇文章很好地展示了像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_MouseMoveFrmSignPad_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);
    }

正如您所看到的,我试图通过添加内联注释使代码更具自解释性;此外,我还将代码分成了三个区域:

  • VARIABLESFrmSignPadPlotter的静态/非静态变量。
  • 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),它构成在签名书写过程中记录的所有点的列表;请查看PlotterSaveAndClearPoints()方法;每次调用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();
}

因此,要保存签名数据,我们只需通过调用PlotterToString()方法将该格式化字符串写入文件即可。

//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_MouseMoveFrmSignPad_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这个很棒的网站上的第一个项目,所以如果您有任何疑问或建议,请联系我。我随时可以合作。

© . All rights reserved.