Search code examples
xamlxamarin.formspull-to-refresh

Xamarin RefreshView causing Tapped Event conflicts on UWP


I have a multi-platform Xamarin project with a MainPage (called OVerviewPage) that has multiple FlexLayouts with each one bound to a different BindableLayout.ItemSource. The BindableLayout.ItemTemplate for each is a ContentView with each of them formatted similarly but each having a different ViewModel.Model. Each of those ContentViews has its own <Frame.GestureRecognizers> TapGestureRocgnizer calling a Tapped event handled in the code behind. These Flexlayouts are wrapped in a ScrollView which is also wrapped in a RefreshView. The intent is for the user to click on one for the ContentViews to be taken to a details page. The problem is that the RefreshView causes a conflict when compiled for UWP on a windows Desktop. The problem is that after scrolling down the list and the user clicks on a ContentView the TappedEvent handler of a different ContentView will be called, not the one the user actually clicked on. In fact, when the form first loads 3 ContentViews are visible on the screen, when others are scrolled to and reside in a place over where one of the first 3 were, only a tapped event of one of those first 3 will respond to a click, depending where on the screen where the user clicked. When I comment out the RefreshView in xaml, the clicks work as expected. I understand that the RefreshView is not designed for desktop units and I tried to disable it using: if(Device.Idiom == DeviceIdiom.Desktop){refreshview.IsEnabled = false} but that doesn't work. The mere fact that it is identified in the xaml causes the conflict. A couple considerations: This page is the main page of a Shell application and any solution will need to take that into consideration. I am also open to most any solution that solves this. Thanks in advance

My OverviewPage: (only the code that is relavant)

            <RefreshView IsRefreshing="{Binding IsRefreshing, Mode=OneWay}"
                     RefreshColor="Teal"
                     Command="{Binding RefreshCommand}">
            
            <ScrollView>
                <StackLayout Margin="10,0,10,0">
                    
                    <FlexLayout Direction="Column"
                                Wrap="NoWrap"
                                AlignItems="Stretch"
                                AlignContent="Center"
                                BindableLayout.ItemsSource="{Binding TrackWarrants}"
                                BindableLayout.ItemTemplate="{StaticResource TrackWarrantTemplate}" 
                                BindableLayout.EmptyViewTemplate="{StaticResource EmptyWarrantTemplate}"/>
                                 
                    <!--Form As-->
                    <FlexLayout Direction="Column"
                            Wrap="NoWrap"
                            AlignItems="Stretch"
                            AlignContent="Center"
                            BindableLayout.ItemsSource="{Binding FormAs}"
                            BindableLayout.ItemTemplate="{StaticResource FormATemplate}" 
                            BindableLayout.EmptyViewTemplate="{StaticResource EmptyFormATemplate}"/>
                    
                    <!--Form Bs-->                      
                    <FlexLayout Direction="Column"
                                Wrap="NoWrap"
                                AlignItems="Stretch"
                                AlignContent="Center"
                                BindableLayout.ItemsSource="{Binding FormBs}"
                                BindableLayout.ItemTemplate="{StaticResource FormBTemplate}" 
                                BindableLayout.EmptyViewTemplate="{StaticResource EmptyFormBTemplate}"/>

                    <!--Form Cs-->
                    <FlexLayout Direction="Column"
                                Wrap="NoWrap"
                                AlignItems="Stretch"
                                AlignContent="Center"
                                BindableLayout.ItemsSource="{Binding FormCs}"
                                BindableLayout.ItemTemplate="{StaticResource FormCTemplate}" 
                                BindableLayout.EmptyViewTemplate="{StaticResource EmptyFormCTemplate}"/>

