The InkCanvas in UWP only got pens by default, it can not perform Undo or Redo. To implement this, we will need to code for ourselves. Official document covered Undo functionalilty, but not redo. Today, I have successfully done it, and I'd like to share with you.

First, you need to add two custom buttons on the InkToolbar for Undo / Redo

<InkToolbarCustomToolButton Name="undoButton" Click="Undo_Click" ToolTipService.ToolTip="Undo">
    <SymbolIcon Symbol="Undo"/>
</InkToolbarCustomToolButton>
<InkToolbarCustomToolButton Name="redoButton" Click="Redo_Click" ToolTipService.ToolTip="Redo">
    <SymbolIcon Symbol="Redo"/>
</InkToolbarCustomToolButton>

1. Undo the Ink


We need a few APIs. To undo a stroke, is to delete a stroke on the canvas, the API is:

InkCanvas.InkPresenter.StrokeContainer.DeleteSelected()

Fo redo, it is actually re-write a stroke on the canvas, API is:

InkCanvas.InkPresenter.StrokeContainer.AddStroke()

To get all the storkes on canvas:

IReadOnlyList<InkStroke> strokes = InkCanvas.InkPresenter.StrokeContainer.GetStrokes();

To the last storke will be:

strokes[strokes.Count - 1]

Then we can achieve the undo functionality like this:

public void UndoLastStorke()
{
    IReadOnlyList<InkStroke> strokes = InkCanvas.InkPresenter.StrokeContainer.GetStrokes();
    if (strokes.Count > 0)
    {
        strokes[strokes.Count - 1].Selected = true;
        InkCanvas.InkPresenter.StrokeContainer.DeleteSelected();
    }
}

2. Redo the Ink


To implement Redo. The most easy way is to make use of the data structure knowedlege we learned in college. That is, by using a Stack to store the deleted stroke.

Let's review the basics of the Stack fist:

In computer science, a stack is an abstract data type that serves as a collection of elements, with two principal operations: push, which adds an element to the collection, and pop, which removes the most recently added element that was not yet removed. The order in which elements come off a stack gives rise to its alternative name, LIFO (for last in, first out). Additionally, a peekoperation may give access to the top without modifying the stack.

Because of it's LIFO feature, we can push() the last stroke into the stack when undo the ink, and pop() will return the last undone stroke.

In this case, we need to define and initialize a Stack object:

public Stack<InkStroke> UndoStrokes { get; set; }
UndoStrokes = new Stack<InkStroke>();

Modify the UndoLastStorke() method, push the ink into stack before delete it:

public void UndoLastStorke()
{
    IReadOnlyList<InkStroke> strokes = InkCanvas.InkPresenter.StrokeContainer.GetStrokes();
    if (strokes.Count > 0)
    {
        strokes[strokes.Count - 1].Selected = true;
        UndoStrokes.Push(strokes[strokes.Count - 1]); // 入栈
        InkCanvas.InkPresenter.StrokeContainer.DeleteSelected();
    }
}

So, when we redo the ink, just use Pop() to get the last stroke:

var stroke = UndoStrokes.Pop();

However, to redraw the ink, we can not do it straight forword like this:

InkCanvas.InkPresenter.StrokeContainer.AddStroke(stroke);

It will blow up your App.

We have to use InkStrokeBuilder to rebuild the last stroke and draw it agin:

public void RedoLastStorke()
{
    if (UndoStrokes.Count > 0)
    {
        var stroke = UndoStrokes.Pop();

        // This will blow up sky high:
        // InkCanvas.InkPresenter.StrokeContainer.AddStroke(stroke);

        var strokeBuilder = new InkStrokeBuilder();
        strokeBuilder.SetDefaultDrawingAttributes(stroke.DrawingAttributes);
        System.Numerics.Matrix3x2 matr = stroke.PointTransform;
        IReadOnlyList<InkPoint> inkPoints = stroke.GetInkPoints();
        InkStroke stk = strokeBuilder.CreateStrokeFromInkPoints(inkPoints, matr);
        InkCanvas.InkPresenter.StrokeContainer.AddStroke(stk);
    }
}