AutomationId for TabBar items not getting set in Android with Xamarin Forms - xamarin

So I have the following code:
<TabBar Route="Dashboard">
<Tab Title="Dashboard" AutomationId="DashboardId">
//more codes here
</Tab>
<Tab AutomationId="AddNewId">
//more codes here
</Tab>
<Tab Title="Statistics" AutomationId="StatisticsId">
//more codes here
</Tab>
</TabBar>
Note that in my MainActivity's OnCreate() I have set up the following:
Xamarin.Forms.Forms.ViewInitialized += (object sender, Xamarin.Forms.ViewInitializedEventArgs e) => {
if (!string.IsNullOrWhiteSpace(e.View.AutomationId))
{
e.NativeView.ContentDescription = e.View.AutomationId;
}
};
This works perfectly with my other elements except for the TabBar items. Somehow the TabBar items are getting the Title property and setting is at the accessibilityId/content-dec.
Anyone knows why this is and how can I make it so it will get the right AutomationId? Thanks

There are multiple issues with AutomationId on Android.
The underlying problem is discussed in Android - Using AutomationId prevents TalkBack screenreader accessibility:
Xamarin.Forms "borrows" the ContentDescription property on Android for Automation IDs. These IDs polute Android's TalkBack accessibility tree, making apps almost impossible to navigate.
This means you can support test automation or accessibility, not both. Our app needs to support both.
In the case of Tabs, presumably Xamarin code is doing what you see: copying Title to content-desc, so that Android text readers will speak it.
The suggested work-around is to write custom renderer(s) that do what you need. Described in a comment by codingL3gend :
i was able to find a work around to this issue by creating a customrenderer and respective custom component to allow for overriding the native android method(s) that get triggered when accessibility events are fired. you will need to create some bindable properties on your custom component that you can access in the custom renderer to allow for setting the content description value to what you want but that's simple enough.
this method gets triggered in the control/custom renderer whenever an accessibility event is fired
public override bool OnRequestSendAccessibilityEvent(Android.Views.View child, AccessibilityEvent e)
{
if (AccessibilityHandler.IsAccessibilityEnabled(_context) && child != null)
{
if (!string.IsNullOrEmpty(_automationId) && _automationId.Equals(child.ContentDescription))
{
child.ContentDescription = $"{_automationName} {_helpText}";
}
}
return base.OnRequestSendAccessibilityEvent(child, e);
}
then you can set the contentDescription value of the control/custom renderer back to what the automationId value was originally when the control/custom renderer is detached from the view.
protected override void OnDetachedFromWindow()
{
base.OnDetachedFromWindow();
if (!string.IsNullOrEmpty(_automationId))
{
Control.ContentDescription = _automationId;
}
}
helper class
public static class AccessibilityHandler
{
public static bool IsAccessibilityEnabled(Context context)
{
var accessibility = (AccessibilityManager)context.GetSystemService(MainActivity.AccessibilityService);
return accessibility?.GetEnabledAccessibilityServiceList(Android.AccessibilityServices.FeedbackFlags.Spoken)?.Count > 0;
}
}
If you only need AutomationId during testing, or you can live with the effect this has on Accessibility Screen Readers (esp. it won't be multi-lingual), then you could make a much simpler custom renderer for use when testing.
Put this in your custom renderer (if isn't Tab, then change <Tab> to appropriate Xamarin class):
protected override void OnElementChanged( ElementChangedEventArgs<Tab> e )
{
base.OnElementChanged( e );
if (e.OldElement != null)
{
// Removing previous element.
// TBD: Remove obsolete references. (usually not needed)
}
if (Element == null)
// Going away with no replacement.
return;
UpdateAutomationId();
}
void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Element.AutomationId))
{
UpdateAutomationId();
}
}
void UpdateAutomationId()
{
var _automationId = Element.AutomationId;
if (!string.IsNullOrEmpty(_automationId))
{
Control.ContentDescription = _automationId;
}
}

