2
votes

I have a ControlTemplate I intend to use to provide basic functionality to most all pages in my app.

Good References on the idea:

I came across something strange while trying to Bind a TapGestureRecognizer to an ICommand in the binding context. I was able to bind to the ICommand property in my View just fine using a Button, but not at all using a TapGestureRecognizer.

I have a back arrow image I'd like to use for backward navigation on most all pages and using an Image with a TapGestureRecognizer seems a natural fit if I can get the command to bind to my View (like it does when using a button).

The button above the frame below was an experiment to prove that the command was coded ok. Strangely the button works just fine with the exact same binding for its command, while the TGR doesn't do a thing.

Part of my ControlTemplate XAML:

<Button Grid.Row="1" HorizontalOptions="Start" VerticalOptions="Start" IsVisible="{TemplateBinding BindingContext.ShowBack}" Text ="Back" Command="{TemplateBinding BindingContext.BackNavCommand}" Margin="90,0,0,0"/>

<!-- LATE EDIT - The TGR on this Frame is doomed not to work.  The whole frame should be defined BELOW the Content Presenter.  Read on below for why. -->
<Frame Grid.Row="1" HorizontalOptions="Start" VerticalOptions="Start" IsVisible="{TemplateBinding BindingContext.ShowBack}" BackgroundColor="Cyan" BorderColor="Transparent" HasShadow="False" HeightRequest="50" WidthRequest="50">
<Frame.Padding>
<OnPlatform x:TypeArguments="Thickness">
<On Platform ="iOS" Value="5,25,0,0"/>
<On Platform ="Android" Value="5,5,0,0"/>
<On Platform ="UWP" Value="5,5,0,0"/>
</OnPlatform>
</Frame.Padding>
<Frame.GestureRecognizers>
<TapGestureRecognizer x:Name="backTapped" Command="{TemplateBinding BindingContext.BackNavCommand}" />
</Frame.GestureRecognizers>
<Image x:Name="backImage"  Source="back.png" Aspect="AspectFit" HeightRequest="20" WidthRequest="30" VerticalOptions="Center" HorizontalOptions="Center" InputTransparent="True"/>
</Frame>
<ContentPresenter Grid.Row="1"/>

I set the backgroundcolor of the Frame containing the image Cyan to make sure my tap region was showing up. It is clearly there, but tapping it never activates the TGR and the associated command I've set for it to fire.

Note: My content to fill in the ContentPresenter has a transparent background, to show the main page's backing image. This is key to this scenario, as it's tricky to tell what is infront of what.

1

1 Answers

0
votes

Aha! As often happens, when you set out to clearly define your problem, you spark ideas for how to fix it.

I came across this before, but had forgotten: Z ORDER!

I knew my ControlTemplate control would share the space of the Content Presenter because it is all being put in Grid.Row 1. To get this TapGestureRecognizer to work properly, I needed to define it in my ControlTemplate XAML below the ContentPresenter. By doing so, the control still winds up in the exact same placement, BUT it winds up on TOP of the template content because it was defined later (higher up in the visual stack you could say).

Just like that the TGR works. The button has less manners it seems. The button worked fine as shown here, above the ContentPresenter.

Note: I found this a nifty way to put very much re-used ActivityIndicator in my ControlTemplate. Again, it had to be below the ContentPreseneter, or the spinner would show up in the background, between entry fields, etc.

Tip: Maybe don't share space with your templated controls, but if you do and you want templated controls to be accessible and in front, then define below them ContentPresenter in your template!

A Note on TemplateBinding.

Many places have noted that when you bind a ControlTemplate control, you use this syntax (binding IsVisible here for example):

IsVisible="{TemplateBinding BindingContext.ShowBack}"

Exactly what BindingContext here refers to depends.... If your page sets the BindingContext to "this" in its constructor, then the template will look for a bindable property 'ShowBack' in the view that the template is wrapping with content.
However, if you set the BindingContext of the view that the template is wrapping with content to be a ViewModel object, then BindingContext will look there for a bindable property named ShowBack. Learned this from experimenting and thought I'd share.