He buscado mucho sobre este tema y, francamente, estoy apilado en ese. Tengo una aplicación de chat. En esta aplicación hay una vista donde hay mensajes tanto míos como de otros miembros del chat. Técnicamente hablando, es un ListView con ItemTemplate con una clase Binded (DataTemplateSelector) que devuelve ViewCells basándose en la regla (ya sea que el mensaje a mostrar sea MÍO u OTROS)

El mensaje (entrante o saliente) está en archivos separados.

Actualmente, TapGestureRecognizer no funciona y el comando ChooseNameToMentionCommand no se activa

Hay muchas preguntas "similares" en las que TapGestureRecognizer no funciona en ListView como esta:

TapGestureRecognizer no funciona dentro de ListView

La respuesta allí (y en cualquier otro tema relacionado) para el comando que no funciona es:

  • use Source={x:Reference MessagesListView} en su enlace de comando

Pero cuando uso esta sugerencia termino con:

Xamarin.Forms.Xaml.XamlParseException: 'Position 30:21. Can not find the object referenced by MessagesListView'

¿Cómo puedo usar esto en mi caso (con ViewCell definido en un archivo separado)? Nota importante: estoy usando el enfoque MVVM y no quiero hacer nada en el código subyacente de ViewCell (entonces incluso podría usar el evento Tapped. Lo he probado . Por supuesto que este enfoque funciona :))

Aquí está mi código:

MainViewModel.cs

public class MainViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public ObservableRangeCollection<MessageModel> Messages { get; set; }

    public Command ChooseNameToMentionCommand { get; set; }
    
    public string NewMessage {get; set;}

    public MainViewModel()
    {
        Messages = new ObservableRangeCollection<MessageModel>();
        ChooseNameToMentionCommand = new Command<string>(async (t) => await ChooseNameToMention(t));
    }

    private Task ChooseNameToMention(string name)
    {
        this.NewMessage += $"@{name}";
    }

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

MainPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
    x:Class="Chat.ClientLibrary.MainPage"
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:converters="clr-namespace:Chat.ClientLibrary.Converters"
    xmlns:local="clr-namespace:Chat.ClientLibrary.CustomCells"
    xmlns:partials="clr-namespace:Chat.ClientLibrary.Views.Partials"
    BackgroundColor="White"
    x:Name="MainChatPage">
    
<ContentPage.Resources>
    <ResourceDictionary>            
        <local:MyDataTemplateSelector x:Key="MessageTemplateSelector"/>
    </ResourceDictionary>
</ContentPage.Resources>

    /* REMOVED UNNECESSARY code */
    <Grid RowSpacing="0" ColumnSpacing="0">
    <Grid.RowDefinitions>
        <RowDefinition Height="50" />
        <RowDefinition Height="*" />
        <RowDefinition Height="1" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
        <ListView   
            Grid.Row="1"
            FlowDirection="RightToLeft"
            Rotation="180"    
            x:Name="MessagesListView" 
            ItemTemplate="{StaticResource MessageTemplateSelector}" 
            ItemsSource="{Binding Messages}" 
            HasUnevenRows="True" 
            ItemSelected="MyListView_OnItemSelected" 
            ItemTapped="MyListView_OnItemTapped" 
            SeparatorVisibility="None" />
        /* REMOVED UNNECESSARY code */
    </Grid>
</ContentPage>

MyDataTemplateSelector.cs

class MyDataTemplateSelector : DataTemplateSelector
{
    public MyDataTemplateSelector()
    {
        // Retain instances!
        this.incomingDataTemplate = new DataTemplate(typeof(IncomingViewCell));
        this.outgoingDataTemplate = new DataTemplate(typeof(OutgoingViewCell));
    }

    protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
    {
        var messageVm = item as MessageModel;
        if (messageVm == null)
            return null;
        return messageVm.IsOwnMessage ? this.incomingDataTemplate : this.outgoingDataTemplate;
    }

    private readonly DataTemplate incomingDataTemplate;
    private readonly DataTemplate outgoingDataTemplate;
}

