【问题标题】:Populating a listview from another thread从另一个线程填充列表视图
【发布时间】:2012-02-08 13:43:42
【问题描述】:

我正在尝试从另一个类填充列表视图,但出现此错误: " 跨线程操作无效:控件 'listView1' 从创建它的线程以外的线程访问。"

在我的班级中,我这样声明我的列表视图:

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

在我的 MainForm 中我使用以下代码:

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

怎么了?

【问题讨论】:

标签: c# .net winforms multithreading


【解决方案1】:

通常我是这样做的:

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"));
    }   
    //...
    }
}

【讨论】:

    【解决方案2】:

    在这里对 SO 进行简单搜索会得到许多结果,告诉您不允许从创建控件的线程以外的线程更改 GUI 控件(跨线程 GUI 访问)。

    为此,与更新ListView 相关的所有操作都必须使用this.Invokethis.Dispatcher.Invoke(在WPF 中)完成。

    编辑
    例如this thread here

    示例代码:

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

    【讨论】:

    • 我的应用是windows窗体,如何使用Invoke方法填充listview
    • 与使用 ISynchronizeInvoke 和 Dispatcher 中的 Invoke 方法在 Windows 窗体上进行控制修改的方式相同。
    【解决方案3】:

    您正在尝试从后台线程更新 GUI 线程。您需要在要更新的控件上使用Invoke。您可以检查该控件上的InvokeRequired 属性,以查看是否需要使用Invoke 来更新控件

    【讨论】:

      【解决方案4】:

      从 UI 线程和其他线程调用函数时避免重复代码或故障的巧妙技巧是:

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

      函数第一次进入另一个线程时,会调用invoke,然后函数再次调用自身,这次是在正确的线程上下文中。 实际工作只在 if() 块之后写下一次。

      【讨论】:

        【解决方案5】:

        这是一个合理的做法,因为通常在应用程序中,您希望 ListView 更新等继续进行,而不会耽误您的代码。

        我已经完成了包含我想在后台更新的控件的用户控件之间的消息系统,它可能会变得非常混乱,因为您最终不得不为消息/事件发送更多信息,而不仅仅是填充/更新,杂乱的代码是错误的代码,所以我尝试了其他方法。

        有一个很好的方法,ListView 填充/更新的缓慢部分通常在 ListViewItems 的创建中,您可以在自己的线程中完全准备这些。

        所以现在,对于这类应用程序(使用填充或更新 ListView,我不需要等待它准备好才能继续我的代码),我的单独线程创建/准备 ListViewItems,然后将准备好的项目添加到线程完成时的 ListView 非常快,因此最终的 ListView 更新可以在用户几乎察觉不到的用户事件上完成。添加到此“仅添加您可以看到的那些”,它确实是即时的。有几行额外的行,所以当滚动开始时,您可以添加更多行。 (你可能已经注意到 youtube/facebook/windows 图片浏览器都是这样做的)。因为在我们的例子中,我们已经准备好了 ListViewItems,所以将它们添加到列表中非常简单。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-01-22
          相关资源
          最近更新 更多