Multilevel Listview in xamarin forms - xamarin.forms

I need to make a Multilevel Listview in Xamarin forms.
Currently, I am using this for my Single level Grouping in my Menu Page
http://www.compliancestudio.io/blog/xamarin-forms-expandable-listview
I need to update something like the attached image. I have tried some of the Solution but all in vain.
Anyone has any idea about this.
Thanks

You could do this with Expander of Xamarin Community Toolkit.
Install Xamarin Community Toolkit from NuGet: https://www.nuget.org/packages/Xamarin.CommunityToolkit/
Usage: xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
Xaml:
<ContentPage.BindingContext>
<viewmodels:RootViewModel />
</ContentPage.BindingContext>
<ContentPage.Content>
<ScrollView Margin="20">
<StackLayout BindableLayout.ItemsSource="{Binding roots}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<xct:Expander>
<xct:Expander.Header>
<Label Text="{Binding Root}"
FontAttributes="Bold"
FontSize="Large" />
</xct:Expander.Header>
<StackLayout BindableLayout.ItemsSource="{Binding Node}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<xct:Expander Padding="10">
<xct:Expander.Header>
<Label Text="{Binding Key.Node}" FontSize="Medium" />
</xct:Expander.Header>
<StackLayout BindableLayout.ItemsSource="{Binding Value}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Label Text="{Binding SubRoot}" FontSize="Small" />
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</xct:Expander>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</xct:Expander>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</ScrollView>
</ContentPage.Content>
Model:
public class Roots
{
public string Root { get; set; }
public Dictionary<Nodes, List<SubRoots>> Node { get; set; }
}
public class Nodes
{
public string Node { get; set; }
}
public class SubRoots
{
public string SubRoot { get; set; }
}
ViewModel:
public class RootViewModel
{
public List<Roots> roots { get; private set; }
public RootViewModel()
{
CreateMonkeyCollection();
}
public void CreateMonkeyCollection()
{
List<SubRoots> subRoot = new List<SubRoots>() { new SubRoots() { SubRoot = "SubNode" } };
Dictionary<Nodes, List<SubRoots>> node = new Dictionary<Nodes, List<SubRoots>>();
node.Add(new Nodes() { Node="Nodo1"}, null);
node.Add(new Nodes() { Node="Nodo2"}, null);
node.Add(new Nodes() { Node="Nodo3"}, null);
node.Add(new Nodes() { Node="Nodo4"}, subRoot);
roots = new List<Roots>()
{
new Roots(){ Root="Root1", Node=node},
new Roots(){ Root="Root2", Node= null}
};
}
}

Related

xamarin.forms second layer of bindable layout does not display binding

I am trying to create a tree similar to this :
I was able to create the first two layers, the parent being a scrollview (displaying the 7 items) the child being a bindable layout, displaying the sublayouts.
But the second sublayer is not binded to. The page just stays blank
<StackLayout BindableLayout.ItemsSource="{Binding dataPoints}" IsVisible="{Binding dataPointsVisible}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout Margin="20,0,0,0">
<StackLayout>
<Label Text="{Binding identifier}"/>
<Label Text="{Binding type}"/>
<StackLayout>
<Label FontAttributes="Bold" Text="see dataPointSettings ->"/>
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"/>
</StackLayout.GestureRecognizers>
</StackLayout>
<StackLayout BackgroundColor="Yellow"
HeightRequest="200"
BindableLayout.ItemsSource="{Binding dataPointSettings}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout BackgroundColor="Green">
<Label Text="{Binding alertingEmail}"/>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</StackLayout>
<BoxView BackgroundColor="Black" HeightRequest="1"/>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
The "alertingEmail" is not displayed unfortunately.
How can I bind my views inside the second layer of bindable layouts?
You could refer to the code below:
Xaml: The same Xaml as yours.
Model:
public class DataPoints
{
public int identifier { get; set; }
public string type { get; set; }
public bool dataPointsVisible { get; set; }
public List<DataPointSettings> dataPointSettings { get; set; }
}
public class DataPointSettings
{
public string alertingEmail { get; set; }
}
ViewModel:
public class Page9ViewMode
{
public ObservableCollection<DataPoints> dataPoints { get; set; }
public Page9ViewMode()
{
dataPoints = new ObservableCollection<DataPoints>()
{
new DataPoints(){ dataPointsVisible=true, identifier=1, type="type1", dataPointSettings=new List<DataPointSettings>(){ new DataPointSettings() { alertingEmail="Email1-1"}, new DataPointSettings(){ alertingEmail="Email1-2" } } },
new DataPoints(){ dataPointsVisible=true, identifier=2, type="type2", dataPointSettings=new List<DataPointSettings>(){ new DataPointSettings() { alertingEmail="Email2-1"}, new DataPointSettings(){ alertingEmail="Email2-2" } } },
new DataPoints(){ dataPointsVisible=true, identifier=3, type="type3", dataPointSettings=new List<DataPointSettings>(){ new DataPointSettings() { alertingEmail="Email3-1"}, new DataPointSettings(){ alertingEmail="Email3-2" } } },
};
}
}
Screenshot:

