TX Text Control .NET for WPF

Comments

Topic

Reviewers leave notes on a draft without touching its wording, and the WPF TextControl stores such notes as comments compatible with MS Word. Users work with them through the context menu, the Proofing tab and a sidebar; your code reads, adds, answers and removes them through the CommentCollection and learns about every change from events.

The code samples use a TextControl named textControl1 in MainWindow.xaml, as in Create Your First WPF Application, with the button handlers in MainWindow.xaml.cs.

How Comments Work

Each comment is a commented text: a range of the document plus the comment string. On the screen, commented text is highlighted in a color that depends on its author, red for the current user. The author is the user who was current when the comment was added, the first name in UserNames, or an unknown user if the list is empty.

A comment can have replies from other users. They refer to the same range of text. When a comment is removed, its replies go with it, while the commented text stays in the document.

The TX Text Control format (TX), RTF, DOC and DOCX keep comments when a document is saved and restore them when it is loaded, including comments written in MS Word.

Comment Text as a User

With text selected, the user adds a comment from the context menu or with Add Comment on the Proofing tab. The comment goes into the document once the user closes the input box with a mouse click or the Enter key. Right-clicking a comment offers to edit it, delete it or reply to it. A Comments sidebar lists the comments of the document:

The TX Text Control Words sample for WPF with a comment balloon above the heading Protected Document and the Comments sidebar showing the comment, its author John and the date.

The Comments button of the RibbonProofingTab opens that sidebar when its CommentsSidebar property holds the name of a sidebar in the window; see Sidebar and Panels. An application without a ribbon shows the same list in the modeless CommentsDialog window of the TXTextControl.WPF namespace:

private void CommentsDialogButton_Click(object sender, RoutedEventArgs e)
{
    TXTextControl.WPF.CommentsDialog dialog = new TXTextControl.WPF.CommentsDialog(textControl1);
    dialog.Show();
}

Add Comments in Code

The Comments property returns a CommentCollection with the comments of the text part that has the input focus. Add inserts a comment given as a CommentedText object, or, without a parameter, opens the input box so that the user types the comment. CommentCollection and CommentedText are classes of the TXTextControl namespace:

using System.Windows;
using TXTextControl;

// ...

private void AddCommentButton_Click(object sender, RoutedEventArgs e)
{
    // Comment the selected text
    CommentCollection.AddResult result =
        textControl1.Comments.Add(new CommentedText("Please check this figure."));

    if (result == CommentCollection.AddResult.NoSelection)
    {
        MessageBox.Show("Select the text you want to comment on.");
    }
}

private void CommentHeadingButton_Click(object sender, RoutedEventArgs e)
{
    // Comment the first 20 characters; the start is one-based
    textControl1.Comments.Add(new CommentedText("Shorten the heading.", 1, 20));
}

What the code does:

  • A CommentedText created with the comment string only refers to the current selection.
  • Add reports the outcome as a value of CommentCollection.AddResult. Successful means the comment was inserted; NoSelection and SelectionTooComplex mean there was no selection or no continuous one.
  • The constructor with a start and a length comments a range without selecting it. The first character has the position 1.

Read and Answer Comments

Enumerating the collection returns all comments and replies. GetItem returns the comment at the input position, and GetItem(true) or GetItem(false) the next or previous one; each returns null if there is none:

private void ListCommentsButton_Click(object sender, RoutedEventArgs e)
{
    System.Text.StringBuilder report = new System.Text.StringBuilder();

    foreach (CommentedText comment in textControl1.Comments)
    {
        // Replies are listed below the comment they answer
        if (comment.RepliedComment != null)
        {
            continue;
        }

        report.AppendLine($"\"{comment.Text}\": {comment.Comment} " +
            $"({comment.UserName}, {comment.CreationTime.ToLocalTime()})");

        foreach (CommentedText reply in comment.Replies ?? new CommentedText[0])
        {
            report.AppendLine($"    {reply.UserName}: {reply.Comment}");
        }
    }

    MessageBox.Show(report.ToString(), "Comments");
}

private void ReplyButton_Click(object sender, RoutedEventArgs e)
{
    CommentedText comment = textControl1.Comments.GetItem();

    if (comment != null)
    {
        textControl1.Comments.Add(new CommentedText("Done.", comment));
    }
}
MemberValue
TextThe commented text
CommentThe comment itself; setting it changes the comment
UserName, CreationTimeThe author and the time of the comment in UTC
RepliesThe replies, or null without replies
RepliedCommentFor a reply, the comment it answers

A reply is a CommentedText created with the answered comment as its second parameter, and Add inserts it like any other comment.

Edit, Remove and Navigate

  • EditComment opens the input box with the comment, and ReplyToComment an input box for a reply. ReplyToComment returns false for a reply.
  • ScrollTo moves the input position to the commented text and scrolls it into view, and Select selects it.
  • Start and Length give the one-based position and the length of the commented text.
  • ID stores an identifier of your own, such as the key of the comment in a database.

CommentCollection.Remove deletes a comment and leaves its text in place:

private void DeleteCommentButton_Click(object sender, RoutedEventArgs e)
{
    CommentedText comment = textControl1.Comments.GetItem();

    if (comment != null)
    {
        textControl1.Comments.Remove(comment);
    }
}

Change the Highlighting

CommentHighlightMode decides when commented text is highlighted. It can be set in XAML, for example CommentHighlightMode="Always":

ValueCommented text is highlighted
ActivatedWhile it contains the input position (default)
AlwaysAlways
NeverNever

The HighlightMode and HighlightColor of a single comment override the setting for that comment. HighlightColor is a System.Drawing.Color. A comment whose Active property is false isn't highlighted, and GetItem doesn't return it.

React to Comments

Six events report comments, each with CommentEventArgs whose CommentedText is the comment concerned:

Connect a handler as an attribute of the control in MainWindow.xaml:

<WPF:TextControl x:Name="textControl1"
                CommentedTextEntered="TextControl1_CommentedTextEntered" />

The handler shows the comment at the input position in the title of the window:

private void TextControl1_CommentedTextEntered(object sender, CommentEventArgs e)
{
    Title = $"{e.CommentedText.UserName}: {e.CommentedText.Comment}";
}

Comments of stored documents, for example for an import into a database, are read without the editor through the Comments collection of a ServerTextControl. See Comments and Tracked Changes and the blog article Extracting Comments from DOCX Files in .NET C#.

Next Steps