How to hide navigation Toolbar icon in xamarin? - 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 !

Related

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

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;
}
}

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);

Event handler ordering in Xamarin

I have a list view that I am popping up in Xamarin forms, that I want to hide if someone taps outside of the box. I have a tap gesture recognizer on the parent layout for the list view that handles that. In Android, it all works good. If I click off, it closes, but if I click on an element in the list view, it properly selects it. In iOS, the opposite happens. The gesture handler on the layout fires first and closes the list view without properly selecting the item.
So my question, is there a way to change the order on how the events are fired? If not, is there a better alternative to how I'm trying to accomplish this? Thanks!
If you are using ListView.ItemSelected or ListView.ItemTapped then I ran into the exact same issue the other day. The fix for me was to not use either of those and instead attach a TapGestureRecognizer to the ViewCell that is within the ListView. I also added an IsSelected property to the object that the ViewCell is being bound to so that I could change the background color of the item once it has been clicked.
public class SomePage : ContentPage {
private SomeModel _selectedModel; //It would be best to put this into your ViewModel
...
public SomePage() {
ListView list = new ListView {
ItemTemplate = new DataTemplate(() => {
ViewCell cell = new ViewCell {
View = new ContentView()
};
cell.View.GestureRecognizers.Add(new TapGestureRecognizer {
Command = new Command(() => {
if(_selectedModel != null) { _selectedModel.IsSelected = false; }
SomeModel model = (SomeModel)cell.BindingContext;
model.IsSelected = true;
_selectedModel = model;
})
}
return cell;
}
}
}
}

Custom tab item

I need to add a custom tab item into my TabbedPage. That shouldn't be a page but an overlay view. It is "More" item for the bottom menu opening a more items menu over the currently shown page.
So far I found the following solution:
protected override void OnCurrentPageChanged()
{
base.OnCurrentPageChanged();
if (this.isInitialized)
{
if (CurrentPage.Title == "More")
{
CurrentPage = this.lastSelectedPage;
}
}
this.lastSelectedPage = CurrentPage;
}
It's enough to prevent opening the corresponding fake page. After this I need to show an overlay view and have no item how to do it from the tabbed page.
Another solution I'm working out now is to write a custom renderer for my tabbed page and manage all the custom work there. The question in this case is how to show my custom view over the existing content. I tried AddView (iOS) in the renderer but getting runtime exception.
public override void ItemSelected(UITabBar tabbar, UITabBarItem item)
{
base.ItemSelected(tabbar, item);
var view = new UILabel()
{
Text = "Test"
};
View.Add(view);
}

Localizing the default windows phone date picker

I have localised the whole app but not able to localise the date picker. A bit of searchin in the forum gave me few answers like this one
but i cant find a properties folder with the resx for different lang for toolkit! I have jus added the toolkit reference in the solution explorer under reference and thats im able to access date picker. I have made a folder called toolkit.content to put the ok and cancel images.
so how do i add the resx for the toolkit date picker :(
You can also create a custom control which inherits from the original DatePicker.
public class MyDatePicker : Microsoft.Phone.Controls.DatePicker
{
public string PickerPageHeader
{
get { return (string)GetValue(PickerPageHeaderProperty); }
set { SetValue(PickerPageHeaderProperty, value); }
}
// Using a DependencyProperty as the backing store for PickerPageHeader. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PickerPageHeaderProperty =
DependencyProperty.Register("PickerPageHeader", typeof(string), typeof(MyDatePicker)
, new PropertyMetadata("Choose date text in your language"));
public MyDatePicker()
{
base.PickerPageUri = new Uri("/Sample;component/CustomControls/MyDatePickerPage.xaml?Header=" + PickerPageHeader, UriKind.Relative);
//Don't forget to change the project name and xaml location
}
}
And create picker page xaml file in a CustomControls folder:
<toolkit:DatePickerPage
x:Class="Sample.MyDatePickerPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
/>
Code behind:
public partial class MyDatePickerPage : Microsoft.Phone.Controls.DatePickerPage
{
public MyDatePickerPage ()
{
InitializeComponent();
foreach (var item in base.ApplicationBar.Buttons)
{
IApplicationBarIconButton button = item as IApplicationBarIconButton;
if (null != button)
{
if ("DONE" == button.Text.ToUpper())
{
button.Text = "done in your language";
}
else if ("CANCEL" == button.Text.ToUpper())
{
button.Text = "cancel in your language";
}
}
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
(base.FindName("HeaderTitle") as TextBlock).Text = e.Uri.OriginalString.Substring(e.Uri.OriginalString.IndexOf("Header=") + 7);
base.OnNavigatedTo(e);
}
}
You have to get the source for the ToolKit and rebuild it with your localization
WP7 ToolKit Source
It's very simple: Parameter - Language.
Xaml code:
<toolkit:DatePicker Language="ru-RU" Margin="-12, 0" Value="{Binding BirthDate, Mode=TwoWay}" />

Resources