TX Text Control .NET for Windows Forms

Track Changes

Topic

Contracts, reports and other documents that several people revise need a record of who changed what. With track changes turned on, the TextControl marks every insertion and deletion with its author and time, and a reviewer later accepts or rejects each change, in the ribbon or in your code. The sections below cover both ways, from turning tracking on to reacting to new changes.

The code goes into Form1.cs of a form with a TextControl named textControl1, such as the one of Create Your First Windows Forms Application.

How Track Changes Works

While tracking is on, the editor records each change as a tracked change, marks it and assigns it to the current user. Inserted text is shown in color, deleted text in color and struck through. The color depends on the author; changes of the current user are red by default.

A tracked change stays in the document until someone accepts or rejects it. Accepting keeps the change and removes the mark. Rejecting undoes it: inserted text is removed and deleted text comes back. Turning tracking off only stops the recording of new changes; changes already recorded stay marked.

Tracked changes are saved with the document and loaded again in the TX Text Control format (TX), RTF, DOC and DOCX. Changes that MS Word recorded in a DOCX file appear in the editor as tracked changes as well.

Some complex operations can't be tracked, for example deleting a table column or merging table cells. In this case, the editor asks the user in a message box whether to make the change anyway. Handle Changes That Can't Be Tracked shows how to decide this in code.

Review Changes in the Ribbon

The Proofing tab of the ribbon, a RibbonProofingTab, has a Track Changes group. Its Track Changes button turns tracking on and off, Accept and Reject handle the current change or all changes, and Previous and Next move between the changes. The Tracked Changes split button opens the Tracked Changes sidebar, which lists every change with its kind, text, author and time:

TX Text Control Words with inserted text shown in red and underlined, and the Tracked Changes sidebar on the right listing one Text Inserted change by the user John with date and time.

The button needs a sidebar to open. Connect one with the TrackedChangesSidebar property of the tab, in the Properties window or in code, as Sidebar and Panels describes. Create a Ribbon Application with Sidebar Support sets up such a form step by step.

Without a ribbon, the TrackedChangesDialog shows the same list in a modeless dialog box, in which the user jumps to a change and accepts or rejects it:

private void trackedChangesDialogButton_Click(object sender, EventArgs e)
{
    TrackedChangesDialog dialog = new TrackedChangesDialog(textControl1);
    dialog.Show();
}

Set the Author and Turn On Tracking

The author of a change is the current user: the first name in UserNames. When the list is empty, changes are recorded for an unknown user. IsTrackChangesEnabled turns tracking on and off.

The following code sets both when the form opens:

using System;
using System.Windows.Forms;
using TXTextControl;

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        Load += Form1_Load;
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        // The first name is the author of new changes and comments
        textControl1.UserNames = new string[] { "Tim Typer" };

        // Record all insertions and deletions from now on
        textControl1.IsTrackChangesEnabled = true;
    }
}

Keep the namespace that Visual Studio created around the class. Set UserNames from the signed-in user of your application, so that each change shows who made it. The same list decides who may edit which regions of a protected document, as Document Protection and Editable Regions explains.

List the Tracked Changes

TrackedChanges returns a TrackedChangeCollection with the changes of the text part that has the input focus: the main text, a header, a footer or a text frame. TextParts returns all text parts, each an IFormattedText with a TrackedChanges collection of its own. The following button handler shows all changes in a message box:

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

    foreach (TrackedChange change in textControl1.TrackedChanges)
    {
        // ChangeTime is UTC; show it in local time
        report.AppendLine($"{change.Number}. {change.ChangeKind} by {change.UserName}, " +
            $"{change.ChangeTime.ToLocalTime()}: {change.Text}");
    }

    MessageBox.Show(report.ToString(), "Tracked Changes");
}

Each TrackedChange describes one change:

  • ChangeKind is InsertedText or DeletedText.
  • UserName is the author, null or an empty string for an unknown user.
  • ChangeTime is the date and time of the change, in Coordinated Universal Time (UTC).
  • Text is the changed text, and Start and Length give its position. Start is one-based, unlike the zero-based positions of a selection.
  • Number counts the changes in the order of the text, starting at 1. The indexer of the collection takes this number.

