XandrUu
XandrUu

Reputation: 1179

Populating a listview from another thread

I'm trying to populate a listview from another class, but I'm geting this error: " Cross-thread operation not valid: Control 'listView1' accessed from a thread other than the thread it was created on. "

In my class I declare my listview like this:

class CheckBlankPages
{

    public String[] pdfFiles
    { get; set; }

    ListView _ListVireRef;
    public int NrCRT = 1;


    public CheckBlankPages(String[] pdfFiles = null, ListView listView = null)
    {
        this.pdfFiles = pdfFiles;
        _ListVireRef = listView;

    }
    public void StartCheckingPDF()
    {
        foreach (string pdf in pdfFiles)
        {
            String[] itm = { (NrCRT++).ToString(), pdf };
            ListViewItem item = new ListViewItem(itm);
            _ListVireRef.Items.Add(item);
        }
    }
}

and in my MainForm I use this code:

DialogResult rezultat = openFileDialog1.ShowDialog();
        if (rezultat == DialogResult.OK)
        {

            CheckBlankPages ck = new CheckBlankPages(openFileDialog1.FileNames, listView1);
            Thread CheckPDFs = new Thread(new ThreadStart(ck.StartCheckingPDF));
            CheckPDFs.Start();
        }

What is wrong?

Upvotes: 3

Views: 11760

Answers (5)

Telanar
Telanar

Reputation: 381

Usually I'm doing it like this:

using System;
using System.Windows.Forms;

namespace TestWinFormsThreding
{
    class TestFormControlHelper
    {
        delegate void UniversalVoidDelegate();

        /// <summary>
        /// Call form control action from different thread
        /// </summary>
        public static void ControlInvoke(Control control, Action function)
        {
            if (control.IsDisposed || control.Disposing)
                return;

            if (control.InvokeRequired)
            {
                control.Invoke(new UniversalVoidDelegate(() => ControlInvoke(control, function)));
                return;
            }
            function();
        }
    }

    public partial class TestMainForm : Form
    {
    // ...
    // This will be called from thread not the same as MainForm thread
    private void TestFunction()
    {
        TestFormCotrolHelper.ControlInvoke(listView1, () => listView1.Items.Add("Test"));
    }   
    //...
    }
}

Upvotes: 7

Bob
Bob

Reputation: 93

This is a reasonable thing to do, because often in application you want ListView updates etc to go on without holding up your code.

I have done the message systems between user controls containing the control/s I want to update in the back ground and it can get quite messy because you end up having to message/event for a lot more than just the fill/updates, messy code is buggy code, so I tried other ways.

There is a nice neat way, the slow part of the ListView fill/updates is generally in the creation of the ListViewItems, and you can fully prepare those in your own thread.

So now, for this sort of applications (With Fill or update ListView where I do not need to wait for it to be ready before my code can continue), My seperate thread Creates/prepares the ListViewItems then adding the prepared items to the ListView when the thread is done is very quick, so the final ListView update can be done on a user event hardly noticeable to the user. Add to this 'Only add those you can see' and it really is instantaneous. With a couple of extra rows so when scrolling starts you can add a couple more. (You might have noticed youtube/facebook/windows picture browser all do it this way). Since in our case we have already prepared the ListViewItems, adding them to the list is very straight forward.

Upvotes: 0

BitTickler
BitTickler

Reputation: 11947

A neat trick to avoid duplicate code or malfunction when a function is called both from UI thread and other threads is:

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }


        void AddItems( string[] items )
        {
            if(InvokeRequired)
            {
                Invoke((MethodInvoker) delegate { this.AddItems(items); });
                return;
            }
            ListViewItem[] range = (items.Select<string, ListViewItem>(item => new ListViewItem(item))).ToArray();
            listView1.Items.AddRange(range);
        }
    }
}

The first time the function enters in another thread, invoke is called and the function simply calls itself again, this time in the right thread context. The actual work is then written down only once after the if() block.

Upvotes: 0

Thorsten Dittmar
Thorsten Dittmar

Reputation: 56727

A simple search here on SO would have brought up many results that tell you that it is not allowed to change a GUI control from a thread other than the thread which created the control (cross-thread GUI access).

To do so, everything related to updating the ListView must be done using this.Invoke or this.Dispatcher.Invoke (in WPF).

EDIT
For example this thread here.

Sample code:

private delegate void MyDelegate(string s);

public void UpdateControl(Control targetControl, string text)
{
    if (targetControl.InvokeRequired)
    {
        // THIS IS STILL THE IN THE CONTEXT OF THE THREAD
        MyDelegate call = new MyDelegate(UpdateControl);
        targetControl.Invoke(call, new object[] { text });
    }
    else
    {
        // do control stuff
        // THIS IS IN THE CONTEXT OF THE UI THREAD
    }
}

Upvotes: 2

Dan McClain
Dan McClain

Reputation: 11920

You are trying to update the GUI thread from you background thread. You will need to use Invoke on the control you would like to update. You can check the InvokeRequired property on that control to see whether or not you will need to use Invoke to update the control

Upvotes: 0

Related Questions