In this post, we see how we can add a DataBound image in the DataTemplate of a WPF ListBox. We experiment with three different approaches of achieving that and discuss on which one to use depending on the situation. In all cases, the image files are embedded within the application as “Resource” files. Later in this post, we will see what needs to be changed in order to support having the image files in a folder outside of the application Assembly.
Our object for binding will be the class named Item having the following properties:
public class Item { public int PictureID { get; set; } public string Name { get; set; } }
We want to display a specific image depending on the value of the property PictureID. The ListBox in the .xaml file be:
<ListBox ItemTemplate="{DynamicResource DataTemplateItem}" ItemsSource="{Binding Items}"/>
The definition of the DataTemplate for the items of the ListBox is:
<DataTemplate x:Key="DataTemplateItem"> <Canvas Width="236" Height="48"> <Image Source="{Binding ??}" Height="32" Width="32" Canvas.Left="8" Canvas.Top="8"/> <TextBlock Text="{Binding Name}" Canvas.Left="56" Canvas.Top="8" Width="172"/> </Canvas> </DataTemplate>
The question in hand is what we need to put in the Binding of the Source property of the Image element in order to get the following result:
The Images reside in the “Images” folder of the project and have their “Build Action” set to “Resource”.
1) Extending the Item class
Our first approach is to introduce a new property within the class Item that generates the appropriate string for the image path within the assembly. So the new extended Item class is:
public class Item { public int PictureID { get; set; } public string Name { get; set; } public string PictureString { get {return "/Images/" + PictureID.ToString() + ".png";} } }
Note the new property PictureString with its accessor calculating the appropriate image path. Now in the Source property of the DataTemplate we just need to add Source=”{Binding PictureString}”.
2) Using DataTemplate Triggers
A different approach that leads to the same result is not changing at all the Item class and introducing the following style in the item’s DataTemplate:
<DataTemplate x:Key="DataTemplateItemStyle"> <Canvas Width="236" Height="48"> <Image Name="ImageName" Height="32" Width="32" Canvas.Left="8" Canvas.Top="8"/> <TextBlock Text="{Binding Name}" Canvas.Left="56" Canvas.Top="8" Width="172"/> </Canvas> <DataTemplate.Triggers> <DataTrigger Binding="{Binding PictureID}" Value="0"> <Setter TargetName="ImageName" Property="Source" Value="Images .png"/> </DataTrigger> <DataTrigger Binding="{Binding PictureID}" Value="1"> <Setter TargetName="ImageName" Property="Source" Value="Images1.png"/> </DataTrigger> </DataTemplate.Triggers> </DataTemplate>
In this DataTemplate, the Source property is not directly DataBound to any property. We use a DataTemplate trigger to test the value of the PictureID property and based on the result decide which image to display.
3) Using a Converter
The third approach uses a Converter to do the job. The converter is written as follows:
public class ImageConverter:IValueConverter { public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture) { return "/Images/" + value.ToString() + ".png"; } public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture){ throw new NotImplementedException(); } }
And the DataTemplate will become:
<local:ImageConverter x:Key="ImageConverter"/> <DataTemplate x:Key="DataTemplateItemConverter"> <Canvas Width="236" Height="48"> <Image Source="{Binding PictureID,Converter={StaticResource ImageConverter}}" (…) /> <TextBlock Text="{Binding Name}" Canvas.Left="56" Canvas.Top="8" Width="172"/> </Canvas> </DataTemplate>
The Converter receives the PictureID as it arrives from the object and converts it accordingly.
Which approach we use?
If you cannot change the Item class (because it is not your code for example) you cannot use the first approach unless you create a wrapper for the class and provide the appropriate extended property. If you do not want to do this, you selection of one of the two other approaches depends on how complex your image selection logic is. If it is complicated then the Data trigger approach may be difficult or impossible to implement and then your only choice is resorting to the converter.
On the other hand if you can alter the properties of the Item class then you can extend your class with the new property. Sometimes, though, your image selection logic does not depend only on properties of your business object and therefore you do not want to extend your POCO entities with the general business logic of you system. If this is the case a converter is either your best choice, or an extension of the Item class in the View augmented with the appropriate logic.
Now if your images are not resources in the Assembly and reside somewhere in the server where you deploy your application you need to change the paths for all approaches to: “pack://siteoforigin:,,,/Images/0.png” where the “pack://siteoforigin:,,,” says “the root of the application” and then you provide the path for the image.
Download the project here
Finally in case you are wondering in the project the property IsSynchonizedWithCurrentItem=true maintains the same selection for all the three ListBoxes in the project since they are bound to the same list of Item objects.