Maui
Maui copied to clipboard
[BUG] TouchBehavior crash
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
- Create a new Maui project
- Install the mct (Maui community toolkit)
- Create some sort of class to represent an object, like fruits.
- create a collectionView
- Create a border
- 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
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
Already tried and nothing
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
Here is a repro: https://github.com/r-work/TouchBehaviorBug
@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="Pageon your root element (assumingContentPage) - Set the
BindingContext:BindingContext="{Binding Source={x:Reference Page}, Path=BindingContext}" - Reference the
Commandnormally:Command="{Binding IncreaseTouchCountCommand}"
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
@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.
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);
}
@r-work can you name your Frame instead?
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.
@r-work can you name your
Frameinstead?
Can you use Border instead? AFAIK Frame is kind compatibility, in .NET MAUI, and should be replaced by Border. From docs
@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.
Not exactly a duplicate. I see 3 issues here.
-
It seems like setting the BindingContext with RelativeSource does not work. It just crashes.
-
How do you set the BindingContext to the parent viewmodel using Source (not RelativeSource) when your control xaml is in a separate file?
-
Why do we need to set the BindingContext, when the Command binding defines its own Source or RelativeSource?
@tranb3r excuse me, I didn't pick up those points.
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.
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.
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 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
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!
also can`t use TemplateBinding directly under ContentPresenter.
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
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?
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.
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.
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
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 https://github.com/CommunityToolkit/Maui/issues/1783#issuecomment-2031418276
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.
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.
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>