7
votes

I'm trying to handle something similar (from UI perspective), to: enter image description here

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.

2
create a custom event in MyItem, fire it when the deletion label is clicked (using TapGestureRecognizer). subscribe to that event from anywhere, or in your case, from the ViewModel and handle those clicks in any way you want.nicks
Ok, that could a workaround... however let imagine, that my app, would contain multiple pages with different lists... I would love to avoid sending and subscribing at such kind of events :/ Any other idea? ThanksNamek
you mean you don't want to subscribe/unsubscribe to every item individually?nicks
No no, I mean - I don't want to subscribe/unsubscribe for each and every page, where I would like to use ListView, with such custom command, that should be invoked at TapGestureRecognizer.Namek
Do you need to initialise the command in the constructor? i.e. out of the constructor: public RelayCommand<object> OnClickableLabel { get; } and then in the constructor OnClickableLabel = new RelayCommand etcRichard Pike

2 Answers

6
votes

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!

0
votes

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