Related

Xamarin Forms Android Autosize Label TextCompat pre android 8 doesn't autosize text

I want to utilise the auto-sizing feature of android textviews in my xamarin forms solution so that as the text length grows, the font sizes shrinks to never overflow the bounds of the label, and doesn't get truncated. I've created a custom Label control to do so and added an android custom renderer. It's not working in Android 7 and below. It is working in Android 8 and above.
According to the docs autosize support was introduced in android 8, but can be supported back to Android 4 with AppCompat.v4. However, my custom rendered label just renders the default font size in Android pre 8. It works fine in 8+ devices, the label text resizes as needed to not overflow the bounds. The accepted answer to this question with a similar issue on native android says it can be to do with not setting a width and height, I've tried setting widthrequest and heightrequest explicitly and it doesn't change anything. Also setting maxlines=1 doesn't change anything. An alternative thread suggests that custom fonts are the culprit. I created a vanilla forms solution using the default device font, and get the same effect.
My code:
internal class AutosizeLabelRenderer : LabelRenderer
{
#region constructor
public AutosizeLabelRenderer(Context context) : base(context)
{
}
#endregion
#region overridable
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (e.NewElement == null || !(e.NewElement is AutoSizeLabel autoLabel) || Control == null) { return; }
TextViewCompat.SetAutoSizeTextTypeUniformWithConfiguration(Control, autoLabel.AutoSizeMinTextSize,
autoLabel.AutoSizeMaxTextSize, autoLabel.AutoSizeStepGranularity, (int)ComplexUnitType.Sp);
}
#endregion
}
public class AutoSizeLabel : Label
{
public int AutoSizeMaxTextSize
{
get => (int)GetValue(AutoSizeMaxTextSizeProperty);
set => SetValue(AutoSizeMaxTextSizeProperty, value);
}
public static readonly BindableProperty AutoSizeMaxTextSizeProperty = BindableProperty.Create(
nameof(AutoSizeMaxTextSize), // the name of the bindable property
typeof(int), // the bindable property type
typeof(AutoSizeLabel)); // the default value for the property
public int AutoSizeMinTextSize
{
get => (int)GetValue(AutoSizeMinTextSizeProperty);
set => SetValue(AutoSizeMinTextSizeProperty, value);
}
public static readonly BindableProperty AutoSizeMinTextSizeProperty = BindableProperty.Create(
nameof(AutoSizeMinTextSize), // the name of the bindable property
typeof(int), // the bindable property type
typeof(AutoSizeLabel)); // the default value for the property
public int AutoSizeStepGranularity
{
get => (int)GetValue(AutoSizeStepGranularityProperty);
set => SetValue(AutoSizeStepGranularityProperty, value);
}
public static readonly BindableProperty AutoSizeStepGranularityProperty = BindableProperty.Create(
nameof(AutoSizeStepGranularity), // the name of the bindable property
typeof(int), // the bindable property type
typeof(AutoSizeLabel)); // the default value for the property
//
}
Not working: Android 7 - text does not shrink
Working as expected: Android 8 and above
Xaml for above images:
<StackLayout HeightRequest="200" WidthRequest="100">
<Label Text="Fixed width and height, sentences get longer, text should shrink" />
<controls:AutoSizeLabel
AutoSizeMaxTextSize="50"
AutoSizeMinTextSize="8"
AutoSizeStepGranularity="1"
BackgroundColor="{StaticResource Shamrock}"
HeightRequest="40"
HorizontalOptions="Start"
MaxLines="1"
Text="A small sentence"
WidthRequest="200" />
<controls:AutoSizeLabel
AutoSizeMaxTextSize="50"
AutoSizeMinTextSize="8"
AutoSizeStepGranularity="1"
BackgroundColor="{StaticResource Shamrock}"
HeightRequest="40"
HorizontalOptions="Start"
MaxLines="1"
Text="A larger sentence that shrinks"
WidthRequest="200" />
<controls:AutoSizeLabel
AutoSizeMaxTextSize="50"
AutoSizeMinTextSize="8"
AutoSizeStepGranularity="1"
BackgroundColor="{StaticResource Shamrock}"
HeightRequest="40"
HorizontalOptions="Start"
MaxLines="1"
Text="An even larger sentence that shrinks more."
WidthRequest="200" />
</StackLayout>
TextView font size changes with the size of the control, which is new in Android 8.0 (API26),therefore, compatibility issues need to be considered when using the previous version.You could change the TextView to AppCompatTextView.
Change your
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (e.NewElement == null || !(e.NewElement is AutoSizeLabel autoLabel) || Control == null) { return; }
AppCompatTextView appCompatTextView = new AppCompatTextView(_context);
appCompatTextView.Text = Element.Text;
appCompatTextView.SetMaxLines(1);
SetNativeControl(appCompatTextView);
TextViewCompat.SetAutoSizeTextTypeUniformWithConfiguration(Control,autoLabel.AutoSizeMinTextSize,autoLabel.AutoSizeMaxTextSize, autoLabel.AutoSizeStepGranularity, (int)ComplexUnitType.Sp);
}
Leo Zhu's answer got me most of the way there. There were a couple of extra steps I needed to take to get it fully working, so I'm posting the code as a separate answer here.
Differences between mine and Leo's answer:
Creating a new native control in scope like Leo suggested meant that it worked for a while but got disposed by the garbage collector and caused an exception when returning to the page after navigating away. To fix this I needed to override a property called ManageNativeControlLifetime to return false, and then manually manage disposing the object by overriding the dispose method and calling Control.RemoveFromParent();. This advice comes from a xamarin staff member in this thread.
Formatting and binding context are not automatically inherited when creating the new native control and need to be set manually. I needed to add those based on my needs using the android specific binding syntax. You may need to add other formatting and binding code based on your needs, I'm just doing font colour, gravity and binding context here.
I set the binding context with
appCompatTextView.SetBindingContext(autoLabel.BindingContext);
Once the binding context was set, I needed to add a new string property to my XF AutoSizeLabel class to pass in through XAML, then use it to set the binding path for the relevant property (In my case the text property). If more than one binding is required, you would need to add multiple new binding path properties for each required property. I set a specific binding like this:
appCompatTextView.SetBinding("Text", new Binding(autoLabel.TextBindingPath));
To facilitate this in my Xamarin Forms Xaml, my Xaml went from <Label Text="{Binding MyViewModelPropertyName}" /> to <controls:AutoSizeLabel TextBindingPath="MyViewModelPropertyName" />
Here's the full code of the renderer:
protected override bool ManageNativeControlLifetime => false;
protected override void Dispose(bool disposing)
{
Control.RemoveFromParent();
base.Dispose(disposing);
}
private AppCompatTextView appCompatTextView;
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (e.NewElement == null || !(e.NewElement is AutoSizeLabel autoLabel) || Control == null) { return; }
//v8 and above supported natively, no need for the extra stuff below.
if (DeviceInfo.Version.Major >= 8)
{
Control?.SetAutoSizeTextTypeUniformWithConfiguration(
autoLabel.AutoSizeMinTextSize,
autoLabel.AutoSizeMaxTextSize, autoLabel.AutoSizeStepGranularity,
(int)ComplexUnitType.Sp);
return;
}
appCompatTextView = new AppCompatTextView(Context);
appCompatTextView.SetTextColor(Element.TextColor.ToAndroid());
appCompatTextView.SetMaxLines(1);
appCompatTextView.Gravity = GravityFlags.Center;
appCompatTextView.SetBindingContext(autoLabel.BindingContext);
appCompatTextView.SetBinding("Text", new Binding(autoLabel.TextBindingPath));
SetNativeControl(appCompatTextView);
TextViewCompat.SetAutoSizeTextTypeUniformWithConfiguration(Control, autoLabel.AutoSizeMinTextSize, autoLabel.AutoSizeMaxTextSize, autoLabel.AutoSizeStepGranularity, (int)ComplexUnitType.Sp);
}

