Search code examples
c#listviewxamarin.formsdata-binding

How to bind an ItemSelected Event from a Re-Usable Listview to a ViewModal Command?


I have a UserList that I want to Reuse on multiple pages in my Xamarin Application, but on each of the different pages a ItemSelected event should do something different.

I know about Bindable properties. I use them to bind a list from my Viewmodel to the Reusable Component. But I don't know how to do this with events

Let me show you some code!

This is the XAML of my Reusable ListView. It contains a list of Users

<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             x:Class="KineAppForms.Views.ReusableViews.UserTable"
             x:Name="this">
    <ContentView.Content>


                                    <ListView ItemsSource="{Binding Source={x:Reference this},Path=Users, Mode=TwoWay}">
                                      <ListView.ItemTemplate>
                                          <DataTemplate>
                                            <TextCell Text="{Binding Name.GivenName}"/>
                                          </DataTemplate>
                                        </ListView.ItemTemplate>
                                    </ListView>


    </ContentView.Content>
</ContentView>

The .cs file where I do the binding looks as follows

namespace KineAppForms.Views.ReusableViews
{
    public partial class UserTable : ContentView
    {

        public ObservableCollection<Patient> Users
        {
            get
            {
                return (ObservableCollection<Patient>)GetValue(UsersProperty);
            }
            set
            {
                SetValue(UsersProperty, value);
            }

        }


        public static readonly BindableProperty UsersProperty =
                  BindableProperty.Create("Users", typeof(ObservableCollection<Patient>), typeof(UserTable), null,
                                     BindingMode.Default, null, OnItemsSourceChanged);


        static void OnItemsSourceChanged(BindableObject bindable, object oldvalue, object newvalue)
        {
            System.Diagnostics.Debug.WriteLine("source changed");
        }

        public UserTable()
        {
            InitializeComponent();
        }
    }
}

Now the way how I Use my Reusable Component

 <views:UserTable  Users="{Binding PatientList ,Mode=TwoWay}" />

The Binding PatientList comes from a ViewModel.

No to conclude the question: How Can I Bind an ItemSelected Event To a Command in a ViewModel.

Say I have 2 pages. 1 page with Patients and 1 page with Doctors. They both use the Same table but the Patients Table should go to a Detailed page of Patients ( Link it to goToPatientDetailCommand in PatientListViewModel) and the doctors table should go to a Detailed Page of a Doctor ( Link it to goToPatientDetailCommand in DoctorListViewModel)

It should be something like this

 <views:UserTable  Users="{Binding PatientList ,Mode=TwoWay}" OnItemSelected="{Binding GoToPatientDetailed, Mode=TwoWay }/>

or

<views:UserTable  Users="{Binding DoctorList ,Mode=TwoWay}" OnItemSelected="{Binding GoToDoctorDetailed, Mode=TwoWay }/>

Thank you!


Solution

  • There are several approaches in here.

    1. You can add ItemSelectedCommand to UserTable class
        public partial class UserTable : ContentView
        {
    
            public ObservableCollection<Patient> Users
            {
                get
                {
                    return (ObservableCollection<Patient>)GetValue(UsersProperty);
                }
                set
                {
                    SetValue(UsersProperty, value);
                }
            }
    
    
            public static readonly BindableProperty UsersProperty =
                BindableProperty.Create("Users", typeof(ObservableCollection<Patient>), typeof(UserTable), null,
                    BindingMode.Default, null, OnItemsSourceChanged);
    
            public static BindableProperty ItemSelectedCommandProperty = BindableProperty.Create(
                propertyName: nameof(ItemSelectedCommand),
                returnType: typeof(ICommand),
                declaringType: typeof(UserTable),
                defaultValue: null);
    
            public ICommand ItemSelectedCommand
            {
                get { return (ICommand)GetValue(ItemSelectedCommandProperty); }
                set { SetValue(ItemSelectedCommandProperty, value); }
            }
    
            static void OnItemsSourceChanged(BindableObject bindable, object oldvalue, object newvalue)
            {
                System.Diagnostics.Debug.WriteLine("source changed");
            }
    
            public UserTable()
            {
                InitializeComponent();
            }
        }
    

    Then in Xaml you can either use EventToCommandBehaviour by adding

    <ListView.Behaviors>
        <behaviors:EventToCommandBehavior EventName="ItemSelected" Command="{Binding Source={x:Reference this},Path=ItemSelectedCommand}"/>
    </ListView.Behaviors>
    

    or you can create your own CustomListView (that inherites from ListView) that has ItemSelected in it like in this example

    1. Second approach is that instead of creating a control with ListView you can create a data template. Here you have documentation on how to do it.

    If you ask for my opinion, I would say go with second approach - later you can reuse that View in other places.