Загрузка ListView и ImageList при использовании backgroundWorker

Загрузка списка изображений и просмотра списка, очевидно, заставляет пользовательский интерфейс немного зависать. Поэтому я хотел бы загрузить пользовательский интерфейс и сообщить пользователю, что мы работаем над вещами, и позволить backgroundWorker сделать это. Я попытался в formLoad вызвать "backgroundWorker1.RunWorkerAsync();"

Но я получил сообщение "Недопустимая операция между потоками: доступ к элементу управления listView1 осуществляется из потока, отличного от потока, в котором он был создан".

Я немного покопался и понял, что мне нужно немного изменить код. Итак, вот что у меня есть:

    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. Но ничего не отображается в listView в пользовательском интерфейсе. Никаких ошибок, хотя.

3 ответа

Решение

Вы должны переместить весь код, который пытается получить доступ к Listview И ImageList, за пределы события DoWork и использовать событие ProgressChanged, возникшее внутри кода DoWork.

Таким образом, код внутри события ProgressChanged выполняется в правильном потоке пользовательского интерфейса (при условии, что BackgroundWorker создан в потоке пользовательского интерфейса).

Например:

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)
     {
        ....
     }
}

Вы можете выполнять то, что вы хотите, в фоновом режиме, как вы должны делать в главном потоке. Но каждый раз, когда вы хотите получить доступ к компоненту пользовательского интерфейса, вы должны делать это в основном потоке (также называемом потоком пользовательского интерфейса). Синтаксис зависит от того, работаете ли вы под WPF, Windows Phone, Xamarin и т. Д.

С WPF у вас должно получиться что-то вроде этого:

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

Вы должны получить доступ ListView а также ImageList из потока пользовательского интерфейса не фонового рабочего потока.

Я предлагаю вам переместить логику инициализации в WatermarkPicker_Load который работает в потоке пользовательского интерфейса, а затем изменить 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);
    });
}
Другие вопросы по тегам