Accept and Reject Changes

TrackedChangeCollection.Remove is the only way to remove a tracked change: with true, it accepts the change, with false, it rejects it. GetItem without a parameter returns the change at the input position, or null:

private void acceptButton_Click(object sender, EventArgs e)
{
    // The change at the input position, or null
    TrackedChange change = textControl1.TrackedChanges.GetItem();

    if (change != null)
    {
        textControl1.TrackedChanges.Remove(change, true);
    }
}

private void RemoveAllChanges(bool accept)
{
    // Every call removes one change, so always take the first one
    while (textControl1.TrackedChanges.Count > 0)
    {
        if (!textControl1.TrackedChanges.Remove(textControl1.TrackedChanges[1], accept))
        {
            break;
        }
    }
}

The code works as follows:

  • acceptButton_Click accepts the change the user has clicked into.
  • RemoveAllChanges(true) accepts all changes of the text part, and RemoveAllChanges(false) rejects them. It takes the first change until none is left, because each removal renumbers the rest. Remove returns false if a change can't be removed, which ends the loop.

Move Through the Changes

GetItem(true) returns the next change after the input position, and GetItem(false) the previous one. Select selects the changed text, and ScrollTo moves the input position to it and scrolls it into view:

private void nextChangeButton_Click(object sender, EventArgs e)
{
    TrackedChange next = textControl1.TrackedChanges.GetItem(true);

    if (next != null)
    {
        next.ScrollTo();
        next.Select();
    }
}

Handle Changes That Can't Be Tracked

Before an operation that can't be tracked, the editor raises CannotTrackChange. Without a handler, a message box asks the user. The handler receives CannotTrackChangeEventArgs and can decide itself. The following handler never allows a change that would not be tracked:

private void textControl1_CannotTrackChange(object sender, CannotTrackChangeEventArgs e)
{
    // Don't change the document, and don't show the message box
    e.Cancel = true;
    e.Handled = true;
}
  • Handled set to true suppresses the message box. It is false by default.
  • Cancel is true by default and cancels the operation. Set it to false to change the document without tracking the change.
  • DefaultMessage is the text of the message box, for a dialog of your own.

Connect the handler in the Properties window: select textControl1, click the Events button and double-click CannotTrackChange. Or connect it in code with textControl1.CannotTrackChange += textControl1_CannotTrackChange; in the constructor after InitializeComponent.

React to Tracked Changes

The TextControl raises four events for tracked changes. Each handler receives TrackedChangeEventArgs, whose TrackedChange property is the change:

EventOccurs when
TrackedChangeCreatedA change has been recorded
TrackedChangeDeletedA change has been accepted or rejected
TrackedChangeChangedThe text of a change has been altered
TrackedChangeStateChangedA change has become active or inactive

The following handler shows the latest change in the title bar of the form:

private void textControl1_TrackedChangeCreated(object sender, TrackedChangeEventArgs e)
{
    Text = $"{e.TrackedChange.ChangeKind} by {e.TrackedChange.UserName}";
}

Events explains how event handlers are connected and lists the other events of the editor.

Change How Changes Are Highlighted

A TrackedChange has properties for its appearance:

  • HighlightColor is a System.Drawing.Color. When it is DefaultHighlightColor, changes of other users get a color that depends on the user name.
  • HighlightMode is HighlightMode.Always or HighlightMode.Never.
  • Active set to false deactivates a change: it is no longer highlighted, and GetItem skips it.

To process the tracked changes of a whole document in one step, for example to accept the changes of one reviewer in every text part or to accept all changes before a final version is saved, see Comments and Tracked Changes. A ServerTextControl has the same collections for documents that no user opens. The blog article Inspect and Process Track Changes in DOCX Documents with TX Text Control with .NET C# filters such changes by author, kind and date.

Next Steps