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);
}
}
然
这个功能用来画画真是太贴心了 另外发现一个小问题,使用触控笔的顶部橡皮擦功能后,无法实现重做功能
www
Thanks
Yevhen
Great work! How to implement buttons availability depending from executed actions (inking, undo, redo)?
Sunjay Kalsi
Thanks for this post really appreciated it!! Keep up the good work :o)
jsera
Or just use stroke.Clone();
javier
I wanted to ask if part of the code goes ViewModel for example: RedoLastStorke with UndoLastStorke