0

I want to call RichTextBox.Find() from another thread. How can I do that? The RichTextBox is located in a UserControl which I'm using in my form. I want to update it from another thread. I was able to change its properties using Invoke. But can't figure out how to call _ucResultRich.rchResult.Find(word, startIndex, RichTextBoxFinds.None); from my thread.

Thread thread=new Thread(thrHighlight);
thread.Start(e.RowIndex);

private void ThrHighlight(object obj)
{
    string[] words = ucSearchControls.rdbExact.Checked
          ? new string[] { ucSearchControls.txtSearch.Text.Trim() }
              : ucSearchControls.txtSearch.Text.Split(' ');
    foreach (string word in words)
    {
        int startIndex = 0;
        while (startIndex < _ucResultRich.rchResult.TextLength)
        {

            int wordStartIndex = _ucResultRich.rchResult.Find(word, startIndex, RichTextBoxFinds.None);
            if (wordStartIndex != -1)
            {
                _ucResultRich.rchResult.SelectionStart = wordStartIndex;
                _ucResultRich.rchResult.SelectionLength = word.Length;
                _ucResultRich.rchResult.SelectionBackColor = Color.Yellow;
            }
            else
            break;
            startIndex += wordStartIndex + word.Length;
        }
    }
}

How can I do that?

P.S: This is the follow-up to my first question and to the @varocarbas comments there

Community
  • 1
  • 1
Ghasem
  • 14,455
  • 21
  • 138
  • 171
  • Why can't you use `Invoke` for that aswell? – Jcl Dec 08 '15 at 10:33
  • 3
    The whole method in your example ('ThrHighlight`) is pure UI, hence should be run on UI thread. – Ivan Stoev Dec 08 '15 at 10:33
  • MY IDEAS ON THIS FRONT (including old post): I said that you were using the backgroundworker wrongly and that, to use it wrongly, better not using it at all. But I do think that for 2-thread situations (GUI + long calculations which ideally interact with the GUI elements as less as possible, what provokes the GUI to get frozen), the backgroundworker does deliver the best solution (very intuitive). That's why I spent some time writing a clear code to show you (and future readers) how to use it properly in this situatuation. In any case and as rightly pointed out by Ivan in the comment above... – varocarbas Dec 08 '15 at 16:15
  • ... you should better rely on just 1 thread for this specific implementation because everything is happening in the GUI thread (as you can confirm by analysing my code, which is systematically moving between GUI and backgroundworker threads = no freezing will occur anyway, which happens precisely when the GUI thread is not accessed for a long time). A scenario requiring 2 threads? When the analysis doesn't affect the GUI controls at all (e.g., reading a file; downloading a file; or analysing text as you are doing, but without relying on the RichTextBox extension methods). – varocarbas Dec 08 '15 at 16:15

2 Answers2

1

You need to decouple your code a bit from UI controls and do your business logic on external thread and update UI control on Dispatcher.BeginInvoke or Invoke.

For example ,you can save the text that your Textbox has in a separate property and do Find on other thread ,once you are done post the UI highlight part on UI thread.

TRS
  • 1,947
  • 4
  • 26
  • 49
1

This answer is exclusively focused on showing how to use properly (i.e., by maximising its in-built functionalities) BackgroundWorker (it is the continuation of some of the comments I wrote in a previous post of the OP) to deliver the intended functionalities.

To use the code below these lines, start a new Winforms project and add the following controls to the main form: Button (button1 with the click event button1), RichTextBox (richTextBox1) and a BackgroundWorker (backgroundWorker1 with the DoWork event backgroundWorker1_DoWork and the ProgressChanged event backgroundWorker1_ProgressChanged); also note that Form1_Load is the Load event of the main form.

To use the application, just input any text in the richTextBox1 by including some of the hardcoded words (i.e., "word1", "word2", "word3", "word4", "word5"), click on button1 and confirm that they are highlighted as expected.

volatile int curWordStartIndex; //I use this global variable to communication between the progressChanged event and findBit, called from the DoWork event

private void Form1_Load(object sender, EventArgs e)
{
    backgroundWorker1.WorkerReportsProgress = true;
}

private void button1_Click(object sender, EventArgs e)
{
    //As far as richTextBox1.TextLength provokes a cross-thread error, I pass it as an argument
    backgroundWorker1.RunWorkerAsync(richTextBox1.TextLength);
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    findBit((int)e.Argument);
}

private void findBit(int textLength)
{
    string[] words = new string[] { "word1", "word2", "word3", "word4", "word5" };
    foreach (string word in words)
    {
        int startIndex = 0;
        while (startIndex < textLength)
        {
            //Rather than performing the actions affecting the GUI thread here, I pass all the variables I need to
            //the ProgressChanged event through ReportProgress and perform the modifications there.
            backgroundWorker1.ReportProgress(0, new object[] { word, startIndex, Color.Yellow });
            if (curWordStartIndex == -1) break;

            startIndex += curWordStartIndex + word.Length;
        }
    }
}

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    object[] curVars = (object[])e.UserState;

    richTextBox1.SuspendLayout(); 

    string word = (string)curVars[0];
    int startIndex = (int)curVars[1];
    Color curColor = (Color)curVars[2];
    curWordStartIndex = richTextBox1.Find(word, startIndex, RichTextBoxFinds.None);

    if (curWordStartIndex != -1)
    {
        richTextBox1.SelectionStart = curWordStartIndex;
        richTextBox1.SelectionLength = word.Length;
        richTextBox1.SelectionBackColor = curColor;
    }

    richTextBox1.ResumeLayout();
}
Community
  • 1
  • 1
