Xamarin: Populating ListView in xcml using a List<T> - c#

I'm having issues getting the data binding to work in xaml with ListView. The ListView displays all cells as expected but doesn't display the value for any of the bound variables.
I have my sample data constructed like this:
public class DataItem
{
public int Number { get; set; }
public string Message { get; set; }
public DataItem (int i)
{
this.Number = i + 1;
this.Message = string.Format ("Data Item Hello {0}", i + 1);
}
}
The ListView in xaml is defined this way:
<ListView x:Name="uiList">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" BackgroundColor="Silver">
<Label TextColor="Green" Text="Pre Xamarin" />
<Label BackgroundColor="Aqua" TextColor="Red" Text="{Binding Path=Message}" />
<Label TextColor="Green" Text="Hello Xamarin" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have created 200 sample objects and assigned to uiList.ItemsSource. This results in the ListView showing 200 items as expected but it doesn't show the value of the Message property in the DataItem.
I've also tried using {Binding Message} (without Path=) but the result didn't change. I'm sure there is something obvious I'm missing but can't figure it out.
--- UPDATE ---
I have a reliable repro now. Create a new project and add the ListView and population as provided above.
Go to the solution properties for the iOS project, select the iOS Build page and change 'Link behavior' to 'Link All' and change the 'Supported architecture' to 'x86_64'. Now Clean, Build and Run and the binding will not work.

Hey I just tried your code and it worked for me out of the box. I just added my logic to populate a list of DataItem and set it as ItemsSource of the listview. Here I is my code and the result.
<?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="TestForms.TestListView" xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms">
<ContentPage.Content>
<ListView x:Name="uiList">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" BackgroundColor="Silver">
<Label TextColor="Green" Text="Pre Xamarin" />
<Label BackgroundColor="Aqua" TextColor="Red" Text="{Binding Message}" />
<Label TextColor="Green" Text="Hello Xamarin" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
public partial class TestListView : ContentPage
{
public TestListView ()
{
InitializeComponent ();
ObservableCollection<DataItem> employeeList = new ObservableCollection<DataItem>();
for(int i=0;i<100;i++)
employeeList.Add(new DataItem(i));
uiList.ItemsSource = employeeList;
//Mr. Mono will be added to the ListView because it uses an ObservableCollection
}
}
public class DataItem
{
public int Number { get; set; }
public string Message { get; set; }
public DataItem (int i)
{
this.Number = i + 1;
this.Message = string.Format ("Data Item Hello {0}", i + 1);
}
}

Related

MAUI not finding property in VIew Model List

