Reputation: 631
I'm trying to bind a ListView with observable collection data but it stills empty. I have some break points and could see that ListaProfissionais(Observable collection) is properly populated. Below you can see my .cs and the .xaml code(the part that matter). Does anyone see what is wrong here?
using System.ComponentModel;
public class Profissionais : INotifyPropertyChanged
{
private string id;
public string Id
{
get { return this.id; }
set
{
if (this.id != value)
{
this.id = value;
this.NotifyPropertyChanged("Id");
}
}
}
private string categoria;
public string Categoria
{
get { return this.categoria; }
set
{
if (this.categoria != value)
{
this.categoria = value;
this.NotifyPropertyChanged("Categoria");
}
}
}
private string titulo;
public string Titulo
{
get { return this.titulo; }
set
{
if (this.titulo != value)
{
this.titulo = value;
this.NotifyPropertyChanged("Titulo");
}
}
}
private string fotoperfil;
public string FotoPerfil
{
get { return this.fotoperfil; }
set
{
if (this.fotoperfil != value)
{
this.fotoperfil = value;
this.NotifyPropertyChanged("FotoPerfil");
}
}
}
private string endereco;
public string Endereco
{
get { return this.endereco; }
set
{
if (this.endereco != value)
{
this.endereco = value;
this.NotifyPropertyChanged("Endereco");
}
}
}
private string distancia;
public string Distancia
{
get { return this.distancia; }
set
{
if (this.distancia != value)
{
this.distancia = value;
this.NotifyPropertyChanged("Distancia");
}
}
}
private string sexo;
public string Sexo
{
get { return this.sexo; }
set
{
if (this.sexo != value)
{
this.sexo = value;
this.NotifyPropertyChanged("Sexo");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public static ObservableCollection<Profissionais> ListaProfissionais = new ObservableCollection<Profissionais>();
try
{
using (WebClient client = new WebClient())
{
Uri uri = new Uri("xxxx.xxxxx.php");
byte[] retorno = await client.UploadValuesTaskAsync(uri, DataSend);
string resultado = System.Text.Encoding.UTF8.GetString(retorno);
ListaProfissionais = JsonConvert.DeserializeObject<ObservableCollection<Profissionais>>(resultado);
}
}
catch
{
throw;
}
finally
{
ListViewProfissionais.ItemsSource = ListaProfissionais;
// crashes here but I can see that ItemsSource has the 4 elements as expected
}
My XAML:
<ListView x:Name="ListViewProfissionais" x:FieldModifier="public static" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" VerticalScrollBarVisibility="Never" HorizontalScrollBarVisibility="Never" BackgroundColor="Transparent" SeparatorColor="Transparent">
<ListView.ItemTemplate>
<DataTemplate>
<Frame HasShadow="True" CornerRadius="5" HeightRequest="215" Margin="8" Padding="4" BackgroundColor="White">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="15"></RowDefinition>
<RowDefinition Height="25"></RowDefinition>
<RowDefinition Height="105"></RowDefinition>
<RowDefinition Height="25"></RowDefinition>
<RowDefinition Height="25"></RowDefinition>
</Grid.RowDefinitions>
<Label Text="{Binding Categoria}" TextColor="#8e8e8e" FontSize="10" Grid.Column="0" Grid.Row="0" VerticalOptions="StartAndExpand" HorizontalOptions="StartAndExpand" Margin="2,0,0,0"></Label>
<Label Text="{Binding Titulo}" FontAttributes="Bold" TextColor="#337760" FontSize="14" Grid.Column="0" Grid.Row="1" VerticalOptions="StartAndExpand" HorizontalOptions="StartAndExpand" Margin="2,0,0,0"></Label>
<Frame Grid.Column="0" Grid.Row="2" BackgroundColor="Transparent" CornerRadius="4" Padding="0" Margin="0">
<Image Source="{Binding FotoPerfil}" HorizontalOptions="FillAndExpand" Aspect="AspectFill" VerticalOptions="FillAndExpand" />
</Frame>
<Label Text="{Binding Endereco}" TextColor="#8e8e8e" FontSize="10" Grid.Column="0" Grid.Row="3" VerticalOptions="EndAndExpand" HorizontalOptions="StartAndExpand"></Label>
<Label Text="" Grid.Column="0" Grid.Row="4" FontSize="10" HorizontalOptions="StartAndExpand" TextColor="#ff9000" Padding="0,6,0,0">
<Label.FontFamily>
<OnPlatform x:TypeArguments="x:String" Android="Font-Awesome-Free-Solid.otf#FontAwesome5Free-Solid" iOS="FontAwesome5Free-Solid" />
</Label.FontFamily>
</Label>
<Label Text="{Binding Distancia}" TextColor="#ff9000" FontSize="10" Grid.Column="0" Grid.Row="4" VerticalOptions="Center" HorizontalOptions="StartAndExpand" Margin="10,0,0,0"></Label>
</Grid>
</Frame>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
It crashes with message "System.InvalidCastException: Specified cast is not valid.". See below:
System.InvalidCastException: Specified cast is not valid.
at at (wrapper castclass) System.Object.__castclass_with_cache(object,intptr,intptr)
at Xamarin.Forms.Internals.TemplatedItemsList`2[TView,TItem].ActivateContent (System.Int32 index, System.Object item) [0x00000] in D:\a\1\s\Xamarin.Forms.Core\TemplatedItemsList.cs:534
at Xamarin.Forms.Internals.TemplatedItemsList`2[TView,TItem].CreateContent (System.Int32 index, System.Object item, System.Boolean insert) [0x00000] in D:\a\1\s\Xamarin.Forms.Core\TemplatedItemsList.cs:543
at Xamarin.Forms.Internals.TemplatedItemsList`2[TView,TItem].GetOrCreateContent (System.Int32 index, System.Object item) [0x00023] in D:\a\1\s\Xamarin.Forms.Core\TemplatedItemsList.cs:602
at Xamarin.Forms.Internals.TemplatedItemsList`2[TView,TItem].get_Item (System.Int32 index) [0x00000] in D:\a\1\s\Xamarin.Forms.Core\TemplatedItemsList.cs:337
at Xamarin.Forms.Platform.Android.ListViewAdapter.GetCellsFromPosition (System.Int32 position, System.Int32 take) [0x0003b] in D:\a\1\s\Xamarin.Forms.Platform.Android\Renderers\ListViewAdapter.cs:539
at Xamarin.Forms.Platform.Android.ListViewAdapter.GetCellForPosition (System.Int32 position) [0x00000] in D:\a\1\s\Xamarin.Forms.Platform.Android\Renderers\ListViewAdapter.cs:454
at Xamarin.Forms.Platform.Android.ListViewAdapter.GetView (System.Int32 position, Android.Views.View convertView, Android.Views.ViewGroup parent) [0x0006d] in D:\a\1\s\Xamarin.Forms.Platform.Android\Renderers\ListViewAdapter.cs:225
at Android.Widget.BaseAdapter.n_GetView_ILandroid_view_View_Landroid_view_ViewGroup_ (System.IntPtr jnienv, System.IntPtr native__this, System.Int32 position, System.IntPtr native_convertView, System.IntPtr native_parent) [0x0001a] in <11f101b564894ca7af6c482ddc51c698>:0
at at (wrapper dynamic-method) Android.Runtime.DynamicMethodNameCounter.74(intptr,intptr,int,intptr,intptr)
Upvotes: 1
Views: 2262
Reputation: 61
UPDATE - JAN 25 2020
I had the time to check your code again and find out that You were putting the Template directly in the DataTemplate (which, in ListView, is not possible). You already post that You find this by yourself (Good job!).
I am leaving here the fix I was going to use for You pointing out a few things:
XAML
<?xml version="1.0" encoding="utf-8"?>
<ContentPage
x:Name="myPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="Playground.MainPage">
<ListView
ItemsSource="{Binding ListaProfissionais, Source={x:Reference myPage}}"
x:Name="ListViewProfissionais" HorizontalOptions="FillAndExpand"
HasUnevenRows="True"
VerticalOptions="FillAndExpand"
VerticalScrollBarVisibility="Never"
HorizontalScrollBarVisibility="Never"
BackgroundColor="Transparent"
SeparatorColor="Transparent">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Frame HasShadow="True" CornerRadius="5" HeightRequest="215" Margin="8" Padding="4" BackgroundColor="White">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="15"></RowDefinition>
<RowDefinition Height="25"></RowDefinition>
<RowDefinition Height="105"></RowDefinition>
<RowDefinition Height="25"></RowDefinition>
<RowDefinition Height="25"></RowDefinition>
</Grid.RowDefinitions>
<Label Text="{Binding Categoria}" TextColor="#8e8e8e" FontSize="10" Grid.Column="0" Grid.Row="0" VerticalOptions="StartAndExpand" HorizontalOptions="StartAndExpand" Margin="2,0,0,0"></Label>
<Label Text="{Binding Titulo}" FontAttributes="Bold" TextColor="#337760" FontSize="14" Grid.Column="0" Grid.Row="1" VerticalOptions="StartAndExpand" HorizontalOptions="StartAndExpand" Margin="2,0,0,0"></Label>
<Frame Grid.Column="0" Grid.Row="2" BackgroundColor="Transparent" CornerRadius="4" Padding="0" Margin="0">
<Image Source="{Binding FotoPerfil}" HorizontalOptions="FillAndExpand" Aspect="AspectFill" VerticalOptions="FillAndExpand" />
</Frame>
<Label Text="{Binding Endereco}" TextColor="#8e8e8e" FontSize="10" Grid.Column="0" Grid.Row="3" VerticalOptions="EndAndExpand" HorizontalOptions="StartAndExpand"></Label>
<Label Text="" Grid.Column="0" Grid.Row="4" FontSize="10" HorizontalOptions="StartAndExpand" TextColor="#ff9000" Padding="0,6,0,0">
<Label.FontFamily>
<OnPlatform x:TypeArguments="x:String" Android="Font-Awesome-Free-Solid.otf#FontAwesome5Free-Solid" iOS="FontAwesome5Free-Solid" />
</Label.FontFamily>
</Label>
<Label Text="{Binding Distancia}" TextColor="#ff9000" FontSize="10" Grid.Column="0" Grid.Row="4" VerticalOptions="Center" HorizontalOptions="StartAndExpand" Margin="10,0,0,0"></Label>
</Grid>
</Frame>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
Code Behind:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace Playground
{
// Learn more about making custom code visible in the Xamarin.Forms previewer
// by visiting https://aka.ms/xamarinforms-previewer
[DesignTimeVisible(false)]
public partial class MainPage : ContentPage
{
public class Profissionais
{
public string Id { get; set; }
public string Categoria { get; set; }
public string Titulo { get; set; }
public string FotoPerfil { get; set; }
public string Endereco { get; set; }
public string Distancia { get; set; }
public string Sexo { get; set; }
}
private ObservableCollection<Profissionais> _listaProfissionais;
public ObservableCollection<Profissionais> ListaProfissionais
{
get => _listaProfissionais;
set
{
_listaProfissionais = value;
OnPropertyChanged(nameof(ListaProfissionais));
}
}
public MainPage()
{
InitializeComponent();
ListaProfissionais = new ObservableCollection<Profissionais>();
}
private async Task Call()
{
// Simulating a call to the "API" and add a new element each 2 seconds
for (int i = 0; i < 10; i++)
{
ListaProfissionais.Add(new Profissionais { Titulo = $"Professional {i}" });
await Task.Delay(2000);
}
}
protected override void OnAppearing()
{
base.OnAppearing();
// Call the "API" when everything is ready
Task.Factory.StartNew(Call);
}
}
}
Here, as You can see, the Class Professionais does not required to inherit from INotifyPropertyChanged. The only property You need to Notify to the Binding engine is your collection. ContentPage already has a OnPropertyChanged method that you can use. After that, your ListView will listen not only a change from the property but the collection itself each time you add a new element.
Hope it helps!
OLD
Assuming that you are trying to bind the property ListaProfissionais
from the code behind:
Approcach 1 (Not a good practice):
You must add ListaProfissionais
to the BindingContext
property of the class. You can approach this as follows:
... assuming you already called your API ...
ListaProfissionais = JsonConvert.DeserializeObject<ObservableCollection<Profissionais>>(resultado);
BindingContext = ListaProfissionais;
On your XAML instead of ItemsSource="{Binding ListaProfissionais}"
use ItemSource="{Binding BindingContext}"
Approach 2 (A better approach):
Set a name in your XAML page for the class (Name="listPage"). In your ListView you can do something like this:
ItemsSource="{Binding ListaProfissionais, Source='{x:Reference listPage}'}"
Note: I'll test this later.
Change ListaProfissionais to a property like this:
private ObservableCollection<Profissionais> _listaProfissionais;
public ObservableCollection<Profissionais> ListaProfissionais
{
get => _listaProfissionais;
set
{
_listaProfissionais = value;
// This should be the method implemented by INotifyPropertyChanged.
OnPropertyChanged(nameof(ListaProfissionais);
}
}
Notes: The list will not notify the ListView if you don't Notify the Binding engine that the property changed. With this, We are making sure that the binding context of the ListView is listening to the property changes from ListProfessionais.
Hope it helps.
Upvotes: 2
Reputation: 631
Have find the root of problem. It was in xaml, I don't know why but tag Grid can't be directly inside DataTemplate, it has to be inside a ViewCell tag, I only added a ViewCell and now it works well.
Upvotes: 2
Reputation: 1
I think you have to notify the property has changed, e.g. implementing INotifyPropertyChanged interface to your class. Further I can not see if DataContext is set. Nice simple example here https://www.wpf-tutorial.com/data-binding/responding-to-changes/
Upvotes: 0