How one ViewModel contains multiple ViewModel xamatin

I have 2 ViewModels (MVVM). I let show 2 as shown, it only shows data 1 ViewModel (the one below).
I put 1 and it shows up as normal.
This is how I display the data
<RefreshView x:DataType="locals:SliderViewModel"
Command="{Binding LoadSliderCommand}"
IsRefreshing="{Binding IsBusy, Mode=OneWay}">
<StackLayout Padding="8,0,8,4"
BindableLayout.ItemsSource="{Binding SliderShowInfos}"
Orientation="Horizontal"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout x:DataType="model:SliderShowInfo">
<Frame Padding="4"
HasShadow="False"
IsClippedToBounds="True"
BackgroundColor="Transparent">
<StackLayout Orientation="Horizontal">
<Frame Padding="0"
HasShadow="False"
CornerRadius="7"
IsClippedToBounds="True">
<Image Source="{Binding ImagesSlider}">
</Image>
</Frame>
</StackLayout>
</Frame>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</RefreshView>
<RefreshView x:DataType="locals:ProductViewModel"
Command="{Binding LoadProductCommand}"
IsRefreshing="{Binding IsBusy, Mode=OneWay}">
<StackLayout Padding="8"
Orientation="Horizontal"
BindableLayout.ItemsSource="{Binding ProductInfos}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Frame Padding="5,0"
HasShadow="False"
IsClippedToBounds="True"
BackgroundColor="#fff">
<StackLayout x:DataType="model:ProductInfo">
</StackLayout>
</Frame>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</RefreshView>
This is how I display the data. I'm trying to display product listing and photo listing data. Please give me a solution that can combine 2 ViewModel
Update
SliderViewModel.cs
public class SliderViewModel:BaseSliderViewModel
{
ISliderShowRepository sliderShowRepository = new SliderShowService();
public Command LoadSliderCommand { get; }
public ObservableCollection<SliderShowInfo> SliderShowInfos { get; }
public SliderViewModel()
{
LoadSliderCommand = new Command(async () => await ExecuteLoadSliderCommand());
SliderShowInfos = new ObservableCollection<SliderShowInfo>();
}
public void OnAppearing()
{
IsBusy = true;
}
async Task ExecuteLoadSliderCommand()
{
}
}
ProductViewModel.cs
public class ProductViewModel : BaseProductViewModel
{
IProductRepository productRepository = new ProductService();
public Command LoadProductCommand { get; }
public ObservableCollection<ProductInfo> ProductInfos { get; }
public Command ProductTappedView { get; }
public ProductViewModel(INavigation _navigation)
{
LoadProductCommand = new Command(async () => await ExecuteLoadProductCommand());
ProductInfos = new ObservableCollection<ProductInfo>();
ProductTappedView = new Command<ProductInfo>(OnViewDetailProduct);
Navigation = _navigation;
}
private async void OnViewDetailProduct(ProductInfo prod)
{
await Navigation.PushAsync(new DetailProduct(prod));
}
public void OnAppearing()
{
IsBusy = true;
}
async Task ExecuteLoadProductCommand()
{
IsBusy = true;
try
{
ProductInfos.Clear();
var prodList = await productRepository.GetProductsAsync();
foreach (var prod in prodList)
{
ProductInfos.Add(prod);
}
}
catch(Exception)
{
throw;
}
finally
{
IsBusy = false;
}
}
}
DashboardViewModel.cs
public class DashboardViewModel
{
public SliderViewModel SliderShowVM { get; set; }
public ProductViewModel ProductVM { get; set; }
}
Dashboard.xaml.cs
public Dashboard()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, true);
BindingContext = new DashboardViewModel();
}
protected override void OnAppearing()
{
base.OnAppearing();
}
You cant just have 2 ViewModels for a single page. Instead, you can create another ViewModel, which will contains those 2 ViewModels that you need.
public class DashboardViewModel
{
public SliderShowViewModel SliderShowVM { get; set; }
public ProductViewModel ProductVM { get; set; }
}
And in dashboard constructor, assign the BindingContext
BindingContext = new DashboardViewModel();
And in your view, bind data like this:
...
<Image Source="{Binding SliderShowVM.ImagesSlider}">
...
BindableLayout.ItemsSource="{Binding ProductVM.ProductInfos}"
...
Also, if you don't need all data from SliderShowViewModel or ProductViewModel on your Dashboard, then you can define the properties that you really need for the dashboard inside DashboardViewModel, and to inject SliderShowViewModel and ProductViewModel instances via constructor (Might not be the best solution, but this way you will keep you view cleaner)
public class DashboardViewModel
{
public string RelevantProperty1 { get; set; }
public int RelevantProperty2 { get; set; }
...
public DashboardViewModel(SliderShowViewModel sliderVm, ProductViewModel productVm)
{
RelevantProperty1 = sliderVm.Something;
RelevantProperty2 = productVm.SomethingElse;
...
}
}
More explicit
DashboardViewModel class: It is a class with two properties of type SliderShowViewModel and ProductViewModel. These properties will store some instances (aka objects) of SliderShowViewModel and ProductViewModel respectively.
Create a DashboardViewModel instance and pass it as BindingContext for your Dashboard View:
public Dashboard()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, true);
var viewModel = new DashboardViewModel();
// Now we have the viewModel, but there is a problem.
// Remember those two viewModels (SliderShowViewModel and ProductViewModel)?
// Well, those properties are null, we have to provide values for them
// before setting the binding context.
viewModel.SliderViewModel = new SliderViewModel();
viewModel.ProductViewModel = new ProductViewModel();
BindingContext = viewModel;
}
Bind values from DashboardViewModel on our View:
<RefreshView Command="{Binding SliderViewModel.LoadSliderCommand}"
IsRefreshing="{Binding SliderViewModel.IsBusy, Mode=OneWay}">
<StackLayout Padding="8,0,8,4"
BindableLayout.ItemsSource="{Binding SliderViewModel.SliderShowInfos}"
Orientation="Horizontal"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout>
<Frame Padding="4"
HasShadow="False"
IsClippedToBounds="True"
BackgroundColor="Transparent">
<StackLayout Orientation="Horizontal">
<Frame Padding="0"
HasShadow="False"
CornerRadius="7"
IsClippedToBounds="True">
<Image Source="{Binding SliderViewModel.ImagesSlider}">
</Image>
</Frame>
</StackLayout>
</Frame>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</RefreshView>
<RefreshView Command="{Binding ProductViewModel.LoadProductCommand}"
IsRefreshing="{Binding ProductViewModel.IsBusy, Mode=OneWay}">
<StackLayout Padding="8"
Orientation="Horizontal"
BindableLayout.ItemsSource="{Binding ProductViewModel.ProductInfos}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Frame Padding="5,0"
HasShadow="False"
IsClippedToBounds="True"
BackgroundColor="#fff">
</Frame>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</RefreshView>
Give it a try and let me know if it works.

