.NET Compact Framework Save Handwritten Signature To File

By Robbe D. Morris

Printer Friendly Version

Robbe Morris
Robbe & Melisa Morris
  Download C# Source Code
By now, most .NET Compact Framework developers have run across the MSDN Signature Control Sample.  The sample application enables you to capture a handwritten signature and transmit it over TCP/IP to an awaiting desktop application.  One of the shortfalls of this sample is that it is not well suited for disconnected mobile device applications because it requires network connectivity and has no easy way to save and retrieve the signature image from disk on the mobile device.  These two items are critical to an application I'm currently working on.
.NET Compact Framework developers have also been a victim to the cruel decision by Microsoft to leave out graphics support for saving images to disk in version 1.1.  Rather than vent my frustrations with this as I'm sure I'd be preaching to the choir, I opted to try a different approach to save the captured signatures to disk.
The code sample below takes Microsoft's signature sample, chops out an enormous amount of unnecessary code, captures the mouse movement coordinates as the signature is being drawn to the control, and eventually stores the movements in a simple text file.  When we want to display the stored signature, we read the file, process the coordinates, and use GDI+ to draw each line of the signature.  This approach enables us to avoid the performance hit of trying to read and save graphics files as well as take up a minimum amount of disk space on the mobile device for each signature.  In fact, my signature takes up less than 2KB.


What is not included in the sample is the ability to implement symmetric encryption.  However, we offer a related article for the .NET Compact Framework: Encryption And File Transmittal With Mobile Devices In C# that demonstrates how to accomplish this task.  These two samples together provide you with a pretty comprehensive means for capturing handwritten signatures on your mobile device.
To see this in action, download and run the sample code above.  When the application first starts up, use your stylus to sign your name in the designated area.  Then, click Save.  You'll see that a new text file named Signature.txt was created.  To view the signature you've just created, click Load.
HandwrittenSignature.cs
using System;
using System.Drawing;
using System.Drawing.Imaging; 
using System.Windows.Forms;
using System.Text;
using System.Collections;
using System.IO;
using System.Reflection; 
using MyApplication;
 
namespace MyApplication
{
 
   public class HandSignature : System.Windows.Forms.Form
   {
 
     static void Main() 
     {
       Application.Run(new HandSignature());
     }

      private string SignatureBackgroundImageFileName="signhere.png";
      private string SignatureFileName="Signature.txt";
      private string AppPath=Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
      private System.Windows.Forms.Button butNew;
      private System.Windows.Forms.Panel areaSignature;
      private System.Windows.Forms.Button butSave;
      private System.Windows.Forms.Button butLoad;

      SignatureControl MySignature;
 
      public HandSignature()
      {
 
        InitializeComponent();

        try
        {
				 
          MySignature = new SignatureControl(AppPath,SignatureBackgroundImageFileName);
          MySignature.Location = areaSignature.Location;
          MySignature.Size = areaSignature.Size;
			
          this.Controls.Add(MySignature);
			
        }
        catch (Exception err) { MessageBox.Show(err.Message); }
      }
        
      protected override void Dispose( bool disposing )
      {
        base.Dispose( disposing );
      }
    
      private void HandSignature_Load(object sender, System.EventArgs e)
      {
        MySignature.Clear(true); 
      }
    
      private void butNew_Click(object sender, System.EventArgs e)
      {
        MySignature.Clear(true);
      }
     
      private void butSave_Click(object sender, System.EventArgs e)
      {
        try
        {
          Cursor.Current = Cursors.WaitCursor;
          MySignature.Save(Path.Combine(AppPath,SignatureFileName));
          MySignature.Clear(false);
        }
        catch (Exception ex)
        {
          MessageBox.Show(ex.Message);
        }
        finally
        {
          Cursor.Current = Cursors.Default;
        }
      }
    
      private void butLoad_Click(object sender, System.EventArgs e)
      {
        try
        {
          Cursor.Current = Cursors.WaitCursor;
          MySignature.Clear(false);
          MySignature.Load(Path.Combine(AppPath,SignatureFileName));
          MySignature.DrawSignature(); 
        }
        catch (Exception ex)
        {
          MessageBox.Show(ex.Message);
        }
        finally
        {
          Cursor.Current = Cursors.Default;
        }
       }
		 
  }	 
}
		 
SignatureControl.cs
using System;
using System.Drawing;
using System.Drawing.Imaging; 
using System.Windows.Forms;
using System.Text;
using System.Collections;
using System.IO;
using System.Reflection; 

namespace MyApplication
{ 
	 
  public class SignatureControl : Control
  {

