Xamarin forms:Listview grouping issue - xamarin.forms

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; }
}

Related

Change toObservableCollection does not update UI

I am new to Xamarin Forms. So therefore I have a trouble with the UI updating. I had create a collection view to display a list of data. Besides that I also created a button for the user to update remarks. While the user click the button, it will call OnRemarksButtonClickedAsync(APIPatrolD obj) and update the patrold_remark to the ObservationList. I already debug and ensure that the remarks had been store in the DAPIPatrolDSiapImbasList but it unable to update to the UI. Did anyone known why?
Below is my source code.
APIDATA Class
public class APIPatrolH
{
public string patrolh_ID { get; set; }
public DateTime patrolh_planDateTime { get; set; }
public DateTime patrolh_actualDateTime { get; set; }
public string patrolh_actualBy { get; set; }
public string patrolh_route { get; set; }
public string patrolh_routeDesc { get; set; }
public APIPatrolD[] patrolh_patrold { get; set; }
}
public class APIPatrolD
{
public string patrold_ID { get; set; }
public string patrold_loc { get; set; }
public int patrold_seq { get; set; }
public string patrold_desc { get; set; }
public byte[] patrold_image { get; set; }
public string patrold_GPS { get; set; } //latitude,longtitue. If 0,0 then no gps tracking
public string patrold_takenGPS { get; set; } //filled by apps
public DateTime patrold_takenDateTime { get; set; } //filled by apps - scan date time
public string patrold_remark { get; set; } //filled by apps
}
Base View Model
public class BaseViewModel : INotifyPropertyChanged
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
View Model
public string _patrold_remark;
public string patrold_remark
{
get { return _patrold_remark; }
set
{
_patrold_remark = value;
OnPropertyChanged();
}
}
public ObservableCollection<APIPatrolD> _DAPIPatrolDSiapImbasList;
public ObservableCollection<APIPatrolD> DAPIPatrolDSiapImbasList
{
get { return _DAPIPatrolDSiapImbasList; }
set
{
if (_DAPIPatrolDSiapImbasList != value)
{
_DAPIPatrolDSiapImbasList = value;
OnPropertyChanged();
}
}
}
public SecurityPortalDetailsPageViewModel(DAPIPatrolH dAPIPatrolH, int v)
{
DAPIPatrolDSiapImbasList = new ObservableCollection<APIPatrolD>();
}
private async void OnRemarksButtonClickedAsync(APIPatrolD obj)
{
// To Do display a customize PopUps=
var result = await App.Current.MainPage.Navigation.ShowPopupAsync(new CustomizePopups(obj));
foreach (APIPatrolD aPIPatrolD in DAPIPatrolDSiapImbasList) {
if (aPIPatrolD.patrold_loc == obj.patrold_loc) {
aPIPatrolD.patrold_remark = (string)result;
break;
}
}
OnPropertyChanged("DAPIPatrolDSiapImbasList");
}
UI Page
<CollectionView Grid.Row="2"
Grid.ColumnSpan="2"
ItemsSource="{Binding DAPIPatrolDSiapImbasList,Mode=TwoWay}">
<CollectionView.EmptyView>
<StackLayout>
<Label Text="Tiada Rekod" HorizontalOptions="CenterAndExpand" TextColor="Black" FontAttributes="Bold" FontSize="18" VerticalOptions="CenterAndExpand"/>
</StackLayout>
</CollectionView.EmptyView>
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="10,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="0.5*"/>
<ColumnDefinition Width="1.2*"/>
<ColumnDefinition />
<ColumnDefinition Width="0.1"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="55"/>
<RowDefinition Height="25"/>
<RowDefinition Height="25"/>
<RowDefinition Height="3" />
</Grid.RowDefinitions>
<Label FontSize="15" FontAttributes="Bold" Text="{Binding patrold_seq}" TextColor="#403f3f" VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand"/>
<Label FontSize="15" FontAttributes="Bold" Grid.Column="1" Text="{Binding patrold_loc}" TextColor="#403f3f" VerticalOptions="CenterAndExpand" HorizontalOptions="StartAndExpand"/>
<Label FontSize="15" FontAttributes="Bold" Grid.Column="2" Text="{Binding patrold_desc}" TextColor="#403f3f" VerticalOptions="CenterAndExpand" HorizontalOptions="StartAndExpand"/>
<Label FontSize="15" FontAttributes="Bold" Grid.ColumnSpan="5" Grid.Row="1" Text="{Binding patrold_takenDateTime, StringFormat='{dd/MM/yy hh:mm tt}'}" TextColor="#403f3f" VerticalOptions="CenterAndExpand" HorizontalOptions="StartAndExpand"/>
<Label FontSize="15" FontAttributes="Bold" Grid.ColumnSpan="5" Grid.Row="2" x:Name="Remarks" Text="{Binding patrold_remark}" TextColor="#403f3f" VerticalOptions="CenterAndExpand" HorizontalOptions="StartAndExpand"/>
<Button Text="Pemerhatian" FontSize="9" FontAttributes="Bold" Grid.Column="3" BackgroundColor="Red" HeightRequest="30" WidthRequest="120" VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand"
Command="{Binding Path=BindingContext.RemarksCommand, Source={x:Reference _SecurityPortalDetailsPage}}"
CommandParameter="{Binding .}"
TextTransform="None"
/>
<controls:CFrame Grid.Row="3" Grid.ColumnSpan="5" BackgroundColor="LightGray"/>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Refer to the following code:
`
public class APIPatrolD:INotifyPropertyChanged
private string _patrold_loc;
public string patrold_loc {
get { return _patrold_loc; }
set {
_patrold_loc = value;
OnPropertyChanged("patrold_loc");
}
}
`

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.

Multilevel Listview in 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}
};
}
}

Xamarin Forms - Using bindable layout to access different parts of the model

I'm trying to setup a gallery of images, that also features the ability to click on the image to show/hide a grid with image description, using the MVVM process.
However, i think my model, view model and view is not correctly setup because i am only able to display the images in the stacklayout if i use BindableLayout.ItemsSource="{Binding GalleryList.Gallery}" in the top stacklayout, but not able to access the other parts of the class, i.e. only the Gallery list.
So essentially, I cant access InfoGridVisible binding, since the it can't find/reach it? (i'm not sure to be honest).
Here is a snippet of my work so far:
View - testpage.xaml:
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="testproject.Pages.testpage"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:styles="clr-namespace:testproject.Styles"
xmlns:behaviours="clr-namespace:testproject.ViewModels"
mc:Ignorable="d"
xmlns:pancakeview="clr-namespace:Xamarin.Forms.PancakeView;assembly=Xamarin.Forms.PancakeView"
xmlns:controls="clr-namespace:ImageCircle.Forms.Plugin.Abstractions;assembly=ImageCircle.Forms.Plugin"
BackgroundColor="Orange"
x:Name="Root">
<ContentPage.Content>
<ScrollView>
<Grid
Grid.Row="3">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!--PROBLEM STARTS HERE -->
<StackLayout x:Name="GalleryStk" Grid.Row="1" Orientation="Vertical" BindableLayout.ItemsSource="{Binding GalleryList.Gallery}" Margin="10,0,10,0">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Grid RowSpacing="5" ColumnSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="AUTO"/>
</Grid.RowDefinitions>
<Frame x:Name="ImageFrame" CornerRadius="20" Padding="0" IsClippedToBounds="True" Grid.Row="0">
<Image Source="{Binding Image}" Aspect="AspectFill" Grid.RowSpan="2" HorizontalOptions="Center" VerticalOptions="Center">
<Image.GestureRecognizers>
<TapGestureRecognizer BindingContext="{Binding Source={x:Reference Root}, Path=BindingContext}" Command="{Binding TapCommand}"/>
</Image.GestureRecognizers>
</Image>
</Frame>
<!--<Image Source="ShadowOverlay" Grid.RowSpan="2" Aspect="Fill" VerticalOptions="End" HorizontalOptions="Fill" />-->
<Grid x:Name="InfoGrid" RowSpacing="10" ColumnSpacing="10" Grid.Row="0" IsEnabled="False" IsVisible="{Binding GalleryList.InfoGridVisible, Source={x:Reference InfoGrid}}">
<Grid.RowDefinitions>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="AUTO"/>
</Grid.ColumnDefinitions>
<BoxView Color="Black" Opacity="0.5" CornerRadius="20" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Grid.Row="0" Grid.RowSpan="5" Grid.ColumnSpan="3"/>
<Label Text="{Binding Title}" Padding="10,10,0,0" Grid.Row="0" Grid.ColumnSpan="3" Style="{StaticResource TitleLabel}" LineBreakMode="NoWrap"/>
<!--<controls:CircleImage Source="{Binding ProfileImage}" Aspect="AspectFill" Grid.Row="1" Grid.Column="0" WidthRequest="25" HeightRequest="25" />-->
<Label Text="{Binding Description}" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" Padding="10,0,10,0" Grid.Row="1" Grid.RowSpan="4" Grid.ColumnSpan="3" Style="{StaticResource HandleLabel}"/>
<!--<Button Text="See More" x:Name="ExpandContractButton" Clicked="ExpandContractButton_Clicked" Padding="10,0,10,0" Grid.Row="2" Grid.RowSpan="3" Grid.ColumnSpan="3"/>-->
<!--<StackLayout Orientation="Horizontal" VerticalOptions="Center" Grid.Column="2" Grid.Row="1" Spacing="5">
<Image Source="Eye"/>
<Label Text="{Binding ViewCount, StringFormat='{0:N0}'}" Style="{StaticResource HandleLabel}" />
<Label Text="views" Style="{StaticResource BodyLabel}"/>
</StackLayout>-->
</Grid>
</Grid>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</Grid>
</ScrollView>
</ContentPage.Content>
</ContentPage>
Code behind for view - testpage.xaml.cs
public partial class testpage : ContentPage
{
private Image _galleryImage;
public Grid GalleryInfoGrid;
private static testpage _instance;
public UserProfileViewModel vm { get; }
public static testpage Instance
{
get
{
if (_instance == null)
_instance = new testpage();
return _instance;
}
}
public testpage()
{
InitializeComponent();
vm = new UserProfileViewModel();
BindingContext = new UserProfileViewModel();
//var _galleryInfoGrid = (Grid)Root.FindByName("InfoGrid");
//_galleryInfoGrid.IsEnabled = true;
//_galleryInfoGrid.IsVisible = true;
}
Model:
{
public List<GalleryImage> Gallery { get; set; }
public string InfoGridVisible;
public string InfoGridEnabled;
}
public class GalleryImage
{
public string Title { get; set; }
public string Image { get; set; }
public string Description { get; set; }
}
Services (data):
public class ProfileService
{
private static ProfileService _instance;
public static ProfileService Instance
{
get
{
if (_instance == null)
_instance = new ProfileService();
return _instance;
}
}
public GalleryList GetGallery()
{
return new GalleryList
{
InfoGridEnabled = "False",
InfoGridVisible = "False",
Gallery = new List<GalleryImage>
{
new GalleryImage { Title="sevilla01.jpg", Image = "sevilla01.jpg", Description="Description1Description1Description1Description1 Description1Description1Description1 Description1Description1" },
new GalleryImage { Title="sevilla02.jpg", Image = "sevilla02.jpg", Description="Description2 Description1Description1Description1Description1Descript ion1Description1" },
new GalleryImage {Title="sevilla03.jpg", Image = "sevilla03.jpg", Description="Description3Description1Description1Description1" },
new GalleryImage {Title="sevilla04.jpg", Image = "sevilla04.jpg", Description="Description4Description1Description1" },
new GalleryImage {Title="sevilla05.jpg", Image = "sevilla05.jpg", Description="Description5Description1" },
new GalleryImage {Title="sevilla06.jpg", Image = "sevilla06.jpg", Description="Description6" },
new GalleryImage {Title="sevilla07.jpg", Image = "sevilla07.jpg", Description="Description7Description1Description1Description1Description1Description1Description1Description1Description1 Description1" }
}
};
}
}
View Model - UserProfileViewModel.cs:
public class UserProfileViewModel : BindableObject
{
//Models
private Profile _profile;
private GalleryList _galleryList;
int taps = 0;
ICommand tapCommand;
public UserProfileViewModel()
{
Profile = ProfileService.Instance.GetProfile();
GalleryList = ProfileService.Instance.GetGallery();
tapCommand = new Command(OnTapped);
}
public ICommand TapCommand
{
get { return tapCommand; }
}
void OnTapped(object s)
{
taps++;
//var info1Grid = testpage.Instance.GalleryInfoGrid;
//info1Grid.IsVisible = true;
//info1Grid.IsEnabled = true;
//GalleryList.InfoGridEnabled = "False";
//GalleryList.InfoGridVisible = "False";
Console.WriteLine("parameter: " + taps + " " + GalleryList.InfoGridVisible);
OnPropertyChanged();
}
public Profile Profile
{
get { return _profile; }
set
{
_profile = value;
OnPropertyChanged();
}
}
public GalleryList GalleryList
{
get { return _galleryList; }
set
{
_galleryList = value;
OnPropertyChanged();
}
}
}
Apologies for the code dump, any help would be appreciated as i have been stuck on this for a few days.
If there is a better method of implementing this, i would love to hear it.
What I want you try is something like this:
public class GalleryList : BindableObject {
private string _InfoGridVisible { get; set; }
private string _InfoGridEnabled { get; set; }
public List<GalleryImage> Gallery { get; set; }
public string InfoGridVisible
{
get { return _InfoGridVisible; }
set
{
_InfoGridVisible = value;
OnPropertyChanged();
}
}
public string InfoGridEnabled
{
get { return _InfoGridEnabled; }
set
{
_InfoGridEnabled = value;
OnPropertyChanged();
}
}
}
public class GalleryImage : BindableObject {
private string _Title { get; set; }
private string _Image { get; set; }
private string _Description { get; set; }
public string Title
{
get { return _Title; }
set
{
_Title = value;
OnPropertyChanged();
}
}
public string Image
{
get { return _Image; }
set
{
_Image = value;
OnPropertyChanged();
}
}
public string Description
{
get { return _Description; }
set
{
_Description = value;
OnPropertyChanged();
}
}
}
InfoGridVisible is not a property and hence binding when performs a lookup will never find it, the way to solve this is :
public class GalleryList
{
public List<GalleryImage> Gallery { get; set; }
public string InfoGridVisible { get; set; }
public string InfoGridEnabled { get; set; }
}
In the case of how to be able to get the Object within a Command you need to tell your TapGesture how to access to that command through this 2 options:
<TapGestureRecognizer Command ="{Binding Path=BindingContext.CommandToCall, Source={x:Reference Name=ParentPage}}" CommandParameter="{Binding .}"/>
Or:
<TapGestureRecognizer Command ="{Binding Source={RelativeSource AncestorType={x:Type vm:vmWhereCommandIs}}, Path=CommandToCall}" CommandParameter="{Binding .}"/>
Just in case you need more info here there are some resources:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/data-binding/relative-bindings
https://www.xamarinexpert.it/how-to-correctly-use-databinding-with-listview-and-collectionview/

Resources