Xamarin 如何将命令传递给模板,并使其在后端代码中执行并传递参数?

Xamarin 如何将命令传递给模板,并使其在后端代码中执行并传递参数?,xamarin,xamarin.forms,Xamarin,Xamarin.forms,我有这个模板: <?xml version="1.0" encoding="utf-8"?> <Grid Padding="20,0" xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:Japanese;assembly=Japanese"

我有这个模板:

<?xml version="1.0" encoding="utf-8"?>
<Grid Padding="20,0" xmlns="http://xamarin.com/schemas/2014/forms" 
      xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
      xmlns:local="clr-namespace:Japanese;assembly=Japanese" 
      x:Class="Japanese.Templates.DataGridTemplate" 
      x:Name="this" HeightRequest="49" Margin="0">
    <Grid.GestureRecognizers>
         <TapGestureRecognizer 
             Command="{Binding TapCommand, Source={x:Reference this}}"
             CommandParameter="1"
             NumberOfTapsRequired="1" />
    </Grid.GestureRecognizers>
    <Label Grid.Column="0" Text="{Binding Test" />
</Grid>
我正在尝试调用文件Settings.xaml.cs中的模板

<template:DataGridTemplate TapCommand="openCFSPage" />
代码可以编译,但当我单击网格时,它不会调用openCFSPage方法

1) 有人知道什么地方出了问题吗

2) 还有一种方法可以将参数添加到模板中,然后在CS后端代码中将该参数传递给我的方法吗


请注意,如果可能,我希望避免添加视图模型。该应用程序很小,我只希望在调用模板的页面的CS代码中包含所需的代码。

检查您帮助的代码。在这里,您必须传递列表视图的引用,并且还需要使用BindingContext绑定命令

 <ListView ItemsSource="{Binding Sites}" x:Name="lstSale">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <StackLayout Orientation="Vertical">
                            <Label Text="{Binding FriendlyName}" />
                            <Button Text="{Binding Name}"
                                    HorizontalOptions="Center"
                                    VerticalOptions="Center"
                                    Command="{Binding 
                                   Path=BindingContext.RoomClickCommand,
                                    Source={x:Reference lstSale}}" 
                                   CommandParameter="{Binding .}" />
                       </StackLayout>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

根据用例的不同,您有两个选项:

