简体   繁体   English

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

[英]C# - Populating Panel with controls from BackgroundWorker

So I am writing a small Twitter client for me to use. 因此,我正在编写一个小型Twitter客户端供我使用。 I am using a combination of one big panel, with smaller panels representing the individual tweets. 我使用的是一个大面板的组合,而较小的面板代表各个推文。 In each smaller panel, I have a PictureBox and a RichTextBox. 在每个较小的面板中,我都有一个PictureBox和一个RichTextBox。

Now, my problem is that loading more than 10 tweets causes a slowdown because I am dynamically generating the panels. 现在,我的问题是,加载10条以上的tweet会导致速度变慢,因为我正在动态生成面板。 So I decided to do this using a BackgroundWorker and then add those panels to the main panel. 因此,我决定使用BackgroundWorker进行此操作,然后将这些面板添加到主面板中。

I've done this numerous times with writing text to a textbox from a different thead(even wrote tutorials on it). 我已经通过将文本从其他主题写入文本框来完成了很多次(甚至为此编写了教程)。 Yet I cannot get this to work. 但是我无法使它起作用。 I get the error message: 我收到错误消息:

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

Code: 码:

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

Anybody notice any problems? 有人注意到任何问题吗?

panelTweet is created on the BackgroundWorker 's thread and is accessed from the main thread in your delegate ( panelMain.Controls.Add(p);// p = panelTweet ). panelTweet是在BackgroundWorker的线程上创建的,并且可以从您的委托的主线程中访问( panelMain.Controls.Add(p);// p = panelTweet )。

You have to call all that function in your main thread, not only the last part. 您必须在主线程中调用所有该函数,而不仅仅是最后一部分。


you can rewrite the function like this: 您可以这样重写函数:

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

When using a background thread, you have to completely separate out the parts that retrieve data from the parts that modify the form controls. 使用后台线程时,必须将修改数据控件的部件与要检索数据的部件完全分开。 All code that modifies the form controls must be invoked on the UI thread, even if it will take some time to do. 即使需要花费一些时间,所有修改表单控件的代码也必须在UI线程上调用。 There is no way round this. 没有办法解决这个问题。

This is normally a good strategy is because, usually, getting the data into memory is the slow part and updating the UI is the fast part (relative to each other). 通常,这是一个很好的策略,因为通常,将数据放入内存是较慢的部分,而更新UI是相对较快的部分。

In your code example, all of the code is the UI-modification part, so it must all go in the UI thread. 在您的代码示例中,所有代码都是UI修改部分,因此必须全部放入UI线程中。

EDIT: To optimise the UI part, you could experiment with calling SuspendLayout and ResumeLayout on the panels that you are modifying. 编辑:要优化UI部件,您可以尝试在要修改的面板上调用SuspendLayoutResumeLayout

You may try to create controls in ProgressChanged handlers. 您可以尝试在ProgressChanged处理程序中创建控件。 This way you'll be able to do some initialization (user picture retrieval etc) in background thread, and visualization in GUI thread. 这样,您将能够在后台线程中进行一些初始化(用户图片检索等),并在GUI线程中进行可视化。

Please note though, that most probably your performance problem is due to big resources required for creating RichTextEdit and PictureBox. 但是请注意,最可能的性能问题是由于创建RichTextEdit和PictureBox所需的大量资源。 Think of creating custom control that will contain only image of user and text, rendered on Paint event, eg 考虑创建自定义控件,该控件仅包含在Paint事件上呈现的用户图像和文本,例如

You can't create any WinForms UI controls in a background thread. 您不能在后台线程中创建任何WinForms UI控件。

There are a couple of ways around this - I'd start with: 有几种解决方法-我先开始:

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

Then in your background worker: 然后在您的后台工作者中:

foreach (UserStatus friendStatus in list)
    panelMain.BeginInvoke(
        delegate ( object o ) { 
            panelMain.Controls.Add(getPanelForUser( o as UserStatus ));
        }, 
        friendStatus );

That might still be slow though - it could be worth loading a subset and then drip feeding further ones in. You could also only load the visible list - hide further ones until they scroll. 不过,这可能仍然很慢-值得加载一个子集,然后滴入更多的子集。您也可以仅加载可见列表-隐藏其他列表,直到它们滚动为止。 Then you're only loading a page at a time. 然后,您一次只加载一个页面。

You try to add Panel p created on external thread X back to the winform Thread Y. 您尝试将在外部线程X上创建的Panel p添加回winform线程Y。

Put the whole creation in the BeginInvoke handler. 将整个创建内容放入BeginInvoke处理程序中。 That way all the controls are created in the winform thread Y. 这样,所有控件都将在Winform线程Y中创建。

Ok, so looking at the answers, it looks like I am just SOL. 好的,所以看看答案,看来我只是SOL。 I need to do all the processing in the UI thread, thereby causing it to become unresponsive. 我需要在UI线程中进行所有处理,从而使它变得无响应。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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