Xamarin.Forms MVVM TapGestureRecognizer 到 ListView 的 ViewCell 中的标签(在部分文件中)

Xamarin.Forms MVVM TapGestureRecognizer to a Label in a ViewCell of ListView (in partial files)

我已经搜索了很多关于这个问题的信息,坦率地说,我对这个问题很满意。我有一个聊天应用程序。在这个应用程序上有一个视图,其中有来自我和其他聊天成员的消息。从技术上讲,它是一个带有 ItemTemplate 的 ListView,具有 class 绑定 (DataTemplateSelector),其中 returns ViewCells 基于规则(无论要显示的消息是我的还是其他的)

消息(入站或出站)在单独的文件中。

目前,TapGestureRecognizer 没有工作,命令 ChooseNameToMentionCommand 没有触发

有很多“类似”问题,其中 TapGestureRecognizer 无法像这样在 ListView 上工作:

TapGestureRecognizer not working inside ListView

Command not working 那里(以及任何其他相关主题)的答案是:

但是当我使用这个建议时,我的结尾是:

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

我如何在我的案例中使用它(ViewCell 在单独的文件中定义)重要说明 - 我正在使用 MVVM 方法并且不想在 ViewCell 的代码隐藏中做任何事情(然后我什至可以使用 Tapped 事件。我已经测试过了。当然这种方法有效:) )

这是我的代码:

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(我不会postOutgoingViewCell - 几乎一样;)颜色不同)

<?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>

[编辑 12:12 2020 年 10 月 1 日] 我忘了放这里 MainPage.cs 所以这里是:

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;

    }
}

添加了 ListOnItemTapped 事件(我忘记了 - 因为它取自一些聊天示例。但我认为它不会破坏任何东西。当我直接为标签添加 OnTapped - 它成功了。

由于缺少代码,我做了一个类似的例子供大家参考,在ListView ViewCell中使用TapGestureRecognizer

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>

PersonDataTemplateSelector.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; }
}

代码隐藏:

 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;
    }

截图:

更新:

使用资源字典创建单独的文件。我更改了这两个文件中命令的绑定路径。

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>

更改内容页面:

<?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>

选择器和视图模型没有变化,请检查。如果您对此问题仍有疑问,请随时告诉我。

添加一个classViewModelLocator,我用的是MVVM Light

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

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

那么你可以在不使用页面引用的情况下使用 BindingContext

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

App.Xaml代码

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

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

用法

选项 1:

您想在列表视图中绑定一个标签,但列表视图的绑定上下文指向一个集合,请使用此方法。

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

选项 2:

您想将其绑定到当前页面的 Viewmodel(带有页面引用)

<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}"/>

选项 2: