ArMaN
ArMaN

Reputation: 2407

Providing an initial image placeholder for the WPF Image class

When i run the project a runtime error ocure: Error: Property 'UriSource' or property 'StreamSource' must be set. because this.ImageUri is null , i don't know why this.ImageUri be null ! help me

I have been working with the WPF ListBox using images as my list box items. The sourced image path points to a server hosting those images. While on fast network, the images appeared without any noticeable delay. However it became apparent over a slow link that the user experience degraded and I really wanted to show a placeholder image while the image was downloaded and decoded.

Surprisingly, I didn't find a solution in the blogosphere for this issue so I coded up a derived class to address this.

The sample XAML below is from my item container style. I replaced Image with my local class implementation local:ImageLoader.

<Window.Resources>
<DataTemplate DataType="{x:Type local:MyData}">
...
<StackPanel Grid.Column="0" Margin="5">
<Border BorderThickness="0">
<MyControl:ImageLoader Width="50" Height="50" ImageUri="{Binding Path=profile_image_url_https, FallbackValue=profile_image_url_https}" InitialImage="/MyProject;component/Images/nopic.png"  HorizontalAlignment="Left"></imgz:ImageLoader>
</Border>
</StackPanel>
...
</DataTemplate>
</Window.Resources>

<Grid>
<ListBox ItemsSource="{Binding Source = {StaticResource MyData}}"    />
</Grid>

The heart of the handling for the initial image is in the OnLoaded() method, where I use a BitmapImage as the source and set the UriSource to the derived class' ImageUri dependency property, which allows for data binding. The initial image is updated to the actual image when the download completes or when a failure event is received. The class also optionally allows you to specify a "LoadFailedImage".

public class ImageLoader : Image
{
    public static readonly DependencyProperty ImageUriProperty = DependencyProperty.Register(
        "ImageUri", typeof(Uri), typeof(ImageLoader), new PropertyMetadata(null, null));

    private BitmapImage loadedImage;

    public ImageLoader()
    {
        this.Loaded += this.OnLoaded;
    }

    public string LoadFailedImage
    {
        get;
        set;
    }

    public Uri ImageUri
    {
        get {return this.GetValue(ImageUriProperty) as Uri;}
        set {this.SetValue(ImageUriProperty, value);}
    }

    public string InitialImage
    {
        get;
        set;
    }

    private new ImageSource Source
    {
        get {return base.Source;}
        set {base.Source = value;}
    }

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        // Loading the specified image            
        this.loadedImage = new BitmapImage();
        this.loadedImage.BeginInit();
        this.loadedImage.CacheOption = BitmapCacheOption.OnDemand;
        this.loadedImage.DownloadCompleted += this.OnDownloadCompleted;
        this.loadedImage.DownloadFailed += this.OnDownloadFailed;
        this.loadedImage.UriSource = this.ImageUri;
        this.loadedImage.EndInit();

        // The image may be cached, in which case we will not use the initial image
        if (!this.loadedImage.IsDownloading)
        {
            this.Source = this.loadedImage;
        }
        else
        {
            // Create InitialImage source if path is specified
            if (!string.IsNullOrWhiteSpace(this.InitialImage))
            {
                BitmapImage initialImage = new BitmapImage();

                // Load the initial bitmap from the local resource
                initialImage.BeginInit();
                initialImage.UriSource = new Uri(this.InitialImage, UriKind.Relative);
                initialImage.DecodePixelWidth = (int)this.Width;
                initialImage.EndInit();

                // Set the initial image as the image source
                this.Source = initialImage;                
            }
        }

        e.Handled = true;
    }

    private void OnDownloadFailed(object sender, ExceptionEventArgs e)
    {
        if (!string.IsNullOrWhiteSpace(this.LoadFailedImage))
        {
            BitmapImage failedImage = new BitmapImage();

            // Load the initial bitmap from the local resource
            failedImage.BeginInit();
            failedImage.UriSource = new Uri(this.LoadFailedImage, UriKind.Relative);
            failedImage.DecodePixelWidth = (int)this.Width;
            failedImage.EndInit();
            this.Source = failedImage;
        }
    }

    private void OnDownloadCompleted(object sender, EventArgs e)
    {
        this.Source = this.loadedImage;
    }
}

When i run the project a runtime error ocured: Error: Property 'UriSource' or property 'StreamSource' must be set. because this.ImageUri is null , i don't know why this.ImageUri be null ! help me

Upvotes: 2

Views: 4004

Answers (1)

LPL
LPL

Reputation: 17083

If it isn't the semicolon typo in InitialImage="/MyProject;component/Images/nopic.png",
maybe it's better to set your InitialImage as Default in ImageUri

 public static readonly DependencyProperty ImageUriProperty = DependencyProperty.Register(
    "ImageUri", typeof(Uri), typeof(ImageLoader), new PropertyMetadata(new Uri("/MyProject/component/Images/nopic.png"), null));


UPDATE:

You have to bind to Image.Source and you could use PriorityBinding to show a placeholder.

<Image.Source>
    <PriorityBinding>
        <!--highest priority sources are first in the list-->
        <Binding Path="YourImageUri"
           IsAsync="True" />
        <Binding Path="InitialImageUri"
           IsAsync="True" />
    </PriorityBinding>
</Image.Source>

For a "LoadFailedImage" a would subsribe to Image.ImageFailed Event.

Hope this helps.

Upvotes: 2

Related Questions