C# – Solution to binding errors thrown by setting Image.Source to a binding to null. (suppression?)

bindingcnetwpf

I've got a scenario where I have Image Sources set to null which are resolved later and then displayed. Any good way to prevent my code from throwing binding errors?

An example:

System.Windows.Data Error: 23 : Cannot convert '' from type '' to type 'System.Windows.Media.ImageSource' for 'en-US' culture with default conversions; consider using Converter property of Binding. NotSupportedException:'System.NotSupportedException: ImageSourceConverter cannot convert from (null).
at System.ComponentModel.TypeConverter.GetConvertFromException(Object value)
at System.Windows.Media.ImageSourceConverter.ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, Object value)
at MS.Internal.Data.DefaultValueConverter.ConvertHelper(Object o, Type destinationType, DependencyObject targetElement, CultureInfo culture, Boolean isForward)'

XAML

<Image x:Name="image" Height="Auto" Width="Auto" Opacity="0">
    <Image.Effect>
        <DropShadowEffect/>
    </Image.Effect>
    <Image.Source>
        <Binding Path="ImageStream">
            <Binding.ValidationRules>
                <validationRules:NotNullStreamValidationRule/>
            </Binding.ValidationRules>
        </Binding>
    </Image.Source>
</Image>

C#

namespace FlickrDemo.ViewModel
{
    public class FlickrPhotoViewModel : ViewModelBase
    {
        public const string ImageStreamPropertyName = "ImageStream";

        private Stream _imageStream = null;

        public Stream ImageStream
        {
            get
            {
                return _imageStream;
            }

            set
            {
                if (_imageStream == value)
                {
                    return;
                }
                _imageStream = value;

                RaisePropertyChanged(ImageStreamPropertyName);
            }
        }

        public const string IsLoadingPropertyName = "IsLoading";

        private bool _isLoading = false;

        public bool IsLoading
        {
            get
            {
                return _isLoading;
            }

            set
            {
                if (_isLoading == value)
                {
                    return;
                }

                _isLoading = value;

                RaisePropertyChanged(IsLoadingPropertyName);
            }
        }

        public const string PhotoIDPropertyName = "PhotoID";

        private string _photoID = String.Empty;

        public string PhotoID
        {
            get
            {
                return _photoID;
            }

            set
            {
                if (_photoID == value)
                {
                    return;
                }

                var oldValue = _photoID;
                _photoID = value;

                RaisePropertyChanged(PhotoIDPropertyName);
            }
        }

        public FlickrPhotoViewModel(string photoID)
        {
            this.PropertyChanged += async (s, e) =>
            {
                if (e.PropertyName == ImageStreamPropertyName)
                {
                    if (!(ImageStream == null || ImageStream == Stream.Null))
                    {
                        IsLoading = false;
                    }
                }
            };
            IsLoading = true;
            PhotoID = photoID;
        }
    }
}

Best Answer

I ran into the same problem trying to use a string URI (rather than a Stream). I resolved the issue by setting up a property on my view model of type ImageSource, which is the type of the Source property on Image, and binding Source to that property. This gets any automatic conversion out of the mix. Within the new property, handle the null case, then defer to the standard ImageSourceConverter. In your case, I think this would look something like this:

Code:

public const string ImageStreamPropertyName = "ImageStream";

private Stream _imageStream = null;

public Stream ImageStream
{
    get
    {
        return _imageStream;
    }

    set
    {
        if (_imageStream == value)
        {
            return;
        }
        _imageStream = value;

        RaisePropertyChanged(ImageStreamPropertyName);
        // Raise for ImageSource too since it changes with ImageStream
        RaisePropertyChanged("ImageSource");
    }
}

public ImageSource ImageSource
{
    get
    {
        if (ImageStream == null)
            return null;

        return (ImageSource)new ImageSourceConverter().ConvertFrom(ImageStream);
    }
}

XAML:

    <Image.Source>
        <Binding Path="ImageSource" />
    </Image.Source>
Related Topic