[英]C# - Populating Panel with controls from BackgroundWorker
因此,我正在编写一个小型Twitter客户端供我使用。 我使用的是一个大面板的组合,而较小的面板代表各个推文。 在每个较小的面板中,我都有一个PictureBox和一个RichTextBox。
现在,我的问题是,加载10条以上的tweet会导致速度变慢,因为我正在动态生成面板。 因此,我决定使用BackgroundWorker进行此操作,然后将这些面板添加到主面板中。
我已经通过将文本从其他主题写入文本框来完成了很多次(甚至为此编写了教程)。 但是我无法使它起作用。 我收到错误消息:
Cross-thread operation not valid: Control '' accessed from a thread other than the thread it was created on.
码:
List<Panel> panelList = new List<Panel>();
foreach (UserStatus friendStatus in list)
{
PictureBox pbTweet = new PictureBox();
// ...
// code to set numerous properties
// ...
RichTextBox rtbTweet = new RichTextBox();
// ...
// code to set numerous properties
// ...
Panel panelTweet = new Panel();
// ...
// code to set numerous properties
// ...
panelTweet.Controls.Add(pbTweet);
panelTweet.Controls.Add(rtbTweet);
panelList.Add(panelTweet);
}
if (panelMain.InvokeRequired)
panelMain.BeginInvoke((MethodInvoker)delegate { foreach (Panel p in panelList) { panelMain.Controls.Add(p); } });
有人注意到任何问题吗?
panelTweet
是在BackgroundWorker
的线程上创建的,并且可以从您的委托的主线程中访问( panelMain.Controls.Add(p);// p = panelTweet
)。
您必须在主线程中调用所有该函数,而不仅仅是最后一部分。
您可以这样重写函数:
private void AddControls()
{
if(panelMain.InvokeRequired)
{
panelMain.BeginInvoke(new MethodInvoker(AddControls));
return;
}
foreach (UserStatus friendStatus in list)
{
PictureBox pbTweet = new PictureBox();
// ...
// code to set numerous properties
// ...
RichTextBox rtbTweet = new RichTextBox();
// ...
// code to set numerous properties
// ...
Panel panelTweet = new Panel();
// ...
// code to set numerous properties
// ...
panelTweet.Controls.Add(pbTweet);
panelTweet.Controls.Add(rtbTweet);
panelMain.Controls.Add(panelTweet)
}
}
使用后台线程时,必须将修改数据控件的部件与要检索数据的部件完全分开。 即使需要花费一些时间,所有修改表单控件的代码也必须在UI线程上调用。 没有办法解决这个问题。
通常,这是一个很好的策略,因为通常,将数据放入内存是较慢的部分,而更新UI是相对较快的部分。
在您的代码示例中,所有代码都是UI修改部分,因此必须全部放入UI线程中。
编辑:要优化UI部件,您可以尝试在要修改的面板上调用SuspendLayout
和ResumeLayout
。
您可以尝试在ProgressChanged处理程序中创建控件。 这样,您将能够在后台线程中进行一些初始化(用户图片检索等),并在GUI线程中进行可视化。
但是请注意,最可能的性能问题是由于创建RichTextEdit和PictureBox所需的大量资源。 考虑创建自定义控件,该控件仅包含在Paint事件上呈现的用户图像和文本,例如
您不能在后台线程中创建任何WinForms UI控件。
有几种解决方法-我先开始:
Control getPanelForUser( UserStatus friendStatus ) {
PictureBox pbTweet = new PictureBox { /* set props */ };
RichTextBox rtbTweet = new RichTextBox { /* set props */ };
Panel panelTweet = new Panel { /* set props */ };
panelTweet.Controls.Add(pbTweet);
panelTweet.Controls.Add(rtbTweet);
return panelTweet;
}
然后在您的后台工作者中:
foreach (UserStatus friendStatus in list)
panelMain.BeginInvoke(
delegate ( object o ) {
panelMain.Controls.Add(getPanelForUser( o as UserStatus ));
},
friendStatus );
不过,这可能仍然很慢-值得加载一个子集,然后滴入更多的子集。您也可以仅加载可见列表-隐藏其他列表,直到它们滚动为止。 然后,您一次只加载一个页面。
您尝试将在外部线程X上创建的Panel p添加回winform线程Y。
将整个创建内容放入BeginInvoke处理程序中。 这样,所有控件都将在Winform线程Y中创建。
好的,所以看看答案,看来我只是SOL。 我需要在UI线程中进行所有处理,从而使它变得无响应。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.