使用 backgroundWorker 加载 ListView 和 ImageList

Loading ListView and ImageList while using a backgroundWorker

加载图像列表和列表视图显然会导致 UI 稍微挂起。所以我想加载 UI 并让用户知道我们正在处理事情并让 backgroundWorker 做这件事。我尝试在 formLoad 上调用 "backgroundWorker1.RunWorkerAsync();"

但是我得到了"Cross-thread operation not valid: Control 'listView1' accessed from a thread other than the thread it was created on."

我查了一下,发现我需要稍微更改一下代码。这就是我所拥有的:

    private void WatermarkPicker_Load(object sender, EventArgs e)
    {
        backgroundWorker1.RunWorkerAsync();
    }


private void GetImages()
    {
        DirectoryInfo dir = new DirectoryInfo(@"c:\pics");
        this.listView1.View = View.LargeIcon;
        this.imageList1.ImageSize = new Size(100, 100);
        if (listView1.InvokeRequired)
        {
            listView1.Invoke(new MethodInvoker(
                () => this.listView1.LargeImageList = this.imageList1));
        }
        else
        {
            this.listView1.LargeImageList = this.imageList1;
        }
        int j = 0;
        foreach (FileInfo file in dir.GetFiles())
        {
            try
            {
                //this.imageList1.Images.Add(Image.FromFile(file.FullName));
                imageList1.Images.Add(file.Name, Image.FromFile(file.FullName));
                ListViewItem item = new ListViewItem(file.Name);
                item.Tag = file.Name;
                item.ImageIndex = j;
                this.listView1.Items.Add(item);
                j++;
            }
            catch
            {
                Console.WriteLine("This is not an image file");
            }
        }
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        GetCurrentLogos();
        GetImages();
    }

if(listView1.InvokeRequired)满足,运行listView1.invoke。但是 UI 中的 listView 中没有显示任何内容。不过没有错误。

您可以像在主线程中那样在后台工作程序中执行您想要的操作。但是每次要访问一个UI组件,都需要在主线程(也叫UI线程)下进行。语法取决于您是否在 WPF、Windows Phone、Xamarin 等下工作

使用 WPF,你应该有这样的东西:

Application.Current.Dispatcher.Invoke(new Action(() => { /* Your code here */ }));

必须从UI线程而不是后台工作线程访问ListViewImageList

我建议您将初始化逻辑移动到 WatermarkPicker_Load 中,它在 UI 线程中运行,然后将 foreach 循环更改为使用 Control.Invoke.

foreach (var file in dir.GetFiles())
{
    var image = Image.FromFile(file.FullName);
    var item = new ListViewItem(file.Name)
    {
        Tag = file.Name;
    }
    listView1.Invoke(() => 
    {
        imageList1.Images.Add(file.Name, image);
        listView1.Items.Add(item);
    });
}

您应该将所有尝试访问 Listview 和 ImageList 的代码移到 DoWork 事件之外,并使用在 DoWork 代码内部引发的 ProgressChanged 事件。

这样,ProgressChanged 事件中的代码在正确的 UI 线程中执行(假设 BackgroundWorker 是在 UI 线程中创建的)。

例如:

private void WatermarkPicker_Load(object sender, EventArgs e)
{
    listView1.View = View.LargeIcon;
    listView1.LargeImageList = imageList1;

    backgroundWorker1.WorkerReportsProgress = true;
    backgroundWorker1.ProgressChanged += backgroundWorker1_ProgressChanged;
    backgroundWorker1.DoWork += backgroundWorker1_DoWork;
    backgroundWorker1.RunWorkerAsync();
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    // Do not try to use in any way an object derived from Control
    // like the ListView when you are inside the DoWork method.....
    BackgroundWorker worker = sender as BackgroundWorker;
    DirectoryInfo dir = new DirectoryInfo(@"c:\pics");
    foreach (FileInfo file in dir.GetFiles())
    {
        // You can't use imageList here
        // imageList1.Images.Add(file.Name, Image.FromFile(file.FullName));

        // Raise the ProgressChanged event. 
        // The code there will execute in the UI Thread
        worker.ReportProgress(1, file);        
    }
}

private void backgroundWorker1_ProgressChanged(object sender,  ProgressChangedEventArgs e)
{
    // This code executes in the UI thread, no problem to 
    // work with Controls like the ListView
    try
    {
        FileInfo file = e.UserState as FileInfo;
        imageList1.Images.Add(file.Name, Image.FromFile(file.FullName));
        ListViewItem item = new ListViewItem(file.Name);
        item.Tag = file.Name;
        item.ImageIndex = imageList1.Images.Count - 1;
        listView1.Items.Add(item);
     }
     catch(Exception ex)
     {
        ....
     }
}