从线程中的表单控件获取数据

时间:2013-07-22 07:36:39

标签: c# multithreading winforms

在线程内从表单控件获取数据时出现问题。我需要访问数据,然后修改它。

以下不起作用我知道,但我用它作为一个例子来看看我想要做什么。

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

我已经阅读了关于进行线程安全调用的MSDN文档,但我似乎无法访问实际的列表视图控件。我见过的例子使用委托来“更新”控件,但在更新控件中的数据之前,我需要访问控件中的数据。

编辑:

我想看一个示例或示例链接,详细说明如何在foreach循环中访问ListView1表单控件。

4 个答案:

答案 0 :(得分:3)

您需要使用Invoke pattern,以便能够从主UI线程以外的线程访问任何 UI元素或其属性。 Windows上的所有UI控件都在主线程上运行,以便在屏幕上显示的OS和UI之间正确处理消息链。

答案 1 :(得分:1)

我正在谈论的(快速编写)示例,这假设您不需要真正使用控件,我包含了一个基于tigran链接的函数

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

答案 2 :(得分:0)

方法1:

像Tigran描述的那样使用Invoke。

对于Winforms,这看起来像:

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

如果从UI-Thread调用,则CheckAccess()调用返回true,否则返回false。

Dispatcher类位于“WindowsBase”NET中的“System.Windows.Threading”命名空间中。装配

https://stackoverflow.com/a/4429009/1469035

复制的调度程序信息

编辑:将代码更改为WinForms。 编辑:代码已修复。

方法2:

使用回叫:

未经测试的代码:

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只读Winforms中允许从UI-Thread以外的线程访问控件。因此,您可以检查所需的任何Control-Property,并将所需信息传递给Delegate。

即使Reading doents以这种方式工作,您也可以创建另一个具有返回值的Delegate。 Invoke()方法返回一个对象:

与此类似:

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

派生自:Link

答案 3 :(得分:0)

你不能做你想做的事。对UI的所有访问和更新必须在UI线程中进行。这是强制性的。 你可以做的是在UI上将原始数据写入缓存,然后在所有处理完成后处理缓存和回调到UI。

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

    }