How to set AutomationId on TabbedPage bar items in Xamarin Forms app for UI test

I'm writing tests in Xamarin UI Test for a tab-based Xamarin Forms app. I'd like to set the automation Ids on each tab item so that my UI Test can click a specific tab, without referring to the tab's Text label, which is localized.
I imagine you need to use a custom renderer and set ContentDescription (Android) and AccessibilityIdentifier (iOS), and I've been trying to do that, with mixed results. What is the correct way to do this? If I'm on the right track with custom renderer, which renderer method(s) should I override in IOS/Android to achieve this?
UPDATE:
iOS:
Answer was provided by #apineda. See his solution below the question.
Android: Seems to required a custom renderer. It's a little yucky but it works. We have to recursively search the view hierarchy for the tab bar items and set "ContentDescription" for each. Since we are using a bottom-navigation bar, we search backwards for better performance. For topside navigation bar, you'll need to search for "TabLayout" instead of "BottomNavigationItemView".
[assembly: ExportRenderer(typeof(MainPage), typeof(CustomTabbedPageRenderer))]
namespace Company.Project.Droid.CustomRenderers
{
public class CustomTabbedPageRenderer : TabbedRenderer
{
private bool tabsSet = false;
public CustomTabbedPageRenderer(Context context)
: base(context)
{
}
protected override void DispatchDraw(Canvas canvas)
{
if (!tabsSet)
{
SetTabsContentDescription(this);
}
base.DispatchDraw(canvas);
}
private void SetTabsContentDescription(Android.Views.ViewGroup viewGroup)
{
if (tabsSet)
{
return;
}
// loop through the view hierarchy backwards. this will work faster since the tab bar
// is at the bottom of the page
for (int i = viewGroup.ChildCount -1; i >= 0; i--)
{
var menuItem = viewGroup.GetChildAt(i) as BottomNavigationItemView;
if (menuItem != null)
{
menuItem.ContentDescription = "TabBarItem" + i.ToString();
// mark the tabs as set, so we don't do this loop again
tabsSet = true;
}
else
{
var viewGroupChild = viewGroup.GetChildAt(i) as Android.Views.ViewGroup;
if (viewGroupChild != null && viewGroupChild.ChildCount > 0)
{
SetTabsContentDescription(viewGroupChild);
}
}
}
}
}
}
You don't need CustomRenderer for this. You just need to set the AutomationId to the children Pages of the TabPage and this is assigned to the bar Item.
Let's say you have this TabPage as below
<?xml version="1.0" encoding="UTF-8"?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyGreatNamespace"
x:Class="MyGreatNamespace.MyTabPage">
<TabbedPage.Children>
<local:MainPage AutomationId="MainTab" Title="Main Page" />
<local:PageOne AutomationId="TabOne" Title="Page One" />
<local:PageTwo AutomationId="TabTwo" Title="Page Two" />
</TabbedPage.Children>
</TabbedPage>
With this configuration you will be able to do:
app.Tap("TabTwo");
And you won't need to use the Text property.
Hope this helps.-
UPDATE:
Just confirmed the above does not work with Android (noticed your original question is for Android) but only with iOS. For some reason the behavior is different.
You can still use the Localized version of the Text to "Tap it" as explained below.
A trick you can use when dealing with Localized Text is that you set the right Culture then use the same resource set in the XAML as part of the Test.
i.e
app.Tap(AppResources.MyMainTabText);

