locked
At a loss with DataBinding and OvervableCollections

    Question

  • I've spent the better part of a day trying to figure this one out, using and trying many different code samples that were more trouble than they were worth, and also using code very similar to that on MSDN/other sites with examples that claim to work.

    The issue I'm having is that for the life of me, I can not get the UI to see changes to my observable collection and update accordingly. I'm pulling data from iTunes podcast database as JSON, de-serializing it, and populating an ObservableCollection list with said data. I've done plenty of testing and debugging to be absolutely certain the data is being collected, processed, and added to the collection as necessary, and it updates when the search terms are changed. 

    Disclaimer: I'm a noob when it comes to windows phone development. I've done some coding in the past, but it's been a while and as such I'm quite rusty, though eager to learn.

    Here is the XAML

    <Page
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:allegro"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:ViewModel="using:allegro.ViewModel"
        x:Class="allegro.FeedTests"
        mc:Ignorable="d"
        Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    
        <Page.DataContext>
            <ViewModel:SearchViewModel SearchTerm="radiolab"/>
        </Page.DataContext>
    
        <Page.Resources>
            <DataTemplate x:Key="ImageTextCollectionTemplate">
                <StackPanel Orientation="Horizontal" Width="380" Height="auto" HorizontalAlignment="Left" Margin="0,0,0,10">
                    <Border Height="90" Width="90">
                        <Image Source="{ Binding PodcastImageUrl, Mode=OneWay}" Stretch="Fill"/>
                    </Border>
                    <StackPanel Orientation="Vertical" VerticalAlignment="Top" Margin="0,0,0,0" HorizontalAlignment="Left">
                        <TextBlock x:Name="PodcastName" Text="{ Binding PodcastName }" Style="{StaticResource TitleTextBlockStyle }"
                           Margin="10,0,0,0" Width="auto" TextTrimming="WordEllipsis" />
                        <StackPanel Margin="10,0,0,0" Width="150" Orientation="Horizontal" HorizontalAlignment="Left">
                            <TextBlock Text="Episodes: " />
                            <TextBlock Text="{ Binding PodcastCount }" 
                                Margin="5,0,0,0" Width="350" TextTrimming="WordEllipsis" 
                                HorizontalAlignment="Left" Opacity="0.49"/>
                        </StackPanel>
                        <TextBlock Text="{ Binding PodcastDescription }" 
                           Margin="10,0,0,0" TextTrimming="WordEllipsis" TextWrapping="Wrap" />
                    </StackPanel>
                </StackPanel>
            </DataTemplate>
        </Page.Resources>
        
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="auto" />
                <RowDefinition Height="auto" />
            </Grid.RowDefinitions>
            <Grid Grid.Row="0" Margin="0,0,0,25">
                <StackPanel>
                    <TextBlock HorizontalAlignment="Left" Margin="10,10,0,0" TextWrapping="Wrap" Text="Find Podcasts" VerticalAlignment="Top" Style="{ThemeResource HeaderTextBlockStyle}"/>
                    <TextBox HorizontalAlignment="Left" Margin="10,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="380" PlaceholderText="Type Search Term Here"
    				    Text="{ Binding SearchTerm, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged }" />
                </StackPanel>
            </Grid>
            <Grid Grid.Row="1">
                <StackPanel>
                    <GridView x:Name="ImageTextCollectionGrid" ItemsSource="{Binding SearchCollection}" 
    				    ItemTemplate="{ StaticResource ImageTextCollectionTemplate }" >
                        <GridView.ItemsPanel>
                            <ItemsPanelTemplate/>
                        </GridView.ItemsPanel>
                    </GridView>
                </StackPanel>
            </Grid>
        </Grid>
    </Page>

    Here is the ViewModel (some unecessary code removed )

    public class SearchViewModel : INotifyPropertyChanged
        {
            private string sSearchTerm;
    
            public string SearchTerm
            {
                get { return sSearchTerm; }
                set { sSearchTerm = value; OnPropertyChanged("SearchTerm"); GetNewPodcasts(); }
            }
    
            private ObservableCollection<PodcastSearchResult> _SearchCollection;
            
            public ObservableCollection<PodcastSearchResult> SearchCollection
            {
                get
                {
                    return _SearchCollection;
                }
                set
                {
                    if (_SearchCollection == value) return;
                    _SearchCollection = value;
                }
            }
    
            private async Task<string> MakeWebRequest()
            {
    
                HttpClient http = new System.Net.Http.HttpClient();
                http.Timeout = TimeSpan.FromSeconds(5);
    
                try
                {
                    HttpResponseMessage response = await http.GetAsync("http://itunes.apple.com/search?entity=podcast&term=" + sSearchTerm + "&limit=10");
                    return await response.Content.ReadAsStringAsync();
                }
                catch (HttpRequestException e)
                {
                    DisplayDialog("HTTP Request Exception: " + e.Message.ToString());
                    return null;
                }
                finally
                {
                    http.Dispose();
                }
            }
    
            private async void GetNewPodcasts()
            {
                string sResults;
                try
                {
                    sResults = await MakeWebRequest();
    
                    SearchResult result = JsonConvert.DeserializeObject<SearchResult>(sResults);
    
                    _SearchCollection = new ObservableCollection<PodcastSearchResult>();
    
                    foreach (var item in result.Results)
                    {
                        _SearchCollection.Add(new PodcastSearchResult { PodcastName = item.collectionName, PodcastDescription = item.artistName, PodcastImageUrl = item.artworkUrl100, PodcastCount = item.trackCount } );
                    }
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.Message.ToString());
                }
                finally
                {
                    sResults = null;
                }
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            private void OnPropertyChanged(string propertyName)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
    
            private async void DisplayDialog(string sMessage)
            {
                MessageDialog msgDialog = new MessageDialog(sMessage);
                await msgDialog.ShowAsync();
            }
        }

    And the Data class (search result)

    public class PodcastSearchResult : INotifyPropertyChanged
        {
            private string _PodcastName;
            private string _PodcastDescription;
            private string _PodcastImageUrl;
            private int _PodcastCount;
    
            public string PodcastName
            {
                get
                {
                    return _PodcastName;
                }
                set
                {
                    _PodcastName = value;
                    RaisePropertyChanged("PodcastName");
                }
            }
    
            public string PodcastDescription
            {
                get
                {
                    return _PodcastDescription;
                }
                set
                {
                    _PodcastDescription = value;
                    RaisePropertyChanged("PodcastDescription");
                }
            }
            public string PodcastImageUrl
            {
                get
                {
                    return _PodcastImageUrl;
                }
                set
                {
                    _PodcastImageUrl = value;
                    RaisePropertyChanged("PodcastImageUrl");
                }
            }
            public int PodcastCount
            {
                get
                {
                    return _PodcastCount;
                }
                set
                {
                    _PodcastCount = value;
                    RaisePropertyChanged("PodcastCount");
                }
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            protected virtual void RaisePropertyChanged(String propertyName)
            {
                if ((PropertyChanged != null))
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
            
    
        }

    The funny thing about all of this is that the designer in VS seems my data, and updates the view just fine......so I'm 98% certain it has to do with the databinding seeing the initial change to the observableCollection....I'm just not sure how to go about doing that.

    Side note: I've already tried setting the data context on the GridView like so: 

    DataContext="{Binding SearchCollection}" ItemsSource="{Binding}"

    and it to works in the designer, but not on device.

    At this point, I can't help but think I'm missing something terribly stupid, and simple but after hours and hours of trying to figure it out using training documents/other peoples answers, I thought I'd ask for help. 

    Thanks very much for your time!

    Friday, October 3, 2014 3:26 AM

Answers

  • Could you please try changing the SearchCollection to also have a notification and then use the Public property whenever setting the same(e.g in your function GetNewPodcasts).

    When the form will initially render it will have nothing to bind to , but as this is an async call to the method the property will be reinitialzed but the UI will not update as it wont have a notification.

    Please try the below code.

    public ObservableCollection<PodcastSearchResult> SearchCollection { get { return _SearchCollection; } set { if (_SearchCollection == value) return;

    OnPropertyChanged("SearchCollection"); _SearchCollection = value; } }

    private async void GetNewPodcasts()
            {
                string sResults;
                try
                {
                    sResults = await MakeWebRequest();
    
                    SearchResult result = JsonConvert.DeserializeObject<SearchResult>(sResults);
    
                    SearchCollection = new ObservableCollection<PodcastSearchResult>();
    
                    foreach (var item in result.Results)
                    {
                        _SearchCollection.Add(new PodcastSearchResult { PodcastName = item.collectionName, PodcastDescription = item.artistName, PodcastImageUrl = item.artworkUrl100, PodcastCount = item.trackCount } );
                    }
                }

    • Proposed as answer by anujsethi06 Friday, October 3, 2014 8:51 AM
    • Marked as answer by not-a-hack Friday, October 3, 2014 1:47 PM
    Friday, October 3, 2014 7:37 AM

All replies

  • Could you please try changing the SearchCollection to also have a notification and then use the Public property whenever setting the same(e.g in your function GetNewPodcasts).

    When the form will initially render it will have nothing to bind to , but as this is an async call to the method the property will be reinitialzed but the UI will not update as it wont have a notification.

    Please try the below code.

    public ObservableCollection<PodcastSearchResult> SearchCollection { get { return _SearchCollection; } set { if (_SearchCollection == value) return;

    OnPropertyChanged("SearchCollection"); _SearchCollection = value; } }

    private async void GetNewPodcasts()
            {
                string sResults;
                try
                {
                    sResults = await MakeWebRequest();
    
                    SearchResult result = JsonConvert.DeserializeObject<SearchResult>(sResults);
    
                    SearchCollection = new ObservableCollection<PodcastSearchResult>();
    
                    foreach (var item in result.Results)
                    {
                        _SearchCollection.Add(new PodcastSearchResult { PodcastName = item.collectionName, PodcastDescription = item.artistName, PodcastImageUrl = item.artworkUrl100, PodcastCount = item.trackCount } );
                    }
                }

    • Proposed as answer by anujsethi06 Friday, October 3, 2014 8:51 AM
    • Marked as answer by not-a-hack Friday, October 3, 2014 1:47 PM
    Friday, October 3, 2014 7:37 AM
  • Ok, that seems to have worked. Thank you! I get it now.

    I now understand you can set a collection to be the changed property. I assumed it was looking at the changed properties in a different way but I see why this works.

    I knew it was something simple and stupid. :)

    Thanks very much for your help!

    Friday, October 3, 2014 1:13 PM
  • Good to hear that. Could you mark it as answer in that case.

    -Anuj Sethi

    Friday, October 3, 2014 1:28 PM
  • Yes, I'll do that. I found a couple other things that were an issue that was me testing things, but your fix was it. Consider it marked!

    Thanks

    Friday, October 3, 2014 1:47 PM