C # - Заполнение панели с элементами управления из BackgroundWorker - PullRequest
0 голосов
/ 02 октября 2009

Итак, я пишу небольшой клиент Twitter для использования. Я использую комбинацию из одной большой панели с меньшими панелями, представляющими отдельные твиты. На каждой меньшей панели у меня есть PictureBox и RichTextBox.

Теперь моя проблема в том, что загрузка более 10 твитов вызывает замедление, потому что я динамически генерирую панели. Поэтому я решил сделать это с помощью 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); } });

Кто-нибудь замечает какие-либо проблемы?

Ответы [ 6 ]

4 голосов
/ 02 октября 2009

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 голос
/ 02 октября 2009

Вы не можете создавать элементы управления WinForms в фоновом потоке.

Есть несколько способов обойти это - я бы начал с:

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

Хотя это все еще может быть медленным - возможно, стоит загрузить подмножество, а затем добавить в него дополнительные. Вы также можете загружать только видимый список - скрывать другие, пока они не прокрутятся. Тогда вы только загружаете страницу за раз.

1 голос
/ 02 октября 2009

Вы можете попытаться создать элементы управления в обработчиках ProgressChanged. Таким образом, вы сможете выполнить некоторую инициализацию (извлечение пользовательских изображений и т. Д.) В фоновом потоке и визуализацию в потоке GUI.

Обратите внимание, что, скорее всего, проблема с производительностью связана с большими ресурсами, необходимыми для создания RichTextEdit и PictureBox. Подумайте о создании пользовательского элемента управления, который будет содержать только изображение пользователя и текст, отображаемый в событии Paint, например,

1 голос
/ 02 октября 2009

При использовании фонового потока необходимо полностью отделить части, которые получают данные, от частей, которые изменяют элементы управления формы. Весь код, который изменяет элементы управления формой, должен вызываться в потоке пользовательского интерфейса, даже если это займет некоторое время. Обойти это невозможно.

Обычно это хорошая стратегия, потому что, как правило, получение данных в память - это медленная часть, а обновление пользовательского интерфейса - быстрая часть (относительно друг друга).

В вашем примере кода весь код является частью модификации пользовательского интерфейса, поэтому он должен идти в потоке пользовательского интерфейса.

РЕДАКТИРОВАНИЕ: Чтобы оптимизировать часть пользовательского интерфейса, вы можете поэкспериментировать с вызовами SuspendLayout и ResumeLayout на изменяемых панелях.

0 голосов
/ 02 октября 2009

Хорошо, поэтому, глядя на ответы, похоже, что я просто SOL. Мне нужно выполнить всю обработку в потоке пользовательского интерфейса, что приведет к тому, что он перестанет отвечать на запросы.

0 голосов
/ 02 октября 2009

Вы пытаетесь добавить Panel p, созданную во внешнем потоке X, обратно в поток winform Y.

Поместите все создание в обработчик BeginInvoke. Таким образом, все элементы управления создаются в потоке winform Y.

...