I've looked all over the internet for this problem but no one seems to have encountered it.
I have a Xamarin.Forms project where I have a CollectionView of products and I wanted to add behaviors to it as I just learned about it, however it doesn't seem to work.
The Problem:
as I use an EventToCommandBehavior template that has a BindableProperty that takes an "EventName" string to get the name of the event I want to trigger, I enter EventName="SelectedItem" which is the main event for selection for CollectionView and the app crashes and the debugger returns this:
iOS Output
iOS Output Details
I've added the behavior to the <CollectionView.Behaviors> and did exactly as every reference to how to use an MVVM Behavior says you should but it still doesn't work.
However some people were saying to maybe use behaviors in the GestureRecognizer of the view I'm using such as <StackLayout.GestureRecognizers> but even after hours looking for how to make it work I haven't found anything on how to add a behavior to a GestureRecognizer.
I really want to use a behavior with a CollectionView to see how it works and why I'm getting this error ( and since every topic on Behaviors in Xamarin.Forms is about ListView ).
I use a MasterPage and as I click on the MasterPage item ( CakesPage ) to go to its NavigationPage it crashes the app and the debugger returns the error.
Please let me know what is not working and how to solve it, thank you so much.
CakesPage.xaml
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage Title="{Binding Title}" xmlns:behaviors="clr-namespace:App.Behaviors" xmlns="http://xamarin.com/schemas/2014/forms" xmlns:model="clr-namespace:App.Models" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="App.Views.CakesPage">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Clicked="ToolbarItem_Clicked"/>
</ContentPage.ToolbarItems>
<RefreshView IsRefreshing="{Binding IsBusy, Mode=TwoWay}" Command="{Binding LoadItemsCommand}">
<CollectionView x:Name="ItemsCollectionView">
<CollectionView.Behaviors>
<!--Here's where it doesn't work-->
<behaviors:EventToCommandBehavior EventName="SelectedItem"
Command="{Binding SelectedItemCommand}"
EventArgsConverter="{StaticResource SelectedItemConverter}">
</behaviors:EventToCommandBehavior>
</CollectionView.Behaviors>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Padding="10">
<Label Text="{Binding Name}" Style="{DynamicResource ListItemTextStyle}"/>
<Label Text="{Binding Description}" Style="{DynamicResource ListItemDetailStyle}"/>
<!--<StackLayout.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1"></TapGestureRecognizer>
</StackLayout.GestureRecognizers>-->
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</RefreshView>
CakesPage.xaml.cs
using System;
using System.Collections;
using System.Collections.Generic;
using coldstoneinventorytest.Models;
using coldstoneinventorytest.Services;
using coldstoneinventorytest.ViewModels;
using Xamarin.Forms;
namespace coldstoneinventorytest.Views
{
public partial class CakesPage : ContentPage
{
CakesViewModel viewModel;
public CakesPage()
{
InitializeComponent();
BindingContext = viewModel = new CakesViewModel();
ItemsCollectionView.ItemsSource = viewModel.Products;
}
protected override void OnAppearing()
{
base.OnAppearing();
if (viewModel.Products.Count == 0)
viewModel.IsBusy = true;
}
void ToolbarItem_Clicked(System.Object sender, System.EventArgs e)
{
}
}
}
CakesViewModel.cs
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using coldstoneinventorytest.Models;
using coldstoneinventorytest.Views;
using Xamarin.Forms;
namespace coldstoneinventorytest.ViewModels
{
public class CakesViewModel : BaseViewModel
{
public ObservableCollection<Product> Products { get; set; }
public Command LoadItemsCommand { get; set; }
public Command SelectedItemCommand { get; }
public CakesViewModel()
{
Title = "Cakes";
Products = new ObservableCollection<Product>();
LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand());
SelectedItemCommand = new Command<Product>(OnItemtapped);
MessagingCenter.Subscribe<NewCakePage, Product>(this, "AddCake", async (obj, cake) =>
{
var newCake = cake as Product;
Products.Add(newCake);
await DataStore.AddItemAsync(newCake);
});
}
private void OnItemtapped(Product product)
{
}
async Task ExecuteLoadItemsCommand()
{
IsBusy = true;
try
{
Products.Clear();
var products = await DataStore.GetItemsAsync(true);
foreach (var product in products)
{
if(product.Category == "Cake")
Products.Add(product);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
}
}
BindableBehavior.cs
using System;
using Xamarin.Forms;
namespace coldstoneinventorytest.Behaviors
{
public class BindableBehavior<T> : Behavior<T> where T : BindableObject
{
public T AssociatedObject { get; private set; }
protected override void OnAttachedTo(T visualElement)
{
base.OnAttachedTo(visualElement);
AssociatedObject = visualElement;
if (visualElement.BindingContext != null)
BindingContext = visualElement.BindingContext;
visualElement.BindingContextChanged += OnBindingContextChanged;
}
private void OnBindingContextChanged(object sender, EventArgs e)
{
OnBindingContextChanged();
}
protected override void OnDetachingFrom(T view)
{
view.BindingContextChanged -= OnBindingContextChanged;
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
BindingContext = AssociatedObject.BindingContext;
}
}
}
EventToCommandBehavior.cs
using System;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Windows.Input;
using Xamarin.Forms;
namespace coldstoneinventorytest.Behaviors
{
public class EventToCommandBehavior : BindableBehavior<View>
{
public static BindableProperty EventNameProperty =
BindableProperty.Create("EventName", typeof(string), typeof(EventToCommandBehavior), null,
BindingMode.OneWay);
public static BindableProperty CommandProperty =
BindableProperty.Create("Command", typeof(ICommand), typeof(EventToCommandBehavior), null,
BindingMode.OneWay);
public static BindableProperty CommandParameterProperty =
BindableProperty.Create("CommandParameter", typeof(object), typeof(EventToCommandBehavior), null,
BindingMode.OneWay);
public static BindableProperty EventArgsConverterProperty =
BindableProperty.Create("EventArgsConverter", typeof(IValueConverter), typeof(EventToCommandBehavior), null,
BindingMode.OneWay);
public static BindableProperty EventArgsConverterParameterProperty =
BindableProperty.CreateAttached("EventArgsConverterParameter", typeof(object), typeof(EventToCommandBehavior), null,
BindingMode.OneWay);
protected Delegate _handler;
private EventInfo _eventInfo;
public string EventName
{
get { return (string)GetValue(EventNameProperty); }
set { SetValue(EventNameProperty, value); }
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public IValueConverter EventArgsConverter
{
get { return (IValueConverter)GetValue(EventArgsConverterProperty); }
set { SetValue(EventArgsConverterProperty, value); }
}
public object EventArgsConverterParameter
{
get { return GetValue(EventArgsConverterParameterProperty); }
set { SetValue(EventArgsConverterParameterProperty, value); }
}
protected override void OnAttachedTo(View visualElement)
{
base.OnAttachedTo(visualElement);
var events = AssociatedObject.GetType().GetRuntimeEvents().ToArray();
if (events.Any())
{
_eventInfo = events.FirstOrDefault(e => e.Name == EventName);
if (_eventInfo == null)
throw new ArgumentException(String.Format("EventToCommand: Can't find any event named '{0}' on attached type", EventName));
AddEventHandler(_eventInfo, AssociatedObject, OnFired);
}
}
protected override void OnDetachingFrom(View view)
{
if (_handler != null)
_eventInfo.RemoveEventHandler(AssociatedObject, _handler);
base.OnDetachingFrom(view);
}
private void AddEventHandler(EventInfo eventInfo, object item, Action<object, EventArgs> action)
{
var eventParameters = eventInfo.EventHandlerType
.GetRuntimeMethods().First(m => m.Name == "Invoke")
.GetParameters()
.Select(p => Expression.Parameter(p.ParameterType))
.ToArray();
var actionInvoke = action.GetType()
.GetRuntimeMethods().First(m => m.Name == "Invoke");
_handler = Expression.Lambda(
eventInfo.EventHandlerType,
Expression.Call(Expression.Constant(action), actionInvoke, eventParameters[0], eventParameters[1]),
eventParameters
)
.Compile();
eventInfo.AddEventHandler(item, _handler);