Forum Xamarin.Forms

Detecting tap on label, inside ViewCell, inside ListView.

NamyslawSzymaniukNamyslawSzymaniuk USMember ✭✭✭✭
edited August 2016 in Xamarin.Forms

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, which is inside given ViewCell element - in example delete given object or smth else

I've already use TapGestureRecognizer for Label in different views, but right now, I have no idea, what I'm doing wrong...

I've tried to use solution proposed here:
https://forums.xamarin.com/discussion/19296/detecting-tap-on-different-controls-inside-a-listview-cell
and
http://forums.xamarin.com/discussion/63236/button-click-inside-a-list-view-in-xaml
but without success.

My XAML code looks like:

<ListView HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" ItemsSource="{Binding MyItemsCollection}" SelectedItem="{Binding SelectedItem}">
      <ListView.ItemTemplate>
        <DataTemplate>
          <ViewCell>
            <StackLayout Orientation="Horizontal">
              <!-- Element Label -->
              <Label Text="{Binding Name}" VerticalOptions="CenterAndExpand" HorizontalOptions="StartAndExpand" />

              <!-- Assessment Menu 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>

My C# ViewModel code looks like:

    private RelayCommand _OnClickableLabel;

        public RelayCommand OnClickableLabel
        {
            get { return _OnClickableLabel?? (_OnClickableLabel= new RelayCommand(() => Test())); }
        }

        private void Test()
        {
            Debug.WriteLine("This should work...");
        }

For me, something it wrong with CommandParameter, but what?
Thanks.

Best Answer

