2

I have a small app that reads in a pipe delimted file and writes out lines to a RTB, highlighting if there are dissallowed characters in certain "columns". This is working perfectly...however, the Users want a progress bar and to see the lines being written "live" and also to be able to cancel mid-way through.

I have the following extension method that I have been using to write to a RichTextBox, while blocking the UI, but this fails using a BackgroundWorker with BeginInvoke.

The fail is when finding the current length of the text.

public static void AppendLine(this RichTextBox richTextBox, string text, List<Char> foundChars, List<int> columns)
        {
            var split = text.Trim().Split(new char[] { '|' });

            for (int i = 0; i < split.Count(); i++)
            {
                **var start = richTextBox.TextLength;**
                richTextBox.AppendText(split[i]);
                var end = richTextBox.TextLength;

                if (columns.Contains(i + 1))
                {
                    foreach (var foundChar in foundChars)
                    {
                        var current = start;

                        while (current > 0)
                        {
                            var position = richTextBox.Find(new char[] { foundChar }, current, end);
                            current = position + 1;
                            if (current > 0)
                            {
                                richTextBox.Select(position, 1);
                                richTextBox.SelectionColor = Color.Red;
                            }
                        }
                    }
                }
                richTextBox.SelectionLength = 0;
                richTextBox.SelectionColor = Color.Black;
            }
            richTextBox.AppendLine();
        }

private void UpdateResultsLine(string line, List<char> foundChars)
        {
            if (txtResults.InvokeRequired)
            {
                txtResults.BeginInvoke(new UpdateResultsLineDelegate(UpdateResultsLine), line, foundChars);
            }
            txtResults.AppendLine(line, foundChars, _fileType.ProcessColumns);
        }

However, if I call any/all of these extensions in the same way, they work?

public static void AppendLine(this RichTextBox richTextBox)
{
    richTextBox.AppendText(Environment.NewLine);
}

public static void AppendLine(this RichTextBox richTextBox, string text)
{
    richTextBox.AppendText(text + Environment.NewLine);
}

public static void AppendLine(this RichTextBox richTextBox, string text, params object[] args)
{
    richTextBox.AppendLine(string.Format(text, args));
}

What am I missing? or is there another way to write coloured text to a RTB?

BlueChippy
  • 5,935
  • 16
  • 81
  • 131
  • 1
    The point of using the `BackgroundWorker` component is that you update the UI in the `ProgressChanged` and/or `RunWorkerCompleted` event handler methods. You don't need to use `BeginInvoke` at all. See the example on MSDN [here](http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx). – Cody Gray - on strike Jan 24 '12 at 07:05
  • Sorry, but that doesn't meet my full requirements. I need to update more than one RTB, plus probably a label, and the progress bar. Yes, I could create a new object to pass around, containing an Enum to say what needs to be updated and switch in each ProgressChanged call, but this seems overkill. – BlueChippy Jan 24 '12 at 07:12
  • I don't understand why you can't update multiple controls inside of the `ProgressChanged` event handler method. You don't need to create any objects or pass them around. The point of handling those events is that they're raised on the UI thread. – Cody Gray - on strike Jan 24 '12 at 07:13
  • My BackgroundWorker is doing the work, so only IT knows what has been changed, where it is upto, what file it is currently processing. It can tell the UI to update...but what does the UI actually write to the RTB if BW doesn't pass that information back? – BlueChippy Jan 24 '12 at 07:18
  • Also, calling any of the other richTextBox.AppendText(...) works - and when in the method you can get the TextLength property...I can't see why that should be different? – BlueChippy Jan 24 '12 at 07:46
  • @BlueChippy: `BeginInvoke` will put it on the dispatcher queue for the UI. Depending on how much work you are doing in the background it might not get around for a while to actually update the UI. Using `Invoke` will force a synchronization hence your background worker will have to wait until the UI has been updated. So using `Invoke` rather than `BeginInvoke` has the potential of slowing down your actual work trading it for a regular updating UI. – ChrisWue Sep 29 '14 at 23:13

2 Answers2

5

Here what you can try, create the extension class as follows

public static class ControlExtensions
{
    public static void Invoke(this Control control, Action action)
    {
        if (control.InvokeRequired) control.Invoke(new MethodInvoker(action), null);
        else action.Invoke();
    }
 }

And when ever and where ever you want to update anything on UI, you just need to do

 richTextBox.Invoke(() => { richTextBox.AppendText(text + Environment.NewLine); });

Hope this works for you.

Amar Palsapure
  • 9,590
  • 1
  • 27
  • 46
  • Daniel answered first and I implemented his solution so he gets the points...however I might switch to this one as I like the clarity. – BlueChippy Jan 24 '12 at 10:54
  • Maybe better BeginInvoke http://kristofverbiest.blogspot.com/2007/02/avoid-invoke-prefer-begininvoke.html – Kiquenet Jul 12 '12 at 06:57
  • Perfect solution for my case, as I have to update current progress in Rich Text Box. Thanks – Sheikh M. Haris Sep 06 '15 at 07:32
  • it also works with adding an item in a listbox --> myListBox.Invoke( ( ) => { myListBox.Items.Add( myListOfStrings[ i ] ); } ); –  Oct 22 '18 at 15:16
4

You need an else statement in your the UpdateResultsLine method. When Invoke is required, you are executing the UpdateResultsLine method using the delegate (invoking), but then you call again without using Invoke

Also, why do you use BeginInvoke (async) and use Invoke (sync)? Are you sure you have no sync problems? Using Invoke and adding an else statement could solve your problems:

private void UpdateResultsLine(string line, List<char> foundChars)
{
    if (txtResults.InvokeRequired)
    {
        txtResults.Invoke(
            new UpdateResultsLineDelegate(UpdateResultsLine),
            line,
            foundChars);
    }
    else
    {
        txtResults.AppendLine(
            line,
            foundChars,
            _fileType.ProcessColumns);
    }
}
Daniel Peñalba
  • 30,507
  • 32
  • 137
  • 219