    private ArrayList Points = new ArrayList();
    private Bitmap BackGroundImage;
    private Graphics GraphicsHandle;
    private Pen SignaturePen = new Pen(Color.Black);
    private Point LastMouseCoordinates = new Point(0,0);
    private bool CaptureMouseCoordinates = false;
    private string AppPath="";
    private string ImageFileName="";
    private string Delimiter=",";
		
    private struct LineToDraw
    { 
      public int StartX;
      public int StartY;
      public int EndX;
      public int EndY;
    }
		 
    public SignatureControl(string ApplicationPath,string BaseImageFileName)
    {
      AppPath = ApplicationPath; 
      ImageFileName = BaseImageFileName;
    }

    protected override void OnPaint(PaintEventArgs e) 
    {
      LoadBackgroundImageIfInvalid();
      e.Graphics.DrawImage(BackGroundImage, 0, 0);			 
    }

    protected override void OnPaintBackground(PaintEventArgs e) 
    {
			
    }

    protected override void OnMouseDown(MouseEventArgs e) 
    {
      base.OnMouseDown(e);

      if (CaptureMouseCoordinates) { return; }
      
      CaptureMouseCoordinates = true;
      LastMouseCoordinates.X = e.X;
      LastMouseCoordinates.Y = e.Y;

    }

    protected override void OnMouseUp(MouseEventArgs e) 
    {
      base.OnMouseUp(e);
      CaptureMouseCoordinates = false;	
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
      base.OnMouseMove(e);			

      if (!CaptureMouseCoordinates) { return; }

      LineToDraw l = new LineToDraw();

      l.StartX = LastMouseCoordinates.X;
      l.StartY = LastMouseCoordinates.Y;
      l.EndX = e.X;
      l.EndY = e.Y;

      Points.Add(l);
 
      GraphicsHandle.DrawLine(SignaturePen, l.StartX, l.StartY, l.EndX,l.EndY);

      LastMouseCoordinates.X = l.EndX;
      LastMouseCoordinates.Y = l.EndY;
			 
      Invalidate();
           
    }

    public void DrawSignature()
    {
      LineToDraw l;

      try
      {
        if (Points.Count < 1) { return; } 

        for(int i=0;i<Points.Count;i++)
        {
          l = (LineToDraw)Points[i];
          GraphicsHandle.DrawLine(SignaturePen, l.StartX, l.StartY, l.EndX,l.EndY);
        }

        Invalidate();
      }
      catch (Exception) { throw; }
    }

    public void Save(string FileName)
    {
      LineToDraw l;

      try
      {

        if (Points.Count < 1) { return; } 
  
        StreamWriter sw = new StreamWriter(FileName,false);
  
        for(int i=0;i<Points.Count;i++)
        {
          l = (LineToDraw)Points[i];
          sw.Write(l.StartX.ToString() + Delimiter);
          sw.Write(l.StartY.ToString() + Delimiter);
          sw.Write(l.EndX.ToString() + Delimiter);
          sw.WriteLine(l.EndY.ToString());
        }
 
        sw.Close(); 
				 
      }
      catch (Exception) { throw; }
    }

    public void Load(string FileName)
    {
      LineToDraw l;

      try
      {

        this.Clear(true);
  
        StreamReader sr = new StreamReader(FileName);

        string line;
	 
        while ((line = sr.ReadLine()) != null)
        {

          l = new LineToDraw();
				   
          string[] linesplit = line.Split(Delimiter.ToCharArray());
  
          l.StartX = int.Parse(linesplit[0].ToString());
          l.StartY = int.Parse(linesplit[1].ToString());
          l.EndX = int.Parse(linesplit[2].ToString());
          l.EndY = int.Parse(linesplit[3].ToString());
          Points.Add(l); 
        }
 
        sr.Close(); 
				 
      }
      catch (Exception) { throw; }
    }


    public void Clear(bool ClearCapturedLines)
    {
       try
       {
          LoadBackgroundImage();
          Invalidate();
          if (ClearCapturedLines == false) { return; }
          Points.Clear();
       }
       catch (Exception) { throw; }
    }

    private void LoadBackgroundImageIfInvalid()
    {
      try
      {
        if (BackGroundImage == null || BackGroundImage.Width != this.Width || BackGroundImage.Height != this.Height)
        {
          LoadBackgroundImage();
        }
      }
      catch (Exception) { throw; }
     }

     private void LoadBackgroundImage()
     {
       try
       {
         BackGroundImage = new Bitmap(Path.Combine(AppPath,ImageFileName));   
         GraphicsHandle = Graphics.FromImage(BackGroundImage);
       }
       catch (Exception) { throw; }
     }


 }
	 
}

Robbe has been a Microsoft MVP in C# since 2004.  He is also the co-founder of NullSkull.com which provides .NET articles, book reviews, software reviews, and software download and purchase advice.