John John - 4 months ago 20
C# Question

UWP Databinding from JSON not working

I'm trying to populate a grid from a json url using JSON.NET. I have been able to deserialize the data from the url and bind it to the model class. However it's not binding to the XAML Page.
Here is my model class and MovieManger class:

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace DataBindingTest3.Models
{
public class AmgMovie
{
public string state { get; set; }
public Data data { get; set; }
}
public class Data
{
public int type { get; set; }
public string type_name { get; set; }
public Movie[] movies { get; set; }
}

public class Movie
{
public string id { get; set; }
public string title { get; set; }
public string movie_desc { get; set; }
public string movie_actors { get; set; }
public string movie_director { get; set; }
public string music_director { get; set; }
public string movie_year { get; set; }
public string movie_month { get; set; }
public string realeased_date { get; set; }
public string image { get; set; }
public string portrait_image { get; set; }
public string movie_list_image_url { get; set; }
public string movie_poster_url { get; set; }
public string duration { get; set; }
public string url { get; set; }
public string download_url { get; set; }
public string trailer_url { get; set; }
public int allow_download { get; set; }
}

public class MovieManager
{
public static async Task<List<Movie>> GetMovies()
{
var movies = new List<Movie>();
var amg = new HttpClient();
HttpResponseMessage response = await amg.GetAsync(new Uri("some URL"));
var json = await response.Content.ReadAsStringAsync();
AmgMovie Movie = JsonConvert.DeserializeObject<AmgMovie>(json);

for (uint i = 0; i < Movie.data.movies.Count(); i++)
{
string id1 = Movie.data.movies[i].id;
string title1 = Movie.data.movies[i].title;
string portrait_image1 = Movie.data.movies[i].portrait_image;

movies.Add(new Movie { id = id1, title = title1, portrait_image = portrait_image1 });
}

return movies;

}
}
}


This is the code behind of the XAML page:

public sealed partial class MainPage : Page
{
private List<Movie> Movie;
public MainPage()
{
this.InitializeComponent();
}

private async void Page_Loaded(object sender, RoutedEventArgs e)
{
Movie = await MovieManager.GetMovies();
}
}


This is the XAML Page markup:

<Page
x:Class="DataBindingTest3.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:DataBindingTest3"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:data="using:DataBindingTest3.Models"
Loaded="Page_Loaded"
mc:Ignorable="d">

<Page.Resources>
<DataTemplate x:DataType="data:Movie" x:Key="MovieDataTemplate">
<StackPanel>
<TextBlock x:Name="movieID" Text="{x:Bind id}" FontSize="12" Foreground="Black" />
<Image x:Name="moviePoster" Source="{x:Bind portrait_image}" Width="145" Height="214"/>
<TextBlock x:Name="movieName" Text="{x:Bind title}" FontSize="16" Foreground="Black"/>
</StackPanel>
</DataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>

<GridView Grid.Row="0" ItemsSource="{x:Bind Movie}" ItemTemplate="{StaticResource MovieDataTemplate}">

</GridView>

</Grid>
</Page>


I also tried binding without json and just inputting some sample data in the MovieManager class and it binds perfectly. I noticed that when debugging both the sample method and json method, that both methods returns data, however only when I used the sample data the debugger when into the XAML Page.g.cs to bind the data.

Answer

You have two options:

  1. In the MainPage.xaml.cs change the Movie field's type to observable collection, and copy the items of the GetMovies() funcion's result to the collection like this: (UI change if the Movies collection's content changed (add/remove elements))

    private ObservableCollection<Movie> Movies = new ObservableCollection<Movie>();
    
    private async void AboutPage_Loaded(object sender, RoutedEventArgs e)
    {
        foreach (var movie in await GetMovies())
        {
            Movie.Add(movie);
        }
    }
    
  2. You make a property for the Movies List with the INotifyPropertyChanged implementation, and change the x:Bind mode to OneWay: (UI change if the Movies property changed (and not its content))

    public sealed partial class MainPage : Page, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        private List<Movie> _movies;
        public List<Movie> Movies
        {
            get { return _movies; }
            set { if(value != _movies) { _movies = value; PropertyChanged?.Invoke(nameof(Movies)); } }
        }
    
        public MainPage()
        {
            this.InitializeComponent();
        }
    
        private async void Page_Loaded(object sender, RoutedEventArgs e)
        {
            Movies = await MovieManager.GetMovies();
        }
    }
    

    Xaml:

    <GridView Grid.Row="0" ItemsSource="{x:Bind Movies, Mode=OneWay}" ItemTemplate="{StaticResource MovieDataTemplate}" />
    

In the first version, the UI won't change if you overwrite the Movies with a new list. In the second version, the UI won't change if you add/remove elements to the list.

You can combine these two versions.