TX Text Control ActiveX

Track Changes

Topic

When several people work on a contract or a report, each of them wants to see what the others changed before accepting it. With track changes turned on, TXTextControl keeps every insertion and deletion in the document as a tracked change, marked up on the screen with the name of the user who made it and the time, until someone accepts or rejects it.

Track changes is available since TX Text Control ActiveX 26.0. The Visual Basic 6 code below turns tracking on, walks through the tracked changes of a document and accepts or rejects them, much like the Review Changes dialog box of the TXWords sample program does.

How Track Changes Works

The IsTrackChangesEnabled property switches tracking on and off. By default it is False. While it is True, the control marks up each new change of the text:

  • Kinds of changes. A tracked change is either inserted text or deleted text. Inserted text is shown in color, deleted text in color and struck through. The color depends on the user; the default color for the current user is red.
  • User and time. Each change belongs to a user and has a time stamp. The user is the current user, the first name of the list that the UserNames property holds. When the list is empty, the user name is an empty string, which stands for an unknown user.
  • Identifiers. Every tracked change has a numeric identifier of the data type Long. All members that read or remove a change take this identifier.
  • Removing changes. A tracked change stays in the document until it is accepted or rejected. Turning tracking off stops the control from marking up new changes, but the changes that are already tracked remain.
  • File formats. Tracked changes are saved and loaded with the TX Text Control format (TX), RTF and the Microsoft Word formats DOC and DOCX. Loading a document that contains tracked changes creates them in the control.

A tracked insertion, shown in red and underlined, and a tracked deletion, shown in red and struck through, in a TXTextControl document.

Some complex changes of a table's layout, such as deleting a table column or merging table cells, cannot be tracked. For these, the control asks the user whether to make the change anyway; Handle Changes That Cannot Be Tracked shows how to take over that decision.

Turn Track Changes On and Off

In TXWords, the Edit menu has a Reviewing submenu with the Track Changes item. Its handler flips the check mark and sets IsTrackChangesEnabled to match:

Private Sub mnuEdit_Reviewing_TrackChanges_Click()
    mnuEdit_Reviewing_TrackChanges.Checked = Not mnuEdit_Reviewing_TrackChanges.Checked
    TXTextControl1.IsTrackChangesEnabled = mnuEdit_Reviewing_TrackChanges.Checked
End Sub

When the Edit menu opens, TXWords sets the check mark from the property, so the menu shows the current state, and enables Review Changes only when the document contains a tracked change:

mnuEdit_Reviewing_TrackChanges.Checked = TXTextControl1.IsTrackChangesEnabled
mnuEdit_Reviewing_ReviewChanges.Enabled = (TXTextControl1.TrackedChangeNext(0) <> 0)

The TXWords Edit menu with the Reviewing submenu open, where Track Changes is checked above Review Changes and Comments.

IsTrackChangesEnabled can also be set at design time in the Properties window of Visual Basic.

Set the Name of the Reviewer

A tracked change takes its user name from the first entry of UserNames. The property is a string in which the names are separated by a | character. Set it before the user starts editing, for example in Form_Load:

Private Sub Form_Load()
    ' The first name is the current user
    TXTextControl1.UserNames = "Jane Smith"
    TXTextControl1.IsTrackChangesEnabled = True
End Sub

Every change typed after this code runs is tracked with the name Jane Smith. UserNames can be set at run time only. It also decides which users may edit which parts of a protected document, which Document Protection describes.

List the Tracked Changes

The TrackedChangeNext method enumerates the tracked changes in the order of the text. Called with zero, it returns the identifier of the first change; called with an identifier, it returns the one that follows. It returns zero when there are no more changes.

The following procedure prints each change to the Immediate window of Visual Basic:

Private Sub ListTrackedChanges()
    Dim id As Long
    Dim kind As String
    Dim reviewer As String

    id = TXTextControl1.TrackedChangeNext(0)
    Do While id <> 0
        ' 1 is inserted text, 2 is deleted text
        If TXTextControl1.TrackedChangeKind(id) = 1 Then
            kind = "Inserted"
        Else
            kind = "Deleted"
        End If
        reviewer = TXTextControl1.TrackedChangeUserName(id)
        If reviewer = "" Then reviewer = "Unknown Reviewer"
        Debug.Print reviewer & " " & kind & " characters " & _
            TXTextControl1.TrackedChangeStart(id) & " to " & _
            TXTextControl1.TrackedChangeEnd(id) & _
            " (" & TXTextControl1.TrackedChangeTime(id) & ")"
        id = TXTextControl1.TrackedChangeNext(id)
    Loop
End Sub

The code works as follows:

  • TrackedChangeKind returns 1 for inserted text and 2 for deleted text.
  • TrackedChangeUserName returns the name of the user who made the change, or an empty string for an unknown user. TXWords shows "Unknown Reviewer" in this case.
  • TrackedChangeStart and TrackedChangeEnd return the first and the last character of the change as one-based indexes.
  • TrackedChangeTime returns the date and time of the change as a Date in Coordinated Universal Time (UTC).

