Maui icon indicating copy to clipboard operation
Maui copied to clipboard

[BUG] TouchBehavior crash

Open eduardoagr opened this issue 1 year ago • 55 comments

Is there an existing issue for this?

  • [X] I have searched the existing issues

Did you read the "Reporting a bug" section on Contributing file?

  • [X] I have read the "Reporting a bug" section on Contributing file: https://github.com/CommunityToolkit/Maui/blob/main/CONTRIBUTING.md#reporting-a-bug

Current Behavior

I have a CollectionView like this

<CollectionView
    IsVisible="{Binding isNotBusy}"
    ItemsSource="{Binding CoursesAssigned}">
    <CollectionView.ItemTemplate>
        <DataTemplate x:DataType="model:Course">
            <Border
                Margin="50"
                Stroke="Purple"
                StrokeThickness="1">
                <Border.StrokeShape>
                    <RoundRectangle CornerRadius="8,8,8,8" />
                </Border.StrokeShape>
                <Border.Behaviors>
                    <mct:TouchBehavior Command="{Binding Path=GoToDetailsCommand, Source={x:RelativeSource AncestorType={x:Type vm:MyCoursesPageViewModel}}}"
                        CommandParameter="{Binding .}"
                        DefaultBackgroundColor="Transparent"
                        HoveredBackgroundColor="CornflowerBlue"
                        PressedBackgroundColor="CornflowerBlue" />
                </Border.Behaviors>`

But when I run it, I get the error

Operation is not valid due to the current state of the object.

Expected Behavior

My command is declared in my viewModel

 [RelayCommand]
 async Task GoToDetails(Course course) {

     await appService.NavigateTo($"{nameof(MyCoursesDetailPage)}",
         true, new Dictionary<string, object> {
         {"Course", course }
     });
 }

So I am expecting my command to go the MyCoursesDetailPage when I click the border

Steps To Reproduce

  1. Create a new Maui project
  2. Install the mct (Maui community toolkit)
  3. Create some sort of class to represent an object, like fruits.
  4. create a collectionView
  5. Create a border
  6. attach the Touch behavior to a border

Link to public reproduction project repository

https://github.com/eduardoagr/TouchBehaviorExample

Environment

- .NET MAUI CommunityToolkit:
- OS: Windows, Android
- .NET MAUI:

Anything else?

No response

eduardoagr avatar Mar 31 '24 17:03 eduardoagr

What if you try something like the following:


<CollectionView
    IsVisible="{Binding isNotBusy}"
    ItemsSource="{Binding CoursesAssigned}"
    x:Name="collectionView">
    <CollectionView.ItemTemplate>
        <DataTemplate x:DataType="model:Course">
            <Border
                Margin="50"
                Stroke="Purple"
                StrokeThickness="1">
                <Border.StrokeShape>
                    <RoundRectangle CornerRadius="8,8,8,8" />
                </Border.StrokeShape>
                <Border.Behaviors>
                    <mct:TouchBehavior Command="{Binding Path=BindingContext.GoToDetailsCommand, Source={x:Reference CollectionView}}"
                        CommandParameter="{Binding .}"
                        DefaultBackgroundColor="Transparent"
                        HoveredBackgroundColor="CornflowerBlue"
                        PressedBackgroundColor="CornflowerBlue" />
                </Border.Behaviors>

This provides a name to the CollectionView and then uses that in the Binding

bijington avatar Mar 31 '24 17:03 bijington

Already tried and nothing

eduardoagr avatar Mar 31 '24 18:03 eduardoagr

Same issue, except I'm adding the touch behavior to a Frame.

This seems to be related to the Command property binding, if you do not set it, it won't crash.

Output window:

[mono-rt] [ERROR] FATAL UNHANDLED EXCEPTION: System.InvalidOperationException: Operation is not valid due to the current state of the object.
[mono-rt]    at Microsoft.Maui.Controls.Binding.ApplyRelativeSourceBinding(BindableObject targetObject, BindableProperty targetProperty, SetterSpecificity specificity) in D:\a\_work\1\s\src\Controls\src\Core\Binding.cs:line 152
[mono-rt]    at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__128_0(Object state)
[mono-rt]    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
[mono-rt]    at Java.Lang.Thread.RunnableImplementor.Run() in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Java.Lang/Thread.cs:line 36
[mono-rt]    at Java.Lang.IRunnableInvoker.n_Run(IntPtr jnienv, IntPtr native__this) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/net8.0/android-34/mcw/Java.Lang.IRunnable.cs:line 84
[mono-rt]    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/JNINativeWrapper.g.cs:line 26

r-work avatar Apr 01 '24 09:04 r-work

Here is a repro: https://github.com/r-work/TouchBehaviorBug

r-work avatar Apr 01 '24 11:04 r-work

@eduardoagr & @r-work can you have a look at this post from @brminnick on SO. Here he shows exactly how to set the binding context on the behavior appropriately, I have seen the crashes when not setting it the recommended way too.

I'm currently in the process of updating the MCT samples & documentation to explicitly call this issue out and show the appropriate way to use the behavior, I have put this in my docs pr but the way you should setup the behavior to pass a command is here:

<ContentPage x:Name="Page">
    <HorizontalStackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="Center">
        <HorizontalStackLayout.Behaviors>
            <mct:TouchBehavior
                BindingContext="{Binding Source={x:Reference Page}, Path=BindingContext}"
                Command="{Binding IncreaseTouchCountCommand}"
                DefaultAnimationDuration="250"
                DefaultAnimationEasing="{x:Static Easing.CubicInOut}"
                PressedOpacity="0.6"
                PressedScale="0.8" />
        </HorizontalStackLayout.Behaviors>

        <ContentView
            BackgroundColor="Gold"
            HeightRequest="100"
            WidthRequest="10" />
        <Label
            LineBreakMode="TailTruncation"
            Text="The entire layout receives touches"
            VerticalOptions="Center" />
        <ContentView
            BackgroundColor="Gold"
            HeightRequest="100"
            WidthRequest="10" />
    </HorizontalStackLayout>
</ContentPage>

The checklist is:

  • Put an x:Name="Page on your root element (assuming ContentPage)
  • Set the BindingContext: BindingContext="{Binding Source={x:Reference Page}, Path=BindingContext}"
  • Reference the Command normally: Command="{Binding IncreaseTouchCountCommand}"

Axemasta avatar Apr 01 '24 13:04 Axemasta

Why do we have to use a name for the page, I always reference the vm, and that is what I was trying to do

eduardoagr avatar Apr 01 '24 13:04 eduardoagr

@Axemasta my data template lives in a separate XAML file, referencing the page by name is not exactly ideal. I've been using a gesture recognizer and it works as expected:

<Frame>
    <Frame.GestureRecognizers>
        <TapGestureRecognizer Command="{Binding BindingContext.ItemClickCommand, Source={RelativeSource AncestorType={x:Type Page}}}" CommandParameter="{Binding .}" />
    </Frame.GestureRecognizers>
<Frame />

The issue happens as soon as I change that to a behavior.

r-work avatar Apr 01 '24 13:04 r-work

Why do we have to use a name for the page, I always reference the vm, and that is what I was trying to do

<CollectionView
    x:Name="ff"
    Margin="20"
    HorizontalOptions="Center"
    ItemsSource="{Binding Students}">
    <CollectionView.ItemTemplate>
        <DataTemplate x:DataType="model:DemyUser">
            <Border
                Padding="10"
                StrokeShape="RoundRectangle 8"
                StrokeThickness="2">
                <Grid RowDefinitions="20,20">

                    <Label
                        FontAttributes="Bold"
                        Text="{Binding DemyId, StringFormat='Id: {0}'}" />

                    <Label
                        Grid.Row="1"
                        FontAttributes="Bold"
                        Text="{Binding FullName, StringFormat='Name: {0}'}" />

                    <Label
                        Grid.RowSpan="2"
                        FontFamily="Mat"
                        FontSize="Header"
                        HorizontalTextAlignment="End"
                        Text="{x:Static helpers:IconFont.Email}"
                        VerticalTextAlignment="Center">
                        <Label.Behaviors>
                            <mct:TouchBehavior
                                BindingContext="{Binding Source={x:RelativeSource AncestorType={x:Type vm:MyCoursesDetailPageViewModel}}, Path=BindingContext}"
                                Command="{Binding Path=ContactCommand, Source={x:RelativeSource AncestorType={x:Type vm:MyCoursesDetailPageViewModel}}}" />
                        </Label.Behaviors>
                    </Label>
                </Grid>
            </Border>
        </DataTemplate>
    </CollectionView.ItemTemplate>
[RelayCommand]
async Task Contact(string email) {

    await EmailHelper.OpenEmailClientAsync(email);
}

eduardoagr avatar Apr 01 '24 14:04 eduardoagr

@r-work can you name your Frame instead?

bijington avatar Apr 01 '24 17:04 bijington

To answer both of your questions @eduardoagr, @r-work we have to reference something else on the view, ie the page or indeed a parent view because the TouchBehavior itself has no BindingContext. In XCT it did because it was an attached effect, the current MCT implementation uses a PlatformBehavior which does not set the BindingContext, this is following Maui documentation guidelines (see article).

So whilst other things like TapGestureRecognizer will work as expected, the TouchBehavior won't unless you set its binding context through an x:Reference and invoke it that way.

Axemasta avatar Apr 01 '24 20:04 Axemasta

@r-work can you name your Frame instead?

Can you use Border instead? AFAIK Frame is kind compatibility, in .NET MAUI, and should be replaced by Border. From docs

image

pictos avatar Apr 01 '24 21:04 pictos

@eduardoagr this is a duplicate of https://github.com/CommunityToolkit/Maui/issues/1781 - @Axemasta is right in https://github.com/CommunityToolkit/Maui/issues/1783#issuecomment-2030510397.

It's a but confusing for existing users of Axemasta's initial port of the TouchEffect or even of TapGestureRecognizer. In that regard, it would have been easier if it was implemented as a GestureRecognizer, but inheriting from Behavior probably brings other advantages.

hansmbakker avatar Apr 02 '24 08:04 hansmbakker

Not exactly a duplicate. I see 3 issues here.

  1. It seems like setting the BindingContext with RelativeSource does not work. It just crashes.

  2. How do you set the BindingContext to the parent viewmodel using Source (not RelativeSource) when your control xaml is in a separate file?

  3. Why do we need to set the BindingContext, when the Command binding defines its own Source or RelativeSource?

tranb3r avatar Apr 02 '24 08:04 tranb3r

@tranb3r excuse me, I didn't pick up those points.

hansmbakker avatar Apr 02 '24 11:04 hansmbakker

I'm not getting crashes, but whenever I set a command like so Command="{Binding BindingContext.ToggleCommand, Source={x:Reference Page}}" the related pressing actions of the behavior do not function at all (but hover will still work). If I remove the command, then pressing the control will again trigger the UI changes on opacity and scale (as configured). This is weird because the same command referenced via a different control works perfectly, and looking at the sample app (where it seems to work) it also looks the same. so something isn't getting set here, and results in broken stuff.

MitchBomcanhao avatar Apr 02 '24 13:04 MitchBomcanhao

I fixed it guys thanks the only problem is that I am using a label with a custom font, so I want to change the text color when hover.

eduardoagr avatar Apr 02 '24 19:04 eduardoagr

Binding issues will be fixed in the next release, see #1791 for details.

The TouchBehavior from this release will inherit the BindingContext of the VisualElement it is attached to (if it exists). This means it will behave in the same way the XCT effect did & the way my Maui.TouchEffect port did. I believe the change will be hotfixed so it should be available soon!

Axemasta avatar Apr 03 '24 10:04 Axemasta

@Axemasta seems that TouchBehavior can`t use TemplateBinding

                <ContentPresenter BackgroundColor="#FFFFFF"
                                  Padding="15, 20">
                    <ContentPresenter.Behaviors>
                        <mct:TouchBehavior PressedBackgroundColor="LightGray"
                                           IsEnabled="{TemplateBinding BindingContext.IsSelectable}"
                                           Command="{Binding Source={x:Reference page}, Path=BindingContext.TappedCommand}"
                                           CommandParameter="{TemplateBinding BindingContext}"/>
                    </ContentPresenter.Behaviors>
                </ContentPresenter>

