WPF

Navigate PDF Annotations in a TreeView Using WPF PDF Viewer

TL;DR: Annotations in PDFs are like digital sticky notes, helpful for comments and information. This blog discusses organizing them effectively using a tree view grouped by pages, making navigation smooth with Syncfusion WPF PDF Viewer.

Annotations are invaluable tools for adding comments, notes, and other relevant information to PDF documents. When managing these annotations, organizing them in a structured manner can significantly enhance navigation and usability. 

In this blog, we’ll explore how to efficiently showcase PDF annotations in a tree view, grouped by the pages they belong to, and seamlessly navigate through them using the Syncfusion WPF PDF Viewer control.

Let’s get started!

Create a WPF application and add dependencies

First, create a new WPF application and install the following NuGet packages in it:

Organize PDF Viewer and TreeView

Let’s create a main content page in the MainWindow.xaml file and add the Syncfusion WPF PDF Viewer and WPF Framework’s TreeView as the children to it. Then, add the event handler for the TreeView’s SelectedItemChanged event.

Refer to the following code example.

<Window
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
       xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
       xmlns:local="clr-namespace:WPF_PDFViewer_Annotations"
       xmlns:PdfViewer="clr-namespace:Syncfusion.Windows.PdfViewer;assembly=Syncfusion.PdfViewer.WPF" xmlns:syncfusion="http://schemas.syncfusion.com/wpf" x:Class="WPF_PDFViewer_Annotations.MainWindow"
       mc:Ignorable="d"
       Title="MainWindow" Height="450" Width="800" WindowState="Maximized">
 <Grid>
  <Grid.ColumnDefinitions>
   <ColumnDefinition Width="*"/>
   <ColumnDefinition Width="250"/>
  </Grid.ColumnDefinitions>
  <PdfViewer:PdfViewerControl x:Name="pdfViewer"/>
  <TreeView x:Name="treeView" Grid.Column="1" SelectedItemChanged="treeView_SelectedItemChanged"/>
 </Grid>
</Window>

In the MainWindow.xaml.cs file, let’s set the theme for Syncfusion WPF PDF Viewer control and trigger the DocumentLoaded event with the AnnotationChanged event for all supported annotations.

Refer to the following code example.

public MainWindow()
{
    SfSkinManager.SetTheme(this, new Theme("FluentLight"));
    InitializeComponent();
    pdfViewer.DocumentLoaded += PdfViewer_DocumentLoaded;
    pdfViewer.FreeTextAnnotationChanged += PdfViewer_FreeTextAnnotationChanged;
    pdfViewer.StickyNoteAnnotationChanged += PdfViewer_StickyNoteAnnotationChanged;
    pdfViewer.InkAnnotationChanged += PdfViewer_InkAnnotationChanged;
    pdfViewer.TextMarkupAnnotationChanged += PdfViewer_TextMarkupAnnotationChanged;
    pdfViewer.ShapeAnnotationChanged += PdfViewer_ShapeAnnotationChanged;
    pdfViewer.StampAnnotationChanged += PdfViewer_StampAnnotationChanged;
    pdfViewer.Load("../../Data/Sample Document.pdf");
    treeView.Background = new SolidColorBrush(Color.FromRgb(235,235,238));
}

After loading the document information in the DocumentLoaded event, add the supported annotation details with the respective page in the TreeView.

Refer to the following code example.

