Search code examples
c#.netxamlmauimaui-community-toolkit

Both `TouchBehavior.CommandParameter` and `TouchBehavior.LongPressCommandParameter` are always passed as `null` to `Command` and `LongPressCommand`


Both TouchBehavior.CommandParameter and TouchBehavior.LongPressCommandParameter are always passed as null to Command and LongPressCommand.

In the following example, when an item in the CollectionView is tapped, the resulting alert will always display Selected value: because the CommandParameter is always null. It should say Selected vale: Item 1, for example, when the user taps Item 1.

enter image description here

<VerticalStackLayout Padding="30,0"
                     Spacing="25">
    <Label Text="{Binding Title}"
           Style="{StaticResource Headline}"
           SemanticProperties.HeadingLevel="Level1" />

    <Label Text="Tapping or long pressing an item does not correctly pass that item as CommandParameter or LongTouchCommandParameter."
           Style="{StaticResource SubHeadline}"
           SemanticProperties.HeadingLevel="Level1"  />

    <CollectionView x:Name="collectionView"
                    ItemsSource="{Binding Items}">
        <CollectionView.ItemTemplate>
            <DataTemplate x:DataType="{x:Type x:String}">
                <Label Padding="10"
                       Text="{Binding .}"
                       x:Name="Label">
                    <Label.Behaviors>
                        <toolkit:TouchBehavior Command="{Binding Source={x:Reference collectionView}, Path=BindingContext.ShowItemCommand}"
                                               CommandParameter="{Binding .}"
                                               LongPressCommand="{Binding Source={x:Reference collectionView}, Path=BindingContext.ShowItemCommand}"
                                               LongPressCommandParameter="{Binding .}"
                        <!--
                        CommandParameter and LongPressCommandParameter are always passed as null in the CommunityToolkit.Maui TouchBehavior.
                        this was not the case with https://github.com/Axemasta/Maui.TouchEffect -->
                    </Label.Behaviors>
                </Label>
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>

</VerticalStackLayout>
public partial class MainViewModel : ObservableObject
{
    public string Title => "Hello, Maui!";

    public ObservableCollection<string> Items { get; set; } = ["Item 1", "Item 2", "Item 3"];

    /// <summary>
    /// item is always null. This was not the case in https://github.com/Axemasta/Maui.TouchEffect
    /// </summary>
    /// <param name="item"></param>
    /// <returns></returns>
    [RelayCommand]
    private Task ShowItem(string item) => Application.Current.MainPage.DisplayAlert($"Selected value:", item, "OK");
}

Sample Reproduction

Here is a link to a reproduction sample for this bug: https://github.com/hansmbakker/bugrepro-communitytoolkit-maui-touchbehavior-parameter


Solution

  • Explanation

    In .NET MAUI, you are required to manually set the BindingContext for every Behavior. In other words, Behaviors do not inherit the BindingContext of the VisualElement to which they are attached.

    Source: https://learn.microsoft.com/dotnet/maui/fundamentals/behaviors?view=net-maui-8.0#create-a-net-maui-behavior

    image

    Updated Code

    Here is the updated XAML to get your code working. It uses a view-to-view binding to bind TouchBehavior.BindingContext to Label.BindingContext:

    <VerticalStackLayout Padding="30,0"
                         Spacing="25">
        <Label Text="{Binding Title}"
               Style="{StaticResource Headline}"
               SemanticProperties.HeadingLevel="Level1" />
    
        <Label Text="Tapping or long pressing an item does not correctly pass that item as CommandParameter or LongTouchCommandParameter."
               Style="{StaticResource SubHeadline}"
               SemanticProperties.HeadingLevel="Level1"  />
    
        <CollectionView x:Name="collectionView"
                        ItemsSource="{Binding Items}">
            <CollectionView.ItemTemplate>
                <DataTemplate x:DataType="{x:Type x:String}">
                    <Label Padding="10"
                           Text="{Binding .}"
                           x:Name="label">
                        <Label.Behaviors>
                            <toolkit:TouchBehavior Command="{Binding Source={x:Reference collectionView}, Path=BindingContext.ShowItemCommand}"
                                                   CommandParameter="{Binding .}"
                                                   LongPressCommand="{Binding Source={x:Reference collectionView}, Path=BindingContext.ShowItemCommand}"
                                                   LongPressCommandParameter="{Binding .}"
                                                   BindingContext="{Binding Source={x:Reference label}, Path=BindingContext}"/>
                        </Label.Behaviors>
                    </Label>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    
    </VerticalStackLayout>
    

    A bit unrelated to the Behavior BindingContext problem, here is my recommendation for small performance improvement to your ViewModel (both Title and Items can be read-only properties):

    public partial class MainViewModel : ObservableObject
    {
        public string Title { get; } = "Hello, Maui!";
    
        public ObservableCollection<string> Items { get; } = ["Item 1", "Item 2", "Item 3"];
    
        [RelayCommand]
        private Task ShowItem(string item) => Application.Current.MainPage.DisplayAlert($"Selected value:", item, "OK");
    }
    

    Screenshot of Updated App

    Here is a screen shot of your app working as expected after updating the code:

    .NET MAUI Community Toolkit Unit Tests

    Here are the Unit Tests I wrote for the TouchBehavior in CommunityToolkit.Maui where we verify the LongPressCommand does indeed pass in LongPressCommandParameter;

    https://github.com/CommunityToolkit/Maui/blob/51dc63a7642370849e413d87072b668fa022e303/src/CommunityToolkit.Maui.UnitTests/Behaviors/TouchBehaviorTests.cs#L612-L674

    And here are the Unit Tests I wrote for the TouchBehavior in CommunityToolkit.Maui where we verify the Command does indeed pass in CommandParameter;

    https://github.com/CommunityToolkit/Maui/blob/51dc63a7642370849e413d87072b668fa022e303/src/CommunityToolkit.Maui.UnitTests/Behaviors/TouchBehaviorTests.cs#L550-L610