Click here to Skip to main content
15,884,177 members
Articles / Desktop Programming / WPF

Saving-Rebuilding InkCanvas Strokes

Rate me:
Please Sign up or sign in to vote.
3.85/5 (7 votes)
29 Nov 2006CPOL2 min read 93.5K   2K   22   8
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).

C#
[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:

C#
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());.

C#
/// <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:

XML
<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:

C#
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:

XML
<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:

C#
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:

C#
/// <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:

C#
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)


Written By
Architect
Romania Romania
is a developer at LetsVR.ro.

Comments and Discussions

 
QuestionSave Signature to File Pin
Member 39146266-May-21 5:00
Member 39146266-May-21 5:00 
QuestionWhy not use the provided API for saving ink? Pin
Josh Smith29-Nov-06 7:38
Josh Smith29-Nov-06 7:38 
AnswerRe: Why not use the provided API for saving ink? Pin
Eusebiu Marcu29-Nov-06 7:57
Eusebiu Marcu29-Nov-06 7:57 
GeneralRe: Why not use the provided API for saving ink? Pin
Josh Smith29-Nov-06 8:52
Josh Smith29-Nov-06 8:52 
Euthebiu wrote:
Hello... First of all...Thank you for your comment!


You're welcome.

Euthebiu wrote:
Second... I like to 'reinvent the wheel'... to do things my own way.


I can see the merit in that. You're a student, right? It's good to "roll your own" when you're learning things, but once you're out in the "real world" it's usually not feasible. Plus, custom solutions prohibit interacting/sharing with other applications. That's part of the reason there are standards committees.

Euthebiu wrote:
Third...when I started to play with InkCanvas/InkPresenter the main ideea was if it`s possible to safe the Ink Strokes into a DB.


You could just stream the ink strokes into a MemoryStream and push the resultant blob off to a db.

Euthebiu wrote:
What benefit does my tehnique have... well.. I really don`t know what is behind the "Save(FileStream)" to give you an exact answer.


That was just a nice way of saying "There is no need to do this!". Smile | :)


Euthebiu wrote:
I am sorry!


Don't be! You contributed something interesting! Big Grin | :-D

Euthebiu wrote:
Thanks again!


You're welcome, again!


:josh:
My WPF Blog[^]

GeneralRe: Why not use the provided API for saving ink? Pin
samgeo2-Mar-07 18:40
samgeo2-Mar-07 18:40 
GeneralRe: Why not use the provided API for saving ink? [modified] Pin
Member 38365558-Jul-08 6:57
Member 38365558-Jul-08 6:57 
GeneralRe: Why not use the provided API for saving ink? Pin
Gavin Rehkemper18-Jul-08 15:38
Gavin Rehkemper18-Jul-08 15:38 
GeneralRe: Why not use the provided API for saving ink? Pin
Member 1462323315-Oct-19 1:39
Member 1462323315-Oct-19 1:39 

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

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