Sebastian Busek
Sebastian Busek

Reputation: 1032

EventToCommandBehavior throws InvalidOperationException in MAUI for checkbox

I need to run a command once a checkbox is checked/unchecked. The checkboxes are rendered in the page via StackLayout's items source, EventToCommandBehavior is from MAUI CommunityToolkit.

The exception is thrown when the page should be rendered, most likely when XAML is parsed and processed. The Exception message says: "Operation is not valid due to the current state of the object.", which is not very helpful (I am missing what state, who is owner, what is invalid).

Maybe a callstact would help someone?

  at Microsoft.Maui.Controls.Binding.ApplyRelativeSourceBinding(BindableObject targetObject, BindableProperty targetProperty) in D:\a\_work\1\s\src\Controls\src\Core\Binding.cs:line 152
  at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__128_0(Object state)
  at Android.App.SyncContext.<>c__DisplayClass2_0.<Post>b__0() in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.App/SyncContext.cs:line 36
  at Java.Lang.Thread.RunnableImplementor.Run() in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Java.Lang/Thread.cs:line 36
  at Java.Lang.IRunnableInvoker.n_Run(IntPtr jnienv, IntPtr native__this) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/net6.0/android-31/mcw/Java.Lang.IRunnable.cs:line 84
  at Android.Runtime.JNINativeWrapper.Wrap_JniMarshal_PP_V(_JniMarshal_PP_V callback, IntPtr jnienv, IntPtr klazz) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Android.Runtime/JNINativeWrap

I've used the same technique several times in Xamarin. It works there as expected, but in MAUI following code throws InvalidOperationException. The content page and view model are created (the constructors pass) correctly, but somewhere later something throws an exception, and honestly don't know why.

<StackLayout 
    BindableLayout.ItemsSource="{Binding GameVariants}">
    <BindableLayout.ItemTemplate>
        <DataTemplate x:DataType="bindable:SelectExpansion">
            <StackLayout Orientation="Horizontal" VerticalOptions="Center">
                <CheckBox
                    IsEnabled="{Binding IsExpansion}"
                    IsChecked="{Binding Selected}">
                    <CheckBox.Behaviors>
                        <toolkit:EventToCommandBehavior
                            EventName="CheckedChanged"
                            Command="{Binding Source={RelativeSource AncestorType={x:Type vm:SelectExpansionsPageViewModel}}, Path=SelectExpansionCommand}"
                            CommandParameter="{Binding .}" />
                    </CheckBox.Behaviors>
                </CheckBox>
                <Label Text="{Binding Expansion}" VerticalTextAlignment="Center" />
            </StackLayout>
        </DataTemplate>
    </BindableLayout.ItemTemplate>
</StackLayout>

The code behind contains only setting context via dependency.

public partial class SelectExpansionsPage : ContentPage
{
    public SelectExpansionsPage(SelectExpansionsPageViewModel vm)
    {
        BindingContext = vm;
        InitializeComponent();
    }
}

And here is View model for page.

public partial class SelectExpansionsPageViewModel : ObservableObject
{
    public SelectExpansionsPageViewModel(Game state)
    {
        GameVariants = new ObservableCollection<SelectExpansion>(AvailableExpansions.Expansions.Select(item =>
        {
            item.Selected = state.Expansions.Contains(item.Expansion);

            return item;
        }));
    }

    public ObservableCollection<SelectExpansion> GameVariants { get; }
    
    [RelayCommand]
    public void SelectExpansion(SelectExpansion item)
    {
        Debug.WriteLine("Select item changed");
    }
}

Upvotes: 5

Views: 2014

Answers (2)

scottr
scottr

Reputation: 21

In my case of observing exactly the same symptoms, I had to tweak the solution provided in this answer and needed to change Path=ViewModel.xxxx to Path=BindingContext.xxxx for it to work. I don't know why the tweak was required, it could be version or framework dependent.

Unfortunately the event triggers on all checks, including data load and scroll into view. This is a problem with the event itself though.

Upvotes: 1

Sebastian Busek
Sebastian Busek

Reputation: 1032

After some research, I've found that behaviors aren't part of the visual tree (sorry, lost the link).

To overcome this one has to reference source binding via the reference, see the "This" which refers to the x:Name of the content page. The ViewModel is just a strongly typed property of ContentPage which refers to the unboxed BindingContext.

Side note: The question refers to the .NET MAUI, however, I had to migrate back to the Xamarin Forms because at the time .NET MAUI has a lot of bugs that block development and app release. The problem is the same for both frameworks.

<?xml version="1.0" encoding="utf-8"?>

<ContentPage
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:vm="clr-namespace:Everdell.ViewModels;assembly=Everdell"
    xmlns:toolkit="http://xamarin.com/schemas/2020/toolkit"
    x:Class="Everdell.Views.SelectExpansionsView"
    x:DataType="vm:SelectExpansionsViewModel"
    x:Name="This">
    <ContentPage.Content>
        <CheckBox>
            <CheckBox.Behaviors>
                <toolkit:EventToCommandBehavior
                    EventName="CheckedChanged"
                    Command="{Binding Source={x:Reference This}, Path=ViewModel.SelectExpansionCommand}"
                    CommandParameter="{Binding .}" />
            </CheckBox.Behaviors>
        </CheckBox>
    </ContentPage.Content>
</ContentPage>

Upvotes: 4

Related Questions