These four properties are read-only and available at run time only.

Select a Tracked Change

To show a change to the user, select its text. The SelStart property counts from 0, while TrackedChangeStart and TrackedChangeEnd count from 1, so TXWords subtracts 1 from both:

Private Sub SelectTrackedChange(ByVal changeId As Long)
    Dim posStart As Long
    Dim posEnd As Long

    posStart = TXTextControl1.TrackedChangeStart(changeId) - 1
    posEnd = TXTextControl1.TrackedChangeEnd(changeId) - 1
    TXTextControl1.SelStart = posStart
    TXTextControl1.SelLength = posEnd - posStart + 1
End Sub

SelLength is the number of selected characters, which is the distance between the two positions plus one.

Accept and Reject Changes

The TrackedChangeDelete method is the only way to remove a tracked change. Its second parameter decides what happens to the text:

  • True accepts the change. The markup is removed and the text stays as it is.
  • False rejects the change. Inserted text is removed and deleted text is inserted again.

The method returns True when the change could be removed. To accept or reject every change, TXWords asks for the first change again after each call, because the removed change no longer has a successor:

Private Sub DeleteAllTrackedChanges(ByVal accept As Boolean)
    Dim id As Long
    Do
        id = TXTextControl1.TrackedChangeNext(0)
        If id <> 0 Then TXTextControl1.TrackedChangeDelete id, accept
    Loop While id <> 0
End Sub

Call DeleteAllTrackedChanges True to accept all changes and DeleteAllTrackedChanges False to reject them.

Build a Review Dialog Box

The Review Changes dialog box of TXWords, in frmReviewTrackedChanges.frm, combines the procedures above. Edit > Reviewing > Review Changes opens it as a modal form, after checking that TrackedChangeNext(0) is not zero; otherwise, a message box says that the document does not contain any tracked changes. The dialog box works like this:

  • The current change. On loading, the form takes the first change with TrackedChangeNext(0), selects its text in the document and shows the user name and the kind above a preview and the time below it.
  • The preview. The form holds a second TXTextControl with its EditMode set to 2 (read only). The form copies the selected text of the document into it with SaveToMemory and LoadFromMemory, and copies the UserNames of the document too.
  • The buttons. Accept and Reject call TrackedChangeDelete for the current change and go to the next one; the form closes when none is left. Accept All and Reject All run the loop of the previous section. Next moves on without a decision, and starts at the first change again after the last one.

The Review Changes dialog box of TXWords showing an inserted text of an unknown reviewer with its time stamp and the buttons Next, Accept, Reject, Accept All, Reject All and Close.

This is how the form fills the preview:

Dim data() As Byte
data = tx.SaveToMemory(8, True)
txChange.LoadFromMemory data, 8

tx is the TXTextControl of the main form, which the menu handler passes to the dialog box, and txChange is the preview control. Format 8 is the internal Text Control format with Unicode text, and the second parameter True of SaveToMemory saves only the current selection.

Handle Changes That Cannot Be Tracked

When the user makes a change that cannot be added to the list of tracked changes, such as deleting a table column, the CannotTrackChange event occurs. By default, the control then shows a message box that asks the user whether to make the change.

To decide in code, set the Handled parameter to True, which suppresses the message box, and set Cancel to False to make the change or to True to cancel it. The following handler refuses all such changes without asking:

Private Sub TXTextControl1_CannotTrackChange(Cancel As Boolean, Handled As Boolean)
    ' Keep the document unchanged and skip the default message box
    Cancel = True
    Handled = True
End Sub

Cancel is True by default, so leaving it unchanged and setting only Handled has the same effect.

React to Tracked Changes

Three events report changes of the list of tracked changes. Each passes the identifier of the change:

The following handler updates a label on the form with the name of the user who made the latest change:

Private Sub TXTextControl1_TrackedChangeCreated(ByVal TrackedChangeId As Long)
    lblLastChange.Caption = "Last change by " & _
        TXTextControl1.TrackedChangeUserName(TrackedChangeId)
End Sub

lblLastChange is a Label control that you add to the form.

Key Members

MemberDescription
IsTrackChangesEnabledTurns tracking of new changes on or off.
UserNamesHolds the list of users; the first name is the user of new changes.
TrackedChangeNextReturns the identifier of the first or the next tracked change.
TrackedChangeKindReturns whether a change is inserted or deleted text.
TrackedChangeStart, TrackedChangeEndReturn the first and the last character of a change.
TrackedChangeUserName, TrackedChangeTimeReturn the user and the UTC time of a change.
TrackedChangeDeleteAccepts or rejects a change and removes it.
CannotTrackChangeOccurs for a change that cannot be tracked.
TrackedChangeCreated, TrackedChangeChanged, TrackedChangeDeletedOccur when a change is created, altered or removed.

The programming category Tracking Document Changes lists all members for tracked changes.