Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Saving-Rebuilding InkCanvas Strokes

0.00/5 (No votes)
29 Nov 2006 2  
An article on the InkCanvas and InkPresenter WPF controls.

InkCanvas and InkPresenter

Introduction

One of the coolest controls in WPF is <InkCanvas…/>. It's definition from Windows SDK documentation: "Defines an area that receives and displays ink strokes". When I saw what this control can do, I thought that it would be nice if we can use this control for saving signatures and save these signatures into a database. This <InkCanvas…/> control has a "brother" in WPF. It's brother is <InkPresenter…/>. The definition for this control is: "Renders ink on a surface".

Using the code

When I started to look into the <InkCanvas…/> proprieties, I saw the Strokes (that is a StrokeCollection) property that "Gets or sets the collection of ink Stroke objects collected by the InkCanvas". So… we serialize these Strokes, insert them into the DB, and when we want to see them, we deserialize the data from DB and draw the Strokes in an InkPresenter control. As it's brother, InkPresenter has a Strokes property. A StrokeCollection has an Add method that "Adds a Stroke to the collection." Now, we have to serialize all the Strokes that InkCanvas has. But Stokes and StrokeCollection are not marked as Serializable. So we have to do a little trick. We build our own class that has a Point[][] that is marked as Serializable (the Point class is the one from the System.Windows namespace).

[Serializable]
public sealed class MyCustomStrokes
{
public MyCustomStrokes() { }

    /// <SUMMARY>
    /// The first index is for the stroke no.
    /// The second index is for the keep the 2D point of the Stroke.
    /// </SUMMARY>
    public Point[][] StrokeCollection;
}

For downloading/uploading an "image" I've made a class "Signature" that has two static methods:

  • UploadImage(string connectionString, byte[] signatureBytes)
  • DownloadImage(string connectionString)

The method's signature can be customized. In this article, I will download only the last signature. As you can see, the MyCustomStrokes.StrokeCollection is a 2D matrix with Point as the type of the elements. You're probably asking yourself why I chose the Point structure. First of all, because it's marked as Serializable. Second, a Stroke has a StylusPoints property that returns a StylusPointCollection : Collection<StylusPoint> that has two more proprieties: X and Y. So we keep this information in MyCustomStrokes.StrokeCollection. Now, for the upload part, we have to save the StylusPointCollection in a MyCustomStrokes.StrokeCollection For this, I've made this method that has a StrokeCollection as it's parameter:

