1

Having an issue getting data from a form control from within a thread. I need to access the data, then modify it.

The following doesn't work I'm aware, but I used it as an example to see what I'm trying to do.

Thread t = new Thread(() => {
    foreach (ListViewItem row in listView1.Items)
    {
        row.SubItems[0].Text = "Checking";
        Thread.Sleep(2000);
    } 
 });
 t.Start();

I've read the MSDN documentation on making thread safe calls, but I can't seem to get access to the actual list view control. The examples I've seen use delegates to "update" controls, but I need access to the data in the controls before I update the data in them.

Edit:

I'd like to see an example, or a link to an example, detailing how to get access to the ListView1 form control in the foreach loop.

M C
  • 626
  • 1
  • 11
  • 27
James Jeffery
  • 12,093
  • 19
  • 74
  • 108
  • 2
    I've always found that I didn't really need access to the control cross thread, also thread.sleep is barely ever a good idea. What is it your trying to accomplish? – Sayse Jul 22 '13 at 07:40
  • Use dispatcher http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.aspx – Kamil Budziewski Jul 22 '13 at 07:40
  • Just make sure that `listView1` is a global variable. – Phong Vo Jul 22 '13 at 07:41
  • 2
    @PhongVo - If that was an answer you'd be getting very fast downvotes – Sayse Jul 22 '13 at 07:41
  • Thread sleep was inserted to do some testing, to replicate network access speeds. It's not in the actual implementation of what I'm trying to do. – James Jeffery Jul 22 '13 at 07:43
  • Which version of .Net are you using? My answer will differ depending on that. – Matthew Watson Jul 22 '13 at 07:51
  • Do you actually intend to do anything with the list or do you just need its contents? – Sayse Jul 22 '13 at 07:59
  • @Sayse I just need its contents. – James Jeffery Jul 22 '13 at 08:00
  • If you just need Contents, you should be able to do it without invoking. Just remove the assignment, and replace it with a message box. – dognose Jul 22 '13 at 08:07
  • If its just the contents then you should be able to pass in an argument, I'd give an example but I don't have VS on this computer, [this](http://stackoverflow.com/questions/3598757/how-to-call-a-method-that-takes-multiple-parameters-in-a-thread) may help – Sayse Jul 22 '13 at 08:11

4 Answers4

3

You need to use Invoke pattern, in order to be able to access any UI element or its properties from the thread other then main UI thread. All UI controls on windows allways run on the main thread, to handle message chain correctly between OS and UI presented on the screen.

Tigran
  • 61,654
  • 8
  • 86
  • 123
1

The (quickly written) example I was talking about, this assumes that you do not need to really use the controls, I included a function that is based off tigran's link

Thread t = new Thread(() => UpdateText(listBox1.Items));
t.Start();

private void UpdateText(ListBox.ObjectCollection items)
{
   foreach (var item in items)
   {
      SetText(item.ToString());
      Thread.Sleep(1000);
   }
}
Sayse
  • 42,633
  • 14
  • 77
  • 146
0

Method 1:

Use Invoke like Tigran describes.

For Winforms this would look like:

        Thread t = new Thread(() =>
        {
            if (!Dispatcher.CurrentDispatcher.CheckAccess())
            {
                Dispatcher.CurrentDispatcher.BeginInvoke(
                    new Action(() =>
                    {
                        foreach (ListViewItem row in listView1.Items)
                        {
                            row.SubItems[0].Text = "Checking";
                            Thread.Sleep(2000);
                        }
                    }),
                    DispatcherPriority.ApplicationIdle,
                    null);
            }
            else
            {
                foreach (ListViewItem row in listView1.Items)
                {
                    row.SubItems[0].Text = "Checking";
                    Thread.Sleep(2000);
                }
            }
        });
        t.Start();

The CheckAccess() Call returns true if called from the UI-Thread otherwise false.

The Dispatcher Class is located in the "System.Windows.Threading" Namespace in the "WindowsBase" NET. Assembly

Dispatcher info copied from: https://stackoverflow.com/a/4429009/1469035

Edit: Changed code to WinForms. Edit: Code Fixed.

Method 2:

Use a Callback:

Untested Code:

public partial class Form1 : Form
{
    private delegate void SetCallback(ListViewItem row, string text);

    public Form1()
    {
        InitializeComponent();
    }

    private void SomeMethod()
    {
        Thread t = new Thread(() =>
        {
            foreach (ListViewItem row in listView1.Items)
            {
                if (listView1.InvokeRequired)
                {
                    SetCallback d = new SetCallback(SetText);
                    this.Invoke(d, new object[] { row, "Checking" });
                }

                Thread.Sleep(2000);
            }
        });
        t.Start();
    }

    private void SetText(ListViewItem row, string text)
    {
        row.SubItems[0].Text = text;
    }
}

AFAIK readonly Access to Controls from Threads other than the UI-Thread is allowed in Winforms. So you can check any Control-Property you want and pass the required information to the Delegate.

And even if Reading doents work that way, you can just make another Delegate that has a return value. The Invoke() Method returns an object:

Similar to this:

private delegate object GetCallback(ListViewItem row);
private object o;

...

GetCallback g = new GetCallback(GetText);
                        o = this.Invoke(g, new object[] { row });


    private string GetText(ListViewItem row)
    {
        return row.SubItems[0].Text;
    }

Derived From: Link

Community
  • 1
  • 1
M C
  • 626
  • 1
  • 11
  • 27
0

You can't do what you want to do. All accesses and updates to UI must go in UI thread. It is mandatory. What you can do is writing your raw data into cache on UI then processing your cache and callbacks to UI after all processings are finished.

   public class CacheData {
        private object row;

        public CacheData(object row)
        {
            //initialization
        }

        public static ProcessedData ProcessData(List<CacheData> dataToProcess)
        {
            return new ProcessedData();
        }
    }

    public class ProcessedData { }

    private void AccessControl()
    {
        ListView list = new ListView();
        List<CacheData> cache = new List<CacheData>();

        //Filling the cache on UI
        foreach (var row in list.Items)
        {
            cache.Add(new CacheData(row));
        }

        //Process result async and then invoke on UI back
        System.ComponentModel.BackgroundWorker bg = new System.ComponentModel.BackgroundWorker();
        bg.DoWork += (sender,e) => {
            e.Result = CacheData.ProcessData(cache);
        };
        bg.RunWorkerCompleted += (sender, e) => { 

            //If you have started your bg from UI result will be invoked in UI automatically. 
            //Otherwise you should invoke it manually.
            list.Dispatcher.Invoke((Action) delegate {
                //pass e.result to control here)
            },null);
        };

        bg.RunWorkerAsync();

    }
gleb.kudr
  • 1,518
  • 12
  • 16