Data binding for listview in xamarin - xamarin

I am trying to populate listview with database table in xamarin forms app
I am getting null pointer exception
Below is XAML for listview
<ListView x:Name="_listView"
ItemsSource="{Binding itemsInList}"
Grid.Column="0"
Grid.Row="0"
SelectedItem="{Binding SelectedItem}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Label Text="{Binding Name}" Grid.Column="0" Grid.Row="0" />
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Below is xaml.cs(code behind)
public List <ServiceProvider> itemlist;
public List <ServiceProvider> itemsInList
{
get {return itemlist;}
}
protected override void OnAppearing()
{
base.OnAppearing();
ExpensesDatabase dbcon = new ExpensesDatabase(completePath);
itemlist = dbcon.GetItems(completePath);
// _listView.ItemsSource = itemlist;
}
Below is db file
public class ExpensesDatabase
{
readonly SQLiteConnection database;
public ExpensesDatabase(string dbPath)
{
database = new SQLiteConnection(dbPath);
database.CreateTable < ServiceProvider > ();
}
public List < ServiceProvider > GetItems(string dbPath)
{
return database.Table < ServiceProvider > ().ToList();
}
}
Data is not displayed in listview

If you want the ListView to automatically update as items are added, removed and changed in the underlying list, you'll need to use an ObservableCollection. ObservableCollection is defined in System.Collections.ObjectModel and is just like List, except that it can notify ListView of any changes:
public ObservableCollection<ServiceProvider> itemsInList { get; set; }
Then make sure you have set the right bindingContext and initialized the ObservableCollection:
public MainPage()
{
InitializeComponent();
itemsInList = new ObservableCollection<ServiceProvider>();
BindingContext = this;
}
I write a sample to test and it works on my side, you can have a look at the full code:
public partial class MainPage : ContentPage
{
public ObservableCollection<ServiceProvider> itemsInList { get; set; }
public MainPage()
{
InitializeComponent();
itemsInList = new ObservableCollection<ServiceProvider>();
BindingContext = this;
}
protected override void OnAppearing()
{
base.OnAppearing();
itemsInList.Add(new ServiceProvider() { Name= "a"});
}
}
public class ServiceProvider : INotifyPropertyChanged
{
string name;
public event PropertyChangedEventHandler PropertyChanged;
public ServiceProvider()
{
}
public String Name
{
set
{
if (name != value)
{
name = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
}
get
{
return name;
}
}
}
Feel free to ask me any question if you still can't solve it.

Related

CollectionView dose not show content, when filled in onappearing

I have a collectionview that is bound to an ObservableRangeCollectionin my ViewModel.
In my ViewModel there is a Method that runs onAppearing and I want my ColletionViewto be filled from there, but when I do so the collectionveiw dose not display the content only when i reload the content is shown.
View:
<RefreshView Grid.Row="1"
Grid.RowSpan="2"
Command="{Binding RefreshCommand}"
IsRefreshing="{Binding IsBusy, Mode=OneWay}">
<RefreshView.RefreshColor>
<OnPlatform x:TypeArguments="Color">
<On Platform="iOS" Value="White"/>
</OnPlatform>
</RefreshView.RefreshColor>
<CollectionView x:Name="Collection"
ItemsSource="{Binding Locations, Mode=OneWay}"
ItemTemplate="{StaticResource ListDataTemplate}"
RemainingItemsThresholdReachedCommand="{Binding LoadMoreCommand}"
RemainingItemsThreshold="10"
SelectionMode="Single"
BackgroundColor="Transparent"
ItemsLayout="VerticalList"
SelectedItem="{Binding SelectedItem}"
SelectionChangedCommand="{Binding SelectedCommand}">
<CollectionView.EmptyView>
<StackLayout Padding="12">
<Label HorizontalOptions="Center" Text="Keine Daten vorhanden!" TextColor="White"/>
</StackLayout>
</CollectionView.EmptyView>
</CollectionView>
</RefreshView>
ViewModel:
namespace YourPartys.ViewModels
{
public class ListViewModel : ViewModelBase
{
#region Variables
#endregion
#region Propertys
LocationModel selectedItem;
public LocationModel SelectedItem
{
get => selectedItem;
set => SetProperty(ref selectedItem, value);
}
public ObservableRangeCollection<LocationModel> Locations { get;set; } = new ObservableRangeCollection<LocationModel>();
double distance;
public double Distance
{
get => distance;
set => SetProperty(ref distance, value);
}
#endregion
#region Commands
public ICommand FilterButtonCommand { get; }
public ICommand RefreshCommand { get; }
public ICommand SelectedCommand { get; }
public ICommand LoadMoreCommand { get; }
#endregion
//Constructor
public ListViewModel()
{
FilterButtonCommand = new Command(OpenFilter);
RefreshCommand = new AsyncCommand(Refresh);
SelectedCommand = new AsyncCommand(Select);
}
public override async void VModelActive(Page sender, EventArgs eventArgs)
{
base.VModelActive(sender, eventArgs);
var locs = await FirestoreService.GetLocations("Locations");
Locations.AddRange(locs);
}
private void OpenFilter(object obj)
{
PopupNavigation.Instance.PushAsync(new ListFilterPage());
}
private async Task Refresh()
{
IsBusy = true;
var locs = await FirestoreService.GetLocations("Locations");
Locations.AddRange(locs);
IsBusy = false;
}
private async Task Select()
{
if (SelectedItem == null)
return;
var route = $"{nameof(DetailPage)}?Locationid={SelectedItem.Locationid}";
SelectedItem = null;
await AppShell.Current.GoToAsync(route);
}
}
}
There are several problems in your demo.
1.Since you set the BindingContext for your page in xaml as follows:
<ContentPage.BindingContext>
<viewmodels:MainViewModel/>
</ContentPage.BindingContext>
you didn't need to recreate another object MainViewModel in a CS file and reference it. These are two different objects.
MainViewModel viewModel;
viewModel = new MainViewModel();
protected override void OnAppearing()
{
base.OnAppearing();
viewModel.VModelActive(this, EventArgs.Empty);
}
So, you can get the BindingContext in MainPage.xaml.cs in function OnAppearing as follows:
protected override void OnAppearing()
{
base.OnAppearing();
viewModel = (MainViewModel)this.BindingContext;
viewModel.VModelActive(this, EventArgs.Empty);
}
The whole code is
public partial class MainPage : ContentPage
{
MainViewModel viewModel;
public MainPage()
{
InitializeComponent();
// viewModel = new MainViewModel();
}
protected override void OnAppearing()
{
base.OnAppearing();
viewModel = (MainViewModel)this.BindingContext;
viewModel.VModelActive(this, EventArgs.Empty);
}
}
2.when we set the text color of the Label to White,this makes it hard to see the text,so you can reset it to another color,for example Black:
<Label Text="{Binding Name}"
FontSize="30"
TextColor="White"/>

Xamarin.Forms block UI when ItemsSource load a big data

I do not load a small data from the API, in C# code, they are loaded in advance and everything seems to be fine, but as soon as I open the page where ItemsSource = "{Binding BigData}", my UI is blocked for 10 seconds.
Are there any ideas to open the page first, then start loading data without blocking the UI?
I would to suggest you can kick off a task in your view models constructor that loads the data. Using Async and await to load bid data.
I do one sample that using ListView to display 100000 records.
<StackLayout>
<Label Text="test ui in xamarin.forms asyn" />
<ActivityIndicator IsRunning="{Binding isBusy}" IsVisible="{Binding isBusy}" />
<ListView x:Name="listview1" ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding name}" />
<Label HorizontalOptions="CenterAndExpand" Text="{Binding age}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
public partial class Page19 : ContentPage
{
public Page19()
{
InitializeComponent();
this.BindingContext = new ItemsViewModel();
}
}
public class ItemsViewModel:ViewModelBase
{
private bool _isBusy;
public bool isBusy
{
get { return _isBusy; }
set
{
_isBusy = value;
RaisePropertyChanged("isBusy");
}
}
public ObservableCollection<people> Items { get; set; }
public ItemsViewModel()
{
Items = new ObservableCollection<people>();
isBusy = true;
Task.Run(async () => await LoadItems());
}
public async Task LoadItems()
{
var items = new ObservableCollection<people>(); // new collection
if (isBusy)
{
await Task.Delay(10000);
// var loadedItems = ItemsService.LoadItemsDirectory();
//foreach (var item in loadedItems)
// items.Add(item);
for (int i = 0; i < 100000; i++)
{
people p = new people();
p.name = "people " + i;
p.age = i;
items.Add(p); // items are added to the new collection
}
Items = items; // swap the collection for the new one
RaisePropertyChanged(nameof(Items)); // raise a property change in whatever way is right for your VM
isBusy = false;
}
}
}
public class people
{
public string name { get; set; }
public int age { get; set; }
}
ViewModelBase is one class that implementing INotifyPropertyChanged
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Xamarin Forms Frame not expanding with content

I'm new to Xamarin Forms, and I've met my first challenge. I want a Frame around my Stacklayout within a Listview. When the user selects an item in the Listview I want some controls to appear. This works fine without the Frame, but the Frame does not expand when the controls appear. How can I change or get around this behavior?
Code below.
XAML:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MVVMTest"
x:Class="MVVMTest.MainPage">
<StackLayout>
<ListView HasUnevenRows="True" SelectedItem="{Binding SelectedViewItem, Mode=TwoWay}" ItemsSource="{Binding Items}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Frame BackgroundColor="White" BorderColor="Black">
<StackLayout>
<Label Text="{Binding Name}"></Label>
<Entry Text="{Binding Details}" IsVisible="{Binding ShowDetails}"></Entry>
</StackLayout>
</Frame>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
XAML.cs
namespace MVVMTest
{
public partial class MainPage : ContentPage
{
public MainPage()
{
BindingContext = new ViewModel()
{
Items = new List<ViewModelItem>()
{
new ViewModelItem()
{
Name = "Test",
Details = "details"
},
new ViewModelItem()
{
Name = "Test2",
Details = "details2"
}
}
};
InitializeComponent();
}
}
}
Model:
namespace MVVMTest
{
public class ViewModel : INotifyPropertyChanged
{
private ViewModelItem _selectedViewItem;
private List<ViewModelItem> _items;
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ViewModelItem SelectedViewItem
{
get
{
return _selectedViewItem;
}
set
{
_selectedViewItem = value;
OnPropertyChanged();
if (value != null)
{
value.ShowDetails = !value.ShowDetails;
SelectedViewItem = null;
}
}
}
public List<ViewModelItem> Items
{
get
{
return _items;
}
set
{
_items = value;
OnPropertyChanged();
}
}
public ViewModel()
{
}
}
public class ViewModelItem : INotifyPropertyChanged
{
private bool _showDetails;
private string _details;
private string _name;
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string Name
{
get
{
return _name;
}
set
{
_name = value;
OnPropertyChanged();
}
}
public bool ShowDetails
{
get
{
return _showDetails;
}
set
{
_showDetails = value;
OnPropertyChanged();
}
}
public string Details
{
get
{
return _details;
}
set
{
_details = value;
OnPropertyChanged();
}
}
}
}
I ended up using the PropertyChanged event to react to when the ListView was displayed or hidden. In the eventhandler, I set the HeightRequest of the Frame and this forces it to resize itself.
Alternative solution/help can be found here:
https://forums.xamarin.com/discussion/comment/366577
XAML:
<StackLayout>
<ListView HasUnevenRows="True" SelectedItem="{Binding SelectedViewItem, Mode=TwoWay}" ItemsSource="{Binding Items}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Frame BackgroundColor="White" BorderColor="Black" Margin="2" Padding="2" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<StackLayout>
<Label Text="{Binding Name}"></Label>
<ListView HasUnevenRows="True" Margin="2" ItemsSource="{Binding DetailObjects}" IsVisible="{Binding ShowDetails}" PropertyChanged="ListView_PropertyChanged">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Entry Text="{Binding Details}"></Entry>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</Frame>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Code behind:
public partial class MainPage : ContentPage
{
public MainPage()
{
BindingContext = new ViewModel()
{
Items = new List<ViewModelItem>()
{
new ViewModelItem()
{
Name = "Test",
DetailObjects = new List<ViewModelItemDetails>
{
new ViewModelItemDetails
{
Details = "details1"
},
new ViewModelItemDetails
{
Details = "details2"
}
}
},
new ViewModelItem()
{
Name = "Test2",
DetailObjects = new List<ViewModelItemDetails>
{
new ViewModelItemDetails
{
Details = "details1"
},
new ViewModelItemDetails
{
Details = "details2"
}
}
}
}
};
InitializeComponent();
}
private void ListView_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (!(sender is ListView list)) return;
if (e.PropertyName == ListView.IsVisibleProperty.PropertyName)
{
Element parent = list;
Frame frame = null;
while (frame == null && parent != null)
{
if (parent is Frame) frame = parent as Frame;
parent = parent.Parent;
}
if (list.IsVisible)
{
list.HeightRequest = list.ItemsSource.Cast<ViewModelItemDetails>().Count() * 50;
if (frame != null) frame.HeightRequest = list.HeightRequest + 50;
}
else
{
if (frame != null) frame.HeightRequest = 50;
}
}
}
}

