InvalidOperationException when concatenating bound string

时间:2015-07-28 16:38:16

标签: c# multithreading winforms data-binding

I am attempting to bind a TextBox for error logging in Windows Forms.

I bind the TextBox like this:

this.operationEventHistoryTextbox.DataBindings.Add("Text", this.Logger, "OperationLog")

The Logger object is an instance of the Logger class, which contains the following property.

public string OperationLog
{
    get
    {
       return this.operationLog;
    }
    set
    {
       if (this.operationLog != value)
       {
           this.operationLog = value;
           System.ComponentModel.PropertyChangedEventHandler handler = this.PropertyChanged;
           if (handler != null)
           {
               handler(this, new System.ComponentModel.PropertyChangedEventArgs("OperationLog"));
            }
        }
    }
}

And I get the error when calling this.Logger.LogEvent("message"). My LogEvent method contains the following:

public void LogEvent(string msg)
{
    this.OperationLog += System.DateTime.Now + ": " + msg + "\r\n";
}

The InvalidOperationException says that there was an invalid Cross-thread operation, and that the Control operationEventHistoryTextBox was accessed from a thread other than the thread it was created on.

I understand this to mean that I've written parts of my code in a way that wasn't thread-safe, but I don't really understand why, or what to fix.

I could just go around setting all of these functions to invoke rather than be directly called, but I'd like really understand what isn't working.

Update: I've attempted to use System.Threading.ScynchronizationContext to raise the PropertyChanged Event on the correct thread, however, I continue to get the error. Here is the new setter for the property:

set
{
    if (this.operationLog != value)
    {
        System.Threading.SynchronizationContext context = System.Threading.SynchronizationContext.Current
                                                          ?? new System.Threading.SynchronizationContext();
        this.operationLog = value;
        context.Send(
            (s) =>
               {
                   System.ComponentModel.PropertyChangedEventHandler handler = this.PropertyChanged;
                   if (handler != null)
                   {
                       handler(this, new System.ComponentModel.PropertyChangedEventArgs("OperationLog"));
                   }
               },
               null);
   }
}

Am I not correctly creating the SynchronizationContext? Or is there something else at work here?

Update 2: If I replace the call of handler(this, ... ) with handler(null, ... ) or handler(this.OperationLog), the setter will run without errors, but does not actually update the text.

For now I'm using a workaround where I will, instead of using a DataBinding to link the text, just manually do that by adding my own handler to the PropertyChanged Event.

3 个答案:

答案 0 :(得分:2)

The problem seems to be that you are calling the LogEvent method from a background thread. Because of the databinding, the text box is then being updated from that background thread resulting in the exception. The solution is to make sure that either the LogEvent method is always executing on the UI thread or - better - the OperationLog setter.

答案 1 :(得分:1)

You trying update view from another thread. Textbox.Text can't be set from other thread then UI thread

答案 2 :(得分:0)

好的,我找到了几乎解决方案的解决方法。

我做了以下事情:

operationEventHistoryTextbox.Invoke( ... )

这可以保证在UI线程上引发事件,并允许它安全地更新文本框。但是,对我来说,它并不是很正确。虽然我已经完成了它所需要的东西,但有些东西感觉它不适合我正在寻找的模型 - 视图分离。尽管如此,它肯定比手动System.Windows.Forms.Form.ActiveForm更好。

一个有趣的缺点是,当活动表单不与您正在处理的内容共享线程时,这可能会导致错误 - 这将包括其他进程中的表单,因此不允许使用alt选项卡。

编辑:我找到了解决该问题的方法。我使用System.Windows.Forms.Application.OpenForms[0]来引用应用中的第一个表单,而不是使用{{1}}。这为我提供了运行调用或开始调用的上下文。