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

问题描述

关于这个问题,我进行了很多搜索,坦率地说,我对此非常满意。我有一个聊天应用程序。在此应用上,有一个视图,其中有来自我和其他聊天成员的消息。从技术上讲,它是一个具有ItemTemplate的ListView,具有一个Binded(DataTemplateSelector)类,该类根据规则返回ViewCells(显示的消息是mine还是OTHERS)

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

当前,TapGestureRecognizer无法正常运行,并且命令ChooseNametoMentionCommand无法启动

在很多类似的问题中,TapGestureRecognizer不能像这样在ListView上工作:

TapGestureRecognizer not working inside ListView

那里(以及任何其他相关主题)对于Command不起作用的答案是:

  • 在命令绑定上使用Source={x:Reference MessagesListView}

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

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

我该如何在我的情况下使用它(在单独的文件中定义ViewCell)重要说明-我正在使用MVVM方法,并且不想在ViewCell的代码背后做任何事情(那么我什至可以使用Tapped事件。我ve进行了测试。这种方法当然有效:))

这是我的代码

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

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

[EDIT 12:12 01.10.2020] 我忘了把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事件(我忘了它-因为它是从某个聊天示例中获取的。但是我不认为它会破坏任何内容。当我直接为Label添加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,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"
        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,new Person { Name = "Tariq",DateOfBirth = new DateTime(1987,10),Location = "UK" },new Person { Name = "Jane",DateOfBirth = new DateTime(1982,8,30),new Person { Name = "Tom",DateOfBirth = new DateTime(1977,3,Location = "UK" }
        };

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

截屏:

enter image description here

已更新:

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

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>

更改ContentPage:

<?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,0"
        ItemTemplate="{StaticResource personDataTemplateSelector}" />
</StackLayout>
</ContentPage>

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

,

添加一个类ViewModelLocator,我使用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: