I am trying to implement an undo functionality in my app, part of the code that gets executed when the undo button is pushed is as follows :
if (this.threadStep > 0)
{
Debug.WriteLine($"thread remove");
this._AllCurrentLinesInCanvas.RemoveAt(this._AllCurrentLinesInCanvas.Count - 1);
_renderTargetDrawingSession.Clear(Colors.Transparent);
foreach (var line in this._AllCurrentLinesInCanvas)
{
_renderTargetDrawingSession.DrawInk(line);
}
this.threadStep -= 1;
}
After putting a debugger and then examining, I have found out that when this line gets executed :
this._AllCurrentLinesInCanvas.RemoveAt(this._AllCurrentLinesInCanvas.Count - 1);
not only is _AllCurrentLinesInCanvas
changing but another list that is not supposed to change is also being altered:
This is how the lists are constructed when a stroke is made :
private int threadStep = 0;
private bool isDeepInThread = true;
private int currentTimeline = 0;
private IReadOnlyList<InkStroke> SingleWetLine;
private List<IReadOnlyList<InkStroke>> _AllCurrentLinesInCanvas = new List<IReadOnlyList<InkStroke>>();
private List<List<IReadOnlyList<InkStroke>>> _DrawingHistoryThread = new List<List<IReadOnlyList<InkStroke>>>();
private List<List<List<IReadOnlyList<InkStroke>>>> Timelines = new List<List<List<IReadOnlyList<InkStroke>>>>();
private bool clearedByUndoing = false;
private void InkPresenter_StrokesCollected(InkPresenter sender, InkStrokesCollectedEventArgs args)
{
this.SingleWetLine = myInkCanvasObject.myInkSynchronizer.BeginDry();
myInkCanvasObject.myInkSynchronizer.EndDry();
this._AllCurrentLinesInCanvas.Add(this.SingleWetLine.ToList());
if (this.threadStep == this._DrawingHistoryThread.Count)
{
this._DrawingHistoryThread.Add(new List<IReadOnlyList<InkStroke>>(this._AllCurrentLinesInCanvas));
if (this.Timelines.Count == 0 || this.clearedByUndoing)
{
this.Timelines.Add(new List<List<IReadOnlyList<InkStroke>>>(this._DrawingHistoryThread));
this.currentTimeline = this.Timelines.Count;
this.clearedByUndoing = false;
}
else
{
this.Timelines[this.Timelines.Count - 1] = new List<List<IReadOnlyList<InkStroke>>>(this._DrawingHistoryThread);
}
this.isDeepInThread = true;
this.threadStep += 1;
}
else // create a new timeline when a fork occurs
{
Debug.WriteLine("Fork State Hit");
this._DrawingHistoryThread.Clear();
this._DrawingHistoryThread.Add(new List<IReadOnlyList<InkStroke>>(this._AllCurrentLinesInCanvas));
this.Timelines.Add(new List<List<IReadOnlyList<InkStroke>>>(this._DrawingHistoryThread));
this.isDeepInThread = true;
this.threadStep = 1;
this.currentTimeline = this.Timelines.Count;
}
myWin2DCanvas.Invalidate();
}
And this is the entire undo function
public void Undo(object sender, RoutedEventArgs e)
{
using (CanvasDrawingSession _renderTargetDrawingSession = myRenderTarget.CreateDrawingSession())
{
if (this.threadStep > 0)
{
Debug.WriteLine($"thread remove");
this._AllCurrentLinesInCanvas.RemoveAt(this._AllCurrentLinesInCanvas.Count - 1);
_renderTargetDrawingSession.Clear(Colors.Transparent);
foreach (var line in this._AllCurrentLinesInCanvas)
{
_renderTargetDrawingSession.DrawInk(line);
}
this.threadStep -= 1;
}
else if (this.currentTimeline > 1) // you are at a node
{
Debug.WriteLine("node remove");
this.currentTimeline -= 1;
this.threadStep = this.Timelines[this.currentTimeline - 1].Count;
this._AllCurrentLinesInCanvas.Clear();
Debug.WriteLine($"_All Current builder : {this.currentTimeline - 1}, {this.threadStep - 1}");
this._AllCurrentLinesInCanvas = this.Timelines[this.currentTimeline - 1][this.threadStep - 1];
_renderTargetDrawingSession.Clear(Colors.Transparent);
foreach (var line in this._AllCurrentLinesInCanvas)
{
_renderTargetDrawingSession.DrawInk(line);
}
this.threadStep -= 1;
}
else // at origin -> first line
{
Debug.WriteLine("Cleared through undoing");
_renderTargetDrawingSession.Clear(Colors.Transparent);
// clear everything
this.threadStep = 0;
this._AllCurrentLinesInCanvas.Clear();
this._DrawingHistoryThread.Clear();
this.clearedByUndoing = true;
}
}
myWin2DCanvas.Invalidate();
}
I think your problem is that the nested lists still have References to each other.
Here you don't make a deep copy of the _DrawingHistoryThread object. To make a deep copy you should use this:
(Untested code)