How to change image source when a property changes through databinding in XAML from viewmodel in xamarin forms?

I am working on providing wishlist feature for my app by tapping wishlist icon on each product in list through MVVM. Once tapped, an API call is made to update database(add/remove from wishlist table). Based on result from api call, I updated the specific product's respective property to either 'True' or 'False'. Once property updated, I want to change the icon image source of corresponding product. I am using trigger on wishlist icon to differentiate non-wishlist and wiahlist products while binding the list itself.
My code is below,
MODEL
public class PublisherProducts
{
public long ProductId { get; set; }
public string ProductName { get; set; }
public string ImageURL { get; set; }
public decimal Price { get; set; }
public bool IsWishlistProduct { get; set; }
}
VIEWMODEL
public class OnlineStoreViewModel : BaseViewModel
{
private ObservableCollection<PublisherProducts> publisherProducts;
public Command<long> WishlistTapCommand { get; }
public OnlineStoreViewModel()
{
publisherProducts = new ObservableCollection<PublisherProducts>();
WishlistTapCommand = new Command<long>(OnWishlistSelected);
}
public ObservableCollection<PublisherProducts> PublisherProducts
{
get { return publisherProducts; }
set
{
publisherProducts = value;
OnPropertyChanged();
}
}
public async Task GetProducts(long selectedCategoryId)
{
try
{
...
PublisherProducts = new ObservableCollection<PublisherProducts>(apiresponse.ProductList);
...
}
catch (Exception ex) { ... }
finally { ... }
}
async void OnWishlistSelected(long tappedProductId)
{
if (tappedProductId <= 0)
return;
else
await UpdateWishlist(tappedProductId);
}
public async Task UpdateWishlist(long productId)
{
try
{
var wishlistResponse = // api call
var item = PublisherProducts.Where(p => p.ProductId == productId).FirstOrDefault();
item.IsWishlistProduct = !item.IsWishlistProduct;
PublisherProducts = publisherProducts; *Stuck here to toggle wishlist icon*
await App.Current.MainPage.DisplayAlert("", wishlistResponse.Message, "Ok");
}
catch (Exception ex) { ... }
finally { ... }
}
}
XAML
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" ... >
<ContentPage.Content>
<ScrollView>
<StackLayout Padding="15,0,15,10">
<FlexLayout x:Name="flxLayout" BindableLayout.ItemsSource="{Binding PublisherProducts}" ...>
<BindableLayout.ItemTemplate>
<DataTemplate>
<AbsoluteLayout Margin="6" WidthRequest="150">
<Frame Padding="0" WidthRequest="150" CornerRadius="10" HasShadow="True">
<StackLayout Orientation="Vertical" Padding="10" HorizontalOptions="FillAndExpand">
<Image Source="{Binding ImageURL}" WidthRequest="130" HeightRequest="130" HorizontalOptions="Center"/>
<Label Text="{Binding ProductName}" Style="{StaticResource ProductNameStyle}"></Label>
...
<StackLayout ...>
...
<Frame x:Name="wlistFrame" Padding="0" WidthRequest="30" HeightRequest="30" CornerRadius="10" BorderColor="#02457A">
<StackLayout Orientation="Horizontal" VerticalOptions="Center" HorizontalOptions="Center">
<Image x:Name="wlImage" WidthRequest="13" HeightRequest="12" HorizontalOptions="Center" VerticalOptions="Center" Source="ic_wishlist_open">
<Image.Triggers>
<DataTrigger TargetType="Image" Binding="{Binding IsWishlistProduct}" Value="true">
<Setter Property="Source" Value="ic_wishlist_close" />
</DataTrigger>
</Image.Triggers>
</Image>
</StackLayout>
<Frame.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type local:OnlineStoreViewModel}}, Path=WishlistTapCommand}" CommandParameter="{Binding ProductId}" NumberOfTapsRequired="1" />
</Frame.GestureRecognizers>
</Frame>
</StackLayout>
</StackLayout>
</Frame>
</AbsoluteLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</FlexLayout>
</StackLayout>
</ScrollView>
</ContentPage.Content>
</ContentPage>
I am stuck at this place to change wishlist icon, when 'IsWishlistProduct' property value is changed in UpdateWishlist().
Guessing through your code, the BaseViewModel contains code similar to the following:
public class BaseViewModel : INotifyPropertyChanged
{
...
public event PropertyChangedEventHandler PropertyChanged;
...
public void OnPropertyChanged(string name)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
...
}
And your viewmodel should be like this:
...
public ObservableCollection<PublisherProducts> PublisherProducts
{
get { return publisherProducts; }
set
{
publisherProducts = value;
OnPropertyChanged(nameof(PublisherProducts));
}
}
...
As Jason mentioned, If there is a change in data in the ViewModel, it is reflected in the UI when it is notified to the View through NotifyPropertyChanged. You already implemented "OnPropertyChanged" function in your BaseViewModel but it seems you don't pass the object name.

