18

I'm binding a ListView to a list of objects, like so;

<ListView 
    ItemsSource="{ Binding Path=. }"
    ItemTemplate="{DynamicResource EventTemplate}">   

I'm binding to an object which declares two properties;

string DisplayName { get; }
System.Drawing.Image Image { get; set; }

I want to populate a DataTemplate but I can't figure out how; if I do this in my template;

<StackPanel Orientation="Horizontal">
    <Image Source="{ Binding Path=Image }" />
    <TextBlock Text="{ Binding Path=DisplayName }" />
</StackPanel>      

The text appears but the image does not. What am I doing wrong? The debug output shows

System.Windows.Data Error: 1 : Cannot create default converter
to perform 'one-way' conversions between types
'System.Drawing.Image' and 'System.Windows.Media.ImageSource'.
Consider using Converter property of Binding.
BindingExpression:Path=Image; DataItem='RealElement'
(HashCode=54308798); target element is 'Image' (Name='');
target property is 'Source' (type 'ImageSource')
Steve Cooper
  • 20,542
  • 15
  • 71
  • 88

2 Answers2

39

Found a way I'm happy with. Using Reed Copsey's pointer and this tutorial I've wrapped the code as a IValueConverter.

Here's the converter from System.Drawing.Image to System.Windows.Media.ImageSource;

using System;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Windows.Data;

namespace System.Windows.Media
{
    /// <summary>
    /// One-way converter from System.Drawing.Image to System.Windows.Media.ImageSource
    /// </summary>
    [ValueConversion(typeof(System.Drawing.Image), typeof(System.Windows.Media.ImageSource))]
    public class ImageConverter : IValueConverter
    {
        public object Convert(object value, Type targetType,
            object parameter, CultureInfo culture)
        {
            // empty images are empty...
            if (value == null) { return null; }

            var image = (System.Drawing.Image)value;
            // Winforms Image we want to get the WPF Image from...
            var bitmap = new System.Windows.Media.Imaging.BitmapImage();
            bitmap.BeginInit();
            MemoryStream memoryStream = new MemoryStream();
            // Save to a memory stream...
            image.Save(memoryStream, ImageFormat.Bmp);
            // Rewind the stream...
            memoryStream.Seek(0, System.IO.SeekOrigin.Begin);
            bitmap.StreamSource = memoryStream;
            bitmap.EndInit();
            return bitmap;
        }

        public object ConvertBack(object value, Type targetType,
            object parameter, CultureInfo culture)
        {
            return null;
        }
    }
}

Then you need to bring the image converter into XAML as a resource;

xmlns:med="clr-namespace:System.Windows.Media"
...

<ListView.Resources>
    <med:ImageConverter x:Key="imageConverter" />
</ListView.Resources>

Then you can use it in XAML to bind directly to the Image, using the new converter;

<Image Source="{ Binding Path=Image, Converter={StaticResource imageConverter} }" />
Steve Cooper
  • 20,542
  • 15
  • 71
  • 88
  • 1
    I have a question about memory usage: does this conversion take place only once or each time the Image is to shown in the visible part of i.e. ListBox ?? – emesx Dec 30 '11 at 22:12
  • 1
    Can't really say -- the conversion is done every time it's needed by WPF -- so I think it's just an internal implementation detail. I don't think there'd be any reason for it to be converted over and over, but it might be worth sticking in a counter and seeing... – Steve Cooper Jan 06 '12 at 00:30
  • 3
    `memoryStream` is never closed or disposed, is that a concern here? I have some very similar code, that might have even come from this question, but it's uglier. – Maslow Sep 02 '15 at 00:19
  • 2
    For the future readers, do remember to dispose the previous `Image` instance in the property setter. This is in response to @Maslow's comment. – Noctis Tong Jan 24 '19 at 04:56
5

You can't bind that directly - you need to have some type of converter that will convert from a GDI Image to a WPF Image.

Here is one approach - it uses a memory stream to pull the data from the GDI image and create a BitmapSource object.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • Hi -- thanks for the pointer. I'm half-way through reading your "Better User and Developer Experiences – From Windows Forms to WPF with MVVM" articles at the moment; great stuff. Anyway, that gave me enough to get to the answer; I've posted it alongside. – Steve Cooper Aug 06 '10 at 20:04
  • @Steve: Glad to hear you're liking the series ;) – Reed Copsey Aug 06 '10 at 20:05
  • It's been really helpful. The WPF model is very different from windows forms, and your essays are helping unlock it in my brain. – Steve Cooper Aug 06 '10 at 20:12
  • The `memoryStream` in that example is never disposed, and not in a using or closed. is this a problem? – Maslow Sep 02 '15 at 00:23