[英]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.