IncomingViewCell.xaml (no publicaré OutgoingViewCell - es casi lo mismo;) Diferentes colores)

<?xml version="1.0" encoding="utf-8" ?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Chat.ClientLibrary.Views.CustomCells.IncomingViewCell"
             xmlns:forms9patch="clr-namespace:Forms9Patch;assembly=Forms9Patch">
    <Grid ColumnSpacing="2"
          Padding="5"
          FlowDirection="LeftToRight"
          Rotation="180"
          >
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="40"></ColumnDefinition>
            <ColumnDefinition Width="*"></ColumnDefinition>
            <ColumnDefinition Width="40"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
        </Grid.RowDefinitions>
        <Label Grid.Row="0"  Grid.Column="1" HorizontalTextAlignment="Start"  Text="{Binding UserName}" TextColor="Blue">
            <Label.GestureRecognizers>
                <TapGestureRecognizer 
                    Command="{Binding Path= BindingContext.ChooseNameToMentionCommand, Source={x:Reference MessagesListView}}" CommandParameter="{Binding UserName}" />
            </Label.GestureRecognizers>
        </Label>
        /* REMOVED UNNECESSARY code */
    </Grid>
</ViewCell>

[EDITAR 12:12 01.10.2020] Olvidé poner aquí MainPage.cs, así que aquí está:

MainPage.cs

public partial class MainPage : ContentPage
{
    MainViewModel vm;

    public MainPage()
    {
        this.BindingContext = vm = new MainViewModel();

        InitializeComponent();
    }

    void MyListView_OnItemSelected(object sender, SelectedItemChangedEventArgs e)
    {
        MessagesListView.SelectedItem = null;
    }

    void MyListView_OnItemTapped(object sender, ItemTappedEventArgs e)
    {
        MessagesListView.SelectedItem = null;

    }
}