Binding ViewModel field outside of Item Source

Hi I have an ItemSource which binds a list of contact
<ListView x:Name="contactsListView" ItemsSource="{Binding contacts}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Image Source="{Binding Should be to my View Model instead of Contacts}"></Image>
<Label Text="{Binding FullName}"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
the Full Name binding works fine. My problem is the Image Source is not included in the Contact model so I need to retrieve that from my view Model How can I do it?
VIEW MODEL
public class ContactsViewModel : INotifyPropertyChanged
{
public ObservableCollection<Contact> contacts { get; set; }
private string _contactImage;
public string ContactImage
{
get => _contactImage; set
{
_contactImage = value;
OnPropertyChanged("ContactImage");
}
}
public ContactsViewModel(List<Contact> _contacts)
{
contacts = new ObservableCollection<Contact>(_contacts);
ContactImage = "arrow.png";
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
CODE BEHIND VIEW
public partial class ContactListPage : TabbedPage
{
public ContactsViewModel vm;
public ContactListPage (List<Contact> _contacts)
{
vm = new ContactsViewModel(_contacts);
BindingContext = vm;
InitializeComponent();
}
}
This is because the scope within a ListView item is different than a level higher. To overcome this, create a reference to a parent control. I see you already named your ListView, so we can use that.
Do it like this: <Image Source="{Binding Path=BindingContext.ContactImage, Source={x:Reference contactsListView}}"></Image>

How can I pass a command to a template and have it execute in my back end code and pass the parameter?

I have this template:
<?xml version="1.0" encoding="utf-8"?>
<Grid Padding="20,0" xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Japanese;assembly=Japanese"
x:Class="Japanese.Templates.DataGridTemplate"
x:Name="this" HeightRequest="49" Margin="0">
<Grid.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding TapCommand, Source={x:Reference this}}"
CommandParameter="1"
NumberOfTapsRequired="1" />
</Grid.GestureRecognizers>
<Label Grid.Column="0" Text="{Binding Test" />
</Grid>
Behind this I have:
public partial class DataGridTemplate : Grid
{
public DataGridTemplate()
{
InitializeComponent();
}
public static readonly BindableProperty TapCommandProperty =
BindableProperty.Create(
"Command",
typeof(ICommand),
typeof(DataGridTemplate),
null);
public ICommand TapCommand
{
get { return (ICommand)GetValue(TapCommandProperty); }
set { SetValue(TapCommandProperty, value); }
}
}
and I am trying to call the template like this in file: Settings.xaml.cs
<template:DataGridTemplate TapCommand="openCFSPage" />
hoping that it will call my method here in file: Settings.cs
void openCFSPage(object sender, EventArgs e)
{
Navigation.PushAsync(new CFSPage());
}
The code compiles but when I click on the grid it doesn't call the openCFSPage method.
1) Does anyone have an idea what might be wrong?
2) Also is there a way that I can add a parameter to the template and then have that parameter passed to my method in the CS back end code?
Note that I would like to avoid adding a view model if possible. The application is small and I'd like to just have the code I need in the CS code of the page that calls the template.
Please note that the simplest way to implement this would be through MVVM (i.e. a view-model), but if you want to side-step this option (as you mentioned in the question) then you can use one of the following options
Option1 : Wrap delegate into command object
If you look at it from the perspective of a XAML parser, you are technically trying to assign a delegate to a property of type ICommand. One way to avoid the type mismatch would be to wrap the delegate inside a command-property in the page's code-behind.
Code-behind [Settings.xaml.cs]
ICommand _openCFSPageCmd;
public ICommand OpenCFSPageCommand {
get {
return _openCFSPageCmd ?? (_openCFSPageCmd = new Command(OpenCFSPage));
}
}
void OpenCFSPage(object param)
{
Console.WriteLine($"Control was tapped with parameter: {param}");
}
XAML [Settings.xaml]
<!-- assuming that you have added x:Name="_parent" in root tag -->
<local:DataGridView TapCommand="{Binding OpenCFSPageCommand, Source={x:Reference _parent}}" />
Option2 : Custom markup-extension
Another option (a bit less mainstream) is to create a markup-extension that wraps the delegate into a command object.
[ContentProperty("Handler")]
public class ToCommandExtension : IMarkupExtension
{
public string Handler { get; set; }
public object Source { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{
if (serviceProvider == null)
throw new ArgumentNullException(nameof(serviceProvider));
var lineInfo = (serviceProvider?.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider)?.XmlLineInfo ?? new XmlLineInfo();
object rootObj = Source;
if (rootObj == null)
{
var rootProvider = serviceProvider.GetService<IRootObjectProvider>();
if (rootProvider != null)
rootObj = rootProvider.RootObject;
}
if(rootObj == null)
{
var valueProvider = serviceProvider.GetService<IProvideValueTarget>();
if (valueProvider == null)
throw new ArgumentException("serviceProvider does not provide an IProvideValueTarget");
//we assume valueProvider also implements IProvideParentValues
var propInfo = valueProvider.GetType()
.GetProperty("Xamarin.Forms.Xaml.IProvideParentValues.ParentObjects",
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if(propInfo == null)
throw new ArgumentException("valueProvider does not provide an ParentObjects");
var parentObjects = propInfo.GetValue(valueProvider) as IEnumerable<object>;
rootObj = parentObjects?.LastOrDefault();
}
if(rootObj != null)
{
var delegateInfo = rootObj.GetType().GetMethod(Handler,
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if(delegateInfo != null)
{
var handler = Delegate.CreateDelegate(typeof(Action<object>), rootObj, delegateInfo) as Action<object>;
return new Command((param) => handler(param));
}
}
throw new XamlParseException($"Can not find the delegate referenced by `{Handler}` on `{Source?.GetType()}`", lineInfo);
}
}
Sample usage
<local:DataGridView TapCommand="{local:ToCommand OpenCFSPage}" />
You have 2 options depending on the the use case :
FYI, there's no way to call another method directly from the view (its a bad design pattern to do so)
Using Event Aggregator :
Create interface
public interface IEventAggregator
{
TEventType GetEvent<TEventType>() where TEventType : EventBase, new();
}
All you have to do is call it from you TapCommand
_eventAggregator.GetEvent<ItemSelectedEvent>().Publish(_selectedItem);
Then in your Settings.cs you can Create a method that can receive the data
this.DataContext = new ListViewModel(ApplicationService.Instance.EventAggregator);
Inheritance and Polymorphism / Making openCFSPage a service :
Creating a interface / service that links both models
public interface IOpenCFSPage
{
Task OpenPage();
}
and a method :
public class OpenCFSPage : IOpenCFSPage
{
private INavigationService _navigationService;
public OpenCFSPage(INavigationService navigationService){
_navigationService = navigationService;
}
public async Task OpenPage()
{
await _navigationService.NavigateAsync(new CFSPage());
}
}
Settings.xaml:
<template:DataGridTemplate TapCommand="{Binding OpenCFSPage}" />
<!-- Uncomment below and corresponding parameter property code in DataGridTemplate.xaml.cs to pass parameter from Settings.xaml -->
<!--<template:DataGridTemplate TapCommand="{Binding OpenCFSPage}" CommandParameter="A" />-->
Settings.xaml.cs:
public Settings()
{
InitializeComponent();
OpenCFSPage = new Command(p => OpenCFSPageExecute(p));
BindingContext = this;
}
public ICommand OpenCFSPage { get; private set; }
void OpenCFSPageExecute(object p)
{
var s = p as string;
Debug.WriteLine($"OpenCFSPage:{s}:");
}
DataGridTemplate.xaml:
<?xml version="1.0" encoding="UTF-8"?>
<Grid xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Japanese;assembly=Japanese"
Padding="0,20"
HeightRequest="49" Margin="0"
x:Class="Japanese.DataGridTemplate">
<Grid.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding TapCommand}"
CommandParameter="1"
NumberOfTapsRequired="1" />
</Grid.GestureRecognizers>
<Label Grid.Column="0" Text="Test" />
</Grid>
DataGridTemplate.xaml.cs:
public partial class DataGridTemplate : Grid
{
public DataGridTemplate()
{
InitializeComponent();
}
public static readonly BindableProperty TapCommandProperty =
BindableProperty.Create(
nameof(TapCommand), typeof(ICommand), typeof(DataGridTemplate), null,
propertyChanged: OnCommandPropertyChanged);
public ICommand TapCommand
{
get { return (ICommand)GetValue(TapCommandProperty); }
set { SetValue(TapCommandProperty, value); }
}
//public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(
// nameof(CommandParameter), typeof(string), typeof(DataGridTemplate), null);
//public string CommandParameter
//{
// get { return (string)GetValue(CommandParameterProperty); }
// set { SetValue(CommandParameterProperty, value); }
//}
static TapGestureRecognizer GetTapGestureRecognizer(DataGridTemplate view)
{
var enumerator = view.GestureRecognizers.GetEnumerator();
while (enumerator.MoveNext())
{
var item = enumerator.Current;
if (item is TapGestureRecognizer) return item as TapGestureRecognizer;
}
return null;
}
static void OnCommandPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is DataGridTemplate view)
{
var tapGestureRecognizer = GetTapGestureRecognizer(view);
if (tapGestureRecognizer != null)
{
tapGestureRecognizer.Command = (ICommand)view.GetValue(TapCommandProperty);
//tapGestureRecognizer.CommandParameter = (string)view.GetValue(CommandParameterProperty);
}
}
}
}
Check this code you help you. Here you have to pass a reference of list view and also you need to bind a command with BindingContext.
<ListView ItemsSource="{Binding Sites}" x:Name="lstSale">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical">
<Label Text="{Binding FriendlyName}" />
<Button Text="{Binding Name}"
HorizontalOptions="Center"
VerticalOptions="Center"
Command="{Binding
Path=BindingContext.RoomClickCommand,
Source={x:Reference lstSale}}"
CommandParameter="{Binding .}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Resources