I have this template:
<?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>
Behind this I have:
public partial class DataGridTemplate : Grid { public DataGridTemplate() { InitializeComponent(); } public static readonly BindableProperty TapCommandProperty = BindableProperty.Create( "Command", typeof(ICommand), typeof(DataGridTemplate), null); public ICommand TapCommand { get { return (ICommand)GetValue(TapCommandProperty); } set { SetValue(TapCommandProperty, value); } } }
and I am trying to call the template like this in file: Settings.xaml.cs
<template:DataGridTemplate TapCommand="openCFSPage" />
hoping that it will call my method here in file: Settings.cs
void openCFSPage(object sender, EventArgs e) { Navigation.PushAsync(new CFSPage()); }
The code compiles but when I click on the grid it doesn't call the openCFSPage method.
1) Does anyone have an idea what might be wrong?
2) Also is there a way that I can add a parameter to the template and then have that parameter passed to my method in the CS back end code?
Note that I would like to avoid adding a view model if possible. The application is small and I'd like to just have the code I need in the CS code of the page that calls the template.
4 Answers
Answers 1
You have 2 options depending on the the use case :
FYI, there's no way to call another method directly from the view (its a bad design pattern to do so)
Create interface
public interface IEventAggregator { TEventType GetEvent<TEventType>() where TEventType : EventBase, new(); }
All you have to do is call it from you TapCommand
_eventAggregator.GetEvent<ItemSelectedEvent>().Publish(_selectedItem);
Then in your Settings.cs you can Create a method that can receive the data
this.DataContext = new ListViewModel(ApplicationService.Instance.EventAggregator);
- Inheritance and Polymorphism / Making openCFSPage a service :
Creating a interface / service that links both models
public interface IOpenCFSPage { Task OpenPage(); }
and a method :
public class OpenCFSPage : IOpenCFSPage { private INavigationService _navigationService; public OpenCFSPage(INavigationService navigationService){ _navigationService = navigationService; } public async Task OpenPage() { await _navigationService.NavigateAsync(new CFSPage()); } }
Answers 2
Please note that the simplest way to implement this would be through MVVM (i.e. a view-model), but if you want to side-step this option (as you mentioned in the question) then you can use one of the following options
Option1 : Wrap delegate into command object
If you look at it from the perspective of a XAML parser, you are technically trying to assign a delegate to a property of type ICommand
. One way to avoid the type mismatch would be to wrap the delegate inside a command-property in the page's code-behind.
Code-behind [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}}" />
Option2 : Custom markup-extension
Another option (a bit less mainstream) is to create a markup-extension that wraps the delegate into a command object.
[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); } }
Sample usage
<local:DataGridView TapCommand="{local:ToCommand OpenCFSPage}" />
Answers 3
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" />-->
Settings.xaml.cs:
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}:"); }
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>
DataGridTemplate.xaml.cs:
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); } } } }
Answers 4
Check this code you help you. Here you have to pass a reference of list view and also you need to bind a command with 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>
0 comments:
Post a Comment