ContentViews Code: (only 1 for example they are all the same except for the data placement)

    <ContentView.Content>
    <Frame>
        <Frame.GestureRecognizers>
        <TapGestureRecognizer CommandParameter="{Binding}" Tapped="FormA_Tapped" />
        </Frame.GestureRecognizers>

        <Grid  >
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="30"/>
                <ColumnDefinition Width="70"/>
                <ColumnDefinition Width="25"/>
                <ColumnDefinition Width="25"/>
                <ColumnDefinition Width="25"/>
                <ColumnDefinition Width="30"/>
                <ColumnDefinition Width="95"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="25"/>
                <RowDefinition Height="25"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="20"/>
            </Grid.RowDefinitions>

            <StackLayout Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" Grid.ColumnSpan="2" HorizontalOptions="StartAndExpand" VerticalOptions="Start" 
                         Orientation="Horizontal" WidthRequest="130" >
                <Label FontFamily="Material" FontSize="Large" Text="{Binding Icon}" HorizontalOptions="Start" VerticalOptions="Start"
                   Grid.Column="0" Grid.Row="0" Grid.RowSpan="2">
                    <Label.Triggers>
                        <DataTrigger TargetType="Label" Binding="{Binding Source={x:Reference Reject},Path=Text}" Value="True">
                            <Setter Property="TextColor" Value="Black"/>
                        </DataTrigger>
                    </Label.Triggers>
                </Label>
                <Label  FontSize="Medium" FontAttributes="Bold" HorizontalOptions="Start"
                    VerticalOptions="Start" x:Name="FormID">
                    <Label.FormattedText>
                        <FormattedString>
                            <Span Text="{Binding FormNumber}"/>
                            <Span Text="-"/>
                            <Span Text="{Binding LineNo}"/>
                        </FormattedString>
                    </Label.FormattedText>
                    <Label.Triggers>
                        <DataTrigger TargetType="Label" Binding="{Binding Source={x:Reference Reject},Path=Text}" Value="True">
                            <Setter Property="TextColor" Value="Black"/>
                        </DataTrigger>
                    </Label.Triggers>
                </Label>
                <StackLayout.Triggers>
                    <DataTrigger TargetType="StackLayout" Binding="{Binding Source={x:Reference Reject},Path=Text}" Value="True">
                        <Setter Property="BackgroundColor" Value="Salmon"/>
                    </DataTrigger>
                </StackLayout.Triggers>
            </StackLayout>
            
            <Label Text="{Binding Territory}" 
               FontSize="Small"
               VerticalOptions="Center"
               HorizontalOptions="Start"
               Grid.Column="0"
               Grid.Row="3" Grid.ColumnSpan="6" />

            <Label FontFamily="Material" 
               FontSize="Small" 
               Text="{x:Static md:MaterialDesignIcons.MyLocation}" 
               HorizontalOptions="Center"
               Grid.Column="2"
               Grid.Row="0"  />

            <Label Text="{Binding From}" 
               FontSize="Small"     
               Grid.Column="3"
               Grid.Row="0" Grid.ColumnSpan="4" />

            <Label FontFamily="Material" 
               FontSize="Small" 
               Text="{x:Static md:MaterialDesignIcons.LocationOn}" 
               HorizontalOptions="Center"
               Grid.Column="2"
               Grid.Row="1" />

            <Label Text="{Binding End}" 
               FontSize="Small"   
               Grid.Column="3"    
               Grid.Row="1"  Grid.ColumnSpan="4"  />
            
            <Label FontSize="Small"  
               FontAttributes="Bold" 
               HorizontalOptions="StartAndExpand"
               Grid.Column="1"
               Grid.Row="2"   
               Grid.ColumnSpan="5">
                <Label.FormattedText>
                    <FormattedString>
                        <Span Text="Speed: "/>
                        <Span Text="{Binding Speed}"/>
                    </FormattedString>
                </Label.FormattedText>
            </Label>

            <Label x:Name="Reject" Text="{Binding Rejected}" 
               FontSize="Small"                  
               Grid.Column="0"
               Grid.Row="3" IsVisible="False" />
            <Label x:Name="status" Text="{Binding StatusID}" 
               FontSize="Small"                  
               Grid.Column="0"
               Grid.Row="3" IsVisible="False" ></Label>
        </Grid>
    </Frame>
</ContentView.Content>

Relevant Code Behind for ContentView:

        public FormACard()
    {
        InitializeComponent();
        formaviewmodel = new FormAViewModel();
        BindingContext = formaviewmodel;
    }
    private async void FormA_Tapped(object sender, EventArgs e)
    {
        var user = App.LoggedInUser.UserName;

        var ea = e as TappedEventArgs;
        if (ea.Parameter != null)
        {
            var forminfo = JsonConvert.SerializeObject(ea.Parameter);
            string _forminfo = Uri.EscapeDataString(forminfo);
            FormA forma = (FormA)ea.Parameter;
            bool rejectAck = forma.RejectionAcked;
            bool rejected = forma.Rejected;
            string requestor = forma.RequestedBy;
            if (!rejectAck && rejected && requestor == user)
            {
                await Shell.Current.GoToAsync($"{nameof(RejectACK)}?{nameof(RejectACK.Contentx)}={_forminfo}");
            }
            else
            {
                await Shell.Current.GoToAsync($"viewforma?Content={_forminfo}");
            }

        }
    }

FYI - In case this looks familiar, I posted this problem in another thread when I had less information. I closed the other one and open this one with fresh information.


Solution

  • The problem is that after scrolling down the list and the user clicks on a ContentView the TappedEvent handler of a different ContentView will be called, not the one the user actually clicked on.

    About "Xamarin RefreshView causing Tapped Event conflicts on UWP", I found this on GitHub: [Bug] Wrong behavior using BindableLayout wrapped by a RefreshView. The link mentions:

    Tested this based on your repro. Found out that its not related to the BindableLayout: you can eliminate it completely, just creating 20 frames directly and it results in the same misbehavior. It seems that - as soon as RefreshView (UWP RefreshContainer) is used - tap events are dispatched to wrong UI controls and/or that scroll offset is not considered correctly. Not sure currently if this is an XF bug or an issue in UWP RefreshContainer, even though I didn't find a matching issue (after short search).

    If RefreshView does not affect the operation of your entire program, you can remove it. Otherwise you can wait for the bug to be fixed.

    Wish it can help you.