Alex
Alex

Reputation: 699

Binding image to embedded resource in wpf

I'm new to WPF, and I'm encountering a problem. I have a ListBox with images arranged in a floorplan, the image source is bound to a uri in the code. uri is to an embedded resource in the project. This all works fine on startup, until I have to change an image. No exceptions, but the images do not change. When I run in debug, I can see that even though the ObservableCollection items change, the mainwindow images do not.

Is this due to caching, or is it something else? I try to disable caching in the xaml, but I get the error The TypeConverter for "CacheMode" does not support converting from a string.

I'll do my best to be brief with the code and only include relevant information:

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public ObservableCollection<ComputerInfo> CompListObs;
    public ObservableCollection<ComputerInfo> compListObsNotifier
    {
        get { return CompListObs; }
        set
        {
            CompListObs = value;
            RaisePropertyChanged("compListObsNotifier");
        }
    }
//...
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = this;
        // This correctly loads the images from the xml and displays them on the main window:
        var items = XDocument.Load(@"path to xml")
                    .Descendants("Computer")
                    .Select(i => new ComputerInfo {
                        MachineName  = (string)i.Element("MachineName"),
                        Lat = (double)i.Element("Long"),
                        Lon = (double)i.Element("Lat"), 
                        CurrentImage = ResourceHelper.LoadBitmapURIFromResource((string)i.Element("Img"))
                     }).ToList();
            CompListObs = new ObservableCollection<ComputerInfo>(items);
    }
    public void MainTimer_Tick(object sender, EventArgs e)
    {
       // This runs fine and I can see the members of CompListObs are changing,
       // but the images on the mainwindow are not changing.
       foreach(var comp in CompListObs) { comp.CurrentImage = ResourceHelper.LoadBitmapURIFromResource("another image");
    }
    // INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged = delegate { };
    private void RaisePropertyChanged(string propName)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propName));
    }

public class ComputerInfo : INotifyPropertyChanged
{
    public ClientInfo ClientsInfo { get; set; }
    public string MachineName { get; set; }
    public Uri CurrentImage { set; get; }
    public double Lat { get; set; }
    public double Lon { get; set; }

    public Uri currentImageNotifier
    {
        get { return CurrentImage; }
        set
        {
            CurrentImage = value;
            RaisePropertyChanged("compListObsNotifier");
        }
    }
    // INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    public void RaisePropertyChanged(string propName)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propName));
    }
}

Here is the xaml:

<ListBox ItemsSource="{Binding compListObsNotifier}">
  <ListBox.ItemTemplate>
    <DataTemplate>
      <Image Source="{Binding Path=CurrentImage}" Height="65"></Image>
    </DataTemplate>
  </ListBox.ItemTemplate>
  <!-- after this, I place <ListBox.ItemsPanel> <ItemsPanelTemplate> and <Canvas> in the ListBox.

Upvotes: 0

Views: 907

Answers (2)

Dustin Cleveland
Dustin Cleveland

Reputation: 618

When you bind to an observable collection you are subscribing to changes in the collection (for instance, adding or removing items from the collection). This does not subscribe you to changes to properties on the individual items in the collection. As others have stated, if you want to bind to a property on the items, you will need to raise a PropertyChanged event when the property changes.

private Uri currentImage;

public Uri CurrentImage
{
    get { return currentImage; }
    set 
    { 
        currentImage = value;
        RaisePropertyChanged("CurrentImage");
    }
}

Note: You may want to wrap the setter in an if statement and use Uri.Compare() to determine if the given value is actually different from the current value. This way, you only raise a PropertyChanged event when the property actually changes.

Also, in your code example you are setting the CurrentImage property in this foreach loop:

foreach(var comp in CompListObs) 
{ 
    comp.CurrentImage = ResourceHelper.LoadBitmapURIFromResource("another image");
}

However, you're raising your PropertyChanged event from the currentImageNotifier property. It's okay to raise PropertyChanged events from a location outside of the property being modified, but you either need to replace the CurrentImage assignment in your foreach loop with currentImageNotifier, or modify your CurrentImage property to raise its own PropertyChanged event. As it stands, you're not actually raising a PropertyChanged event on the property you're binding to.

Honestly, it doesn't look like you even need the currentImageNotifier property. It's not doing anything you couldn't just do with the CurrentImage property directly.

Upvotes: 1

Silas Reinagel
Silas Reinagel

Reputation: 4223

When you are binding to CurrentImage and the value of CurrentImage changes, you must raise the property changed event for CurrentImage.

Based on the supplied code, you also could do this:

public Uri currentImageNotifier
{
    get { return CurrentImage; }
    set
    {
        CurrentImage = value;
        RaisePropertyChanged("CurrentImage");
    }
}

Upvotes: 1

Related Questions