Hay para el evento ListOnItemTapped agregado (lo olvidé, porque fue tomado de algún ejemplo de chat. Pero no creo que rompa nada. Cuando agregué OnTapped para Label directamente, funcionó.

0
Piotr 1 oct. 2020 a las 11:44

2 respuestas

La mejor respuesta

Debido a la falta del código, hago una muestra similar para su referencia para usar TapGestureRecognizer en ListView ViewCell.

Xaml:

<ContentPage
x:Class="Selector.HomePage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Selector"
x:Name="MainPage">
<ContentPage.Resources>
    <ResourceDictionary>
        <DataTemplate x:Key="validPersonTemplate">
            <ViewCell>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="0.4*" />
                        <ColumnDefinition Width="0.3*" />
                        <ColumnDefinition Width="0.3*" />
                    </Grid.ColumnDefinitions>
                    <Label
                        FontAttributes="Bold"
                        Text="{Binding Name}"
                        TextColor="Green">
                        <Label.GestureRecognizers>
                            <TapGestureRecognizer Command="{Binding Path=BindingContext.TapCommand, Source={x:Reference MainPage}}" CommandParameter="false" />
                        </Label.GestureRecognizers>
                    </Label>
                    <Label
                        Grid.Column="1"
                        Text="{Binding DateOfBirth, StringFormat='{0:d}'}"
                        TextColor="Green" />
                    <Label
                        Grid.Column="2"
                        HorizontalTextAlignment="End"
                        Text="{Binding Location}"
                        TextColor="Green" />
                </Grid>
            </ViewCell>
        </DataTemplate>
        <DataTemplate x:Key="invalidPersonTemplate">
            <ViewCell>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="0.4*" />
                        <ColumnDefinition Width="0.3*" />
                        <ColumnDefinition Width="0.3*" />
                    </Grid.ColumnDefinitions>
                    <Label
                        FontAttributes="Bold"
                        Text="{Binding Name}"
                        TextColor="Red">
                        <Label.GestureRecognizers>
                            <TapGestureRecognizer Command="{Binding Path=BindingContext.TapCommand, Source={x:Reference MainPage}}" CommandParameter="false" />
                        </Label.GestureRecognizers>
                    </Label>
                    <Label
                        Grid.Column="1"
                        Text="{Binding DateOfBirth, StringFormat='{0:d}'}"
                        TextColor="Red" />
                    <Label
                        Grid.Column="2"
                        HorizontalTextAlignment="End"
                        Text="{Binding Location}"
                        TextColor="Red" />
                </Grid>
            </ViewCell>
        </DataTemplate>
        <local:PersonDataTemplateSelector
            x:Key="personDataTemplateSelector"
            InvalidTemplate="{StaticResource invalidPersonTemplate}"
            ValidTemplate="{StaticResource validPersonTemplate}" />
    </ResourceDictionary>
</ContentPage.Resources>
<StackLayout Margin="20">
    <Label
        FontAttributes="Bold"
        HorizontalOptions="Center"
        Text="ListView with a DataTemplateSelector" />
    <ListView
        x:Name="listView"
        Margin="0,20,0,0"
        ItemTemplate="{StaticResource personDataTemplateSelector}" />
</StackLayout>
</ContentPage>

Persona DataTemplateSelector.cs:

public class PersonDataTemplateSelector : DataTemplateSelector
{
    public DataTemplate ValidTemplate { get; set; }

    public DataTemplate InvalidTemplate { get; set; }

    protected override DataTemplate OnSelectTemplate (object item, BindableObject container)
    {
        return ((Person)item).DateOfBirth.Year >= 1980 ? ValidTemplate : InvalidTemplate;
    }
}

Person.cs:

 public class Person
{
    public string Name { get; set; }
    public DateTime DateOfBirth { get; set; }
    public string Location { get; set; }
}

Código detrás:

 public Command TapCommand
    {
        get
        {
            return new Command(val =>
            {
                DisplayAlert("Alert", val.ToString(), "OK");
            });
        }
        
    }
    public HomePage()
    {
        InitializeComponent();

        var people = new List<Person>
        {
            new Person { Name = "Kath", DateOfBirth = new DateTime(1985, 11, 20), Location = "France" },
            new Person { Name = "Steve", DateOfBirth = new DateTime(1975, 1, 15), Location = "USA" },
            new Person { Name = "Lucas", DateOfBirth = new DateTime(1988, 2, 5), Location = "Germany" },
            new Person { Name = "John", DateOfBirth = new DateTime(1976, 2, 20), Location = "USA" },
            new Person { Name = "Tariq", DateOfBirth = new DateTime(1987, 1, 10), Location = "UK" },
            new Person { Name = "Jane", DateOfBirth = new DateTime(1982, 8, 30), Location = "USA" },
            new Person { Name = "Tom", DateOfBirth = new DateTime(1977, 3, 10), Location = "UK" }
        };

        listView.ItemsSource = people;
        this.BindingContext = this;
    }

Captura de pantalla:

enter image description here

Actualizado:

Utilice diccionarios de recursos para crear archivos separados. Cambio la ruta de enlace para el comando en estos dos archivos.

MyResourceDictionary.xaml:

<?xml version="1.0" encoding="UTF-8" ?>
<ResourceDictionary
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Selector">

<DataTemplate x:Key="validPersonTemplate">
    <ViewCell x:Name="MyCell">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="0.4*" />
                <ColumnDefinition Width="0.3*" />
                <ColumnDefinition Width="0.3*" />
            </Grid.ColumnDefinitions>
            <Label
                FontAttributes="Bold"
                Text="{Binding Name}"
                TextColor="Green">
                <Label.GestureRecognizers>
                    <TapGestureRecognizer Command="{Binding Path=Parent.BindingContext.TapCommand, Source={x:Reference MyCell}}" CommandParameter="false" />
                </Label.GestureRecognizers>
            </Label>
            <Label
                Grid.Column="1"
                Text="{Binding DateOfBirth, StringFormat='{0:d}'}"
                TextColor="Green" />
            <Label
                Grid.Column="2"
                HorizontalTextAlignment="End"
                Text="{Binding Location}"
                TextColor="Green" />
        </Grid>
    </ViewCell>
</DataTemplate>

</ResourceDictionary>

MyResourceDictionary2.xaml:

<?xml version="1.0" encoding="UTF-8" ?>
<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">

<DataTemplate x:Key="invalidPersonTemplate">
    <ViewCell x:Name="MyCell2">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="0.4*" />
                <ColumnDefinition Width="0.3*" />
                <ColumnDefinition Width="0.3*" />
            </Grid.ColumnDefinitions>
            <Label
                FontAttributes="Bold"
                Text="{Binding Name}"
                TextColor="Red">
                <Label.GestureRecognizers>
                    <TapGestureRecognizer Command="{Binding Path=Parent.BindingContext.TapCommand, Source={x:Reference MyCell2}}" CommandParameter="false" />
                </Label.GestureRecognizers>
            </Label>
            <Label
                Grid.Column="1"
                Text="{Binding DateOfBirth, StringFormat='{0:d}'}"
                TextColor="Red" />
            <Label
                Grid.Column="2"
                HorizontalTextAlignment="End"
                Text="{Binding Location}"
                TextColor="Red" />
        </Grid>
    </ViewCell>
</DataTemplate>
</ResourceDictionary>

Cambiar la página de contenido:

<?xml version="1.0" encoding="UTF-8" ?>
<ContentPage
x:Class="Selector.HomePage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Selector">
<ContentPage.Resources>
    <ResourceDictionary> 
        <ResourceDictionary Source="MyResourceDictionary.xaml" />
        <ResourceDictionary Source="MyResourceDictionary2.xaml" />

        <local:PersonDataTemplateSelector
            x:Key="personDataTemplateSelector"
            InvalidTemplate="{StaticResource invalidPersonTemplate}"
            ValidTemplate="{StaticResource validPersonTemplate}" />

    </ResourceDictionary>
</ContentPage.Resources>
<StackLayout Margin="20">
    <Label
        FontAttributes="Bold"
        HorizontalOptions="Center"
        Text="ListView with a DataTemplateSelector" />
    <ListView
        x:Name="listView"
        Margin="0,20,0,0"
        ItemTemplate="{StaticResource personDataTemplateSelector}" />
</StackLayout>
</ContentPage>

Sin cambios con el selector y el modelo de vista, compruébalo. Si aún tiene preguntas sobre este problema, no dude en hacérmelo saber.

1
Wendy Zang - MSFT 7 oct. 2020 a las 08:17

Agregue una clase ViewModelLocator, yo uso MVVM Light

public class ViewModelLocator
{
   public ViewModelLocator()
   {
      ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);

      SimpleIoc.Default.Register<MainViewModel>();
   }
   public MainViewModel MainVM
   {
      get { return ServiceLocator.Current.GetInstance<MainViewModel>(); }
   }
}

Entonces puede usar BindingContext sin usar la referencia de la página

BindingContext="{Binding Path=MainVM, Source={StaticResource VMLocator}}"

Código App.Xaml

xmlns:vm="clr-namespace:xxx.xx.ViewModels"

<Application.Resources>
<vm:ViewModelLocator x:Key="VMLocator" />
</Application.Resources>

Uso

Opción 1:

Desea vincular una etiqueta en una vista de lista, pero el contexto de vinculación de la vista de lista apunta a una colección, use esto.

<Label.GestureRecognizers>
       <TapGestureRecognizer Command="{Binding YourVM.YourCommand,Source={StaticResource VMLocator}}" CommandParameter="{Binding UserName}" />
</Label.GestureRecognizers>

Opcion 2 :

Quieres vincularlo al modelo de vista de la página actual (con la referencia de la página)

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="xxxx.xx.xx.App"
             x:Name="MyViewName">

<Label Text="My Text" IsVisible="{Binding Path=BindingContext.IsLoading,Source={x:Reference MyViewName},Mode=TwoWay}"/>

Opcion 2 :

0
Shubham Tyagi 6 oct. 2020 a las 09:19