【问题标题】:Changing color of text in a WPF RichTextBox from a different thread从不同的线程更改 WPF RichTextBox 中文本的颜色
【发布时间】:2014-11-25 19:25:55
【问题描述】:

这可能是一个非常简单的问题,如果有重复,请提前抱歉,但我还没有找到任何关于这个特定问题的信息。

我在为来自不同线程的 RichTextBox 中的特定文本部分着色时遇到问题。我想在 RichTextBox 中附加一行文本,同时用指定的颜色为该行着色。我能够附加文本并为其着色,但无法从另一个线程执行此操作,尽管我能够从 Dispatcher 附加纯文本。

这是我的代码:

private void UpdateTextbox(string message, Brush color)
{
    rtbOutput.Dispatcher.Invoke((Action)(() =>
        {
            rtbOutput.Selection.Select(rtbOutput.Document.ContentEnd, rtbOutput.Document.ContentEnd);
            rtbOutput.Selection.Text = message;
            rtbOutput.Selection.ApplyPropertyValue(TextElement.ForegroundProperty, color);
        })
    );
}

当我尝试从另一个线程运行它时,我收到一条错误消息,告诉我无法访问该对象,因为它属于另一个线程。我该如何解决?

编辑:似乎 RichTextBox 不是问题,但 Brush 对象是问题,因为如果我在 Invoke 方法中指定它,我可以很好地更改颜色,但如果我将它作为参数传递则不行。

【问题讨论】:

  • UI 对象只能由 UI 线程更新。如果您使用的是 Tasks,则可以使用 task.ContinueWith(() => { // code here }, TaskScheduler.FromCurrentSynchronizationContext());

标签: c# wpf richtextbox dispatcher


【解决方案1】:

事实证明,另一个线程拥有的不是 RichTextBox,而是 Brush 对象。如果我只是将 Color 对象传递给方法并将其转换为 Dispatcher 内的 SolidColorBrush,我设法让它与下面的所有解决方案一起使用。

private void UpdateTextbox(string message, Color color)
{
    rtbOutput.Dispatcher.Invoke((Action)(() =>
        {
            TextRange range = new TextRange(rtbOutput.Document.ContentEnd, rtbOutput.Document.ContentEnd);
            range.Text = message;
            range.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush((Color)color));
        })
    );
}

它也适用于 pquest 建议的代码:

private void UpdateTextbox(string message, Brush color)
{
    uiContext.Send(o =>
        {
            TextRange range = new TextRange(rtbOutput.Document.ContentEnd, rtbOutput.Document.ContentEnd);
            range.Text = message;
            range.ApplyPropertyValue(TextElement.ForegroundProperty, color);
        }, null);
}

【讨论】:

    【解决方案2】:

    您需要将实际的更改工作推回 GUI 线程。在您的应用程序启动期间,您可以运行:

    SynchronizationContext ctxt = SynchronizationContext.Current; 然后将其传递到您的多线程代码可以访问的地方。然后使用它:

    //Code that determines the color to use
    
    Color clr = result;
    
    ctxt.Send((clr_obj) =>
    {
    //Code to set color
    }, clr);
    

    编辑:

    这是一篇很好的文章,更深入地解释了SyncronizationContextArticle

    【讨论】:

    • 这似乎应该可以工作,但我在 ApplyPropertyValue 函数上收到相同的错误消息。我正在使用这样的 SynchronizationContext: uiContext.Send(new SendOrPostCallback( delegate(object state) { // 对 Richtextbox 的操作 }), null);我应该以不同的方式使用它吗?
    • 此时您不需要使用委托。您可以正常调用方法。只需将所有内容放在您的调度程序中即可。
    • 我也刚刚用 codeplex 的一篇关于课程的文章编辑了我的答案
    • 现在可以使用了。原来我的代码有一个与我想象的不同的问题。但是您的回答也有效,感谢您的文章,这很有趣。
    【解决方案3】:

    您在使用 Dispatcher 时处于正确的轨道上,但您并不总是需要调用 Invoke。我使用以下扩展方法来处理这类事情:

        public static void InvokeIfNeeded(this Dispatcher dispatcher, Action action)
        {
            if (dispatcher == null || dispatcher.CheckAccess())
                action();
            else
                dispatcher.Invoke(action, DispatcherPriority.Normal);
        }
    

    你可以这样使用它:

    private void UpdateTextbox(string message, Brush Color)
    {
        rtbOutput.Dispatcher.InvokeIfNeeded(() =>
            {
                rtbOutput.Selection.Select(rtbOutput.Document.ContentEnd, rtbOutput.Document.ContentEnd);
                rtbOutput.Selection.Text = message;
                rtbOutput.Selection.ApplyPropertyValue(TextElement.ForegroundProperty, Color);
            }
        );
    }
    

    【讨论】:

    • 这很有用,但它不会改变我无法从 Dispatcher 中访问 ApplyPropertyValue 函数的问题。
    • 如果您在 UI 线程上创建 rtbOutput,这应该适合您。
    • 你是对的,你的方法有效。它只是没有解决我的问题,因为我在错误的地方寻找解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-02-01
    • 2013-06-15
    • 2012-07-28
    • 2013-10-25
    • 1970-01-01
    • 2013-01-15
    • 2010-11-06
    相关资源
    最近更新 更多