I have a simple view model where one property contains a model, and another property contains a list of models.
I am able to bind the "Test" model's properties without an issue but I'm not able to get the XAML to recognize that "ListModel" contains a list with its own properties. I have looked at several examples for how to set up the view model and initialize the list correctly before binding it to the view, and while the XAML understands that "ListModel" is a property, I can't get it to recognize that it's a list, and thus it will not compile so that I can at least see if it isn't the intellisense that could be failing for whatever reason.
This is the view model in question with the list named "ListModel"
public class TestViewModel
{
public TestModel Test { get; } = new TestModel();
public List<TestListModel> ListModel { get; set; }
public TestViewModel()
{
Initialize();
}
public void Initialize()
{
ListModel = new List<TestListModel>();
ListModel.Add(new TestListModel
{
ListProp1 = "First",
ListProp2 = "Second",
ListProp3 = "Third"
});
}
}
This is the Model that is being put into a list. It seems like the view isn't seeing these properties.
public class TestListModel
{
public string ListProp1 { get; set; }
public string ListProp2 { get; set; }
public string ListProp3 { get; set; }
}
This is my XAML Currently.
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MauiApp1.MainPage"
xmlns:local="clr-namespace:ViewModels"
x:DataType="local:TestViewModel"
>
<ScrollView>
<VerticalStackLayout
Spacing="25"
Padding="30,0"
VerticalOptions="Center">
<!--This works-->
<Entry Text="{Binding Test.Property1}"/>
<Entry Text="{Binding Test.Property2}"/>
<Entry Text="{Binding Test.Property3}"/>
<!--This does not work-->
<ListView
ItemsSource="{Binding ListModel}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding ListProp1}"/>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</VerticalStackLayout>
</ScrollView>
</ContentPage>
For anyone stumbling in on this: Jason in the comments has answered the question. The fix was simply to remove x:DataType from the top of the XAML, though I did not remove the "xmlns:local" from it.
What I had was a View Model that had more than just one model in it, which seemed to upset the intellisense when removing x:DataType. Removing it originally prevented the application from compiling because it couldn't find the properties I had in the XAML. Once I cleaned and rebuilt the solution it compiled and worked without a hitch.
The view and the viewmodel are often connected through data bindings defined in XAML. The BindingContext for the view is usually an instance of the viewmodel.So I think you forget to connect these two elements with BindingContext.
Code behind View:
public partial class MainPage : ContentPage
{
TestViewModel tv = new TestViewModel();
public MainPage()
{
InitializeComponent();
BindingContext = tv;
}
}
Code in Xaml:
<ScrollView>
<VerticalStackLayout
Spacing="25"
Padding="30,0"
VerticalOptions="Center">
<!--This works-->
<Entry Text="{Binding Test.Property1}"/>
<Entry Text="{Binding Test.Property2}"/>
<Entry Text="{Binding Test.Property3}"/>
<!--This works too-->
<ListView
HasUnevenRows="True"
ItemsSource="{Binding ListModel}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<VerticalStackLayout>
<Label Text="{Binding ListProp1}"/>
<Label Text="{Binding ListProp2}"/>
<Label Text="{Binding ListProp3}"/>
</VerticalStackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</VerticalStackLayout>
</ScrollView>
Outcome:
Reference link.
I was able to fix the error while also keeping the compiled bindings in place by making the ItemSource bind to a List with a private backing field. It seems like the compiler is unable to resolve the list correctly without the private property being there. After i added listModel it compiled. So the issue seems to be that the setter is missing.
private List<TestListModel> listModel;
public List<TestListModel> ListModel { get => listModel; set => listModel = value; }

Why won't my Xamarin.Forms ListView Data Binding reflect in other Views?

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.

Filtering SQLite database with SearchBar

i´ve been able to generate and show a SQList database and now i wanna filter it with a Search Bar, i´ve been reading many tutorials, but i´m not able to make it work...
I have an SQLite Model with this code:
using SQLite;
namespace AppListo
{
public class Employee
{
[PrimaryKey, AutoIncrement]
public long EmpId
{ get; set; }
[NotNull]
public string EmpName
{ get; set; }
}
}
...and i retrieve a datalist from the database with:
public List<Employee> GetAllEmployees()
{
return dbConn.Query<Employee>("Select * From [Employee]");
}
...and i show the list in a ContentPage with this xaml code:
<ListView x:Name="lstData" HasUnevenRows="false" Header="Header Value" Footer="Footer" ItemSelected="OnSelection" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal" Padding="5,5,5,5">
<Label Text="{Binding EmpName}" FontSize="Medium" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
...and this code behind:
var vList = App.DAUtil.GetAllEmployees();
lstData.ItemsSource = vList;
...but i don´t know how to filter this list with a searchbar.
Could you help me to achieve it?
Thanks in advance
use LINQ
var vList = App.DAUtil.GetAllEmployees();
lstData.ItemsSource = vList.Where(e => e.Name.Contains(mySearchString));
Create a property on your View Model like
public ObservableCollection<Employee> Employees { get; set; }
<ListView x:Name="lstData" HasUnevenRows="false" ItemsSource="{Binding Employees}" Header="Header Value" Footer="Footer" ItemSelected="OnSelection" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal" Padding="5,5,5,5">
<Label Text="{Binding EmpName}" FontSize="Medium" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Then create a method to bind your dataset values to your list view
private async Task BindEmployees()
{
List<Employee> employees = await GetAllEmployees();
Employees = new ObservableCollection<Employee>(
vList.Where(e => e.Name.Contains(mySearchString)).
OrderBy(x => x.EmpName).ToList());
await RaisePropertyChanged("Employees");
}
Call this method on page load or inside your View Model's constructor
Task.Run(async() => {
await BindEmployees();
});
Also, create a Handler method which will be called every time your enter any thing on search bar
and again call BindEmployees method inside that handler function
FilterEmployee_Handler(TextChangedEventArgs args) {
await BindEmployees(),
}

Retrieving ListView selected item value in Xamarin.Forms

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
}

Detecting tap on label, inside ViewCell, inside ListView

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. :)

Categories

Resources