How to hide navigation Toolbar icon in xamarin?

I want to hide navigation bar button in xamarin. how can i do that using binding. Toolbar item doesn't have "IsVisible" property.
Following is my xaml code
please help me to sort out this issue.
I would suggest to build a bindable ToolBoxItem. That way you can control the visibility through a view model property.
An implementation could look like that:
public class BindableToolbarItem : ToolbarItem
{
public static readonly BindableProperty IsVisibleProperty = BindableProperty.Create(nameof(IsVisible), typeof(bool), typeof(BindableToolbarItem), true, BindingMode.TwoWay, propertyChanged: OnIsVisibleChanged);
public bool IsVisible
{
get => (bool)GetValue(IsVisibleProperty);
set => SetValue(IsVisibleProperty, value);
}
private static void OnIsVisibleChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var item = bindable as BindableToolbarItem;
if (item == null || item.Parent == null)
return;
var toolbarItems = ((ContentPage)item.Parent).ToolbarItems;
if ((bool)newvalue && !toolbarItems.Contains(item))
{
Device.BeginInvokeOnMainThread(() => { toolbarItems.Add(item); });
}
else if (!(bool)newvalue && toolbarItems.Contains(item))
{
Device.BeginInvokeOnMainThread(() => { toolbarItems.Remove(item); });
}
}
}
As you have discovered yourself there is not IsVisible. So you will have to implement functionality like that yourself if you still want it.
Another way would be to handle it in the pages' code-behind and remove or add the toolbar item whenever needed.
Adding and removing is simple, just add and remove items to the ToolbarItems collection: ToolbarItems.RemoveAt(0); for instance will remove the first toolbar item.
Putting #Gerald answer in action, it would be done this way:
void Done_Clicked(System.Object sender, System.EventArgs e)
{
//Do somthing and hide the done item
ShowDoneToolbarItem(false, (ToolbarItem)sender);
}
void Entry_Focused(System.Object sender, Xamarin.Forms.FocusEventArgs e)
{
//Show the done item
ShowDoneToolbarItem(true);
}
void ShowDoneToolbarItem(bool show, ToolbarItem item = null)
{
if(show)
{
ToolbarItem done = new ToolbarItem();
done.Text = "Done";
done.Clicked += Done_Clicked;
ToolbarItems.Add(done);
}
else if(item != null)
{
ToolbarItems.Remove(item);
}
}
This is cleaner and works from the code behind.
Well we need the IsVisible property for the front end, as xamarin doesn't have it, you can use Device.RuntimePlatform to check in real time which device the application is running. Since my code is in .cs of the XAML file, we can use xaml .cs to insert items into the screen.I put if () to do the logic and check if my device is on which platform, because I don't want it to display in UWP a toolbar.
The code is in .cs of the XAML file:
public kingTest()
{
InitializeComponent();
if((Device.RuntimePlatform == "Android")||(Device.RuntimePlatform == "iOS"))
{
ToolbarItem toolbar = new ToolbarItem();
toolbar.IconImageSource = "ic_ToolBar.png";
this.ToolbarItems.Add(toolbar);
}
};
I've achieved this easily using overloaded constructors. Here's an example:
View (add the name property):
<ContentPage x:Name="ContentPage"
<!-- rest of the tag -->
/>
Code-behind (add the toolbar items):
public partial class ExamplePage : ContentPage
{
public ExamplePage()
{
InitializeComponent();
BindingContext = this;
var saveToolbarItem = new ToolbarItem { Text = "Save" };
saveToolbarItem.Clicked += YourMethodToBeRan;
ContentPage.ToolbarItems.Add(saveToolbarItem);
}
public ExamplePage(Object object)
{
InitializeComponent();
BindingContext = this;
var updateToolbarItem = new ToolbarItem { Text = "Update" };
updateToolbarItem.Clicked += YourMethodToBeRan;
var deleteToolbarItem = new ToolbarItem { Text = "Delete" };
deleteToolbarItem.Clicked += YourMethodToBeRan;
ContentPage.ToolbarItems.Add(updateToolbarItem);
ContentPage.ToolbarItems.Add(deleteToolbarItem);
}
// rest of the class
}
The above pseudocode will add the "Save" toolbar item when the class is instantiated with no parameter, or the "Update" and "Delete" when a parameter is provided.
This isn't as elegant as IsEnabled / IsVisible booleans but it's a step in the right direction. Following this train of thought, you could modify the children of your toolbar during runtime to "show" and "hide" by adding and removing them as children.
Good luck!
I don't know if #tequila slammer's solution fully worked on Xamarin, but for us it only kind of works in .Net Maui (the evolution of Xamarin) and binding the IsVisible property to a variable.
Once the BindableToolbarItem is removed from the ContentPage's list of ToolbarItems, it is disconnected from the object that IsVisible is bound to forever.
For example: We want to use this control to hide or show a ToolbarItem that navigates to the admin screen, if I log in as the administrator on app launch, the item is there...great. If I then log out and log in as a non-admin, the item is not there...perfect. If I then log out and log in as an admin, the item is not there (the propertyChanged: OnIsVisibleChanged never fired)...:-(.
Not a big deal for us, if you want admin access then stopping the app and starting the app to log in as the admin is not a big ask.
In the newest release with .Net 7 the workaround works never more !
The reason is because the toolbar item which revomed will destoyed !

TailTruncation - Ellipsize the text of a picker control in Xamarin Forms

Is it possible to truncate long texts with ellipsis in a picker control. I have already created a custom renderer to set a fontsize and no border in order to achieve the following result.
Also tried to set Control.Ellipsize = TextUtils.TruncateAt.End; but nothing happens
[assembly: ExportRenderer(typeof(NoBorderPicker), typeof(CustomPicker))]
namespace Prj.Droid.Renderers
{
public class CustomPicker : PickerRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if (Control != null)
{
var customBG = new GradientDrawable();
customBG.SetColor(Android.Graphics.Color.Transparent);
customBG.SetCornerRadius(3);
Control.SetBackground(customBG);
Control.Ellipsize = TextUtils.TruncateAt.End;
var custdatepicker = (NoBorderPicker) this.Element;
this.Control.TextSize = (float)custdatepicker.FontSize;
}
}
}
}
Now, I could be sure that Control.SetSingleLine(true); will work.
if you are using a custom renderer can be the incorret inheritance.
Use Xamarin.Forms.Platform.Android.AppCompat.PickerRenderer not Xamarin.Forms.Platform.Android.PickerRenderer
Tks to https://www.damirscorner.com/blog/posts/20201204-CustomPickerRendererOnAndroid.html
Oddly enough, for me in the latest Xamarin Forms, on Android the Picker automatically truncates text, but on iOS the Picker becomes arbitrarily wide, covering up other UI elements.
The fix is to set the MinimumWidthRequest = 1, which for some reason re-enables text truncation. I have no idea why. Welcome to Xamarin.

