C# – 使用BackgroundWorker中的控件填充Panel

所以我正在写一个小的Twitter客户端供我使用。 我正在使用一个大面板的组合,较小的面板代表各个推文。 在每个较小的面板中,我有一个PictureBox和一个RichTextBox。

现在,我的问题是加载超过10条推文会导致速度减慢,因为我正在动态生成面板。 所以我决定使用BackgroundWorker执行此操作,然后将这些面板添加到主面板。

我已经多次这样做了,将文本写入来自不同thead的文本框(甚至在其上编写了教程)。 然而,我无法让它发挥作用。 我收到错误消息:

Cross-thread operation not valid: Control '' accessed from a thread other than the thread it was created on.

码:

 List panelList = new List(); 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); } }); 

有人注意到有什么问题吗?

使用后台线程时,必须完全分离从修改表单控件的部分检索数据的部分。 必须在UI线程上调用修改表单控件的所有代码,即使它需要一些时间。 没有办法解决这个问题。

这通常是一个好策略,因为通常将数据放入内存是缓慢的部分,更新UI是快速部分(相对于彼此)。

在您的代码示例中,所有代码都是UI修改部分,因此它必须全部进入UI线程。

编辑:要优化UI部分,您可以尝试在要修改的面板上调用SuspendLayoutResumeLayout

panelTweetBackgroundWorker的线程上创建,可以从委托中的主线程访问( 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) } } 

您可以尝试在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线程中进行所有处理,从而导致它无响应。