void UploadStrokes(StrokeCollection strokes)
{
    if (strokes.Count > 0)
    {
        MyCustomStrokes customStrokes = new MyCustomStrokes();
        customStrokes.StrokeCollection = new Point[strokes.Count][];

        for (int i = 0; i < strokes.Count; i++)
        {
            customStrokes.StrokeCollection[i] = 
              new Point[this.MyInkCanvas.Strokes[i].StylusPoints.Count];

            for (int j = 0; j < strokes[i].StylusPoints.Count; j++)
            {
                customStrokes.StrokeCollection[i][j] = new Point();
                customStrokes.StrokeCollection[i][j].X = 
                                      strokes[i].StylusPoints[j].X;
                customStrokes.StrokeCollection[i][j].Y = 
                                      strokes[i].StylusPoints[j].Y;
            }
        }

        //Serialize our "strokes"
        MemoryStream ms = new MemoryStream();
        BinaryFormatter bf = new BinaryFormatter();
        bf.Serialize(ms, customStrokes);

        try
        {
            Signature.UploadImage(ConnectionString, ms.GetBuffer());
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }
}

As you can see, in the end, I've called Signature.UploadImage(ConnectionString, ms.GetBuffer());.

/// <summary>
/// Inserts a BLOB into an Oracle DataBase
/// </summary>
/// <param name="connectionString">The ConnectionString to the database
/// <param name="signatureBytes">Signature bytes to be saved
public static void UploadImage(string connectionString, byte[] signatureBytes)
{
    try
    {
        OracleConnection conn = new OracleConnection(connectionString);
        conn.Open() ; 

        OracleCommand cmd = new OracleCommand("insert into " + 
                            "tabel(Signature) values(:Signature)", conn); 
        cmd.Parameters.Add("Signature", OracleType.Blob) ;
        cmd.Parameters["Signature"].Value = signatureBytes;
        cmd.ExecuteNonQuery();
        conn.Close(); 
    }
    catch(Exception ex)
    {
        throw ex;
    }
}

The XAML code for this window is:

<Window x:Class="WindowsApplication2.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="305" Width="820">
  <StackPanel>
    <InkCanvas x:Name="MyInkCanvas"></InkCanvas>
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition/>
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
        <ColumnDefinition/>
      </Grid.ColumnDefinitions>
    <Button x:Name="UploadButton" Grid.Row="0" Grid.Column="0" 
            Click="UploadButton_Click">Upload</Button>
      <Button x:Name="DownloadButton" Grid.Row="0" Grid.Column="1" 
              Click="DownloadButton_Click">Download</Button>
      <Button x:Name="ClearButton" Grid.Row="0" Grid.Column="2" 
              Click="ClearButton_Click">Clear</Button>
    </Grid>
  </StackPanel>
</Window>

The methods for uploading and clearing are:

void UploadButton_Click(object sender, RoutedEventArgs e)
{
    this.UploadStrokes(this.MyInkCanvas.Strokes);
}

void ClearButton_Click(object sender, RoutedEventArgs e)
{
    this.MyInkCanvas.Strokes.Clear();
}

That's it for the upload part.

For the download part, we do it backwards. I've made a second WPF window that has only a <InkPresenter…/> control as its content. The XAML code is:

<Window x:Class="WindowsApplication2.Window2"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WindowsApplication2" Height="427" 
    Width="567" Loaded="Window2_Loaded"
    >
    <Grid>
      <InkPresenter x:Name="MyInkPresenter"></InkPresenter>
    </Grid>
</Window>

First, we download the "image" from the DB, we deserialize the "image", and then we rebuild it. I've made a method that does all three things that is called on Window2's Loaded event:

void BuildImage()
{
    try
    {
        //download the image
        byte[] signature = Signature.DownloadImage(ConnectionString);

        //deserialize it
        BinaryFormatter bf = new BinaryFormatter();
        MemoryStream ms = new MemoryStream(signature);

        MyCustomStrokes customStrokes = bf.Deserialize(ms) as MyCustomStrokes;

        //rebuilt it
        for (int i = 0; i < customStrokes.StrokeCollection.Length; i++)
        {
            if (customStrokes.StrokeCollection[i] != null)
            {
                StylusPointCollection stylusCollection = new 
                  StylusPointCollection(customStrokes.StrokeCollection[i]);

                Stroke stroke = new Stroke(stylusCollection);
                StrokeCollection strokes = new StrokeCollection();
                strokes.Add(stroke);

                this.MyInkPresenter.Strokes.Add(strokes);
            }
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

The DownloadImage method from the Signature class is:

/// <summary>
/// Downloads a byte[] from a BLOB
/// </summary>
/// <param name="connectionString"></param>
/// <returns>The BLOB as byte[]</returns>
public static byte[] DownloadImage(string connectionString)
{
    try
    {
        OracleConnection conn = new OracleConnection(connectionString);
        conn.Open();

        OracleCommand cmd = new OracleCommand("select signature" + 
                            " from tabel order by id desc", conn); 
        byte[] barrImg = cmd.ExecuteScalar() as byte [];

        conn.Close();

        return barrImg;
    }
    catch(Exception ex)
    {
        throw ex;
    }
}

The method that handles the Download button in Window1 is:

void DownloadButton_Click(object sender, RoutedEventArgs e)
{
    Window2 win = new Window2();
    win.Height = this.Height;
    win.Width = this.Width;
    win.Show();
}

That's about it!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here