How to make Xamarin bubble up gestures

I'm currently in the process of developing a SideDrawer for Xamarin.Forms, because at this point, the one from telerik is rather awful sideeffect-wise.
I know how to do this in WPF, since it's rather easy, but in Xamarin it's way different.
My code for the GestureFrame is pretty much the same as this.
I've used the sources at some github project/xamarin docs/XLabs to get started. At first it was going well, but as soon as i'm placing controls within the gestureframe i will not receive any events anymore, because the childcontrols appear to consume any touch/gesture events there are.
Does this ring a bell to anyone? Right now i'm not sure what i might be doing wrong for the control to behave this way
The Only Gestures that Xamarin Forms handles currently are Tap and DoubleTap these bubble up by default. For Android, Windows and presumably IOS each handle other gestures differently.
Quick Review of Event Handling in the Xamarin.Forms world:
On Android
Gestures are handled by the Renderer each renderer has a Touch event. Touch is raised in the renderer when a gesture occurs. By subscribing to the Touch event and intupreting the EventArgs you can determine what is happening on the screen. Now you could make all the determinations yourself of what the user is doing or use the Mono.Android.GestureDetector to make those decisions for you. GestureDetector requires a GestureListener which it notifies when it believes an event like a tap or double have occured. Your Gesture listener can then contain whatever code you want to respond to these events.
On Windows
Each native control determines for itself When an event has occurred and exposes a set of EventHandlers for those events. To respond to these events you create a custom renderer and subscribe to the events on the native controls that then execute your own code.
On IOS?
Don't know yet haven't got that far in my project https://github.com/Indiponics/IndiXam-Lib maybe someone else can give you that piece.
Bubbling up the Events
Lets look at a simple bubbling situation:
public class App : Application
{
public App()
{
// The root page of your application
MainPage = new ContentPage
{
Content = new Frame
{
Content =
new Label {
Text = "Hold Me, Thrill Me, Kiss Me"
}
}
};
}
}
Lets put some Custom Renderers together and look at whats happening. To start with we'll need a renderer for every control in the stack so in our case a Label Renderer and a Frame Renderer.
We'll Start with Windows:
[assembly: ExportRenderer(typeof(Label), typeof(myLabelCustomRenderer))]
[assembly: ExportRenderer(typeof(Frame), typeof(myFrameCustomRenderer))]
namespace App4.WinPhone
{
public class myFrameCustomRenderer:FrameRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Frame> e)
{
base.OnElementChanged(e);
if(e.NewElement!=null)
{
this.Control.Hold += Control_Hold;
}
}
void Control_Hold(object sender, System.Windows.Input.GestureEventArgs e)
{
System.Diagnostics.Debug.WriteLine("Frame Held");
e.Handled = false;
}
}
public class myLabelCustomRenderer : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Label> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
this.Control.Hold += Control_Hold;
}
}
void Control_Hold(object sender, System.Windows.Input.GestureEventArgs e)
{
System.Diagnostics.Debug.WriteLine("Label Held");
e.Handled = false;
}
}
}
Running this we find that
Bubbling actually occurs by default in windows. If we wanted we could turn off bubbling by changing
e.Handled = true;
In our Label Renderer and the frame would never get notified of the Hold Event.
Now For Android
On Android things get a bit messier. Again we'll create two renderers.
[assembly: ExportRenderer(typeof(Label), typeof(myLabelCustomRenderer))]
[assembly: ExportRenderer(typeof(Frame), typeof(myFrameCustomRenderer))]
namespace App4.Droid
{
public class myFrameCustomRenderer : FrameRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Frame> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
this.Touch += myFrameCustomRenderer_Touch;
}
}
void myFrameCustomRenderer_Touch(object sender, Android.Views.View.TouchEventArgs e)
{
System.Diagnostics.Debug.WriteLine("You Touched My Frame");
e.Handled = false;
}
}
public class myLabelCustomRenderer : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Label> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
this.Touch += myFrameCustomRenderer_Touch;
}
}
void myFrameCustomRenderer_Touch(object sender, Android.Views.View.TouchEventArgs e)
{
System.Diagnostics.Debug.WriteLine("You Touched My Label");
e.Handled = false;
}
}
}
If we run this it appears that everything works the same as windows we geta touch event in the label and a touch event in the Frame. The bubbling up appears to be automatic. It Gets messy when we attempt to disable bubbling. If we change
e.Handled=true;
in the Label Renderer and run the app again---
Touch fires twice IN THE LABEL RENDERER. Once for when we touch the screen and once for when we stop. If we set the labelrenderer's e.Handled=false; and set the Frame to true. Then the label touch fires followed by the Frame but only the Frame Fires the second time.
In addition if we remove e.Handled=false from both renderer and run the app we find that only the LabelRenderer's Touch event fires. Implying that the default for Handled appears to be true. If you do not set e.Handled=false in the renderer the event will fire in the LabelRenderer and not bubble up the stack to the FrameRenderer.
In Conclusion:
Bubbling works out of the box on Windows. On Android it doesn't work like you might expect. First you have to explicitly set the Handled=false in every child so the parent gets notification and even then only the Handler that Handled the event gets notified that the touch event ended the rest of the stack gets notified of the start but never knows its over.

Resources