繁体   English   中英

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

[英]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部件,您可以尝试在要修改的面板上调用SuspendLayoutResumeLayout

您可以尝试在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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM