0

I am trying to load in (asynchronously) a picture from a directory which shall be displayed when loaded.

In my XAML-Code I bound some List to the data template:

<ScrollViewer Grid.Row="2">
            <ItemsControl x:Name="display">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <StackPanel Orientation="Horizontal"/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Image Source="{Binding Source ,IsAsync=True}" Width="100" Height="100" Margin="5"/>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </ScrollViewer>

Together with a class created:

public class ImageViewModel
{
    public ImageViewModel(string path)
    {
        Path = path;
    }
    public string Path { get; }

    private BitmapImage image;

    public BitmapImage Source
    {
        get
        {
            if (image == null)
            {
                image = new BitmapImage();
                image.BeginInit();
                image.CacheOption = BitmapCacheOption.OnLoad;
                image.DecodePixelWidth = 500;
                image.UriSource = new Uri(Path);
                image.EndInit();
                image.Freeze();
            }

            return Source;
        }
    }
}

with the rest of code:

ObservableCollection<ImageViewModel> images { get; } = new ObservableCollection<ImageViewModel>();

string workingDir = "some_path";
List<string> files = new List<string>();

public MainWindow()
{
    InitializeComponent();
    display.ItemsSource = images;
    DataContext = this;
}

private void On_Loaded(object sender, RoutedEventArgs e)
{
    files = DirSearch(workingDir); //returns List of strings with paths

    foreach (string file in files)
    {
        images.Add(new ImageViewModel(file));
    }
}

So, the code executes and does something, but does not display any images: enter image description here

Max
  • 103
  • 7
  • why not bind directly to Path? – Rand Random Jul 30 '23 at 14:34
  • how does this work? `Binding Path=workingDir`? – Max Jul 30 '23 at 14:37
  • 1
    instead of ` – Rand Random Jul 30 '23 at 15:33
  • your code behind `public BitmapImage Source…` seems unnecessary just use the `Path` property don’t need to manually turn it into an `BitmapImage`. – Rand Random Jul 30 '23 at 15:34
  • But I need the code behind `BitmapImage Source` to not fully decode (too much RAM usage) and (although your suggestion works) the UI freezes partially – Max Jul 30 '23 at 15:50
  • never worked with DecodePixelWidth, so can’t really tell if your scenario needs it or not, but since you specify width/height on the image wpf would scale it - which is more performant I don’t know - instead I would try if virtualization would give you a benefit have a look at: https://stackoverflow.com/questions/2783845/virtualizing-an-itemscontrol – Rand Random Jul 30 '23 at 16:21
  • 2
    @RandRandom DecodePixelWidth/Height is a common way to reduce bitmap memory consumption. – Clemens Jul 30 '23 at 17:41
  • @Max Are there any data binding error messages in the Output Window in Visual Studio when you debug the application? Have you checked by debugging that the values of the Path property are valid absolute paths? If the paths are relative, use `new Uri(Path, UriKind.Relative)` or `new Uri(Path, UriKind.RelativeOrAbsolute)` – Clemens Jul 30 '23 at 17:43
  • there are no binding errors, I tried `UriKind.Relative` bevor, but I can try it once more once I get home – Max Jul 30 '23 at 17:52

1 Answers1

1

Your code throws a StackOverflowException because the Source property getter calls

return Source;

It should look like shown below. Note that it also uses UriKind.RelativeOrAbsolute to support both absolute and relative image file paths.

public BitmapImage Source
{
    get
    {
        if (image == null)
        {
            image = new BitmapImage();
            image.BeginInit();
            image.CacheOption = BitmapCacheOption.OnLoad;
            image.DecodePixelWidth = 500;
            image.UriSource = new Uri(Path, UriKind.RelativeOrAbsolute);
            image.EndInit();
            image.Freeze();
        }

        return image;
    }
}

Since you are already setting the MainWindow's DataContext and images is already a property, you should also use data binding for the ItemsSource property of the ItemsControl instead of assigning it in code behind.

Make the source property public and follow the usual naming conventions:

public ObservableCollection<ImageViewModel> Images { get; }
    = new ObservableCollection<ImageViewModel>();

public MainWindow()
{
    InitializeComponent();
    DataContext = this;
}

Then write a Binding in XAML:

<ItemsControl ItemsSource="{Binding Images}">
...
Clemens
  • 123,504
  • 12
  • 155
  • 268