WPF Listbox with images - how to detect if an image didn't download
I have a wpf ListBox, and each item has an image that the list needs to download from a server - the list definition looks like so:
<ListBox x:Name="List" BorderThickness="0" AlternationCount="2" ItemContainerStyle="{StaticResource alternatingWithBinding}"
HorizontalContentAlignment="Stretch" ScrollViewer.HorizontalScrollBarVisibility="Hidden">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid x:Name="itemsGrid" Margin="3" ShowGridLines="False" >
<Grid.RowDefinitions>
<RowDefinition Height="59"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="45" />
<ColumnDefinition Width="60" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="150" />
</Grid.ColumnDefinitions>
<Button x:Name="btn" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" Tag="{Binding}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}" />
<Image x:Name="Thumb" Grid.Column="1" Stretch="Uniform" Opacity="1.0" Source="{Binding Path=Image, Converter={StaticResource ImageConverter}}" Height="65" VerticalAlignment="Center"/>
<TextBlock x:Name="Name" Grid.Column="2" Padding="2" Margin="17,0" VerticalAlignment="Center" Text="{Binding Path=Name}"
Tag="{Binding}" />
</Grid>
<DataTemplate.Triggers>
...
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
and the converter looks like:
public class ImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is string)
{
value = new Uri((string)value);
}
if (value is Uri)
{
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.DecodePixelWidth = 150;
bi.UriSource = value;
bi.DownloadFailed += new EventHandler<ExceptionEventArgs>(bi_DownloadFailed);
bi.EndInit();
开发者_运维问答 return bi;
}
return null;
}
The idea is to show a default image when the sourceUrl returns nothing from the server. But since i'm using the converter in the XAML code,
Source="{Binding Path=Image, Converter={StaticResource ImageConverter}}"
I'm not sure how to intercept that case. I see that BitmapImage has the DownloadFailed event which is perfect for me, I just don't know how to use that in this context.
Have you looked at the Binding class TargetNullValue property?
I'd have the Converter return null if you can't download the file.
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
if (parameter.ToString()=="blue")
{
return new Uri("Butterfly1.png", UriKind.RelativeOrAbsolute);
}
return null;
}
Then setup a default image in the XAML
<Window.Resources>
<my:ImageConverter x:Key='ImageConverter1' />
<BitmapImage x:Key='defaultImage'
UriSource='/WpfApplication1;component/default.png' />
Then in your binding specify the TargetNullValue.
<Image Source='{Binding Converter={StaticResource ImageConverter1},
ConverterParameter="red",TargetNullValue={StaticResource defaultImage}}'
Height='100' />
<Image Source='{Binding Converter={StaticResource ImageConverter1},
ConverterParameter="blue",TargetNullValue={StaticResource defaultImage}}'
Height='100' />
Hmmm.... I believe download occurs on EndInit, which matters. Since you're using a static resource for a converter, you end up having a single instance per application. That means if you create a variable scoped to the class-level and use that to store the results of the download, you can have issues if you aren't careful with the state of your variables between calls to Convert. However, since the UI is rendered in a single thread, you don't have to worry too much.
public class ImageConverter : IValueConverter
{
private static readonly BitmapImage _default = MakeDefault();
private bool _downloadFailed;
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is string)
{
value = new Uri((string)value);
}
if (value is Uri)
{
_downloadFailed = false;
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.DecodePixelWidth = 150;
bi.UriSource = value;
// the event handler sets _downloadFailed to true!
bi.DownloadFailed += bi_DownloadFailed;
bi.EndInit();
// unhook so we don't unintentionally keep the instance alive
// this is important; you will leak BI instances otherwise!
bi.DownloadFailed -= bi_DownloadFailed;
if(_downloadFailed)
return _default;
return bi;
}
return null;
}
精彩评论