unless I change to this:

                <ContentPresenter BackgroundColor="#FFFFFF"
                                  BindingContext="{TemplateBinding BindingContext}"
                                  Padding="15, 20">
                    <ContentPresenter.Behaviors>
                        <mct:TouchBehavior PressedBackgroundColor="LightGray"
                                           IsEnabled="{Binding IsSelectable}"
                                           Command="{Binding Source={x:Reference page}, Path=BindingContext.TappedCommand}"
                                           CommandParameter="{Binding}"/>
                    </ContentPresenter.Behaviors>
                </ContentPresenter>

I also got the same error:

[mono-rt] [ERROR] FATAL UNHANDLED EXCEPTION: System.InvalidOperationException: Operation is not valid due to the current state of the object.
[mono-rt]    at Microsoft.Maui.Controls.Binding.ApplyRelativeSourceBinding(BindableObject targetObject, BindableProperty targetProperty, SetterSpecificity specificity) in D:\a\_work\1\s\src\Controls\src\Core\Binding.cs:line 152
[mono-rt]    at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__128_0(Object state)
[mono-rt]    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
[mono-rt]    at Java.Lang.Thread.RunnableImplementor.Run() in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/Java.Lang/Thread.cs:line 36
[mono-rt]    at Java.Lang.IRunnableInvoker.n_Run(IntPtr jnienv, IntPtr native__this) in /Users/runner/work/1/s/xamarin-android/src/Mono.Android/obj/Release/net8.0/android-34/mcw/Java.Lang.IRunnable.cs:line 84
[mono-rt]    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/JNINativeWrapper.g.cs:line 26

