We have a custom ListView which has an ItemClickCommand property. We are binding this in a XAML file using the local keyword. It's working fine for Xamarin.Forms Android, but its not clickable in Xamarin.forms iOS.
Please suggest how to proceed or what we are doing wrong in code.
Code is written below:
XAML file:
<StackLayout x:Class="XYZ"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XYZ.UI;assembly=XYZ">
<local:ListView ItemClickCommand="{Binding OnDataSelectionCommand, Mode=TwoWay}" ItemsSource="{Binding DataList}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Label FontSize="17"
HorizontalOptions="Start"
Text="{Binding }"
TextColor="Black"
VerticalOptions="Center" />
<Label FontSize="17"
HorizontalOptions="Start"
Text=" , "
TextColor="Black"
VerticalOptions="Center" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</local:ListView>
View Model :
private Command _onDataSelectionCommand;
public Command OnDataSelectionCommand
{
get
{
return _onDataSelectionCommand ?? (_onDataSelectionCommand = new Command(async (s) =>
{
// TODO: get selected item from list !!
}));
}
}
Custom List Created :
using System.Windows.Input;
using Xamarin.Forms;
namespace XYZ.UI
{
public class ListView : Xamarin.Forms.ListView
{
public static BindableProperty ItemClickCommandProperty = BindableProperty.Create<ListView, ICommand>(x => x.ItemClickCommand, null);
public ListView()
{
this.ItemTapped += this.OnItemTapped;
}
public ICommand ItemClickCommand
{
get { return (ICommand)this.GetValue(ItemClickCommandProperty); }
set { this.SetValue(ItemClickCommandProperty, value); }
}
private void OnItemTapped(object sender, ItemTappedEventArgs e)
{
if (e.Item != null && this.ItemClickCommand != null && this.ItemClickCommand.CanExecute(e))
{
this.ItemClickCommand.Execute(e.Item);
}
}
}
You have to use the SelectedItem property of ListView to Bind. This will give you the object (item) of the collection you bound to the list.
Sample project can be found here.
Also please note that ItemTapped is not same as ItemSelected.
If you are interested in Binding a Command to ListView Item Tapped then please have a look at this forum post about the same - How to bind a Command to ListView.ItemTapped.
Related
I am trying to bind the ItemSelected of a ListView to a View Model, but am experiencing some issues (due to my own misunderstands around how it all works).
I have view:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:Local="clr-namespace:FireLearn.ViewModels"
x:Class="FireLearn.MainPage"
Title="Categories">
<ContentPage.BindingContext>
<Local:CategoryViewModel/>
</ContentPage.BindingContext>
<NavigationPage.TitleView>
<Label Text="Home"/>
</NavigationPage.TitleView>
<ListView
ItemsSource="{Binding Categories}"
HasUnevenRows="True"
IsPullToRefreshEnabled="True"
IsRefreshing="{Binding ListRefreshing, Mode=OneWay}"
RefreshCommand="{Binding RefreshCommand}"
ItemSelected="{Binding OnItemTappedChanged}"
SelectionMode="Single"
SelectedItem="{Binding SelectedCategory}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<HorizontalStackLayout
Padding="8"
VerticalOptions="Fill"
HorizontalOptions="Fill">
<Image Source="cafs_bubbles.png"
HeightRequest="64"
MaximumWidthRequest="64"
HorizontalOptions="CenterAndExpand"
VerticalOptions="CenterAndExpand"/>
<VerticalStackLayout
Padding="8"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<Label Text="{Binding FormattedName}"
SemanticProperties.HeadingLevel="Level1"
FontSize="Title"
HorizontalOptions="Start"/>
<Label Text="{Binding ItemCount}"
FontSize="Subtitle"/>
<Label Text="{Binding Description}"
HorizontalOptions="Center"
LineBreakMode="WordWrap"
FontSize="Caption"
VerticalOptions="CenterAndExpand"
MaxLines="0"/>
</VerticalStackLayout>
</HorizontalStackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
This is linked to a view model:
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FireLearn.Models;
namespace FireLearn.ViewModels
{
public partial class CategoryViewModel : ObservableObject
{
public ObservableCollection<CategoryModel> categories = new ObservableCollection<CategoryModel>();
public ObservableCollection<CategoryModel> Categories
{
get => categories;
set => SetProperty(ref categories, value);
}
public bool listRefreshing = false;
public bool ListRefreshing
{
get => listRefreshing;
set => SetProperty(ref listRefreshing, value);
}
public CategoryModel selectedCategory = new CategoryModel();
public CategoryModel SelectedCategory
{
get => selectedCategory;
set
{
SetProperty(ref selectedCategory, value);
// Tap(value);
}
}
public RelayCommand RefreshCommand { get; set; }
//public RelayCommand TapCellCommand { get; set; }
public CategoryViewModel()
{
loadFromSource();
RefreshCommand = new RelayCommand(async () =>
{
Debug.WriteLine($"STARTED::{ListRefreshing}");
if (!ListRefreshing)
{
ListRefreshing = true;
try
{
await loadFromSource();
}
finally
{
ListRefreshing = false;
Debug.WriteLine($"DONE::{ListRefreshing}");
}
}
});
}
public async Task loadFromSource()
{
HttpClient httpClient = new()
{
Timeout = new TimeSpan(0, 0, 10)
};
Uri uri = new Uri("https://somewebsite.co.uk/wp-json/wp/v2/categories");
HttpResponseMessage msg = await httpClient.GetAsync(uri);
if (msg.IsSuccessStatusCode)
{
var result = CategoryModel.FromJson(await msg.Content.ReadAsStringAsync());
Categories = new ObservableCollection<CategoryModel>(result);
}
Debug.WriteLine("List Refreshed");
}
public void OnItemTappedChanged(System.Object sender, Microsoft.Maui.Controls.SelectedItemChangedEventArgs e)
{
var x = new ShellNavigationState();
Shell.Current.GoToAsync(nameof(NewPage1),
new Dictionary<string, object>
{
{
nameof(NewPage1),
SelectedCategory
}
});
}
}
}
I get compiler error "No property, BindableProperty, or event found for "ItemSelected", or mismatching type between value and property" and am really unsure of how to resolve. If I let XAML create a new event for me, it adds it in MainPage.Xaml.Cs rather than the VM
ItemSelected expects an event handler which usually only exists in the View's code behind. Since the ViewModel shouldn't know anything about the View, it's better not to mix concepts. You have a couple of options to get around this without breaking the MVVM pattern.
Option 1: Use Event Handler and invoke method of ViewModel
First, set up the code behind with the ViewModel by passing it in via the constructor and also add the event handler, e.g.:
public partial class MainPage : ContentPage
{
private CategoryViewModel _viewModel;
public MainPage(CategoryViewModel viewModel)
{
_viewModel = viewModel;
}
public void OnItemSelectedChanged(object sender, SelectedItemChangedEventArgs e)
{
//call a method from the ViewModel, e.g.
_viewModel.DoSomething(e.SelectedItem);
}
//...
}
Then use the event handler from within the XAML:
<ListView
ItemsSource="{Binding Categories}"
HasUnevenRows="True"
IsPullToRefreshEnabled="True"
IsRefreshing="{Binding ListRefreshing, Mode=OneWay}"
RefreshCommand="{Binding RefreshCommand}"
ItemSelected="OnItemSelectedChanged"
SelectionMode="Single"
SelectedItem="{Binding SelectedCategory}">
<!-- skipping irrelevant stuff -->
</ListView>
Mind that this does not use bindings.
In your CategoryViewModel you could then define a method that takes in the selected item as an argument:
public partial class CategoryViewModel : ObservableObject
{
//...
public void DoSomething(object item)
{
//do something with the item, e.g. cast it to Category
}
}
Option 2: Use EventToCommandBehavior
Instead of handling the invocation of a ViewModel method from your code behind, you could also use the EventToCommandBehavior from the MAUI Community Toolkit:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:Local="clr-namespace:FireLearn.ViewModels"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Class="FireLearn.MainPage"
Title="Categories">
<ContentPage.Resources>
<ResourceDictionary>
<toolkit:SelectedItemEventArgsConverter x:Key="SelectedItemEventArgsConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<ListView
ItemsSource="{Binding Categories}"
HasUnevenRows="True"
IsPullToRefreshEnabled="True"
IsRefreshing="{Binding ListRefreshing, Mode=OneWay}"
RefreshCommand="{Binding RefreshCommand}"
SelectionMode="Single"
SelectedItem="{Binding SelectedCategory}">
<ListView.Behaviors>
<toolkit:EventToCommandBehavior
EventName="ItemSelected"
Command="{Binding ItemSelectedCommand}"
EventArgsConverter="{StaticResource SelectedItemEventArgsConverter}" />
</ListView.Behaviors>
<!-- skipping irrelevant stuff -->
</ListView>
</ContentPage>
Then, in your ViewModel, you can define the ItemSelectedCommand:
public partial class CategoryViewModel : ObservableObject
{
[RelayCommand]
private void ItemSelected(object item)
{
//do something with the item, e.g. cast it to Category
}
// ...
}
This is the preferred way to do it. Option 1 is just another possiblity, but the EventToCommandBehavior is the better choice.
Note that this is an example using MVVM Source Generators (since you're already using the MVVM Community Toolkit). The full Command would normally be implemented like this:
public partial class CategoryViewModel : ObservableObject
{
private IRelayCommand<object> _itemSelectedCommand;
public IRelayCommand<object> ItemSelectedCommand => _itemSelectedCommand ?? (_itemSelectedCommand = new RelayCommand<object>(ItemSelected));
private void ItemSelected(object item)
{
//do something with the item, e.g. cast it to Category
}
// ...
}
My MainPage.xaml page is bound to ClientsViewModel.cs. This page has a ListView bound to an ObservableCollection property.
The NewClient.xaml page and entry fields are also bound to the ClientsViewModel.cs.
When I save a new client using the NewClient.xaml form and navigate back to MainPage.xaml (using the navigation back arrow) I expect to see the newly added client in the MainPage.xaml ListView however I do not see this change.
How come the ListView in MainPage.xaml isn't showing the newly updated record? Where am I going wrong?
It may be worthwhile mentioning that my actual project will be using SQLite, so the ObseravbleCollection will eventually be obtaining records directly from an SQLite database, so any help or advice around this would be greatly appreciated also.
Refer below code, or clone from my GitHub repository https://github.com/minlopalis/XamarinForms-ListView-DataBinding.git
(Model) Client.cs
public class Client
{
public int Id { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
}
(ViewModel) BaseViewModel.cs
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
(View Model) ClientViewModel.cs
public class ClientViewModel : BaseViewModel
{
private ObservableCollection<Client> clients;
public ObservableCollection<Client> Clients
{
get { return clients; }
set
{
clients = value;
OnPropertyChanged();
}
}
public Command SaveClientCommand { get; }
public ClientViewModel()
{
this.Clients = new ObservableCollection<Client>();
SaveClientCommand = new Command(()=> {
Client client = new Client()
{
Name = Name,
Phone = Phone
};
Clients.Add(client);
OnPropertyChanged(nameof(Clients));
});
}
private int id;
public int Id
{
get { return id; }
set
{
id = value;
OnPropertyChanged();
}
}
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged();
}
}
private string phone;
public string Phone
{
get { return phone; }
set
{
phone = value;
OnPropertyChanged();
}
}
}
(View) MainPage.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"
xmlns:viewModels="clr-namespace:DataBinding.ViewModels"
x:Class="DataBinding.MainPage">
<ContentPage.BindingContext>
<viewModels:ClientViewModel/>
</ContentPage.BindingContext>
<StackLayout>
<Label Text="Client List"></Label>
<ListView ItemsSource="{Binding Clients}">
<ListView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding Name}"/>
<Label Text="{Binding Phone}"/>
</StackLayout>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Text="Add Client"
Clicked="AddClientButton_Clicked"/>
</StackLayout>
</ContentPage>
(View) NewClient.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"
xmlns:viewModels="clr-namespace:DataBinding.ViewModels"
x:Class="DataBinding.Views.NewClient">
<ContentPage.BindingContext>
<viewModels:ClientViewModel/>
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout>
<Label Text="Add New Client" />
<Label Text="Name"/>
<Entry Text="{Binding Name}"/>
<Label Text="Phone"/>
<Entry Text="{Binding Phone}"/>
<Button Text="Save"
Command="{Binding SaveClientCommand}"/>
<!-- Added ListView -->
<ListView ItemsSource="{Binding Clients}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Name}"/>
<Label Text="{Binding Phone}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
I've downloaded your code from the repo and I think there is one big flaw in it causing this. You're setting your BindingContext in XAML on both pages. If you set a breakpoint in the constructor of the ClientViewModel, you will notice it gets called twice: once when the app boots, once when you click "Add Client".
This means you are looking at two separate instances of this class so your Client is in the wrong instance. You want to make sure that you are looking at the same view model.
Even more so, you might even want to make the separation of concerns even better by creating an extra, i.e.: CreateClientViewModel which is only responsible for creating the client and returning that object to the ClientViewModel which then in its turn adds that to the collection.
Hope this helps!
According to your description, you want to pass data when navigate between pages, I suggest you can use MessagingCenter.
MainPage:
<StackLayout>
<Label Text="Client List" />
<ListView ItemsSource="{Binding Clients}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Name}" />
<Label Text="{Binding Phone}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Command="{Binding SaveClientCommand}" Text="Add Client" />
</StackLayout>
public partial class Page9 : ContentPage
{
private ClientViewModel _clientmodel;
public ClientViewModel clientmodel
{
get { return _clientmodel; }
set
{
_clientmodel = value;
}
}
public Page9()
{
InitializeComponent();
this.BindingContext = new ClientViewModel(this.Navigation);
}
}
public class ClientViewModel
{
public ObservableCollection<Client> Clients { get; set; }
public Command SaveClientCommand { get; }
private INavigation _navigation;
public ClientViewModel(INavigation navitation)
{
Clients = new ObservableCollection<Client>();
Clients.Add(new Client() { Name = "client1", Phone = "123" });
_navigation = navitation;
SaveClientCommand = new Command(async() => {
await _navigation.PushAsync(new NewClient());
});
MessagingCenter.Subscribe<string, string[]>("test", "Add", (sender, values) =>
{
Client client = new Client() { Name=values[0],Phone=values[1]};
Clients.Add(client);
});
}
}
NewClient.xaml:
public partial class NewClient : ContentPage
{
public NewClient()
{
InitializeComponent();
}
private void Button_Clicked(object sender, EventArgs e)
{
string name = entry1.Text;
string phone = entry2.Text;
string[] values = { name,phone};
MessagingCenter.Send<string, string[]>("test", "Add", values);
Navigation.PopAsync();
}
}
By the way, you don't need to call PropertyChanged for ObservableCollection, because ObservableCollection Class Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
Thanks for everyone's help, I have solved my issue.
There were two problems with my code.
1. Two ViewModel Instances
As pointed out by Gerald Versluis I had two instances of my ViewModel. I fixed this issue by creating an instance of my view model in Application.Resources in my App.xaml page.
<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DataBinding.App"
xmlns:ClientViewModel="clr-namespace:DataBinding.ViewModels">
<Application.Resources>
<ClientViewModel:ClientViewModel x:Key="ClientViewModel" />
</Application.Resources>
</Application>
And binding each page to the Static Resource (as below)
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:ViewModels="clr-namespace:DataBinding.ViewModels"
x:Class="DataBinding.Views.NewClient">
<ContentPage.BindingContext>
<StaticResource Key="ClientViewModel"/>
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout>
<Label Text="Add New Client" />
<Label Text="Name"/>
<Entry Text="{Binding Name}"/>
<Label Text="Phone"/>
<Entry Text="{Binding Phone}"/>
<Button Text="Save"
Command="{Binding SaveClientCommand}"/>
<!-- Added ListView -->
<ListView ItemsSource="{Binding ClientList}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Name}"/>
<Label Text="{Binding Phone}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Thanks Gerald Versluis for your help. Check out his YouTube channel here.
2. Missing ViewCell
My MainPage.xaml was missing a ViewCell in the ListView. This was a simple typing oversight but was throwing a "'Specified cast is not valid" error. Big thanks to Alexander Fauland for his reply to this thread which helped me solve my missing ViewCell problem.
I want to add the data from my selected item in the ListView to a few entries immediately when clicking on the item.
My XAML Code:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:WishListProject.ViewModels"
x:Class="WishListProject.Views.UpdateGames">
<ContentPage.BindingContext>
<local:GameListViewModel />
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout>
<ListView ItemsSource="{Binding Games}" SelectedItem="{Binding SelectedGame}" ItemSelected="{Binding InsertGame }">
</ListView>
<Entry Placeholder="ID" IsVisible="False" Text="{Binding IdEntry}"></Entry>
<Entry Placeholder="GameName" Text="{Binding GameNaamEntry}"></Entry>
<Entry Placeholder="GameGenre" Text="{Binding GameGenreEntry}"></Entry>
<Entry Placeholder="GameRelease" Text="{Binding GameReleaseEntry}"></Entry>
<Button Text="Update Game" Command="{Binding UpdateGameCommand}" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
And this is the function I want to start for displaying the selected item data in to a few entries USING THE VIEWMODEL:
public void InsertGame(object sender, SelectedItemChangedEventArgs e)
{
Game game = new Game();
game = SelectedGame;
IdEntry.Text = game.Id.ToString();
GameNaamEntry = game.GameNaam;
GameGenreEntry = game.GameGenre;
GameReleaseEntry = game.GameRelease;
}
What is the best way to start a function in a VIEWMODEL just by clicking on the item in the ListView?
You can call your method at the time SelectedItem is set. Since you are using bindings, I assume you have something like this in your ViewModel:
Game _selectedGame;
public Game SelectedGame
{
get { return _selectedGame; }
set {
SetProperty(ref _selectedGame, value);
// code to add in the setter
if (value != null)
InsertGame();
//----------------------------
}
}
Just make sure the method is called after the property is changed.
Then you will also need to simplify your method signature to:
public void InsertGame(){
...
}
And you should be all set.
Happy coding!
ItemSelected is an event and you can't bind it to a method. I will give you a solution about how to start a function in a ViewMomdel just by clicking on the item in the ListView:
In xaml:
<ListView ItemsSource="{Binding Games}" SelectedItem="{Binding SelectedGame}" ItemSelected="ListView_ItemSelected">
And in code behind, get the current ViewModel in the ListView_ItemSelected method and then call InsertGame of ViewModel, you can pass the current selectedGame also:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
private void ListView_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
Game selectedGame = e.SelectedItem as Game;
var currentVm = this.BindingContext as GameListViewModel;
currentVm.InsertGame(selectedGame);
}
}
And your ViewModel:
public class GameListViewModel : INotifyPropertyChanged
{
public void InsertGame(Game currentGame)
{
GameNaamEntry = currentGame.GameNaam;
}
...
}
I'm developing/learning XamarinForms. I'm using Web Api to get values and use them to populate a ListView. Now I want to get current values on ItemSelected and store them for later use, but I don't seem to work it out. I'm using the MVVM.
This is my ListView
<ListView x:Name="ProfileDetails" ItemsSource="{Binding Profiles}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Margin="20,0,0,0" Orientation="Horizontal" HorizontalOptions="FillAndExpand">
<Label Text="{Binding ProfileType}" VerticalTextAlignment="Center" HorizontalOptions="StartAndExpand" TextColor="LightGray"/>
<Label Text="{Binding ProfileChamber}" VerticalTextAlignment="Center" HorizontalOptions="StartAndExpand" TextColor="LightGray"/>
<Label Text="{Binding ProfileWidhtMM}" VerticalTextAlignment="Center" HorizontalOptions="StartAndExpand" TextColor="LightGray"/>
<Label Text="{Binding ProfilePrice}" VerticalTextAlignment="Center" HorizontalOptions="StartAndExpand" TextColor="LightGray"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This is the ViewModel
APIServices _apiServices = new APIServices();
private List<Profile> _profiles;
public List<Profile> Profiles
{
get { return _profiles; }
set
{
_profiles = value;
OnPropertyChanged();
}
}
public ICommand GetProfilesCommand
{
get
{
return new Command(async () =>
{
Profiles = await _apiServices.GetProfilesAsync(_accessToken);
});
}
}
And this is my API request
public async Task<List<Profile>> GetProfilesAsync(string accessToken)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var json = await client.GetStringAsync(Constants.GetProfilesUrl);
var profiles = JsonConvert.DeserializeObject<List<Profile>>(json);
return profiles;
}
And this is the Model
public long Id { get; set; }
public string ProfileType { get; set; }
public int ProfileChamber { get; set; }
public int ProfileWidhtMM { get; set; }
public double ProfilePrice { get; set; }
Answer from Oluwasayo is correct but I see that you are using MVVM already so using this solution might be better for you, just in case if you want to keep your code-behind class clean and leave any mixing code-behind and ViewModel code for one functionality.
I don't know if you are using some MVVM framework or not, but there is a very nice and helpful behavior called EventToCommandBehavior you can use it to "translate" any event and bind it to a command in your ViewModel.
You can find implementation of it here on GitHub, you can easly include it in your project. There is a lot of other implementations from MVVM frameworks.
Steps to make this in the way I advice you are:
Include that class in your project and you can use it in xaml pages.
Edit VM code and add some aditional lines of code.
ViewModel code:
// Property for binding SelectedItem from ListView.
private Profile _selectedProfile;
public Profile SelectedProfile
{
get { return _profiles; }
set
{
_selectedProfile= value;
OnPropertyChanged();
}
}
// Command which we will use for Event to Command binding.
public DelegateCommand ItemTappedCommand{ get; private set; }
// ...
// Code inside of the ctor in VM:
ItemTappedCommand = new Command(ItemTapped);
// ...
// Method which will be executed using our command
void ItemTapped()
{
// Here you can do whatever you want, this will be executed when
// user clicks on item in ListView, you will have a value of tapped
// item in SlectedProfile property
}
Edit your XAML page code and add few code lines.
XAML Page:
<!-- In your page xaml header add xaml namespace to EventToCommandBehaviour class-->
xmlns:b="clr-namespace:Namespace.ToYour.EventToCommandBehaviourClass"
<ListView x:Name="ProfileDetails" ItemsSource="{Binding Profiles}"
SelectedItem={Binding SelectedProfile}>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Margin="20,0,0,0" Orientation="Horizontal" HorizontalOptions="FillAndExpand">
<Label Text="{Binding ProfileType}" VerticalTextAlignment="Center" HorizontalOptions="StartAndExpand" TextColor="LightGray"/>
<Label Text="{Binding ProfileChamber}" VerticalTextAlignment="Center" HorizontalOptions="StartAndExpand" TextColor="LightGray"/>
<Label Text="{Binding ProfileWidhtMM}" VerticalTextAlignment="Center" HorizontalOptions="StartAndExpand" TextColor="LightGray"/>
<Label Text="{Binding ProfilePrice}" VerticalTextAlignment="Center" HorizontalOptions="StartAndExpand" TextColor="LightGray"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Behaviors>
<b:EventToCommandBehavior EventName="ItemTapped"
Command="{Binding ItemTappedCommand}" />
</ListView.Behaviors>
</ListView>
Hope that this makes sense to you, using this approach your code-behind class will stay clean without mixing the code between vm and code-behind, just my tip for you.
Wishing you lots of luck with coding!
In your xaml add the ItemTapped property. Can be:
ItemTapped="ProfileDetails_Selected"
Then you should have the method handling the event in your code behind as:
private void ProfileDetails_Selected(object sender, ItemTappedEventArgs e)
{
var myItem = e.Item ...; //from here you can get your item and store for later use
}
I'm trying to handle something similar (from UI perspective), to:
in order to invoke two different business logics for:
tapping at ViewCell element itself (inside ListView) - in example navigate to different page
tapping at Label element (Clickable Label), which is inside given ViewCell element - in example delete given object or smth else
I would like to have whole "tapping" logic inside page ViewModel.
Based on Xamarin forum proposes, I'm able to invoke some logic of "tapping" my delete action from cell, however directly inside my data model - which in my PoV is not good solution, as I would like to manipulate my List collection (so the most preferable way, would be to have this logic at page ViewModel).
What I have right now:
My page View XAML code looks like:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="App.Views.TestView">
<ContentPage.Content>
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<ListView HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" ItemsSource="{Binding MyItemsCollection}" SelectedItem="{Binding SelectedItem}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<!-- Name Label -->
<Label Text="{Binding Name}" VerticalOptions="CenterAndExpand" HorizontalOptions="StartAndExpand" />
<!-- Delete "Icon" -->
<Label Text="Clickable Label" VerticalOptions="CenterAndExpand" HorizontalOptions="EndAndExpand">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding OnClickableLabel}" CommandParameter="{Binding .}" />
</Label.GestureRecognizers>
</Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
My page View C# code looks like (not specific code there, except binding **BindingContext* to page ViewModel):
public partial class TestView : ContentPage
{
public TestView()
{
InitializeComponent();
BindingContext = ServiceLocator.Current.GetInstance<TestViewModel>();
}
}
My page ViewModel C# code looks like:
public class TestViewModel : ViewModelBase
{
public TestViewModel()
{
MyItemsCollection = GetMyItemsCollection();
}
private List<MyItem> GetMyItemsCollection()
{
return new List<MyItem>
{
new MyItem
{
ID = 1L,
Name = "Item 1 Name"
},
new MyItem
{
ID = 2L,
Name = "Item 2 Name"
},
new MyItem
{
ID = 3L,
Name = "Item 3 Name"
}
};
}
private List<MyItem> _myItemsCollection { get; set; }
public List<MyItem> MyItemsCollection
{
get
{
return _myItemsCollection;
}
set
{
_myItemsCollection = value;
RaisePropertyChanged();
}
}
private MyItem _SelectedItem { get; set; }
public MyItem SelectedItem
{
get
{
return _SelectedItem;
}
set
{
if (_SelectedItem != value)
{
_SelectedItem = value;
RaisePropertyChanged();
Debug.WriteLine("SelectedItem: " + _SelectedItem.Name);
}
}
}
private RelayCommand<object> _OnClickableLabel;
public RelayCommand<object> OnClickableLabel
{
get { return _OnClickableLabel ?? (_OnClickableLabel = new RelayCommand<object>((currentObject) => Test(currentObject))); }
}
private void Test(object currentObject)
{
Debug.WriteLine("This should work... but it's not working :(");
}
}
My data model code looks like:
public class MyItem
{
public long ID { get; set; }
public string Name { get; set; }
private RelayCommand<object> _OnClickableLabel;
public RelayCommand<object> OnClickableLabel
{
get { return _OnClickableLabel ?? (_OnClickableLabel = new RelayCommand<object>((currentObject) => Test(currentObject))); }
}
private void Test(object currentObject)
{
Debug.WriteLine("This works... but it's not good idea, to have it here...");
}
}
Any idea what needs to be changed, in order to invoke OnClickableLabel directly inside my page ViewModel ?
I know, that it's something wrong at:
<TapGestureRecognizer Command="{Binding OnClickableLabel}" CommandParameter="{Binding .}" />
but don't know what :/.
Help! Thanks a lot.
Ok, I found solution, by extending XAML code:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="App.Views.TestView" x:Name="Page">
<ContentPage.Content>
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<ListView HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" ItemsSource="{Binding MyItemsCollection}" SelectedItem="{Binding SelectedItem}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<!-- Name Label -->
<Label Text="{Binding Name}" VerticalOptions="CenterAndExpand" HorizontalOptions="StartAndExpand" />
<!-- Delete "Icon" -->
<Label Text="Clickable Label" VerticalOptions="CenterAndExpand" HorizontalOptions="EndAndExpand">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Path=BindingContext.OnClickableLabel, Source={x:Reference Page}}" CommandParameter="{Binding .}" />
</Label.GestureRecognizers>
</Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
After that, I got OnClickableLabel command invoked inside my page ViewModel, as expected :).
If someone know "better" solution (better from XAML code point of view), I would like to see it ;).
Thanks a lot everyone!
continuing with what #Namek said i would suggest to get the object of list view item first and then call the command or viewmodel method.
for more you can refer my blog post about interactive Listview at https://adityadeshpandeadi.wordpress.com/2018/07/15/the-more-interactive-listview/
feel free to drop by. :)