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

时间:2009-10-02 14:06:18

标签: c# backgroundworker

所以我正在写一个小的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<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); } });

有人注意到任何问题吗?

6 个答案:

答案 0 :(得分:4)

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)
    }
}

答案 1 :(得分:1)

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

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

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

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

答案 2 :(得分:1)

您可以尝试在ProgressChanged处理程序中创建控件。这样你就可以在后台线程中进行一些初始化(用户图片检索等),并在GUI线程中进行可视化。

请注意,很可能是您的性能问题是由于创建RichTextEdit和PictureBox所需的大量资源。考虑创建自定义控件,该控件将仅包含在Paint事件上呈现的用户和文本的图像,例如

答案 3 :(得分:1)

您无法在后台线程中创建任何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 );

虽然可能仍然很慢 - 可能值得加载一个子集,然后滴下进一步输入。你也可以只加载可见列表 - 隐藏更多的列表直到它们滚动。然后你一次只加载一个页面。

答案 4 :(得分:0)

您尝试将在外部线程X上创建的Panel p添加回winform线程Y.

将整个创建放在BeginInvoke处理程序中。这样,所有控件都在winform线程Y中创建。

答案 5 :(得分:0)

好的,所以看看答案,看起来我只是SOL。我需要在UI线程中进行所有处理,从而导致它无响应。