maonaoda avatar Apr 04 '24 10:04 maonaoda

Can everyone please update to 8.0.1, this release should fix all the issues we are seeing with data binding!

You can migrate normally now, I've added a little XCT to MCT migration guide on the TouchBehavior docs incase it is of use to anyone!

Axemasta avatar Apr 04 '24 20:04 Axemasta

also can`t use TemplateBinding directly under ContentPresenter.

maonaoda avatar Apr 05 '24 00:04 maonaoda

also can`t use TemplateBinding directly under ContentPresenter.

Which version are you using, if you are using 8.0.0, please upgrade, if its 8.0.1 please file a new issue with a reproduction

Axemasta avatar Apr 05 '24 08:04 Axemasta

I'm finishing the migration of my app to TouchBehavior and the Relative Bindings don't seem to work at all, same crash reported in this thread.

I can work around by referencing the command via an x:Reference to the page:

Command="{Binding Source={x:Reference Page}, Path=BindingContext.FormSelectedCommand}"

But its not super convenient, Im not sure why the underlying maui framework is throwing an InvalidOperationException...

This is the method throwing the exception on the maui side, I have no idea where the blame lies for this issue... could it be a maui problem?

Axemasta avatar Apr 05 '24 10:04 Axemasta

which MauiVersion are you using? I`m using 8.0.20-ci.net8.10420 (Maui) and 8.0.1 (CommunityToolkit.Maui)。 x:Reference works fine on Android.

maonaoda avatar Apr 05 '24 10:04 maonaoda

which MauiVersion are you using?

8.0.14, the x:Reference works for me, I'm talking about the relative binding Command="{Binding Source={RelativeSource AncestorType={x:Type vm:MyViewModel}}, Path=MyCommand}" not working.

This is definitely a regression from XCT since my Maui TouchEffect was able to use relative bindings, I noticed this issue when removing my port from my apps.

Axemasta avatar Apr 05 '24 10:04 Axemasta

8.0.14, the x:Reference works for me, I'm talking about the relative binding Command="{Binding Source={RelativeSource AncestorType={x:Type vm:MyViewModel}}, Path=MyCommand}" not working.

This is definitely a regression from XCT since my Maui TouchEffect was able to use relative bindings, I noticed this issue when removing my port from my apps.

Yes, as TemplateBinding does, No issues with Maui TouchEffect i can fix it by setting BindingContext. but RelativeSource maybe a little difficulty

maonaoda avatar Apr 05 '24 10:04 maonaoda

ugh just got into this RelativeSource crashing issue now while adapting TouchBehavior to my existing project... :( my TouchBehavior is inside a control template which exists in a resource dictionary file, so it doesn't have a direct way of linking to its ancestors other than specifying their type....

MitchBomcanhao avatar Apr 05 '24 14:04 MitchBomcanhao

@MitchBomcanhao https://github.com/CommunityToolkit/Maui/issues/1783#issuecomment-2031418276

tranb3r avatar Apr 05 '24 15:04 tranb3r

not sure what you meant there, @tranb3r - the binding situation has changed since 8.0.1 was released, but it seems the relative source crashing (in my case when binding a command) is still there.

MitchBomcanhao avatar Apr 05 '24 15:04 MitchBomcanhao

not sure what you meant there, @tranb3r - the binding situation has changed since 8.0.1 was released, but it seems the relative source crashing (in my case when binding a command) is still there.

Exactly. I just wanted to remind that this issue has already been reported. There is crash when using RelaliveSource. This has nothing to do with the BindingContext being set automatically in 8.0.1 or manually in 8.0.0.

tranb3r avatar Apr 05 '24 15:04 tranb3r

I'm struggling to get the touch behavior to work in a DataTemplate.

My workaround is to use the TouchBehavior for my animation, and use a Gesture Recognizer for the command and parameter

<DataTemplate x:Key="RecentDeviceTemplate" x:DataType="models:DeviceItem">
                <Frame Padding="16,8,16,8" BackgroundColor="Transparent">
                    <Frame
                        x:Name="Frame"
                        AutomationId="DeviceCell"
                        Style="{StaticResource AppPancakeView}">
                        <Frame.GestureRecognizers>
                            <TapGestureRecognizer Command="{Binding ConnectDeviceCommand, Source={RelativeSource AncestorType={x:Type viewModels:ConnectNewDeviceViewModel}}}"
                                                  CommandParameter="{Binding .}"/>
                        </Frame.GestureRecognizers>
                        <Frame.Behaviors>
                            <mct:TouchBehavior DefaultAnimationDuration="250"
                                               DefaultAnimationEasing="{x:Static Easing.SpringOut}"
                                               PressedScale=".95"/> 
                        </Frame.Behaviors>
                        <StackLayout Orientation="Horizontal" Spacing="8">
                            ...
                        </StackLayout>
                    </Frame>
                </Frame>
            </DataTemplate>

Hackmodford avatar Apr 05 '24 16:04 Hackmodford