Click here to Skip to main content
Click here to Skip to main content

Saving-Rebuilding InkCanvas Strokes

, 29 Nov 2006 CPOL
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Eusebiu Marcu
Software Developer (Senior)
Romania Romania
is a developer at IBM Romania.

Comments and Discussions

 
QuestionWhy not use the provided API for saving ink? PinmemberJosh Smith29-Nov-06 8:38 
AnswerRe: Why not use the provided API for saving ink? PinmemberEuthebiu29-Nov-06 8:57 
GeneralRe: Why not use the provided API for saving ink? PinmemberJosh Smith29-Nov-06 9:52 
GeneralRe: Why not use the provided API for saving ink? Pinmembersamgeo2-Mar-07 19:40 
GeneralRe: Why not use the provided API for saving ink? [modified] PinmemberMember 38365558-Jul-08 7:57 
GeneralRe: Why not use the provided API for saving ink? PinmemberGavin Rehkemper18-Jul-08 16:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141223.1 | Last Updated 29 Nov 2006
Article Copyright 2006 by Eusebiu Marcu
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid