我正在编写一个WinForms应用程序,它有两种模式:控制台或GUI。同一解决方案中的三个项目,一个用于控制台应用程序,一个用于UI表单,第三个用于保存两个接口都将连接的逻辑。控制台应用程序运行绝对顺利。
一个包含用户选择的模型,它有一个IList<T>
,其中T是一个本地对象,Step
,它实现了INotifyPropertyChanged
,所以在UI中它被挂载到一个DataGridView。一切都在运行时很好,对象的初始状态反映在屏幕上。
每个Step
个对象都是依次执行的任务;一些属性将改变,反映回IList并传递给DataGridView。
UI版本中的此操作是通过创建BackgroundWorker将事件提升回UI来完成的。 Step
完成它并生成一个StepResult
对象,它是一个指示结果的枚举类型(例如Running,NotRun,OK,NotOK,Caveat)和一个表示消息的字符串(因为该步骤已运行)但并不像预期的那样,即有一个警告)。通常,操作将涉及数据库交互,但在调试模式下,我随机生成结果。
如果消息为空,则永远不会有问题,但如果我生成这样的响应:
StepResult returnvalue = new StepResult(stat, "completed with caveat")
我收到一条错误消息,说明正在从创建它的线程以外的线程访问DataGridView。 (我通过一个自定义处理程序传递它,它应该在需要时处理调用 - 也许它不会?)
然后,如果我生成一个唯一的响应,例如使用随机数r
:
StepResult returnvalue = new StepResult(stat, r.ToString());
操作成功没有问题,数字写得干净利落地写入DataGridView。
我很困惑。我假设它在某种程度上是一个字符串文字问题,但任何人都可以提出更明确的解释吗?
答案 0 :(得分:4)
你已经回答了自己的问题: -
我收到一条错误消息,说明正在从创建它的线程以外的线程访问DataGridView。
WinForms坚持认为,对表单和控件执行的所有操作都是在创建表单的线程的上下文中完成的。原因很复杂,但与底层的Win32 API有很大关系。有关详细信息,请参阅The Old New Thing博客上的各种条目。
您需要做的是使用InvokeRequired和Invoke方法来确保始终从同一个线程(伪代码)访问控件:
object Form.SomeFunction (args)
{
if (InvokeRequired)
{
return Invoke (new delegate (Form.Somefunction), args);
}
else
{
return result_of_some_action;
}
}
答案 1 :(得分:3)
由于您通过事件订阅进行UI绑定,you might find this helpful;这是我前一段时间写的一个例子,它展示了如何子类化BindingList<T>
,以便自动将通知编组到UI线程。
如果没有同步上下文(即控制台模式),则它将恢复为简单的直接调用,因此没有开销。在UI线程中运行时,请注意这实际上使用Control.Invoke
,如果它位于UI线程上,它本身只是直接运行委托。因此,如果从非UI线程编辑数据,则只有任何切换 - 突然出现我们想要的内容;-p
答案 2 :(得分:0)
之前我遇到过同样的问题。也许我发布的这篇文章可以提供帮助。
http://cyberkruz.vox.com/library/post/net-problem-async-and-windows-forms.html
答案 3 :(得分:0)
我发现这篇文章 - “Updating IBindingList from different thread” - 它将指责指向了BindingList -
因为没有为异步操作设置BindingList,所以必须从控制它的同一个线程更新BindingList。
明确地将父表单作为ISynchronizeInvoke
对象传递,并为BindingList<T>
创建包装器。
答案 4 :(得分:0)