private void PdfViewer_DocumentLoaded(object sender, EventArgs args)
{
    PdfLoadedDocument loadedDocument = pdfViewer.LoadedDocument;
    treeView.Items.Clear();
    for (int i = 0; i < loadedDocument.Pages.Count; i++)
    {
        TreeViewItem viewItem = new TreeViewItem();
        if (loadedDocument.Pages[i].Annotations.Count > 0)
        {
            viewItem.Header = "PAGE - " + (i + 1);
            viewItem.FontSize = 16;
            treeView.Items.Add(viewItem);
            viewItem.IsExpanded = true;
            for (int j = 0; j < loadedDocument.Pages[i].Annotations.Count; j++)
            {
                PdfLoadedAnnotation annotation = loadedDocument.Pages[i].Annotations[j] as PdfLoadedAnnotation;
                if (annotation is PdfLoadedInkAnnotation || annotation is PdfLoadedTextMarkupAnnotation || annotation is PdfLoadedEllipseAnnotation
                    || annotation is PdfLoadedLineAnnotation || annotation is PdfLoadedRectangleAnnotation || annotation is PdfLoadedSquareAnnotation
                    || annotation is PdfLoadedCircleAnnotation || annotation is PdfLoadedFreeTextAnnotation || annotation is PdfLoadedRubberStampAnnotation
                    || annotation is PdfLoadedPopupAnnotation || annotation is PdfLoadedPolygonAnnotation || annotation is PdfLoadedPolyLineAnnotation)
                {
                    string name = annotation.ToString();
                    string[] annotNames = name.Split('.');
                    TreeViewItem childItem = new TreeViewItem();
                    childItem.Header = annotation.Type.ToString();
                    childItem.Tag = annotation;
                    viewItem.Items.Add(childItem);
                }
            }
            if (viewItem.Items.Count == 0)
                treeView.Items.Remove(viewItem);
        }
    }
}

While adding/removing the annotations, we need to update the respective annotation data in the treeview. To do so, we will use the AnnotationChanged event to update the treeview values.

Refer to the following code example.

private void PdfViewer_InkAnnotationChanged(object sender, InkAnnotationChangedEventArgs e)
{
    PerformAddorRemoveAnnotations("InkAnnotation",e.Name,e.Action,e.PageNumber);
}

private void PerformAddorRemoveAnnotations(string name ,string annotationName ,AnnotationChangedAction action ,int annotationPageNumber)
{
    if (action == AnnotationChangedAction.Add)
    {
        PdfLoadedDocument lDoc = pdfViewer.LoadedDocument;
        if (treeView.Items.Count > 0)
        {
            for (int i = 0; i < treeView.Items.Count; i++)
            {
                string[] page = (treeView.Items[i] as TreeViewItem).Header.ToString().Split(' ');
                int pageNumber = int.Parse(page[page.Length - 1]);
                if (pageNumber == annotationPageNumber)
                {
                    TreeViewItem item = treeView.Items[i] as TreeViewItem;
                    TreeViewItem childItem = new TreeViewItem();
                    for (int j = 0; j < lDoc.Pages[annotationPageNumber - 1].Annotations.Count; j++)
                    {
                        if (lDoc.Pages[annotationPageNumber - 1].Annotations[j].Name == annotationName)
                        {
                            childItem.Header = name;
                            childItem.Tag = lDoc.Pages[annotationPageNumber - 1].Annotations[j];
                            break;
                        }
                    }
                    item.Items.Add(childItem);
                    break;
                }
                else if (pageNumber > annotationPageNumber)
                {
                    TreeViewItem item = new TreeViewItem();
                    item.Header = "PAGE - " + annotationPageNumber;
                    treeView.Items.Insert(i, item);
                    TreeViewItem childItem = new TreeViewItem();
                    for (int j = 0; j < lDoc.Pages[annotationPageNumber - 1].Annotations.Count; j++)
                    {
                        if (lDoc.Pages[annotationPageNumber - 1].Annotations[j].Name == annotationName)
                        {
                            childItem.Header = name;
                            childItem.Tag = lDoc.Pages[annotationPageNumber - 1].Annotations[j];
                            break;
                        }
                    }
                    item.Items.Add(childItem);
                }
            }
        }
        else
        {
            TreeViewItem item = new TreeViewItem();
            item.Header = "PAGE - " + annotationPageNumber;
            item.FontSize = 16;
            treeView.Items.Add(item);
            TreeViewItem childItem = new TreeViewItem();
            for (int j = 0; j < lDoc.Pages[annotationPageNumber - 1].Annotations.Count; j++)
            {
                if (lDoc.Pages[annotationPageNumber - 1].Annotations[j].Name == annotationName)
                {
                    childItem.Header = name;
                    childItem.Tag = lDoc.Pages[annotationPageNumber - 1].Annotations[j];
                    break;
                }
            }
            item.Items.Add(childItem);
        }
    }
    else if (action == AnnotationChangedAction.Remove)
    {
        for (int i = 0; i < treeView.Items.Count; i++)
        {
            string[] page = (treeView.Items[i] as TreeViewItem).Header.ToString().Split(' ');
            int pageNumber = int.Parse(page[page.Length - 1]);
            if (pageNumber == annotationPageNumber)
            {
                TreeViewItem item = treeView.Items[i] as TreeViewItem;
                for (int j = 0; j < item.Items.Count; j++)
                {
                    PdfLoadedAnnotation loadedAnnotation = (item.Items[j] as TreeViewItem).Tag as PdfLoadedAnnotation;
                    PdfAnnotation annotation = (item.Items[j] as TreeViewItem).Tag as PdfAnnotation;
                    if (loadedAnnotation != null)
                    {
                        if (annotationName == loadedAnnotation.Name)
                        {
                            item.Items.RemoveAt(j);
                            break;
                        }
                    }
                    else if (annotation != null)
                    {
                        if (annotationName == annotation.Name)
                        {
                            item.Items.RemoveAt(j);
                            break;
                        }
                    }
                }
                if (item.Items.Count == 0)
                {
                    treeView.Items.RemoveAt(i);
                }
                break;
            }
        }
    }
}

