Create and update UI elements Async Wpf

2019-08-07 15:17发布

I'm trying to load several images async into the ui.

When an image is loaded (a bitmap is created form a path), the image should be set as the fill of a rectangle on the window.

When I put the creation of the bitmapimage also in the Dispatcher.Invoke method, the code works. But obviously I want the heavy work (creation of the bitmap) done in the tread and not in the invoke.

I've tried several solutions including backgroundworker but I can't get it to work. Right now I have the following code:

private void Window_ContentRendered(object sender, EventArgs e)
{
    Thread loadThread = new Thread(new ThreadStart(LoadImagesAsync));
    loadThread.Start();
}

private void LoadImagesAsync()
{
    IEnumerable<string> images = System.IO.Directory.GetFiles(IMAGE_FOLDER, "*.jpg").Skip(_PageNumber * NUMBER_OF_IMAGES).Take(NUMBER_OF_IMAGES);

    for (int i = 0; i < NUMBER_OF_IMAGES; i++)
    {
        var bitm = new BitmapImage(new Uri(images.ElementAt(i)));                

        this.Dispatcher.Invoke(() =>
        {
            Grid grid = (Grid)grd_photoBox.Children[i];

            var rectangle = (from e in grid.Children.OfType<Rectangle>()
                                where e is Rectangle
                                select e).First();

            ImageBrush brush = new ImageBrush(bitm);

            rectangle.Fill = brush;
        });
    }
}

I get the following exception:

The calling thread cannot access this object because a different thread owns it.

Any clues?

3条回答
ら.Afraid
2楼-- · 2019-08-07 15:43

In case of big images, use the following code, it will directly rescale the images and load the in other thread then the main thread which makes sure the images are loaded instantly. Edit the following values to change the load format:

bitm.DecodePixelWidth = 200;
bitm.DecodePixelHeight = 100;

private void LoadImagesAsync()
        {
            IEnumerable<string> images = System.IO.Directory.GetFiles(IMAGE_FOLDER, "*.jpg").Skip(_PageNumber * NUMBER_OF_IMAGES).Take(NUMBER_OF_IMAGES);

            for (int i = 0; i < NUMBER_OF_IMAGES; i++)
            {
                int j = i;
                var bitm = new BitmapImage();

                bitm.BeginInit();
                bitm.CacheOption = BitmapCacheOption.OnLoad;
                bitm.UriSource = new Uri(images.ElementAt(i));
                bitm.DecodePixelWidth = 200;
                bitm.DecodePixelHeight = 100;
                bitm.EndInit();

                ImageBrush brush = new ImageBrush(bitm);
                brush.Freeze();

                this.Dispatcher.BeginInvoke(new Action(() => 
                {
                    Grid grid = (Grid)grd_photoBox.Children[j];

                    var rectangle = (from e in grid.Children.OfType<Rectangle>()
                                        where e is Rectangle
                                        select e).First();

                    rectangle.Fill = brush;
                }));
            }
        }
查看更多
等我变得足够好
3楼-- · 2019-08-07 15:54

I tried below code and it is working for me.

Task<IEnumerable<string>>.Factory.StartNew(() => System.IO.Directory.GetFiles(
            imagePath,
            "*.jpg")).
            ContinueWith(task =>
                        {
                            foreach (var item in task.Result)
                            {
                                this.Dispatcher.BeginInvoke((Action)(() =>
                                {
                                    var img = new Image
                                                    {
                                                        Source =
                                                            new BitmapImage(
                                                            new Uri(item))
                                                    };
                                    LayoutRoot.Children.Add(img);

                                }));

                            }
                        });

LayoutRoot is my grid in xaml.

Hope it will help.

查看更多
Emotional °昔
4楼-- · 2019-08-07 16:00

The trick is to freeze the bitmap to allow access from another thread. Therefore you also have to make sure that the bitmap is loaded immediately on creation, as the default behaviour is to load lazily.

var bitm = new BitmapImage();
bitm.BeginInit();
bitm.CacheOption = BitmapCacheOption.OnLoad; // load immediately
bitm.UriSource = new Uri(images.ElementAt(i));
bitm.EndInit();
bitm.Freeze();
查看更多
登录 后发表回答