1

I am having difficulties with binding and INotifyPropertyChanged. I have a ListView with is bound to an ObservableCollection and there is no problem at startup: data is correctly added to the ListView. When I add a new item to the collection, however, it doesn't update the UI. I'm sure the collection contains the object because I added a button to show the whole content of the collection.

Here is my UI code:

<StackPanel>
                <Button Content="Show title" Tapped="Button_Tapped"/>
                <ListView ItemsSource="{Binding Subscriptions}">
                    <ListView.ItemTemplate>
                        <DataTemplate>
                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto"/>
                                    <RowDefinition Height="*"/>
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto"/>
                                    <ColumnDefinition Width="*"/>
                                </Grid.ColumnDefinitions>
                                <Image Grid.RowSpan="2" Margin="0 0 10 0"
                                    Source="{Binding IconUri.AbsoluteUri}"/>
                                <TextBlock Grid.Column="1"
                                    Text="{Binding Title.Text}" Style="{StaticResource BaseTextBlockStyle}"/>
                                <TextBlock Grid.Row="1" Grid.Column="1"
                                    Text="{Binding LastUpdatedTime.DateTime}"/>
                            </Grid>
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
            </StackPanel>

And here is the data context class:

class RssReaderData : INotifyPropertyChanged
{
    private string[] acceptContentTypes = {
                                              "application/xml",
                                              "text/xml"
                                          };

    private ObservableCollection<SyndicationFeed> _subscriptions;
    public ObservableCollection<SyndicationFeed> Subscriptions
    {
        get { return _subscriptions; }
        set { NotifyPropertyChanged(ref _subscriptions, value); }
    }

    public int SubscriptionsCount
    {
        get { return Subscriptions.Count; }
    }

    public RssReaderData()
    {
        Subscriptions = new ObservableCollection<SyndicationFeed>();
        AddFeedAsync(new Uri("http://www.theverge.com/rss/index.xml"));
        AddFeedAsync(new Uri("http://blogs.microsoft.com/feed/"));
    }

    public async Task<bool> AddFeedAsync(Uri uri)
    {
        // Download the feed at uri
        HttpClient client = new HttpClient();
        var response = await client.GetAsync(uri);

        // Check that we retrieved the resource without error and that the resource has XML content
        if (!response.IsSuccessStatusCode || !acceptContentTypes.Contains(response.Content.Headers.ContentType.MediaType))
            return false;

        var xmlFeed = await response.Content.ReadAsStringAsync();

        // Create a new SyndicationFeed and load the XML to it
        SyndicationFeed newFeed = new SyndicationFeed();
        newFeed.Load(xmlFeed);

        // If the title hasn't been set, the feed is invalid
        if (String.IsNullOrEmpty(newFeed.Title.Text))
            return false;

        Subscriptions.Add(newFeed);
        return true;
    }

    #region INotifyPropertyChanged management
    public event PropertyChangedEventHandler PropertyChanged;
    public void NotifyPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    public bool NotifyPropertyChanged<T> (ref T variable, T value, [CallerMemberName] string propertyName = null)
    {
        if (object.Equals(variable, value)) return false;
        variable = value;
        NotifyPropertyChanged(propertyName);
        return true;
    }
    #endregion
}

As you can see I implemented the INotifyPropertyChanged interface while I think I shouldn't even have to (the ObservableCollection does that for me). I don't care about notifying the changes in the items I add to my collection, what I need is to notify when a new item is added to it.

I would say that my code is OK as is, but it seems not and I don't see why :-/

Also, while I'm at it, I have two quick questions: what's are the differences between a ListView and a ListBox and between a Grid and a GridView ?

Thank you for you help :-)

EDIT : as requested, here's the code-behind of the page

 RssReaderData context = new RssReaderData();

    public FeedsPage()
    {
        this.InitializeComponent();

        this.NavigationCacheMode = NavigationCacheMode.Required;
    }

    private async void Button_Tapped(object sender, TappedRoutedEventArgs e)
    {
        string feedsTitles = "\n";
        foreach (var feed in context.Subscriptions)
        {
            feedsTitles += "\n " + feed.Title.Text;
        }

        MessageDialog d = new MessageDialog("There are " + context.SubscriptionsCount + " feeds:" + feedsTitles);
        await d.ShowAsync();
    }

    private async void NewFeedSubscribeButton_Tapped(object sender, TappedRoutedEventArgs e)
    {
        string feedUri = NewFeedUriInput.Text;
        if (String.IsNullOrEmpty(feedUri))
            return;

        if (!Uri.IsWellFormedUriString(feedUri, UriKind.Absolute))
        {
            MessageDialog d = new MessageDialog("The URL you entered is not valid. Please check it and try again.", "URL Error");
            await d.ShowAsync();
            return;
        }

        bool feedSubscribed = await context.AddFeedAsync(new Uri(feedUri));
        if (feedSubscribed)
        {
            NewFeedUriInput.Text = String.Empty;
            FeedsPivot.SelectedIndex = 0;
        }
        else
        {
            MessageDialog d = new MessageDialog("There was an error fetching the feed. Are you sure the URL is referring to a valid RSS feed?", "Subscription error");
            await d.ShowAsync();
            return;
        }
    }

    private void FeedsList_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (FeedsList.SelectedIndex > -1 && FeedsList.SelectedIndex < context.SubscriptionsCount)
        {
            Frame.Navigate(typeof(FeedDetailsPage), context.Subscriptions[FeedsList.SelectedIndex]);
        }
    }