varocarbas
  • 12,354
  • 4
  • 26
  • 37
  • Thanks for your efforts to show me the right way to do my things. I'm not trying to say I'm totally right since I'm not looking myself as a pro programmer. I've been doing programming for a few years and still looking myself as a newbie who needs to learn. I'm sure you're right here and on the previous comments. I'll try to learn how to use things properly. BTW haven't still tried your code. I'll do it 2mrw at work. Thanks again for your time and your (good-faith) action. – Ghasem Dec 08 '15 at 17:33
  • @AlexJolig No problem. Everyone is here to learn (if you are not systematically learning you wouldn't be a good programmer). Take all the time you need and let me know if there is any unclear issue. – varocarbas Dec 08 '15 at 17:41
  • I tried your code and it works fine. (although there's one error I haven't figure it out yet) But the whole idea is cool. Thank you. However I still like to find out *how to call a control method from another thread* for future uses. – Ghasem Dec 09 '15 at 05:39
  • @AlexJolig ... But illustrates pretty well how to use the backgroundworker to communicate with GUI elements without any problem. Not sure what error you are talking about, but it certainly belongs to your code (which I haven't changed a bit). My code has simply taken your original algorithm and modified such that the backgroundworker can be used properly. As said, take deeper look at the code (debug it, read the comments, do some research) and ask any issue which is not clear: you have there enough information to understand how to use the backgroundworker in any situation. – varocarbas Dec 09 '15 at 08:29
  • @AlexJolig The whole point of my code is showing how to use the backgroundworker. One of the situations taken care of is precisely communicating between different threads (GUI & backgroundworker threads). This is precisely the point you have to understand. Rather than just executing this code, try to understand it (read the comments I included). Rough ideas: when `DoWork` is reached, the backgroundworker thread is started; you cannot perform any modification on controls (or any other GUI element) from there.. – varocarbas Dec 09 '15 at 08:30
  • @AlexJolig ... Once the backgroundworker has been started, you can move back to the GUI thread (and perform the modifications in the controls there) by relying on the `ProgressChanged` event (which is called with `ReportPorgress`), when you reach this event you are already in the GUI thread (when you leave it, you are back in the backgroundworker thread; unless the backgroundworker has finished, what you know by relying on the other event of the backgroundworker: the complete one). As said, all this movement between threads isn't required in your specific case (it should have just 1 thread)... – varocarbas Dec 09 '15 at 08:31
  • @AlexJolig ... In any case, it illustrates pretty well how to use the backgroundworker to communicate with GUI elements without any problem. Not sure what error you are talking about, but it certainly belongs to your code (which I haven't changed a bit). My code has simply included your original algorithm with the modifications required to use the backgroundworker properly. As said, take a deeper look at the code (debug it, read the comments, do some research) and ask any issue which is not clear: you have there enough information to understand how to use the backgroundworker in any situation. – varocarbas Dec 09 '15 at 08:33
  • Yes, there was a good point in your code. I'll try to do my things the way I won't need invoking controls anymore. As I said I've used `backgroundworker` for download, trim videos, copy, etc.. without needing to invoke somthing. But this particular case got me confused a lil bit which your example showed me there's always a way avoiding invoke. and that how to write codes the way they're suppose to be wrriten – Ghasem Dec 09 '15 at 08:35