仅供参考,无法直接从视图调用另一个方法(这样做是一种糟糕的设计模式)

  • :
  • 创建接口

    public interface IEventAggregator
    {
        TEventType GetEvent<TEventType>() where TEventType : EventBase, new();
    }
    
  • /MakingopenCFSPagea服务:
  • 创建链接两个模型的接口/服务

    public interface IOpenCFSPage
    {
         Task OpenPage();
    }
    
    以及一种方法:

    public class OpenCFSPage : IOpenCFSPage
    {
    private INavigationService _navigationService;
    public OpenCFSPage(INavigationService navigationService){
     _navigationService = navigationService;
    }
            public async Task OpenPage()
            {
                 await _navigationService.NavigateAsync(new CFSPage());
            }
    }
    
    请注意,实现这一点的最简单方法是通过MVVM(即视图模型),但如果您想避开此选项(如问题中所述),则可以使用以下选项之一

    选项1:将委托包装到命令对象中 如果从XAML解析器的角度来看,从技术上讲,您试图将委托分配给
    ICommand
    类型的属性。避免类型不匹配的一种方法是将委托包装在页面代码隐藏的命令属性中

    代码隐藏[Settings.xaml.cs]

    ICommand _openCFSPageCmd;
    public ICommand OpenCFSPageCommand {
        get {
            return _openCFSPageCmd ?? (_openCFSPageCmd = new Command(OpenCFSPage));
        }
    }
    
    void OpenCFSPage(object param)
    {
        Console.WriteLine($"Control was tapped with parameter: {param}");
    }
    
    XAML[Settings.XAML]

    <!-- assuming that you have added x:Name="_parent" in root tag -->
    <local:DataGridView TapCommand="{Binding OpenCFSPageCommand, Source={x:Reference _parent}}" />
    
    
    
    选项2:自定义标记扩展 另一个选项(稍微不是主流)是将委托包装到命令对象中

    [ContentProperty("Handler")]
    public class ToCommandExtension : IMarkupExtension
    {
        public string Handler { get; set; }
        public object Source { get; set; }
    
        public object ProvideValue(IServiceProvider serviceProvider)
        {
            if (serviceProvider == null)
                throw new ArgumentNullException(nameof(serviceProvider));
            var lineInfo = (serviceProvider?.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider)?.XmlLineInfo ?? new XmlLineInfo();
    
    
            object rootObj = Source;
            if (rootObj == null)
            {
                var rootProvider = serviceProvider.GetService<IRootObjectProvider>();
                if (rootProvider != null)
                    rootObj = rootProvider.RootObject;
            }
    
            if(rootObj == null)
            {
                var valueProvider = serviceProvider.GetService<IProvideValueTarget>();
                if (valueProvider == null)
                    throw new ArgumentException("serviceProvider does not provide an IProvideValueTarget");
    
                //we assume valueProvider also implements IProvideParentValues
                var propInfo = valueProvider.GetType()
                                            .GetProperty("Xamarin.Forms.Xaml.IProvideParentValues.ParentObjects", 
                                                         BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                if(propInfo == null)
                    throw new ArgumentException("valueProvider does not provide an ParentObjects");
    
                var parentObjects = propInfo.GetValue(valueProvider) as IEnumerable<object>;
                rootObj = parentObjects?.LastOrDefault();
            }
    
            if(rootObj != null)
            {
                var delegateInfo = rootObj.GetType().GetMethod(Handler,
                                                               BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                if(delegateInfo != null)
                {
                    var handler = Delegate.CreateDelegate(typeof(Action<object>), rootObj, delegateInfo) as Action<object>;
                    return new Command((param) => handler(param));
                }
            }
    
            throw new XamlParseException($"Can not find the delegate referenced by `{Handler}` on `{Source?.GetType()}`", lineInfo);        
        }
    }
    
    [ContentProperty(“处理程序”)]
    CommandExtension的公共类:IMarkupExtension
    {
    公共字符串处理程序{get;set;}
    公共对象源{get;set;}
    公共对象ProviderValue(IServiceProvider服务提供者)
    {
    if(serviceProvider==null)
    抛出新ArgumentNullException(nameof(serviceProvider));
    var lineInfo=(serviceProvider?.GetService(typeof(IXmlLineInfoProvider))作为IXmlLineInfoProvider)?.XmlLineInfo??新的XmlLineInfo();
    对象rootObj=源;
    if(rootObj==null)
    {
    var rootProvider=serviceProvider.GetService();
    if(rootProvider!=null)
    rootObj=rootProvider.RootObject;
    }
    if(rootObj==null)
    {
    var valueProvider=serviceProvider.GetService();
    if(valueProvider==null)
    抛出新ArgumentException(“serviceProvider不提供IProviderValueTarget”);
    //我们假设valueProvider还实现IProvideParentValue
    var propInfo=valueProvider.GetType()
    .GetProperty(“Xamarin.Forms.Xaml.iprovideparentvalue.ParentObjects”,
    BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
    if(propInfo==null)
    抛出新ArgumentException(“valueProvider不提供ParentObjects”);
    var parentObjects=propInfo.GetValue(valueProvider)作为IEnumerable;
    rootObj=parentObjects?.LastOrDefault();
    }
    if(rootObj!=null)
    {
    var delegateInfo=rootObj.GetType().GetMethod(处理程序,
    BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
    如果(delegateInfo!=null)
    {
    var handler=Delegate.CreateDelegate(typeof(Action)、rootObj、delegateInfo)作为操作;
    返回新命令((param)=>handler(param));
    }
    }
    抛出新的XamlParseException($”在`{Source?.GetType()}`,lineInfo上找不到由`{Handler}`引用的委托);
    }
    }
    
    示例用法

    <local:DataGridView TapCommand="{local:ToCommand OpenCFSPage}" />
    
    
    
    Settings.xaml:

    <template:DataGridTemplate TapCommand="{Binding OpenCFSPage}" />
    
    <!-- Uncomment below and corresponding parameter property code in DataGridTemplate.xaml.cs to pass parameter from Settings.xaml -->
    <!--<template:DataGridTemplate TapCommand="{Binding OpenCFSPage}" CommandParameter="A" />-->
    
    DataGridTemplate.xaml:

    <?xml version="1.0" encoding="UTF-8"?>
        <Grid xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:Japanese;assembly=Japanese" 
             Padding="0,20" 
             HeightRequest="49" Margin="0"
             x:Class="Japanese.DataGridTemplate">
        <Grid.GestureRecognizers>
            <TapGestureRecognizer 
                 Command="{Binding TapCommand}"
                 CommandParameter="1"
                 NumberOfTapsRequired="1" />
        </Grid.GestureRecognizers>
        <Label Grid.Column="0" Text="Test" />
    </Grid>
    

    很抱歉,您的答案(其他代码的一个简短片段)似乎根本无法回答我的问题。谢谢LeRoy,我会仔细研究您的答案,如果我有任何问题,请告诉您。您的答案看起来是迄今为止最好的,我想我会接受。如果您有时间,您还可以展示一下如何使用MVVM实现这一点,正如您所说,这可能更简单。
    [ContentProperty("Handler")]
    public class ToCommandExtension : IMarkupExtension
    {
        public string Handler { get; set; }
        public object Source { get; set; }
    
        public object ProvideValue(IServiceProvider serviceProvider)
        {
            if (serviceProvider == null)
                throw new ArgumentNullException(nameof(serviceProvider));
            var lineInfo = (serviceProvider?.GetService(typeof(IXmlLineInfoProvider)) as IXmlLineInfoProvider)?.XmlLineInfo ?? new XmlLineInfo();
    
    
            object rootObj = Source;
            if (rootObj == null)
            {
                var rootProvider = serviceProvider.GetService<IRootObjectProvider>();
                if (rootProvider != null)
                    rootObj = rootProvider.RootObject;
            }
    
            if(rootObj == null)
            {
                var valueProvider = serviceProvider.GetService<IProvideValueTarget>();
                if (valueProvider == null)
                    throw new ArgumentException("serviceProvider does not provide an IProvideValueTarget");
    
                //we assume valueProvider also implements IProvideParentValues
                var propInfo = valueProvider.GetType()
                                            .GetProperty("Xamarin.Forms.Xaml.IProvideParentValues.ParentObjects", 
                                                         BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                if(propInfo == null)
                    throw new ArgumentException("valueProvider does not provide an ParentObjects");
    
                var parentObjects = propInfo.GetValue(valueProvider) as IEnumerable<object>;
                rootObj = parentObjects?.LastOrDefault();
            }
    
            if(rootObj != null)
            {
                var delegateInfo = rootObj.GetType().GetMethod(Handler,
                                                               BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                if(delegateInfo != null)
                {
                    var handler = Delegate.CreateDelegate(typeof(Action<object>), rootObj, delegateInfo) as Action<object>;
                    return new Command((param) => handler(param));
                }
            }
    
            throw new XamlParseException($"Can not find the delegate referenced by `{Handler}` on `{Source?.GetType()}`", lineInfo);        
        }
    }
    
    <local:DataGridView TapCommand="{local:ToCommand OpenCFSPage}" />
    
    <template:DataGridTemplate TapCommand="{Binding OpenCFSPage}" />
    
    <!-- Uncomment below and corresponding parameter property code in DataGridTemplate.xaml.cs to pass parameter from Settings.xaml -->
    <!--<template:DataGridTemplate TapCommand="{Binding OpenCFSPage}" CommandParameter="A" />-->
    
    public Settings()
    {
        InitializeComponent();
    
        OpenCFSPage = new Command(p => OpenCFSPageExecute(p));
    
        BindingContext = this;
    }
    
    public ICommand OpenCFSPage { get; private set; }
    
    void OpenCFSPageExecute(object p)
    {
        var s = p as string;
        Debug.WriteLine($"OpenCFSPage:{s}:");
    }
    
    <?xml version="1.0" encoding="UTF-8"?>
        <Grid xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:Japanese;assembly=Japanese" 
             Padding="0,20" 
             HeightRequest="49" Margin="0"
             x:Class="Japanese.DataGridTemplate">
        <Grid.GestureRecognizers>
            <TapGestureRecognizer 
                 Command="{Binding TapCommand}"
                 CommandParameter="1"
                 NumberOfTapsRequired="1" />
        </Grid.GestureRecognizers>
        <Label Grid.Column="0" Text="Test" />
    </Grid>
    
    public partial class DataGridTemplate : Grid
    {
        public DataGridTemplate()
        {
            InitializeComponent();
        }
    
        public static readonly BindableProperty TapCommandProperty = 
            BindableProperty.Create(
                nameof(TapCommand), typeof(ICommand), typeof(DataGridTemplate), null,
                propertyChanged: OnCommandPropertyChanged);
    
        public ICommand TapCommand
        {
            get { return (ICommand)GetValue(TapCommandProperty); }
            set { SetValue(TapCommandProperty, value); }
        }
    
        //public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(
        //    nameof(CommandParameter), typeof(string), typeof(DataGridTemplate), null);
    
        //public string CommandParameter
        //{
        //    get { return (string)GetValue(CommandParameterProperty); }
        //    set { SetValue(CommandParameterProperty, value); }
        //}
    
        static TapGestureRecognizer GetTapGestureRecognizer(DataGridTemplate view)
        {
            var enumerator = view.GestureRecognizers.GetEnumerator();
            while (enumerator.MoveNext())
            {
                var item = enumerator.Current;
                if (item is TapGestureRecognizer) return item as TapGestureRecognizer;
            }
            return null;
        }
    
        static void OnCommandPropertyChanged(BindableObject bindable, object oldValue, object newValue)
        {
            if (bindable is DataGridTemplate view)
            {
                var tapGestureRecognizer = GetTapGestureRecognizer(view);
                if (tapGestureRecognizer != null)
                {
                    tapGestureRecognizer.Command = (ICommand)view.GetValue(TapCommandProperty);
                    //tapGestureRecognizer.CommandParameter = (string)view.GetValue(CommandParameterProperty);
                }
            }
        }
    }