Undo and redo annotations

Nutrient UWP SDK supports the undo and redo functionality when working with annotations. Users can undo and redo their changes using either the corresponding built-in toolbar items — undo and redo — or the standard shortcut key combinations Control-Z (undo), and Control-Y (redo).

The undo and redo toolbar buttons are hidden by default. To display them in the toolbar, use the pdfView.SetToolbarItemsAsync() API method.

To enable the undo and redo functionality, turn on the History API in one of the following ways:

Once the History API is enabled, you can disable it in one of the following ways:

This example enables the History API in XAML and adds the default undo and redo buttons in the event handler for InitializationCompletedHandler:

<ui:PdfView Name="PDFView"
            Grid.Row="0" IsHistoryEnabled="True"
            License="{StaticResource PSPDFKitLicense}"
            PdfUriSource="ms-appx:///Assets/pdfs/PSPDFKit.pdf"
            InitializationCompletedHandler="{x:Bind _viewModel.OnPDFViewInitializationCompleted}"/>
// In the `viewModel`.

internal async void OnPDFViewInitializationCompleted(PdfView sender, Document args)
{
   IList<IToolbarItem> toolbarItems = sender.GetToolbarItems();
   toolbarItems.Add(new UndoToolbarItem());
   toolbarItems.Add(new RedoToolbarItem());

   await sender.SetToolbarItemsAsync(toolbarItems);
}

Controlling undo and redo using the API

In addition to the dependency property mentioned above, the History API includes the following methods to perform different tasks related to this feature:

  • History.UndoAsync — Rolls back the last annotation change — either a creation, an update, or a deletion — performed with the UI or the API. It returns a Task<bool> instance that resolves to true when the operation succeeds. Otherwise, it returns false — for example, when there are no operations left to be undone.

This example uses the API to create an annotation, moves it, and then undoes the move:

var annotation = new Note
{
    Contents = "PSPDFKit is awesome!",
    BoundingBox = new Rect(50,50,50,50)
};

await pdfView.Document.CreateAnnotationAsync(annotation);
Debug.WriteLine("Annotation created!");

createdAnnotation.BoundingBox = new Rect(100, 100, 50, 50);
await pdfView.Document.UpdateAnnotationAsync(createdAnnotation);
Debug.WriteLine("Annotation moved!");

await pdfView.Document.History.UndoAsync();
Debug.WriteLine("Annotation creation undone: annotation moved back to original position!");
  • History.RedoAsync — Repeats the annotation change rolled back by the last undo operation. It can be performed with either the UI or the API. It returns a Task<bool> instance that resolves to true when the operation succeeds. Otherwise, it returns false — for example, when there are no operations left to be done.

This example uses the API to create an annotation, delete it, undo the deletion, and delete it again by using the redo functionality:

var annotation = new Note
{
    Contents = "PSPDFKit is awesome!",
    BoundingBox = new Rect(50,50,50,50)
};
var createdAnnotation = await pdfView.Document.CreateAnnotationAsync(annotation);
Debug.WriteLine("Annotation created!");

await pdfView.Document.DeleteAnnotationAsync(createdAnnotation.Id);
Debug.WriteLine("Annotation deleted!");

await pdfView.Document.History.UndoAsync();
Debug.WriteLine("Annotation deletion undone: annotation restored!");

await pdfView.Document.History.RedoAsync();
Debug.WriteLine("Annotation deletion redone: annotation deleted!");
Information

To redo an operation, an undo operation is required first.

  • History.CanUndoAsync — Returns true when there are operations that can be undone. Otherwise, it returns false:

    if (pdfView.Document.History.CanUndoAsync()) {
    	pdfView.Document.History.UndoAsync();
    }
  • History.CanRedoAsync — Returns true when there are operations that can be redone. Otherwise, it returns false:

    if (pdfView.Document.History.CanRedoAsync()) {
    	pdfView.Document.History.RedoAsync();
    }
  • History.ClearAsync — Resets the list of possible operations to be undone and redone:

    var history = pdfView.Document.History;
    Debug.WriteLine(await history.CanUndoAsync()); // `false`
    Debug.WriteLine(await history.CanRedoAsync()); // `false`
    
    var annotation = new Note
    {
       Contents = "PSPDFKit is awesome!",
       BoundingBox = new Rect(50, 50, 50, 50)
    };
    var createdAnnotation = await pdfView.Document.CreateAnnotationAsync(annotation);
    Debug.WriteLine("Annotation created!");
    
    await pdfView.Document.DeleteAnnotationAsync(createdAnnotation.Id);
    Debug.WriteLine("Annotation deleted!");
    
    Debug.WriteLine(await history.CanUndoAsync()); // `true`
    Debug.WriteLine(await history.CanRedoAsync()); // `false`
    
    await history.UndoAsync();
    
    Debug.WriteLine(await history.CanUndoAsync()); // `true`
    Debug.WriteLine(await history.CanRedoAsync()); // `true`
    
    await history.ClearAsync();
    
    Debug.WriteLine(await history.CanUndoAsync()); // `false`
    Debug.WriteLine(await history.CanRedoAsync()); // `false`
Information

Any creation, update, or deletion performed either with the UI or the API that isn’t a result of undoing or redoing will clear the list of possible operations.

Tracking history events

Undo and redo operations can be tracked by listening to the following events:

  • UndoEvent occurs after performing an undo operation, either with the API or with the UI.

  • RedoEvent occurs after performing a redo operation, either with the API or with the UI.

  • HistoryChanged occurs after performing an undo or a redo operation, either with the API or with the UI.

The event handlers for the above events will receive a History object that raised the event, along with the HistoryEventArgs object:

public sealed class HistoryEventArgs
{
    /// <summary>
    /// Previous state of the annotation, or `null` if it's being restored.
    /// </summary>
    public IAnnotation Before { get; }

    /// <summary>
    /// Resulting state of the annotation, or `null` if it's being deleted.
    /// </summary>
    public IAnnotation After { get; }

    /// <summary>
    /// Action that resulted in the event i.e. `HistoryAction.Undo` or `HistoryAction.Redo/>`.
    /// </summary>
    public HistoryAction Action { get; }
}
  • Cleared occurs after the history is cleared by the History.ClearAsync API call.

Unlike the event handlers of other events, event handlers of the Cleared event only receive the corresponding History object, and the EventArgs parameters are set to null.

You can subscribe or unsubscribe to these events at any moment — even when the History API is disabled.

Exceptions

Here’s a description of how the behavior of undo and redo may affect different elements of the SDK:

  • Annotation Z Order — The original annotation Z order won’t be restored for annotations that are deleted and later restored with undo. Those annotations will be recreated in the foreground.

  • Annotation Presets — Annotation presets aren’t affected by undo and redo operations, and they won’t be restored to a previous or following state as a result of undo or redo.