Xamarin Forms CollectionView Command not working

I have a collection view with the command binded, but for some reason when I select an item the action is never called in the viewmodel, heres my ViewModel code:
public class PlatillosViewModel : INotifyPropertyChanged
{
private INavigation Navigation;
public event PropertyChangedEventHandler PropertyChanged;
public List<PlatilloModel> Platillos { get; set; }
public List<GrupoModel> Grupos { get; set; }
public ICommand SelectedGroupCommand => new Command(SelectedGroup);
public PlatillosViewModel(INavigation navigation)
{
Navigation = navigation;
PlatillosRepository repository = new PlatillosRepository();
Platillos = repository.GetAll().ToList();
GrupoRepository grupoRepository = new GrupoRepository();
Grupos = grupoRepository.GetAll().ToList();
}
public ICommand SelectedPlatilloCommand => new Command<PlatilloModel>(async platillo =>
{
await Navigation.PushAsync(new PlatilloView());
});
void SelectedGroup()
{
PlatillosRepository platillosRepository = new PlatillosRepository();
//Platillos = platillosRepository.GetFilteredByGroup(grupoSeleccionado);
}
protected virtual void OnPropertyChanged(string property = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
And here is my Page:
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ComanderoMovil.Views.PlatillosView"
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
ios:Page.UseSafeArea="true"
xmlns:behaviorsPack="clr-namespace:Xamarin.Forms.BehaviorsPack;assembly=Xamarin.Forms.BehaviorsPack">
<ContentPage.Content>
<StackLayout>
<SearchBar> </SearchBar>
<StackLayout Orientation="Horizontal">
<CollectionView ItemsSource="{Binding Grupos}"
HeightRequest="50"
ItemsLayout="HorizontalList"
SelectionMode="Single"
SelectedItem="{Binding SelectedGroupCommand, Mode=TwoWay}">
<CollectionView.ItemTemplate>
<DataTemplate>
<ContentView>
<Label Margin="2"
BackgroundColor="Black"
Text="{Binding nombre}"
TextColor="White"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Center"
FontSize="Small"></Label>
</ContentView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
<ListView Grid.Column="2"
HasUnevenRows="True"
SeparatorVisibility="None"
ItemsSource="{Binding Platillos}">
<ListView.Behaviors>
<behaviorsPack:SelectedItemBehavior Command="{Binding SelectedPlatilloCommand}"/>
</ListView.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ContentView Padding="2, 5, 5, 0">
<Frame OutlineColor="Black"
Padding="10"
HasShadow="False">
<StackLayout Orientation="Horizontal">
<Label Margin="10"
Text="{Binding clave_platillo}"
FontSize="Large"
HorizontalOptions="Start"></Label>
<Label Margin="10"
HorizontalTextAlignment="End"
Text="{Binding nombre}"></Label>
</StackLayout>
</Frame>
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
I have tried adding the command to the items inside the collection view, replacing labels for buttons, but still doesn't work, I've also tried to use SelectionChangedCommand in the collection view, and still the same issue, the only way I can make it work is handling the item selection in the View, but I want to stay true to MVVM.
Here is my GrupoModel:
public class GrupoModel
{
public string clave_grupo { get; set; }
public int id_clasificacion { get; set; }
public int id_grupo { get; set; }
public string nombre { get; set; }
public bool pedirClave { get; set; }
public bool status { get; set; }
public int tipo { get; set; }
}
and here is an image of what im trying to do:
If you read the document:
When the SelectionMode property is set to Single, a single item in the
CollectionView can be selected. When an item is selected, the
SelectedItem property will be set to the value of the selected item.
When this property changes, the SelectionChangedCommand is executed
(with the value of the SelectionChangedCommandParameter being passed
to the ICommand), and the SelectionChanged event fires.
When you want to bind a Commond, you should bind to the SelectionChangedCommand instead of SelectedItem. Change your code like below and it will work:
<CollectionView
HeightRequest="50"
ItemsLayout="HorizontalList"
SelectionMode="Single"
SelectionChangedCommand="{Binding SelectedGroupCommand, Mode=TwoWay}"
>
The command should go in the class of GrupoModel instead of the PlatillosViewModel
public List<GrupoModel> Grupos { get; set; }
Should be "linked" to class GrupoModel that have properties and a commandwhich will listen, something like:
Class GrupoModel
{
public int Id { get; set; }
public string Foo { get; set; }
public ICommand SelectedGroupCommand => new Command(Completar);
private async void Completar()
{
await ViewModels.PlatillosViewModel.GetInstancia().SelectedGroup(this);
}
}
This way each element of Grupos will have a command to listen.
BTW: Shouldn't Grupos be an ObservableCollection?

Xamarin forms:Listview grouping issue

I am trying to implement listview grouping for my following JSON data.
JSON Sample:
{
"cbrainBibleBooksHB":[ {
"book":"2 John",
"cbrainBibleTOList":[
{
"bookName":"2 John",
"chapter":"1",
"pageUrl":"/edu-bible/9005/1/2-john-1"
},
{....}
]
},
{
"book":"3 John",
"cbrainBibleTOList":[
{
"bookName":"3 John",
"chapter":"1",
"pageUrl":"/edu-bible/9007/1/3-john-1"
},
{...}
]
}
]
}
I am trying to group the JSON data by its book name.
I tried like below:
Model:
public class BibleTestament
{
public List<CbrainBibleBooksHB> cbrainBibleBooksHB { get; set; }
}
public class CbrainBibleBooksHB : ObservableCollection<CbrainBibleTOList>
{
public string book { get; set; }
public List<CbrainBibleTOList> cbrainBibleTOList { get; set; }
}
public class CbrainBibleTOList
{
public string chapter { get; set; }
public string pageUrl { get; set; }
public string bookName { get; set; }
}
Viewmodel
HttpClient client = new HttpClient();
var Response = await client.GetAsync("rest api");
if (Response.IsSuccessStatusCode)
{
string response = await Response.Content.ReadAsStringAsync();
Debug.WriteLine("response:>>" + response);
BibleTestament bibleTestament = new BibleTestament();
if (response != "")
{
bibleTestament = JsonConvert.DeserializeObject<BibleTestament>(response.ToString());
}
AllItems = new ObservableCollection<CbrainBibleBooksHB>(bibleTestament.cbrainBibleBooksHB);
XAML
<ContentPage.Content>
<StackLayout>
<ListView
HasUnevenRows="True"
ItemsSource="{Binding AllItems,Mode=TwoWay}"
IsGroupingEnabled="True">
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
<Label
Text="{Binding book}"
Font="Bold,20"
HorizontalOptions="CenterAndExpand"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
Margin="3"
TextColor="Black"
VerticalOptions="Center"/>
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout
HorizontalOptions="StartAndExpand"
VerticalOptions="FillAndExpand"
Orientation="Horizontal">
<Label
Text="{Binding cbrainBibleTOList.chapter}"
Font="20"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
HorizontalOptions="CenterAndExpand"
TextColor="Black"
VerticalOptions="Center"/>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Footer>
<Label/>
</ListView.Footer>
</ListView>
</StackLayout>
</ContentPage.Content>
But no data is showing on the UI when running the project. Getting Binding: 'book' property not found on 'System.Object[]', target property: 'Xamarin.Forms.Label.Text' message on output box. It is very difficult to implement grouping for a listview in xamarin forms. Can anyone help me to do this? I have uploaded a sample project here.
You can use the latest BindableLayout of Xamarin.Forms version >=3.5 instead of using grouped Listview with less effort involved.
Update your Model class
public class CbrainBibleBooksHB
{
public string book { get; set; }
public List<CbrainBibleTOList> cbrainBibleTOList { get; set; }
}
XAML:
<ScrollView>
<FlexLayout
BindableLayout.ItemsSource="{Binding AllItems}"
Direction="Column"
AlignContent="Start">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Label Grid.Row="0"
Text="{Binding book}"
HorizontalOptions="FillAndExpand"
BackgroundColor="LightBlue"/>
<StackLayout Grid.Row="1"
BindableLayout.ItemsSource="{Binding cbrainBibleTOList}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Label Text="{Binding chapter}">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding BindingContext.TapCommand, Source={x:Reference Name=ParentContentPage}}" CommandParameter="{Binding .}"/>
</Label.GestureRecognizers>
</Label>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</Grid>
</DataTemplate>
</BindableLayout.ItemTemplate>
</FlexLayout>
</ScrollView>
Note: Here ParentContentPage is the x:Name of parent content page which is used to give reference for command.
ViewModel:
class BibleTestamentViewModel : INotifyPropertyChanged
{
public ICommand TapCommand { get; private set; }
public BibleTestamentViewModel()
{
TapCommand = new Command(ChapterClickedClicked);
}
private void ChapterClickedClicked(object sender)
{
//check value inside sender
}
}
Output:
I tested your demo with static data and there are some issues in your case .
Firstly CbrainBibleBooksHB is a subclass of ObservableCollection ,so you don't need to set the property cbrainBibleTOList any more
public class CbrainBibleBooksHB : ObservableCollection<CbrainBibleTOList>
{
public string book { get; set; }
public List<CbrainBibleTOList> cbrainBibleTOList { get; set; }
}
Secondly , you set the wrong binding path of the label .
<Label
Text="{Binding chapter}"
...
/>
Following is my code ,because of I could not accsess to your url so I used the static data.
in xaml
...
<Label
Text="{Binding chapter}"
HeightRequest="30"
Font="20"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
HorizontalOptions="CenterAndExpand"
TextColor="Black"
VerticalOptions="Center"/>
...
in viewmodel
namespace TestamentSample
{
public class BibleTestamentViewModel
{
public ObservableCollection<CbrainBibleBooksHB> AllItems
{
get;set;
}
public BibleTestamentViewModel()
{
var cbrainBibleBooksHB = new CbrainBibleBooksHB() {book = "group1",};
cbrainBibleBooksHB.Add(new CbrainBibleTOList() { chapter = "1111" });
cbrainBibleBooksHB.Add(new CbrainBibleTOList() { chapter = "2222" });
cbrainBibleBooksHB.Add(new CbrainBibleTOList() { chapter = "3333" });
cbrainBibleBooksHB.Add(new CbrainBibleTOList() { chapter = "4444" });
cbrainBibleBooksHB.Add(new CbrainBibleTOList() { chapter = "5555" });
var cbrainBibleBooksHB2 = new CbrainBibleBooksHB() { book = "group2", };
cbrainBibleBooksHB2.Add(new CbrainBibleTOList() { chapter = "6666" });
cbrainBibleBooksHB2.Add(new CbrainBibleTOList() { chapter = "7777" });
cbrainBibleBooksHB2.Add(new CbrainBibleTOList() { chapter = "8888" });
cbrainBibleBooksHB2.Add(new CbrainBibleTOList() { chapter = "9999" });
cbrainBibleBooksHB2.Add(new CbrainBibleTOList() { chapter = "0000" });
AllItems = new ObservableCollection<CbrainBibleBooksHB>() {
cbrainBibleBooksHB,cbrainBibleBooksHB2
};
}
}
}
public class CbrainBibleBooksHB : ObservableCollection<CbrainBibleTOList>
{
public string book { get; set; }
}
You should make sure that the object which you download from remote url has the same level with my demo .
You can do this way aswell
<ListView ItemsSource="{Binding Itens}" SeparatorColor="#010d47" RowHeight="120" x:Name="lvEmpresaPending" SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Nome da Empresa:" FontAttributes="Bold" ></Label>
<Label Text="{Binding Main.Nome}"></Label>
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="CNPJ da Empresa:" FontAttributes="Bold"></Label>
<Label Text="{Binding Main.Cnpj}"></Label>
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Cargo: " FontAttributes="Bold"></Label>
<Label Text="{Binding Cargo}"></Label>
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Inicio" FontAttributes="Bold"></Label>
<Label Text="{Binding DataInicio}"></Label>
<Label Text="Término" FontAttributes="Bold"></Label>
<Label Text="{Binding DataFim}"></Label>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
public class ModelosPPP
{
public Empresa Main { get; set; }
public string DataInicio { get; set; }
public string DataFim { get; set; }
public string Cargo { get; set; }
public string Status { get; set; }
}
public class Empresa
{
public string Nome { get; set; }
public string Cnpj { get; set; }
}

Resources