【问题标题】:How to call a control method from another thread如何从另一个线程调用控制方法
【发布时间】:2015-12-08 10:28:11
【问题描述】:

我想从另一个线程调用RichTextBox.Find()。我怎样才能做到这一点? RichTextBox 位于我在表单中使用的UserControl 中。 我想从另一个线程更新它。我能够使用Invoke 更改其属性。但不知道如何从我的线程中调用_ucResultRich.rchResult.Find(word, startIndex, RichTextBoxFinds.None);

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;
        }
    }
}

我该怎么做?

P.S:这是my first question 和@varocarbas cmets 的后续行动

【问题讨论】:

  • 你为什么不能同时使用Invoke
  • 您的示例中的整个方法('ThrHighlight`)是纯 UI,因此应该在 UI 线程上运行。
  • 我在这方面的想法(包括旧帖子):我说你错误地使用了后台工作程序,如果使用不当,最好不要使用它。但我确实认为,对于 2 线程情况(GUI + 长计算,理想情况下与 GUI 元素交互尽可能少,导致 GUI 冻结的原因),backgroundworker 确实提供了最佳解决方案(非常直观)。这就是为什么我花了一些时间编写清晰的代码来向您(和未来的读者)展示如何在这种情况下正确使用它。无论如何,正如伊万在上面的评论中正确指出的那样......
  • ... 你最好只依赖 1 个线程来实现这个特定的实现,因为一切都发生在 GUI 线程中(正如你可以通过分析我的代码来确认的那样,它在 GUI 和后台工作线程之间系统地移动= 无论如何都不会发生冻结,这恰好发生在长时间不访问 GUI 线程时)。需要 2 个线程的场景?当分析完全不影响 GUI 控件时(例如,读取文件;下载文件;或正在分析文本,但不依赖 RichTextBox 扩展方法)。

标签: c# multithreading winforms invoke


【解决方案1】:

这个答案只专注于展示如何正确使用(即通过最大化其内置功能)BackgroundWorker(它是我在previous post of the OP 中写的一些 cmets 的延续)以提供预期的功能。

要使用这些行下面的代码,请启动一个新的 Winforms 项目并将以下控件添加到主窗体:Button (button1 with the click event button1), RichTextBox (richTextBox1)和BackgroundWorkerbackgroundWorker1DoWork 事件backgroundWorker1_DoWorkProgressChanged 事件backgroundWorker1_ProgressChanged);还要注意Form1_Load 是主窗体的Load 事件。

要使用该应用程序,只需在richTextBox1 中输入任何文本,包括一些硬编码的单词(即“word1”、“word2”、“word3”、“word4”、“word5”),点击button1 并确认它们按预期突出显示。

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();
}

【讨论】:

  • 感谢您向我展示正确的做事方式。我并不是想说我完全正确,因为我并不认为自己是一名专业程序员。我已经做了几年编程,但仍然把自己看作一个需要学习的新手。我敢肯定你就在这里,在以前的 cmets 上。我会尝试学习如何正确使用东西。顺便说一句,您还没有尝试过您的代码。我会在工作中做2mrw。再次感谢您的时间和(善意的)行动。
  • @AlexJolig 没问题。每个人都在这里学习(如果你没有系统地学习,你就不会成为一个好的程序员)。如果有任何不清楚的问题,请随时告诉我。
  • 我试过你的代码,它工作正常。 (虽然有一个错误我还没有弄清楚)但整个想法很酷。谢谢你。不过我还是想了解如何从另一个线程调用控制方法以备将来使用。
  • @AlexJolig ... 但是很好地说明了如何使用后台工作程序与 GUI 元素进行通信而没有任何问题。不确定你在说什么错误,但它肯定属于你的代码(我没有改变一点)。我的代码只是采用了您的原始算法并进行了修改,以便可以正确使用后台工作程序。如前所述,更深入地查看代码(调试代码、阅读 cmets、进行一些研究)并提出任何不清楚的问题:您有足够的信息来了解如何在任何情况下使用 backgroundworker。
  • @AlexJolig 我的代码的重点是展示如何使用后台工作程序。处理的一种情况是在不同线程(GUI 和后台工作线程)之间进行精确通信。这正是您必须了解的重点。不要仅仅执行这段代码,试着理解它(阅读我包含的 cmets)。粗略思路:当达到DoWork时,启动backgroundworker线程;您不能从那里对控件(或任何其他 GUI 元素)进行任何修改..
【解决方案2】:

您需要将您的代码与 UI 控件解耦,并在外部线程上执行您的业务逻辑,并在 Dispatcher.BeginInvoke 或 Invoke 上更新 UI 控件。

例如,您可以将文本框的文本保存在一个单独的属性中,然后在其他线程上执行查找,一旦您在 UI 线程上发布了 UI 突出显示部分。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-17
    • 2015-06-15
    • 1970-01-01
    • 2011-09-23
    相关资源
    最近更新 更多