Implement navigation on annotation selection

Next, we will implement the code to select an annotation in the treeview that will navigate you to its corresponding annotation position in the PDF. PDF Viewer provides support to select and bring an annotation to view programmatically using the overload SelectAnnotation method with BringIntoView Parameter.

The annotation’s name and true value for BringIntoView should be passed as a parameter to select and bring an annotation into view, which should be handled in the SelectedItemChanged event of TreeView.

Implement the following code in the treeView_SelectedItemChanged method, which is present in the MainWindow.xaml.cs file.

private void treeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
    var selectedItem = treeView.SelectedItem as TreeViewItem;
    if (selectedItem != null && !selectedItem.Header.ToString().Contains("PAGE"))
    {
        PdfLoadedAnnotation loadedAnnotation = selectedItem.Tag as PdfLoadedAnnotation;
        PdfAnnotation annotation = selectedItem.Tag as PdfAnnotation;
        if (loadedAnnotation != null)
            pdfViewer.SelectAnnotation(loadedAnnotation.Name, true);
        else if (annotation != null)
            pdfViewer.SelectAnnotation(annotation.Name, true);
    }
}

Refer to the following image.

Viewing annotations in a tree view and navigating to them using Syncfusion WPF PDF Viewer

GitHub reference

Also, refer to view PDF annotations in the tree view and navigate to them using the Syncfusion WPF PDF Viewer demo on GitHub.

Conclusion

Thanks for reading! In this blog, we have seen how to view annotations in a PDF document using a tree view and easily navigate to them using Syncfusion WPF PDF Viewer control.  Try out the steps given in this blog and leave your feedback in the comments section given below.

The existing customers can download the latest version of Essential Studio from the License and Downloads page. If you are new, try our 30-day free trial to explore our incredible features. 

If you require assistance, please don’t hesitate to contact us via our support forumssupport portal, or feedback portal. We are always eager to help you!

Related blogs

Vikas S

Vikas S is a Product Manager at Syncfusion with specialized skills in File Format products, WinForms, WPF, and Xamarin controls. He began his career at Syncfusion in 2014 as a Software Developer and has since evolved into a technology enthusiast.