Answers

  • NamyslawSzymaniukNamyslawSzymaniuk USMember ✭✭✭✭

    Anyone? Any idea?

  • JulienRosenJulienRosen CAMember ✭✭✭✭
    edited August 2016

    your test method should be accepting an object parameter for the CommandParameter. You also didn't state what exactly is wrong.

  • NamyslawSzymaniukNamyslawSzymaniuk USMember ✭✭✭✭

    I'm able to detect when I'm tapping the whole item (cell), via SelectedItem="{Binding SelectedItem}" - so inside my ViewModel, my SelectedItem I got object, against which, tap has been performed.
    What is also working "fine" is - when I'm tapping the Clickable Label, there is no update at binded SelectedItem (which is fine, because I wanted to invoke something else - OnClickableLabel ) so it smells that I'm close to the issue...

    However, my OnClickableLabel command at ViewModel is not being invoked.

    I'm sure, how could I know, against which cell, my label has been tapped (that would be most probably my next question :smile: ), but I'm not even able to invoke anything, on my label tapped.
    Any working example?

    Thanks a lot!

  • FriedSlothFriedSloth DEMember ✭✭

    Well first of all check if @JulienRosen suggestion helps ...

    `private RelayCommand _OnClickableLabel;

        public RelayCommand<object> OnClickableLabel
        {
            get { return _OnClickableLabel?? (_OnClickableLabel= new RelayCommand<object>((object) => Test())); }
        }
    
        private void Test()
        {
            Debug.WriteLine("This should work...");
        }`
    

    The object is the bindingContext of the Cell, that answers your next question which cell has been tapped aswell.

  • NamyslawSzymaniukNamyslawSzymaniuk USMember ✭✭✭✭

    @FriedSloth thanks a lot for help.
    Right now I understand, what was the suggestion of @JulienRosen.

    Your proposed code, should looks most probably like:

            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...");
            }
    

    right?

    However I still cannot see **This should work... ** in logs, nor got debbuger inside **Test ** method :neutral: .

    • clicking at any other place of ViewCell (outside my Clickable Label) causes updating SelectedItem in ViewModel (as expected)
    • clicking at Clickable Label didn't cause updating SelectedItem in ViewModel (also as expected), but still, I cannot invoke any code at tapping this Clickable Label :neutral: .

    HELP!
    Thanks a lot.

  • JulienRosenJulienRosen CAMember ✭✭✭✭

    the command for the clickable label needs to be inside the data context of the list box item. aka, your command method needs to be inside an item's viewmodel. it's not clear from your examples where that method lives currently.

  • NamyslawSzymaniukNamyslawSzymaniuk USMember ✭✭✭✭

    @JulienRosen my command and Test method, are inside whole page (let say TestView) view model.
    So I got:

    • View XML - TextView.xaml

      <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">
                        <!-- Element Label -->
                        <Label Text="{Binding Name}" VerticalOptions="CenterAndExpand" HorizontalOptions="StartAndExpand" />
                        <!-- Assessment Menu 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>
      
    • View C# (not specific code there, except binding **BindingContext* to page ViewModel) - TextView.xaml.cs

      public partial class TestView : ContentPage
      {
          public TestView()
          {
              InitializeComponent();
              BindingContext = ServiceLocator.Current.GetInstance<TestViewModel>();
          }
      }
      
    • ViewModel - TestViewModel.cs

      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...");
          }
      }
      
    • MyItem data model - MyItem.cs

      public class MyItem
      {
          public long ID { get; set; }
          public string Name { get; set; }
      }
      

    Do you mean, that Command should be inside MyItem data model? I didn't get this quiet well...
    Thanks for your help!

  • JulienRosenJulienRosen CAMember ✭✭✭✭

    Yes, your command needs to be inside the MyItem view model.

    When you are inside a DataTemplate, your bindings will work against the context of the current item.

  • NamyslawSzymaniukNamyslawSzymaniuk USMember ✭✭✭✭

    Ok, got it.
    Any working sample/example/link/github code?
    Thanks a lot mate :smiley: !

  • NamyslawSzymaniukNamyslawSzymaniuk USMember ✭✭✭✭

    Ok, I've just extended MyItem model:

    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...");
        }
    }
    

    And it's at least working. However, I'm looking for some solution, to invoke command inside my page ViewModel - TestViewModel, as I don't think it's good to have command (and it's logic) in data model...
    I would not even able to manipulate List - let's imagine, that I would like to have delete action, so my intention is to manipulate objects collection in my ViewModel.
    Any other solution/idea?

    Thanks a lot!

  • FriedSlothFriedSloth DEMember ✭✭
    edited August 2016

    Hey,
    the trick is to tell your binding the location of the viewmodel.
    In C# that would look like this:
    yourControl.SetBinding(TapGestureRecognizer.CommandParameterProperty, new Binding(".", source: BindingContext)); yourControl.SetBinding(TapGestureRecognizer.CommandProperty, new Binding("CommandName", source: App.Locator.YourViewModel));

    Its a bit different in XAML ofcourse, but i guess you can translate it yourself or just write it in C# in the code behind.

  • NamyslawSzymaniukNamyslawSzymaniuk USMember ✭✭✭✭

    @FriedSloth thanks, but actually I've already resolved it inside XAML, and the solutions looks similarly to your C# propose - https://forums.xamarin.com/discussion/comment/216288/#Comment_216288
    Anywat thanks a lot :wink:

  • FriedSlothFriedSloth DEMember ✭✭

    Oh, didnt see the answer, great that it works :)

  • Esaavedra89Esaavedra89 USMember ✭✭

    @NamyslawSzymaniuk said:
    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">
                      <!-- Element Label -->
                      <Label Text="{Binding Name}" VerticalOptions="CenterAndExpand" HorizontalOptions="StartAndExpand" />
                      <!-- Assessment Menu 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 :smile: .

    If someone know "better" solution (better from XAML code point of view), I would like to see it :wink: .

    Thanks a lot everyone!

    @NamyslawSzymaniuk great solution here.

  • DevMaranDevMaran Member ✭✭

    Hi @NamyslawSzymaniuk I have tried your solution but
    I am getting an error in the block of code says:

    It would be great if you share the complete solution.

Sign In or Register to comment.