Jeahel
  • 1,012
  • 2
  • 13
  • 30
  • 1
    I see nothing obviously wrong in your code. Can you show the part of the code where you add new items to the collection? – Kevin Gosse Apr 08 '15 at 09:57
  • Sure, I edited my message to include the code I omitted in the AddFeedAsync method. This is where I actually add the object to the ObservableCollection. – Jeahel Apr 08 '15 at 11:05
  • 2
    Do it the other way: populate your item, *then* add to the list. `var newFeed = new SyndicationFeed(); newFeed.Load(xmlFeed); Subscriptions.Add(newFeed);` My theory is that your item is properly added to the list, but is displayed as an empty item because the `SyndicationFeed` class does not implement `INotifyPropertyChanged`. If my theory is correct, this simple change should fix your app. – Kevin Gosse Apr 08 '15 at 11:09
  • Jus a reference to KooKiz's comment - [the question along with its answers](http://stackoverflow.com/q/1427471/2681948) may interest you. – Romasz Apr 08 '15 at 11:23
  • I edited my first post to reflect the recommendations you made, though it's a bit more logical, it's doesn't solve the problem :/ – Jeahel Apr 08 '15 at 12:00
  • @Jeahel Where do you call `AddFeedAsync`? Are you sure you are using the same *DataContext*? – Romasz Apr 08 '15 at 12:09
  • The ObservableCollection implements INotify*Collection*Changed, not INotifyPropertyChanged. It should notify when you add/move/remove items, so anything correctly bound should update. – slugster Apr 08 '15 at 12:16
  • @Romasz I call it from the page code-behind so they share the same context. @ slugster Whether it is INotifyCollection or PropertyChanged, I don't have to implement it, right? I know I implemented INotifyPropertyChanged but it's not necessary with my code as normally ObservableCollection already handles that. I mean I could just delete the whole region of code that manages notifications and it should work as expected, am I wrong? – Jeahel Apr 08 '15 at 12:31
  • 1
    Your code seems fine to me as well. Can you share the code in the code-behind file? Also (just making sure), have you tried debugging it and are you 100% sure that the `Subscriptions.Add(newFeed);` line is reached and successfully executed? – yasen Apr 08 '15 at 12:34
  • @Jeahel Like yasen has said - please add a code, especially where you set the *DataContext* of the Page (which refers to *Subscriptions*) and the code when you call `AddFeedAsync` which is not updating the UI. – Romasz Apr 08 '15 at 12:37
  • I edited my first post. @yasen Yes I am sure of that, I added a button on the UI that displays a popup. Right before showing the popup I populate it with all the collection items. You can see it in my code-behind. – Jeahel Apr 08 '15 at 12:42
  • @Jeahel Where do you set the *DataContext* of your page or *ListView*? – Romasz Apr 08 '15 at 12:48
  • Oh sorry I forgot to say that. In my XAML, it's the data context of the whole page: ` `. Should I also post my whole XAML instead of just the stackpanel containing the listview ? – Jeahel Apr 08 '15 at 12:49
  • 1
    @Jeahel With this line in xaml you are referring to two different RssReaderData classes. Try to add in your Page constructor this line: `this.DataContext = context;`, remove the DataContext from xaml and check if that helped. – Romasz Apr 08 '15 at 12:52
  • Of course! Both the code-behind and the XAML code instantiated an RssReaderData object so they had two different instances... Thank you for pointing that out! Could you post that as an answer so I can mark it as solved? – Jeahel Apr 08 '15 at 12:55

1 Answers1

1

It turned out that you have created two instances of RssReaderData - one in code behind and one in xaml with:

<Page.DataContext> 
     <DataModel:RssReaderData/> 
</Page.DataContext

In this situation the collection to which your ListView is bound to is not the same you refer in the code behind - context.

The simple solution may be to remove above lines from XAML and set DataContext in the code behind:

public FeedsPage()
{
    this.InitializeComponent();
    this.NavigationCacheMode = NavigationCacheMode.Required;
    this.DataContext = context;
}

The other case is that it also probably may not update properly the UI, as KooKiz has pointed out - better would be first to create the item, then add it to a collection.

You may also take a look at this question and its answers which deals with problem when item changes inside ObservableCollection.

Community
  • 1
  • 1
Romasz
  • 29,662
  • 13
  • 79
  • 154