(Print this page)

Comparing Text to highlight differences in .Net
Published date: Sunday, June 21, 2020
On: Moer and Éric Moreau's web site

In the last few weeks, I needed a feature that would compare the text of two side-by-side RichTextBox controls. I wanted the differences to be highlighted. I also wanted the RichTextBox controls to scroll in sync to ease the comparison.

I was not really surprised to find snippets of code here and there doing all that but nothing combining the 3 requirements all together.

The most complex task of these 3 is that text comparison. I found a free library dedicated for that task. This library called “Diff Match Patch” is made available by Google on their github page.

Figure 1: The demo application in action

Available source code

Both VB and C# projects are provided this month.

The solution was created using Visual Studio 2019 but should also work in earlier versions.

All the required source code is provided in the downloadable demo. It will not all be shown on this article. I strongly suggest that you download the source code if you want to build something like this.

Creating the class library

The class provided by Google to compare the text is provided in C# (as well as other languages but not in VB). Because the class is in C# and I really don’t feel like converting a class of 2687 lines of code into VB (and there is really no need for that), I have created a C# class library named DiffMatchPatch in my solution that contains this class.

I will use this class library from both my VB and C# demo projects.

I strongly suggest that you download the DiffMatchPatch class from the Google github page. This way, if there is an update, you will get the latest bits. It is a simple C# class that you need to copy into your project. Get it from https://github.com/google/diff-match-patch/tree/master/csharp.

To get this project to compile, you will need to add a reference to System.Web because the class is using HttpUtility.

To this same class library, I have added a Component item named cRichTextBox. This component contains an extension to the RichTextBox control that will let us support the “scroll in synch” feature easily.

Better compile this class library project to ensure everything needed is found. This will also add the cRichTextBox to your Toolbox for you to drag it to a form.

The demo projects

Now that we have a class library that contains the most important part, we can go ahead and create the 2 demo projects (because I want to show both VB and C# usage).

To these 2 projects, I have added a reference to the class library project we just created (called DiffMatchPatch).

As shown on figure 1, to my forms, I have added a button to run the comparison, a checkbox to synchronize the scrolling, a split container having each panel containing a cRichTextBox control (from the class library).

Comparing the text

The toughest part in all the code is really the comparison of text. It is true that the DiffMatchPatch does the hardest part but once the comparison has been done, the results must be translated into something else to color the content of the 2 RichTextBox controls.

I invite you to have a look at a thread from StackOverflow that provides the solution to this problem: https://stackoverflow.com/questions/24887238/how-to-compare-two-rich-text-box-contents-and-highlight-the-characters-that-are.

In short, the CompareText method is calling the diff_main method of the DiffMatchPatch class. This method returns a list of Diff from the 2 strings. The CollectChunks method is then called to find out in the RichTextBox controls what chunks of text must be colorized. Finally, the PaintChunks method applies the color to the RichTextBox controls.

Private Sub CompareText()
    _diffs = _diff.diff_main(richTextBox1.Text, richTextBox2.Text)
    _diff.diff_cleanupSemanticLossless(_diffs)

    _chunklist1 = CollectChunks(richTextBox1)
    _chunklist2 = CollectChunks(richTextBox2)

    PaintChunks(richTextBox1, _chunklist1)
    PaintChunks(richTextBox2, _chunklist2)

    richTextBox1.SelectionLength = 0
    richTextBox2.SelectionLength = 0
End Sub
private void CompareText()
{

    _diffs = _diff.diff_main(richTextBox1.Text, richTextBox2.Text);
    _diff.diff_cleanupSemanticLossless(_diffs);    

    _chunklist1 = CollectChunks(richTextBox1);
    _chunklist2 = CollectChunks(richTextBox2);

    PaintChunks(richTextBox1, _chunklist1);
    PaintChunks(richTextBox2, _chunklist2);

    richTextBox1.SelectionLength = 0;
    richTextBox2.SelectionLength = 0;
}

Keeping the RichTextBox controls in sync

Because of the extended cRichTextBox controls, it is now easy to keep 2 of these controls in sync.

If you look at the CheckedChanged event handler, you will find this code:

Private Sub chkSyncScrolling_CheckedChanged(sender As Object, e As EventArgs) Handles chkSyncScrolling.CheckedChanged
    'add the required controls into scroll sync
    Dim syncedCtrls As Control() = {richTextBox1, richTextBox2}
    For Each ctl In syncedCtrls
        CType(ctl, cRichTextBox).Buddies = If(chkSyncScrolling.Checked, syncedCtrls, Nothing)
    Next

    If chkSyncScrolling.Checked Then
        'reposition both controls at the top
        richTextBox1.SelectionStart = 0
        richTextBox1.ScrollToCaret()

        richTextBox2.SelectionStart = 0
        richTextBox2.ScrollToCaret()
    End If
End Sub
private void chkSyncScrolling_CheckedChanged(object sender, EventArgs e)
{
    // add the required controls into scroll sync
    Control[] syncedCtrls = { richTextBox1, richTextBox2 };
    foreach (var ctl in syncedCtrls)
    {
        ((cRichTextBox)ctl).Buddies = chkSyncScrolling.Checked ? syncedCtrls : null;
    }

    if (chkSyncScrolling.Checked)
    {
        //reposition both controls at the top
        richTextBox1.SelectionStart = 0;
        richTextBox1.ScrollToCaret();

        richTextBox2.SelectionStart = 0;
        richTextBox2.ScrollToCaret();
    }
}

The first line defines an array of controls containing the names of the 2 cRichTextBox controls I want to keep in sync. Next, I loop through this array setting the Buddies property of each cRichTextBox control to either nothing or the array of controls depending on the checked state of the CheckBox.

The remaining lines just force the 2 controls to go back to top to ensure we start the scrolling at the same place.

Conclusion

I now have another great feature in my application that compares history versions of some text highlighting the differences and synchronizing the scroll. All this was done almost like playing Legos. Grabbing snippets of code from different sources and trying to build something customized just for me!


(Print this page)