Xamarin.Forms MVVM TapGestureRecognizer 到 ListView 的 ViewCell 中的标签(在部分文件中)
Posted
技术标签:
【中文标题】Xamarin.Forms MVVM TapGestureRecognizer 到 ListView 的 ViewCell 中的标签(在部分文件中)【英文标题】:Xamarin.Forms MVVM TapGestureRecognizer to a Label in a ViewCell of ListView (in partial files) 【发布时间】:2021-01-16 23:14:18 【问题描述】:我已经对这个问题进行了很多搜索,坦率地说,我对此很感兴趣。我有一个聊天应用程序。在这个应用程序上有一个视图,其中有来自我和其他聊天成员的消息。从技术上讲,它是一个带有 ItemTemplate 的 ListView,它有一个 Binded 类(DataTemplateSelector),它根据规则返回 ViewCells(无论要显示的消息是 MINE 还是 OTHERS)
消息(入站或出站)位于单独的文件中。
目前,TapGestureRecognizer 不工作,ChooseNameToMentionCommand 命令未触发
有很多“类似”的问题是 TapGestureRecognizer 不能像这样在 ListView 上工作:
TapGestureRecognizer not working inside ListView
那里(以及任何其他相关主题)命令不起作用的答案是:
在您的命令绑定上使用Source=x:Reference MessagesListView
但是当我使用这个建议时,我的结尾是:
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
(我不会发布 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>
[编辑 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 时 - 它确实有效.
【问题讨论】:
发布示例 git repo 的链接以查看 它只在私人仓库中。我已经发布了代码中最重要的部分。 你可以做一个示例回购,我无法从这里得到问题 @ShubhamTyagi - 问题完全来自下面的响应(Wendy Zang - MSFT)在他发布的状态下 - 它正在工作。但是,如果您将这些模板复制粘贴到单独的文件中(例如,您将拥有数百个模板,并且您不想在主 xaml 中创建混乱),那么它不起作用 - 给出消息:“Xamarin.Forms.Xaml。 XamlParseException: '位置 30:21。找不到 MessagesListView 引用的对象'" 【参考方案1】:由于缺少代码,我制作了一个类似的示例供您参考,以便在 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>
选择器和视图模型没有变化,请检查。如果您对此问题仍有疑问,请随时告诉我。
【讨论】:
太棒了。现在复制粘贴那些validPersonTemplate
和invalidPersonTemplate
到单独的文件(作为部分),然后再试一次:)
有什么更新吗?你解决了这个问题吗?如果您对此仍有疑问,请随时告诉我。
我确实给你写过。只有在同一个 XAML 中有两个 DataTemplates 时,您的解决方案才有效。我确实给你写过。尝试删除它们并将它们移动到单独的文件中。我确实发布了我的 DataTemplateSelector 的样子。试试这个方法,你会发现你的 anwser 失败了。
我们可以通过Stand-alone resource dictionaries
做到这一点。并用绑定做一些改变。我会更新我的回复。
我已经更新了我的回复。请检查一下。如果您需要,我可以将我的项目上传到 github。【参考方案2】:
添加一个类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:
您想在列表视图中绑定标签,但列表视图的绑定上下文指向集合使用 this。
<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:
【讨论】:
我可以尝试这种方法,但对我来说,使用 ViewModelLocator 感觉是一种反模式。也许只是因为 ServiceLocator 是众所周知的反模式。但目前没有其他选择,所以我会尝试;) @Piotr VM 定位器不是反模式,它引入了单例检查此链接以获取更多详细信息***.com/questions/14130327/… 好的,但还有一个问题。我在哪里设置这个 BindingContext?部分:IncomingViewCell.xaml
在 ViewCell 元素中?如果是这样,它将不起作用,因为我使用的是从 Message 中获取的 Text="Binding UserName"。它是 ListView 绑定的一个元素: ItemsSource="Binding Messages" 如果我理解正确 - 在更改 ViewCell 的 BindingContext 后,它将在 MainViewModel 上查找 UserName 属性(并且没有这样的属性)
@Piotr 我已经更新了答案,如果它满足您的查询,请点赞/接受
而ServiceLocator
--> 应该在哪个nuget包/lib中?以上是关于Xamarin.Forms MVVM TapGestureRecognizer 到 ListView 的 ViewCell 中的标签(在部分文件中)的主要内容,如果未能解决你的问题,请参考以下文章
Xamarin.Forms:如何避免在 MVVM 绑定中硬编码字符串
从按钮命令 xamarin.forms MVVM 获取 ListView
关于xamarin.forms在MVVM情况下如何DisplayActionSheet