I have a "Main" page in xaml which contains my cellview.
<ContentPage.Content>
<ListView
Margin="0,15,0,0"
SelectionMode="None"
RowHeight= "150"
ItemsSource="{Binding ObjectItems}">
<ListView.ItemTemplate>
<DataTemplate>
<viewcells:ObjectItemViewCell/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
In my CellView I want to add 2 convertors as BindableContext and to integrate them in some my fields:
<<?xml version="1.0" encoding="UTF-8"?>
<ViewCell 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"
xmlns:local="clr-namespace:OperaMobile.Views.Postlogin"
xmlns:converterFontFamily="clr-namespace:OperaMobile.Converters"
xmlns:converterColor="clr-namespace:OperaMobile.Converters"
mc:Ignorable="d"
x:Class="OperaMobile.ViewCells.ObjectItemViewCell">
<ViewCell.BindingContext>
<converterFontFamily:BoolToStringConverter x:Key="fontFamilyConverter"/>
<!--<converterColor:BoolToStringConverter x:Key="fontFamilyConverter"/>-->
</ViewCell.BindingContext>
<ViewCell.View>
<StackLayout>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80*"/>
<ColumnDefinition Width="50*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackLayout
Padding="0,10"
Grid.Column="0"
Grid.Row="0"
Margin="15,0"
VerticalOptions="Center"
Orientation="Horizontal">
<Image Source="pin"/>
<Label FontAttributes="Bold" Text="{Binding Label}" Grid.Column="0" Grid.Row="0"/>
</StackLayout>
<StackLayout
Grid.Column="0"
Grid.Row="1"
BindableLayout.ItemsSource="{Binding InfoBox.CountDetailsItemsRows}"
Orientation="Horizontal"
Margin="13,10,0,0">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label FontAttributes="Bold" Text="{Binding BoldLabelTitle}"/>
<Label Text="{Binding LabelValue}"/>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
<ListView
Margin="15,0"
Grid.Column="0"
Grid.Row="1"
SeparatorVisibility="None"
HasUnevenRows="True"
IsEnabled="False"
VerticalScrollBarVisibility="Never"
ItemsSource="{Binding InfoBox.DetailsObjectInfos}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Grid.Column="1" Orientation="Horizontal" Padding="0" Spacing="0">
<Label FontAttributes="Bold" Text="{Binding BoldLabelTitle}" Padding="0"/>
<Label Text="{Binding LabelValue}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackLayout VerticalOptions="Start" Grid.Column="1" Grid.RowSpan="2" Spacing="15" Padding="20,10" Orientation="Horizontal" HorizontalOptions="End">
<StackLayout.Resources>
<ResourceDictionary>
<converterColor:BoolToStringConverter
x:Key="colorConverter"
TrueValue="#0275BA"
FalseValue="#949494"/>
<converterFontFamily:BoolToStringConverter
x:Key="fontFamilyConverter"
TrueValue="FA-S"
FalseValue="FA-R"/>
</ResourceDictionary>
</StackLayout.Resources>
<!--"{Binding IsFavorite, Converter={StaticResource fontFamilyConverter}}-->
<Label FontSize="Medium" FontFamily="{Binding IsFavorite, Converter={StaticResource Key=fontFamilyConverter}}" TextColor="{Binding IsFavorite, Converter={StaticResource Key=fontFamilyConverter}}" Text="{StaticResource IconStar}">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Path=BindingContext.ToggleFavoriteObjectCommand}" CommandParameter="{Binding Id}"/>
</Label.GestureRecognizers>
</Label>
<Label FontSize="Medium" Style="{DynamicResource BlueColorStyle}" Text="{StaticResource IconEye}">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Path=BindingContext.ViewObjectDetailsCommand}" CommandParameter="{Binding Id}"/>
</Label.GestureRecognizers>
</Label>
<Label FontSize="Medium" Style="{DynamicResource BlueSolidColorStyle}" Text="{StaticResource IconPin}">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Path=BindingContext.ViewObjectOnMapCommand}" CommandParameter="{Binding Id}"/>
</Label.GestureRecognizers>
</Label>
</StackLayout>
</Grid>
</StackLayout>
</ViewCell.View>
</ViewCell>
this is the VM of my main page where I added viewcell as DataTemplate, so here it is:
public class SearchObjectsViewModel : BaseViewModel, INotifyPropertyChanged
{
public SearchObjectsViewModel()
{
Task.Run(async () => { await GetObjectInstancesList(); });
ToggleFavoriteObjectCommand = new Command(async(data) => await ToggleFavoriteObjects(data));
ViewObjectDetailsCommand = new Command(async (data) => await GetObjectDetails(data));
ViewObjectOnMapCommand = new Command(async (data) => await ViewObjectOnMap(data));
}
#region Properties
private string searchedText;
public string SearchedText
{
get { return searchedText; }
set
{
SetProperty(ref searchedText, value);
ObjectsSearch(searchedText);
}
}
ObservableCollection<CustomPin> _objectItems { get; set; }
public ObservableCollection<CustomPin> ObjectItems
{
get
{
return _objectItems;
}
set
{
if (_objectItems != value)
{
_objectItems = value;
OnPropertyChanged(nameof(ObjectItems));
}
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
public ICommand ViewObjectOnMapCommand { get; set; }
public ICommand ViewObjectDetailsCommand { get; set; }
public ICommand ToggleFavoriteObjectCommand { get; set; }
#endregion
#region Methods
private async Task GetObjectInstancesList()
{
ObjectItems = new ObservableCollection<CustomPin>();
var objectsResponse = await ApiServiceProvider.GetObjectInstances();
Device.BeginInvokeOnMainThread(() =>
{
if (objectsResponse.Succeeded)
{
foreach (var item in objectsResponse.ObjectInstances)
{
CustomPin pinData = new CustomPin();
pinData.Id = item.IdObjectInstance;
pinData.Label = item.ObjectClassName;
pinData.IsFavorite = item.IsFavorite.HasValue ? item.IsFavorite.Value : false;
if (item.Points != null)
{
pinData.Position = new Position(item.Points.FirstOrDefault().Latitude, item.Points.FirstOrDefault().Longitude);
}
else
{
//add polygon
}
foreach (var s in item.Strings)
{
if (s.ShowInBallon)
{
pinData.InfoBox.DetailsObjectInfos.Add(new Models.MapModels.DetailsObjectInfo
{
BoldLabelTitle = s.ClassParameterName + ": ",
LabelValue = s.StringValue
});
}
}
foreach (var i in item.Integers)
{
if (i.ShowInBallon)
{
pinData.InfoBox.DetailsObjectInfos.Add(new Models.MapModels.DetailsObjectInfo
{
BoldLabelTitle = i.ClassParameterName + ": ",
LabelValue = i.IntValue.ToString()
});
}
}
foreach (var date in item.Dates)
{
if (date.ShowInBallon)
{
pinData.InfoBox.DetailsObjectInfos.Add(new Models.MapModels.DetailsObjectInfo
{
BoldLabelTitle = date.ClassParameterName + ": ",
LabelValue = date.DateValue.ToString()
});
}
}
ObjectItems.Add(pinData);
pinData.InfoBox.CountDetailsItemsRows = pinData.InfoBox.DetailsObjectInfos.Count * 85;
}
}
TemporalData.ObjectsData = ObjectItems;
OnPropertyChanged(nameof(ObjectItems));
OnPropertyChanged(nameof(TemporalData.ObjectsData));
});
}
private void ObjectsSearch(string searchedText)
{
if (!string.IsNullOrWhiteSpace(searchedText))
{
var result = TemporalData.ObjectsData.Where(x => x.Label.ToLowerInvariant().Contains(searchedText.ToLowerInvariant())).ToList();
ObjectItems = new ObservableCollection<CustomPin>(result);
}
else
{
ObjectItems = new ObservableCollection<CustomPin>(TemporalData.ObjectsData);
}
OnPropertyChanged(nameof(ObjectItems));
}
private async Task ViewObjectOnMap(object objectId)
{
CustomPin selectedPin = App.SelectedPin = ObjectItems.Where(x => x.Id == Convert.ToInt32(objectId)).FirstOrDefault();
App.GoToPinCommand = new Command(() => App.GoToPinCommand.Execute(selectedPin));
await Shell.Current.GoToAsync(Routes.MapPage);
}
private async Task GetObjectDetails(object objectId)
{
App.SelectedPin = ObjectItems.Where(x => x.Id == Convert.ToInt32(objectId)).FirstOrDefault();
await Shell.Current.GoToAsync(Routes.ItemDetailsPage);
}
private async Task ToggleFavoriteObjects(object objectId)
{
int id = Convert.ToInt32(objectId);
var objectItem = ObjectItems.Where(x => x.Id == id).FirstOrDefault();
var favoriteToggleResponse = await ApiServiceProvider.ToggleFavoriteObjectById(id, !objectItem.IsFavorite);
if (!favoriteToggleResponse.Succeeded)
{
await Shell.Current.DisplayAlert("Error", "Lost communication with server. Try again.", "OK");
}
else
{
ObjectItems.Where(x => x.Id == id).Select(c => { c.IsFavorite = !c.IsFavorite; return c; }).ToList();
}
}
#endregion
}
Im getting the error: BindingCOntext is set more than once.
Other issue that I have is that emulator does not recognize TapGestureRecognizer for example this Label which you can refer to up code:
<Label FontSize="Medium" FontFamily="{Binding IsFavorite, Converter={StaticResource Key=fontFamilyConverter}}" TextColor="{Binding IsFavorite, Converter={StaticResource Key=fontFamilyConverter}}" Text="{StaticResource IconStar}">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Path=BindingContext.ToggleFavoriteObjectCommand}" CommandParameter="{Binding Id}"/>
</Label.GestureRecognizers>
</Label>
I need to put something like Command="{Binding Path=BindingContext.ToggleFavoriteObjectCommand,Source={x:Reference Page}}" but I don't know if I can reference "parrent page" in my case it would be a "Main" page where I invoke cellView.
Cause
You're setting BindingContext twice .
The first : actually the following code does set BindingContext automatically on Cell , the content is the item in list ObjectItems .
<ListView.ItemTemplate>
<DataTemplate>
<viewcells:ObjectItemViewCell/>
</DataTemplate>
</ListView.ItemTemplate>
The second
<ViewCell.BindingContext>
<converterFontFamily:BoolToStringConverter x:Key="fontFamilyConverter"/>
<!--<converterColor:BoolToStringConverter x:Key="fontFamilyConverter"/>-->
</ViewCell.BindingContext>
Solution
Do not set BindingContext inside ViewCell , if you want to use converter , you could add it in page directly .
<ListView
Margin="0,15,0,0"
SelectionMode="None"
RowHeight= "150"
ItemsSource="{Binding ObjectItems}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell BindingContext="{Binding Converter = {StaticResource fontFamilyConverter}}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The command does not trigger because the binding path is incorrect , place those commands into model CustomPin would solve the problem .
The problem is, you are setting converters in BindingContext tag. They needs to be set like this:
<ContentPage.BindingContext>
YOUR BINDING CONTEXT
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<converterFontFamily:BoolToStringConverter x:Key="fontFamilyConverter"/>
<converterColor:BoolToStringConverter x:Key="fontFamilyConverter"/>
</ResourceDictionary>
</ContentPage.Resources>
Your commands not working because of BindingContext error.
Related
When ever I look at my UI the elements function as expected but anything bound to my view model doesn't show up however the binds seem to be working.
This is my view XAML:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Fate.Views.GlobalChatPage"
x:DataType="viewmodels:GlobalChatPageViewModel"
xmlns:viewmodels="clr-namespace:Fate.ViewModels"
xmlns:converters="clr-namespace:Fate.Converters"
BackgroundColor="#242022">
<ContentPage.BindingContext>
<viewmodels:GlobalChatPageViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<converters:InverseBoolConverter x:Key="InverseBool" />
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout>
<StackLayout Orientation="Horizontal">
<Button x:Name="ConnectButton" Text="Connect" Command="{Binding ConnectCommand}" />
<Button x:Name="DisconnectButton" Text="Disconnect" Command="{Binding DisconnectCommand}" />
<Entry IsReadOnly="False" x:Name="namenameEntry" Text="{Binding Name}" Placeholder="Enter Namename" />
</StackLayout>
<ListView x:Name="MessagesListView" ItemsSource="{Binding Messages}" HasUnevenRows="true" SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackLayout Grid.Column="0" IsVisible="{Binding IsOwnMessage, Converter={StaticResource InverseBool}}">
<Label Text="{Binding Name}" />
<Frame x:Name="MessageFrame" CornerRadius="10" Padding="10" BackgroundColor="LightGray" HeightRequest="{Binding Source={x:Reference MessageLabel}, Path=Height}" Margin="0,0,0,20">
<Label x:Name="MessageLabel" Text="{Binding Message}"/>
</Frame>
</StackLayout>
<StackLayout Grid.Column="1" IsVisible="{Binding IsOwnMessage}">
<Label Text="{Binding Name}" HorizontalOptions="FillAndExpand" />
<Frame x:Name="MessageFrame2" CornerRadius="10" Padding="10" BackgroundColor="LightGray" HeightRequest="{Binding Source={x:Reference MessageLabel}, Path=Height}" Margin="0,0,0,20" HorizontalOptions="FillAndExpand">
<Label x:Name="MessageLabel2" Text="{Binding Message}" HorizontalOptions="FillAndExpand" />
</Frame>
</StackLayout>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackLayout Orientation="Horizontal">
<Entry IsReadOnly="False" x:Name="MessageEntry" Text="{Binding Message}" HorizontalOptions="FillAndExpand" />
<Button x:Name="SendButton" Text="Send" Command="{Binding SendMessageCommand}" />
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
And this is my view model:
using Microsoft.AspNetCore.SignalR.Client;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace Fate.ViewModels
{
public class GlobalChatPageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
private string _message;
private ObservableCollection<string> _messages;
private bool _isConnected;
public bool _IsOwnMessage;
public bool _IsSystemMessage;
public bool IsSystemMessage
{
get
{
return _IsSystemMessage;
}
set
{
_IsSystemMessage = value;
OnPropertyChanged();
}
}
public bool IsOwnMessage
{
get
{
return _IsOwnMessage;
}
set
{
_IsOwnMessage = value;
OnPropertyChanged();
}
}
public string Name
{
get
{
return _name;
}
set
{
_name = value;
OnPropertyChanged();
}
}
public string Message
{
get
{
return _message;
}
set
{
_message = value;
OnPropertyChanged();
}
}
public ObservableCollection<string> Messages
{
get
{
return _messages;
}
set
{
_messages = value;
OnPropertyChanged();
}
}
public bool IsConnected
{
get
{
return _isConnected;
}
set
{
_isConnected = value;
OnPropertyChanged();
}
}
private HubConnection hubConnection;
public Command SendMessageCommand { get; }
public Command ConnectCommand { get; }
public Command DisconnectCommand { get; }
public GlobalChatPageViewModel()
{
Messages = new ObservableCollection<string>();
SendMessageCommand = new Command(async () => { await SendMessage(Name, Message); });
ConnectCommand = new Command(async () => await Connect());
DisconnectCommand = new Command(async () => await Disconnect());
IsConnected = false;
hubConnection = new HubConnectionBuilder()
.WithUrl($"https://placeholderlink/chatHub")
.Build();
hubConnection.On<string>("JoinChat", (user) =>
{
Messages.Add($"{user} has joined the chat");
IsSystemMessage = true;
IsOwnMessage = false;
});
hubConnection.On<string>("LeaveChat", (user) =>
{
Messages.Add($"{user} has left the chat");
IsSystemMessage = true;
IsOwnMessage = false;
});
hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
{
Messages.Add($"{user}: {message}");
IsSystemMessage = false;
if (Name == user)
{
IsOwnMessage = true;
}
});
}
public async Task Connect()
{
if (IsConnected == false)
{
await hubConnection.StartAsync();
await hubConnection.InvokeAsync("JoinChat", Name);
IsConnected = true;
}
}
public async Task SendMessage(string user, string message)
{
await hubConnection.InvokeAsync("SendMessage", user, message);
}
public async Task Disconnect()
{
if (IsConnected == true)
{
await hubConnection.InvokeAsync("LeaveChat", Name);
await hubConnection.StopAsync();
IsConnected = false;
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And this is my converter:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using Xamarin.Forms;
namespace Fate.Converters
{
public class InverseBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return !(bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return !(bool)value;
}
}
}
I was expecting for the text from each message to display on the page, on the left if its from someone else and on the right if its from the user but the text doesn't display at all. I've checked my binding context and I get intellisense and no build errors but it still doesn't display.
Problem 1
You're using compiled bindings by applying the x:DataType property. Therefore, you need to set the correct x:DataType also for any DataTemplate further down in the View hierarchy, because the binding context changes.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Fate.Views.GlobalChatPage"
x:DataType="viewmodels:GlobalChatPageViewModel"
xmlns:viewmodels="clr-namespace:Fate.ViewModels"
xmlns:converters="clr-namespace:Fate.Converters"
BackgroundColor="#242022">
<ContentPage.BindingContext>
<viewmodels:GlobalChatPageViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<converters:InverseBoolConverter x:Key="InverseBool" />
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout>
<StackLayout Orientation="Horizontal">
<Button x:Name="ConnectButton" Text="Connect" Command="{Binding ConnectCommand}" />
<Button x:Name="DisconnectButton" Text="Disconnect" Command="{Binding DisconnectCommand}" />
<Entry IsReadOnly="False" x:Name="namenameEntry" Text="{Binding Name}" Placeholder="Enter Namename" />
</StackLayout>
<ListView x:Name="MessagesListView" ItemsSource="{Binding Messages}" HasUnevenRows="true" SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate
x:DataType="YOUR_TYPE_HERE">
<ViewCell>
<!-- ... -->
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackLayout Orientation="Horizontal">
<Entry IsReadOnly="False" x:Name="MessageEntry" Text="{Binding Message}" HorizontalOptions="FillAndExpand" />
<Button x:Name="SendButton" Text="Send" Command="{Binding SendMessageCommand}" />
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Where it says YOUR_TYPE_HERE, you need to specify the same type as the collection you're passing to the ItemsSource, in your case string (or the XAML equivalent of it, which would be {x:Type x:String}). You can also reset the binding to classic bindings by setting the type on the DataTemplate's x:DataType to {x:Null}.
Problem 2
In your DataTemplate you're binding to properties that don't exist in that context, because your ListView is populated with an ObservableCollection<string>.
If you want to bind to Name from your ViewModel, for example, you'll need to specify a relative binding:
<ViewCell>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackLayout Grid.Column="0">
<Label Text="{Binding Name, Source={RelativeSource AncestorType={x:Type viewmodels:GlobalChatPageViewModel}}" />
<!-- ... -->
</StackLayout>
</Grid>
</ViewCell>
Alternative
A quick fix might be to simply remove the x:DataType on the ContentPage.
Further observations
You're binding to Name multiple times. It's unclear how the two chat participants are managed and where each participant's name and the messages are coming from, especially since there is only a single Name property and a single Messages collection in your ViewModel.
So I have a xamarin forms app that currently only implemented for android. I am attempting to implement a tap event. When tapped though this never hits the command in the ViewModel. I'm not sure if I have something the matter with my code or I am just implementing it wrong.
ViewModel Code:
private RelayCommand<object> _OnClickableLabel;
public RelayCommand<object> OnClickableLabel
{
get { return _OnClickableLabel ?? (_OnClickableLabel = new RelayCommand<object>((currentObject) => Test(currentObject))); }
}
private void Test(object currentObject)
{
Application.Current.MainPage.DisplayAlert("Alert", "were going down cap", "OK");
}
Page Xaml:
<ListView Grid.Row="1" ItemsSource="{Binding Notifications}" RowHeight="100">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" BackgroundColor="{Binding BackgroundColor}">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding OnClickableLabel }" />
</StackLayout.GestureRecognizers>
<Label FontSize="Large" Text="{Binding Title}"></Label>
<Label FontSize="Small" Text="{Binding Text}"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have tested it using a method in the page's cs code and that works fine but it has to be implemented in the ViewModel because it affects that data.
From your description, you want to add a tap gesture recognizer in ListView, and want to pass ListView current row data to TapGestureRecognizer event, am I right?
If yes, as Jason's opinion, you need to take a look Xamarin.Forms Relative Bindings firstly,name ListView as listview1, then take a look the following code:
<ListView
x:Name="listview1"
Grid.Row="1"
ItemsSource="{Binding Notifications}"
RowHeight="100">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout BackgroundColor="{Binding BackgroundColor}" Orientation="Vertical">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding BindingContext.OnClickableLabel, Source={x:Reference listview1}}" CommandParameter="{Binding .}" />
</StackLayout.GestureRecognizers>
<Label FontSize="Large" Text="{Binding Title}" />
<Label FontSize="Small" Text="{Binding Text}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
public partial class Page16 : ContentPage
{
public ObservableCollection<Notclass> Notifications { get; set; }
public ICommand OnClickableLabel { get; set; }
public Page16()
{
InitializeComponent();
Notifications = new ObservableCollection<Notclass>()
{
new Notclass(){Title="title 1",Text="notification 1"},
new Notclass(){Title="title 2",Text="notification 2"},
new Notclass(){Title="title 3",Text="notification 3"},
new Notclass(){Title="title 4",Text="notification 4"},
new Notclass(){Title="title 5",Text="notification 5"}
};
OnClickableLabel = new Command(n=> {
var vm = (Notclass)n;
Application.Current.MainPage.DisplayAlert("Alert",vm.Title , "OK");
});
this.BindingContext = this;
}
}
public class Notclass
{
public string Title { get; set; }
public string Text { get; set; }
public Color BackgroundColor { get; set; } = Color.White;
}
I am having issues with displaying data, I can see my data FieldValue and FieldDescriptor in the List property however I still see in output that:
Binding: 'Results' property not found on 'ViewModel.ResultPageViewModel', target property: 'Xamarin.Forms.ListView.ItemsSource'
I can't see any data FieldVisualData in the horizontal list view even thought I can see them in the view model adding to detail and the command also can't be found.
Binding: 'EditTextCommand' property not found on 'Model.DocumentData', target property: 'Xamarin.Forms.TapGestureRecognizer.Command'
Maybe you see something that I am missing?
<StackLayout Spacing="0">
<!--Pictures-->
<StackLayout VerticalOptions="Start" Spacing="0" >
<controls:HorizontalScrollList VerticalOptions="Start" HeightRequest="300" x:Name="carouselView" ItemsSource="{Binding Results, Mode=TwoWay}">
<controls:HorizontalScrollList.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Results.FieldVisualData}"
Margin="5">
<!--<Image.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding HandlePreviewTapped, Source={x:Reference vm}}"
CommandParameter="{Binding}"/>
</Image.GestureRecognizers>-->
</Image>
</DataTemplate>
</controls:HorizontalScrollList.ItemTemplate>
</controls:HorizontalScrollList>
</StackLayout>
<StackLayout VerticalOptions="Start" BackgroundColor="{DynamicResource SeparatorLineColor}" Spacing="10">
<!--DocumentData-->
<Label Grid.Row="0" HorizontalOptions="CenterAndExpand" Text="Občanský průkaz" VerticalOptions="End" ></Label>
<StackLayout BackgroundColor="{DynamicResource PageBackgroundColor}" VerticalOptions="FillAndExpand">
<ListView x:Name="list" BackgroundColor="{DynamicResource PageBackgroundColor}"
HasUnevenRows="True"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"
VerticalScrollBarVisibility="Never"
CachingStrategy="RecycleElement"
ItemsSource="{Binding Results, Mode=TwoWay}"
SeparatorVisibility="Default"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid BackgroundColor="{DynamicResource PageBackgroundColor}" Padding="10" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="15*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="12*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="1" Padding="0" Text ="{Binding FieldDescriptor}" Style="{StaticResource SubLabelBlackStyle}" HorizontalOptions="Start" BackgroundColor="{DynamicResource PageBackgroundColor}" HorizontalTextAlignment="Start"/>
<Label Grid.Column="3" Padding="0" Text="{Binding FieldValue}" FontSize="Small" TextColor="#6781a3" BackgroundColor="{DynamicResource PageBackgroundColor}" HorizontalOptions="Start" HorizontalTextAlignment="Start">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding EditTextCommand}" CommandParameter="{Binding FieldValue}" />
</Label.GestureRecognizers>
</Label>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</StackLayout>
</StackLayout>
public partial class ResultPage : ContentPage
{
public ResultPage(IEnumerable<DocumentData> data)
{
InitializeComponent();
BindingContext = new ResultPageViewModel(data);
//carouselView.ItemsSource = data;
// list.ItemsSource = data;
}
}
public class ResultPageViewModel : BaseViewModel
{
public ObservableCollection<DocumentData> Results { get; } = new ObservableCollection<DocumentData>();
public ICommand EditTextCommand { get; }
object param = "";
public ResultPageViewModel(IEnumerable<DocumentData> data)
{
EditTextCommand = new Command(async () => await EditTextAsync(param));
Load(data);
}
public void Load(IEnumerable<DocumentData> data)
{
foreach (var result in data)
{
var detail = new DocumentData()
{
FieldVisualData = result.FieldVisualData,
FieldDescriptor = result.FieldDescriptor,
FieldValue = result.FieldValue,
};
Results.Add(detail);
}
}
public async Task EditTextAsync(object param)
{
PromptResult pResult = await UserDialogs.Instance.PromptAsync(new PromptConfig
{
InputType = InputType.Password,
Text = param.ToString(),
Title = Resources.AppResources.Password_lbl,
});
}
}
public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create(
"ItemTemplate",
typeof(DataTemplate),
typeof(HorizontalScrollList),
null,
propertyChanged: (bindable, value, newValue) => ((HorizontalScrollList)bindable).Populate());
public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(
"ItemsSource",
typeof(IEnumerable),
typeof(HorizontalScrollList),
null,
BindingMode.OneWay,
propertyChanged: (bindable, value, newValue) =>
{
var obs = value as INotifyCollectionChanged;
var self = (HorizontalScrollList)bindable;
if (obs != null)
obs.CollectionChanged -= self.HandleItemChanged;
self.Populate();
obs = newValue as INotifyCollectionChanged;
if (obs != null)
obs.CollectionChanged += self.HandleItemChanged;
});
public IEnumerable ItemsSource
{
get => (IEnumerable)this.GetValue(ItemsSourceProperty);
set => this.SetValue(ItemsSourceProperty, value);
}
public DataTemplate ItemTemplate
{
get => (DataTemplate)this.GetValue(ItemTemplateProperty);
set => this.SetValue(ItemTemplateProperty, value);
}
private bool willUpdate = true;
private void HandleItemChanged(object sender, NotifyCollectionChangedEventArgs eventArgs)
{
if (!willUpdate)
{
willUpdate = true;
Device.BeginInvokeOnMainThread(Populate);
}
}
public HorizontalScrollList()
{
this.Orientation = ScrollOrientation.Horizontal;
}
private void Populate()
{
willUpdate = false;
Content = null;
if (ItemsSource == null || ItemTemplate == null)
{
return;
}
var list = new StackLayout { Orientation = StackOrientation.Horizontal };
foreach (var viewModel in ItemsSource)
{
var content = ItemTemplate.CreateContent();
if (!(content is View) && !(content is ViewCell))
{
throw new Exception($"Invalid visual object {nameof(content)}");
}
var view = content is View ? content as View : ((ViewCell)content).View;
view.BindingContext = viewModel;
list.Children.Add(view);
}
if (list.Children.Count == 0)
{
list.Padding = 20;
list.Children.Add(new Label
{
WidthRequest = (list as VisualElement).Width - 30,
HorizontalOptions = new LayoutOptions(LayoutAlignment.Fill, true),
VerticalOptions = new LayoutOptions(LayoutAlignment.Fill, true),
HorizontalTextAlignment = TextAlignment.Center,
VerticalTextAlignment = TextAlignment.Center,
FontSize = 15,
});
}
Content = list;
}
Do you want to acheve the result like following GIF?
I do not which style or backgroundcolor that you setted, I set it with static background color.
Here is my editted layout.controls:HorizontalScrollList is a picture, so I comment it.
I change the Command in the TapGestureRecognizer
<StackLayout Spacing="0">
<!--Pictures-->
<StackLayout VerticalOptions="Start" Spacing="0" >
<!--<controls:HorizontalScrollList VerticalOptions="Start" HeightRequest="300" x:Name="carouselView" ItemsSource="{Binding Results, Mode=TwoWay}">
<controls:HorizontalScrollList.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Results.FieldVisualData}"
Margin="5">
--><!--<Image.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding HandlePreviewTapped, Source={x:Reference vm}}"
CommandParameter="{Binding}"/>
</Image.GestureRecognizers>--><!--
</Image>
</DataTemplate>
</controls:HorizontalScrollList.ItemTemplate>
</controls:HorizontalScrollList>-->
</StackLayout>
<StackLayout VerticalOptions="Start" BackgroundColor="White" Spacing="10">
<!--DocumentData-->
<Label Grid.Row="0" HorizontalOptions="CenterAndExpand" Text="Občanský průkaz" VerticalOptions="End" ></Label>
<StackLayout BackgroundColor="Green" VerticalOptions="FillAndExpand">
<ListView x:Name="list" BackgroundColor="Red"
HasUnevenRows="True"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"
VerticalScrollBarVisibility="Never"
CachingStrategy="RecycleElement"
ItemsSource="{Binding Results, Mode=TwoWay}"
SeparatorVisibility="Default"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid BackgroundColor="Beige" Padding="10" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="15*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="12*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="1" Padding="0" Text ="{Binding FieldDescriptor}" HorizontalOptions="Start" BackgroundColor="Gray" HorizontalTextAlignment="Start"/>
<Label Grid.Column="3" Padding="0" Text="{Binding FieldValue}" FontSize="Small" TextColor="#6781a3" BackgroundColor="AliceBlue" HorizontalOptions="Start" HorizontalTextAlignment="Start">
<Label.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding BindingContext.EditTextCommand, Source={x:Reference Name=list}}"
CommandParameter="{Binding .}"
/>
</Label.GestureRecognizers>
</Label>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</StackLayout>
</StackLayout>
Here is layout background code. I add three data to make a test.
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
IEnumerable<DocumentData> data = new DocumentData[] { new DocumentData() { FieldDescriptor="test 1", FieldValue=1, FieldVisualData=1 } };
data = data.Append(new DocumentData() { FieldDescriptor = "test 2", FieldValue = 2, FieldVisualData = 2 });
data = data.Append(new DocumentData() { FieldDescriptor = "test 3", FieldValue = 3, FieldVisualData = 3 });
this.BindingContext = new ResultPageViewModel(data);
}
}
Here is ResultPageViewModels code.
using Acr.UserDialogs;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
namespace XFormsListviewMvvm
{
public class ResultPageViewModel : BaseViewModel
{
public ObservableCollection<DocumentData> Results { get; } = new ObservableCollection<DocumentData>();
public ICommand EditTextCommand { get; }
object param = "";
public ResultPageViewModel(IEnumerable<DocumentData> data)
{
EditTextCommand = new Command<DocumentData>(async (key) => await EditTextAsync(key));
Load(data);
}
public void Load(IEnumerable<DocumentData> data)
{
foreach (var result in data)
{
var detail = new DocumentData()
{
FieldVisualData = result.FieldVisualData,
FieldDescriptor = result.FieldDescriptor,
FieldValue = result.FieldValue,
};
Results.Add(detail);
}
}
public async Task EditTextAsync(DocumentData param)
{
PromptResult pResult = await UserDialogs.Instance.PromptAsync(new PromptConfig
{
InputType = InputType.Password,
Text = param.FieldValue.ToString(),
Title = "Insert your Password",
});
if (pResult.Ok)
{
param.FieldValue = pResult.Text;
}
}
}
}
If you need to change the value, then layout will display it. you need to implement the BaseViewModel in the DocumentData.
namespace XFormsListviewMvvm
{
public class DocumentData:BaseViewModel
{
private object fieldVisualData = "Hello world";
public object FieldVisualData
{
get => fieldVisualData;
set => SetValue(ref fieldVisualData, value);
}
private object fieldValue = "Hello world";
public object FieldValue
{
get => fieldValue;
set => SetValue(ref fieldValue, value);
}
public object FieldDescriptor { get; internal set; }
}
}
=============Update===================
I add your controls:HorizontalScrollList code. And I change the from <Image Source="{Binding Results.FieldVisualData}"> to <Image Source="{Binding FieldVisualData}" >
Here is running gif.Image could be seen normally.
Here is editted layout.
<StackLayout Spacing="0">
<!--Pictures-->
<StackLayout VerticalOptions="Start" Spacing="0" >
<controls:HorizontalScrollList VerticalOptions="Start" HeightRequest="300" x:Name="carouselView" ItemsSource="{Binding Results, Mode=TwoWay}">
<controls:HorizontalScrollList.ItemTemplate>
<DataTemplate>
<Image Source="{Binding FieldVisualData}"
Margin="5">
<!--<Image.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding HandlePreviewTapped, Source={x:Reference vm}}"
CommandParameter="{Binding}"/>
</Image.GestureRecognizers>-->
</Image>
</DataTemplate>
</controls:HorizontalScrollList.ItemTemplate>
</controls:HorizontalScrollList>
</StackLayout>
<StackLayout VerticalOptions="Start" BackgroundColor="White" Spacing="10">
<!--DocumentData-->
<Label Grid.Row="0" HorizontalOptions="CenterAndExpand" Text="Občanský průkaz" VerticalOptions="End" ></Label>
<StackLayout BackgroundColor="Green" VerticalOptions="FillAndExpand">
<ListView x:Name="list" BackgroundColor="Red"
HasUnevenRows="True"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"
VerticalScrollBarVisibility="Never"
CachingStrategy="RecycleElement"
ItemsSource="{Binding Results, Mode=TwoWay}"
SeparatorVisibility="Default"
SelectionMode="None">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid BackgroundColor="Beige" Padding="10" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="15*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="12*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="1" Padding="0" Text ="{Binding FieldDescriptor}" HorizontalOptions="Start" BackgroundColor="Gray" HorizontalTextAlignment="Start"/>
<Label Grid.Column="3" Padding="0" Text="{Binding FieldValue}" FontSize="Small" TextColor="#6781a3" BackgroundColor="AliceBlue" HorizontalOptions="Start" HorizontalTextAlignment="Start">
<Label.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding BindingContext.EditTextCommand, Source={x:Reference Name=list}}"
CommandParameter="{Binding .}"
/>
</Label.GestureRecognizers>
</Label>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</StackLayout>
</StackLayout>
Here is layout background code.
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
IEnumerable<DocumentData> data = new DocumentData[] { new DocumentData() { FieldDescriptor="test 1", FieldValue=1, FieldVisualData= "https://aka.ms/campus.jpg" } };
data = data.Append(new DocumentData() { FieldDescriptor = "test 2", FieldValue = 2, FieldVisualData = "https://aka.ms/campus.jpg" });
data = data.Append(new DocumentData() { FieldDescriptor = "test 3", FieldValue = 3, FieldVisualData = "https://aka.ms/campus.jpg" });
this.BindingContext = new ResultPageViewModel(data);
}
}
I would like to get the value of a selectedItem in A listView,this is my code.
public class BlogClass{
public string NewId;
public BlogClass()
{
additem();
}
public class BlogViews
{
public string id { get; set; }
public string DisplayTopic { get; set; }
public string DisplayMain { get; set; }
public ImageSource BlogImageSource { get; set; }
}
public List<BlogViews> BlogList1 = new List<BlogViews>();
public void additem()
{
BlogList1.Add(new BlogViews { id = "1", DisplayMain = "Margret", DisplayTopic = "Mensah" });
BlogList1.Add(new BlogViews { id = "2", DisplayMain = "Maet", DisplayTopic = "Meah" });
BlogList1.Add(new BlogViews { id = "3", DisplayMain = "dargret", DisplayTopic = "sah" });
BlogList1.Add(new BlogViews { id = "4", DisplayMain = "gret", DisplayTopic = "Meh" });
BlogListView.ItemsSource = BlogList1;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="demoListView.ImageCellPage">
<ContentPage.Content>
<ListView x:Name="BloglistView">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout BackgroundColor="#eee"
Orientation="Vertical">
<StackLayout Orientation="Horizontal">
<Image Source="{Binding BlogImageSource}" />
<Label Text="{Binding id}"
TextColor="#f35e20" />
<Label Text="{Binding DisplayTopic}"
HorizontalOptions="EndAndExpand"
TextColor="#503026" />
<Label Text="{Binding DisplayMain}"
HorizontalOptions="EndAndExpand"
TextColor="#503026" />
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>
Question
Now When i select an item on BlogListView I want to set the Value of NewId to that of id according to the selecteditem.Hence gatting the value of id
You just need to add ItemTapped event for the listview:
XAML:
<ListView x:Name="BloglistView" ItemTapped="Handle_ItemTapped">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout BackgroundColor="#eee"
Orientation="Vertical">
<StackLayout Orientation="Horizontal">
<Image Source="{Binding BlogImageSource}" />
<Label Text="{Binding id}"
TextColor="#f35e20" />
<Label Text="{Binding DisplayTopic}"
HorizontalOptions="EndAndExpand"
TextColor="#503026" />
<Label Text="{Binding DisplayMain}"
HorizontalOptions="EndAndExpand"
TextColor="#503026" />
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Code Behind:
void Handle_ItemTapped(object sender, Xamarin.Forms.ItemTappedEventArgs e)
{
var selectedItem = e.Item as BlogViews;
NewId = selectedItem.id;
}
From this example:
https://blog.verslu.is/stackoverflow-answers/alternate-row-color-listview/
How can I implement it with a grouped listview? I want to have the alternate row color inside every group of the listview, I already tried to implement it with the grouped Listview, but is always giving me "System.InvalidCastException: Specified cast is not valid." inside the DataTemplateSelector.
Listview Code:
<DataTemplate x:Key="evenTemplate">
<ViewCell>
<customRenders:GridConf Margin="0,0,0,0" HorizontalOptions="FillAndExpand" ColumnSpacing="0" RowSpacing="0" ConfigurationItem ="{Binding .}">
<customRenders:GridConf.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="*"/>
</customRenders:GridConf.ColumnDefinitions>
<customRenders:GridConf.RowDefinitions>
<RowDefinition Height="*"/>
</customRenders:GridConf.RowDefinitions>
<BoxView VerticalOptions="CenterAndExpand" HeightRequest="50" Grid.ColumnSpan="1" Margin="-30,0,0,0" Grid.Column="1" HorizontalOptions="FillAndExpand" BackgroundColor="LightGray"/>
<Label VerticalOptions="CenterAndExpand" Margin="10,0,0,0" Grid.Column="1" VerticalTextAlignment="Center" HorizontalOptions="StartAndExpand" Text="tetetetetetet"></Label>
<Image Grid.Column="1" Source="HidePass.png" HeightRequest="30" VerticalOptions="CenterAndExpand" HorizontalOptions="End">
<Image.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" ></TapGestureRecognizer>
</Image.GestureRecognizers>
</Image>
<customRenders:CachedImageItem Grid.Column="0" ConfigurationItem ="{Binding .}" HorizontalOptions="Start" HeightRequest="80" VerticalOptions="Center" x:Name="Image2" Source="{Binding Img}"/>
<customRenders:GridConf.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" ></TapGestureRecognizer>
</customRenders:GridConf.GestureRecognizers>
</customRenders:GridConf>
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="unevenTemplate">
<ViewCell>
<customRenders:GridConf Margin="20,0,0,0" HorizontalOptions="FillAndExpand" ColumnSpacing="0" RowSpacing="0" ConfigurationItem ="{Binding .}">
<customRenders:GridConf.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="*"/>
</customRenders:GridConf.ColumnDefinitions>
<customRenders:GridConf.RowDefinitions>
<RowDefinition Height="*"/>
</customRenders:GridConf.RowDefinitions>
<BoxView VerticalOptions="CenterAndExpand" HeightRequest="50" Grid.ColumnSpan="1" Margin="-30,0,0,0" Grid.Column="1" HorizontalOptions="FillAndExpand" BackgroundColor="LightGray"/>
<Label VerticalOptions="CenterAndExpand" Margin="10,0,0,0" Grid.Column="1" VerticalTextAlignment="Center" HorizontalOptions="StartAndExpand" Text="teteteteteette"></Label>
<Image Grid.Column="1" Source="HidePass.png" HeightRequest="30" VerticalOptions="CenterAndExpand" HorizontalOptions="End">
<Image.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" ></TapGestureRecognizer>
</Image.GestureRecognizers>
</Image>
<customRenders:CachedImageItem Grid.Column="0" ConfigurationItem ="{Binding .}" HorizontalOptions="Start" HeightRequest="80" VerticalOptions="Center" x:Name="Image2" Source="{Binding Img}"/>
<customRenders:GridConf.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" ></TapGestureRecognizer>
</customRenders:GridConf.GestureRecognizers>
</customRenders:GridConf>
</ViewCell>
</DataTemplate>
<customRenders1:AlternateColorDataTemplateSelector2 x:Key="alternateColorDataTemplateSelector"
EvenTemplate="{StaticResource evenTemplate}"
UnevenTemplate="{StaticResource unevenTemplate}" />
</ResourceDictionary>
</ContentPage.Resources>
<ListView x:Name="lst" IsGroupingEnabled="True" ItemTemplate="{StaticResource alternateColorDataTemplateSelector}" ItemsSource="{Binding Item}" Margin="5,5,0,0" HasUnevenRows="True" SeparatorVisibility="None">
<ListView.GroupHeaderTemplate>
<DataTemplate>
<customRenders:NativeCell>
<customRenders:NativeCell.View>
<ContentView Padding="10,0,0,0">
<StackLayout>
<Label Text="{Binding Key.Category}" VerticalOptions="Center"/>
</StackLayout>
<ContentView.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Source={x:Reference ProtocolosPage}, Path=BindingContext.HeaderSelectedCommand}" CommandParameter="{Binding .}"/>
</ContentView.GestureRecognizers>
</ContentView>
</customRenders:NativeCell.View>
</customRenders:NativeCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
</ListView>
AlternateColorDataTemplateSelector:
public DataTemplate EvenTemplate { get; set; }
public DataTemplate UnevenTemplate { get; set; }
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
// TODO: Maybe some more error handling here
return ((List<Product>)((ListView)container).ItemsSource).IndexOf(item as Product) % 2 == 0 ? EvenTemplate : UnevenTemplate;
}
ViewModel
public class ProductsViewModel: BindableBase
{
public class SelectCategoryViewModel
{
public string Category { get; set; }
public bool Selected { get; set; }
}
private ObservableCollection<Grouping<string, Product>> _ProductsGrouped;
public ObservableCollection<Product> Productitems { get; set; }
public ObservableCollection<Grouping<string, Models.Product>> ProductsGrouped
{
get
{
return _ProductsGrouped;
}
set
{
_ProductsGrouped = value;
OnPropertyChanged();
}
}
public ObservableCollection<Grouping<SelectCategoryViewModel, Product>> Item { get; set; }
public DelegateCommand<Grouping<SelectCategoryViewModel, Product>> HeaderSelectedCommand
{
get
{
return new DelegateCommand<Grouping<SelectCategoryViewModel, Product>>(g =>
{
if (g == null) return;
g.Key.Selected = !g.Key.Selected;
if (g.Key.Selected)
{
Productitems.Where(i => (i.Category.Equals(g.Key.Category)))
.ForEach(g.Add);
}
else
{
g.Clear();
}
});
}
}
public ProductsViewModel()
{
Productitems = new ObservableCollection<Product>
{
new Product
{
Img = "dss.png",
Url = "Teste",
Category = "service",
Title = "sdsadsadsdsdsa"
},
new Product
{
Img = "dss.png",
Url = "Teste3",
Category = "service",
Title = "sdsadsadsdsdsatest2"
},
new Product
{
Img = "dss.png",
Url = "Teste2",
Category = "Farmacy",
Title = "sdsadsadsdsdsaes"
},
new Product
{
Img = "dss.png",
Url = "Teste4",
Category = "Farmacy",
Title = "sdsadsadsdsdsaF"
},
new Product
{
Img = "dss.png",
Url = "Teste7",
Category = "Farmacy",
Title = "sdsadsadsdsdsaFarmarcia2"
},
new Product
{
Img = "dss.png",
Url = "Teste9",
Category = "Farmacy",
Title = "sdsadsadsdsdsae"
}
};
Item = new ObservableCollection<Grouping<SelectCategoryViewModel, Product>>();
var selectCategories =
Productitems.Select(x => new SelectCategoryViewModel { Category = x.Category, Selected = false })
.GroupBy(sc => new { sc.Category })
.Select(g => g.First())
.ToList();
selectCategories.ForEach(sc => Item.Add(new Grouping<SelectCategoryViewModel, Product>(sc, new List<Product>())));
}
}
This is not working, since the code in the AlternateColorDataTemplateSelector is casting the ItemSource to a List. When you're using grouping, it cannot be a simple list.
On the other hand, when you do an IndexOf on one group, you will receive the index within that group, which does not need to correspond to the index in the full list.
Find a sample repository here: https://github.com/jfversluis/GroupedListViewAlternateRowColor.
In the adapted DataTemplateSelector I flatten out the whole list and get the index from there. Here is the code:
public class AlternateColorDataTemplateSelector : DataTemplateSelector
{
public DataTemplate EvenTemplate { get; set; }
public DataTemplate UnevenTemplate { get; set; }
private List<string> flatList;
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
if (flatList == null)
{
var groupedList = (ObservableCollection<Grouping<string, string>>)((ListView)container).ItemsSource;
flatList = groupedList.SelectMany(group => group).ToList();
}
return flatList.IndexOf(item as string) % 2 == 0 ? EvenTemplate : UnevenTemplate;
}
}
As an optimization, I just create the flat list once. This could go wrong whenever the list is updated with new items. I didn't test that.
The result looks like this: