C# - Populating Panel with controls from BackgroundWorker

社会主义新天地 提交于 2019-12-08 11:44:53

问题


So I am writing a small Twitter client for me to use. 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.

Now, my problem is that loading more than 10 tweets causes a slowdown because I am dynamically generating the panels. So I decided to do this using a BackgroundWorker and then add those panels to the main panel.

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?


回答1:


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

In your code example, all of the code is the UI-modification part, so it must all go in the UI thread.

EDIT: To optimise the UI part, you could experiment with calling SuspendLayout and ResumeLayout on the panels that you are modifying.




回答2:


panelTweet is created on the BackgroundWorker's thread and is accessed from the main thread in your delegate (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)
    }
}



回答3:


You may try to create controls in ProgressChanged handlers. This way you'll be able to do some initialization (user picture retrieval etc) in background thread, and visualization in GUI thread.

Please note though, that most probably your performance problem is due to big resources required for creating RichTextEdit and PictureBox. Think of creating custom control that will contain only image of user and text, rendered on Paint event, e.g.




回答4:


You can't create any WinForms UI controls in a background thread.

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.




回答5:


You try to add Panel p created on external thread X back to the winform Thread Y.

Put the whole creation in the BeginInvoke handler. That way all the controls are created in the winform thread Y.




回答6:


Ok, so looking at the answers, it looks like I am just SOL. I need to do all the processing in the UI thread, thereby causing it to become unresponsive.



来源:https://stackoverflow.com/questions/1509737/c-sharp-populating-panel-with-controls-from-backgroundworker

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!