How to keep keyboard on Xamarin.Forms? - c#

Entry.unfocus/Entry.completed hides keyboard, how to cancel it?
I have a page with some entries and when I press keyboard enter key, I want the keyboard not hides. How to do that with PCL project (Android e iOS)?

Just to point out another solution for Android. In case you want to keep always visible the keyboard for a specific Editor Renderer, you need to override the following methods in the MainActivity class:
private bool _lieAboutCurrentFocus;
public override bool DispatchTouchEvent(MotionEvent ev)
{
var focused = CurrentFocus;
bool customEntryRendererFocused = focused != null && focused.Parent is YourCustomEditorRenderer;
_lieAboutCurrentFocus = customEntryRendererFocused;
var result = base.DispatchTouchEvent(ev);
_lieAboutCurrentFocus = false;
return result;
}
public override Android.Views.View CurrentFocus
{
get
{
if (_lieAboutCurrentFocus)
{
return null;
}
return base.CurrentFocus;
}
}
You can find a more detail explanation here
Hope this helps.
Regards

If you want to do that from the PCL there's a nice and easy way to navigate through your entries and keep them focused one after the other (If this is what you're looking for, and not just keep keyboard open)
Let's say you have around 5 entries in your page, and you want to cycle through them when user presses the done or enter key.
CurrentPage.FindByName<Entry>("FirstEntry").Completed += (o, args) =>
{
CurrentPage.FindByName<Entry>("SecondEntry").Focus();
};
CurrentPage.FindByName<Entry>("SecondEntry").Completed += (o, args) =>
{
CurrentPage.FindByName<Entry>("ThirdEntry").Focus();
};
CurrentPage.FindByName<Entry>("ThirdEntry").Completed += (o, args) =>
{
CurrentPage.FindByName<Entry>("ForthEntry").Focus();
};
CurrentPage.FindByName<Entry>("ForthEntry").Completed += (o, args) =>
{
CurrentPage.FindByName<Entry>("FifthEntry").Focus();
};
CurrentPage.FindByName<Entry>("FifthEntry").Completed += (o, args) =>
{
//Keep going or execute your command, you got the drill..
};
You can add this to your ViewIsAppearing or Init method.

Recently i did something similar. I want to keep keyboard always open in a page and not to hide when a button clicked. To accomplish this, i followed different ways both on iOS and Android.
iOS
In iOS, i created a custom editor renderer
public class CustomEditorRenderer : EditorRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
base.OnElementChanged(e);
var element = this.Element as CustomEditor;
Control.InputAccessoryView = null;
Control.ShouldEndEditing += DisableHidingKeyboard;
MessagingCenter.Subscribe<ReportEventDetailPage>(this, "FocusKeyboardStatus", (sender) =>
{
if (Control != null)
{
Control.ShouldEndEditing += EnableHidingKeyboard;
}
MessagingCenter.Unsubscribe<ReportEventDetailPage>(this, "FocusKeyboardStatus");
});
}
private bool DisableHidingKeyboard(UITextView textView)
{
return false;
}
private bool EnableHidingKeyboard(UITextView textView)
{
return true;
}
}
In this piece of code:
Control.ShouldEndEditing += DisableHidingKeyboard; makes keyboard always opened after focusing custom editor. However, the keyboard does not hide when changing current page to another page. To solve this problem i used MessagingCenter and when dissapering of the current page i send a message to hide keyboard.
Android
For Android, i created a keyboard helper interface and implemented it.
Here is my interface:
public interface IKeyboardHelper
{
void ShowKeyboard();
void HideKeyboard();
}
Keyboard Helper class for Android:
public class KeyboardHelper : IKeyboardHelper
{
public void ShowKeyboard()
{
var context = Forms.Context;
var inputMethodManager = context.GetSystemService(Context.InputMethodService) as InputMethodManager;
if (inputMethodManager != null && context is Activity)
{
var activity = context as Activity;
var token = activity.CurrentFocus?.WindowToken;
inputMethodManager.ToggleSoftInput(ShowFlags.Forced, HideSoftInputFlags.ImplicitOnly);
}
}
public void HideKeyboard()
{
var context = Forms.Context;
var inputMethodManager = context.GetSystemService(Context.InputMethodService) as InputMethodManager;
if (inputMethodManager != null && context is Activity)
{
var activity = context as Activity;
var token = activity.CurrentFocus?.WindowToken;
inputMethodManager.HideSoftInputFromWindow(token, HideSoftInputFlags.None);
activity.Window.DecorView.ClearFocus();
}
}
in Constructor of the current page:
else if (Device.OS == TargetPlatform.Android)
{
MessagingCenter.Send(this, "AndroidFocusEditor");
}
and OnAppearing method of the current page:
protected override void OnAppearing()
{
base.OnAppearing();
if (Device.OS == TargetPlatform.Android)
{
DependencyService.Get<IKeyboardHelper>().ShowKeyboard();
//EventEditor.Focus();
MessagingCenter.Subscribe<ReportEventDetailPage>(this, "AndroidFocusEditor", (sender) => {
Device.BeginInvokeOnMainThread(async () => {
await Task.Run(() => Task.Delay(1));
EventEditor.Focus();
MessagingCenter.Unsubscribe<ReportEventDetailPage>(this, "AndroidFocusEditor");
});
});
}
else if (Device.OS == TargetPlatform.iOS)
{
EventEditor.Focus();
}
}
One last thing: if user clicks another button on the page, keyboard is hiding. To prevent this i followed this link and it really helped me a lot
Keep Keyboard Open For Android

In case you have a custom Keyboard, you can implement a "show" and a "hide" method on android renderer.
Then on your page, show keyboard on your custom control without hiding it. You can hide it when changing page, by overriding OnBackButtonPressed.
In OnBackButtonPressed, send a message using MessagingCenter. Then subscribe to it on your custom control constructor.
Declare an EventHandler that you invoke in the callback method.
Subscribe to this event on your android custom entry renderer and hide the keyboard there.

I had a similar problem and handled it like below:
using System.Runtime.CompilerServices;
using Xamarin.Forms;
public class CustomEntry: Entry
{
public static readonly BindableProperty KeyboardAliveProperty =
BindableProperty.Create(nameof(KeyboardAliveType), typeof(KeyboardAliveType),
typeof(CustomEntry), KeyboardAliveType.Default);
public KeyboardAliveType KeyboardAliveType
{
get { return (KeyboardAliveType)GetValue(KeyboardAliveProperty); }
set { SetValue( KeyboardAliveProperty, value);}
}
}
public enum KeyboardAliveType
{
Default =0,
OnCompleted = 1,
OnButtonClicked = 2,
OnCompletedAndButtonClicked = 3
}
Renderer for Android:
using System;
using Android.Content;
using Android.OS;
using Android.Views;
using Android.Views.InputMethods;
using Android.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Entry = Xamarin.Forms.Entry;
[assembly: ExportRenderer(typeof(CustomEntry), typeof(CustomEntryRenderer))]
/// <summary>
/// Allow and support changes to Border styling and Keyboard with Custom Entry.
/// </summary>
public class CustomEntryRenderer: EntryRenderer, TextView.IOnEditorActionListener
{
private ImeAction _currentInputImeFlag;
public CustomEntryRenderer(Context context) : base(context)
{
//do nothiing
}
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (e.NewElement!=null)
{
}
}
bool TextView.IOnEditorActionListener.OnEditorAction(TextView v, ImeAction actionId, KeyEvent e)
{
// Fire Completed and dismiss keyboard for hardware / physical keyboards
if (actionId == ImeAction.Done || actionId == _currentInputImeFlag ||
(actionId == ImeAction.ImeNull && e.KeyCode == Keycode.Enter && e.Action == KeyEventActions.Up))
{
global::Android.Views.View nextFocus = null;
if (_currentInputImeFlag == ImeAction.Next)
{
nextFocus = FocusSearch(v, FocusSearchDirection.Forward);
}
if (nextFocus != null)
{
nextFocus.RequestFocus();
if (!nextFocus.OnCheckIsTextEditor())
{
if (Element is CustomEntry cE)
{
if (cE.KeyboardAliveType != KeyboardAliveType.OnCompleted &&
cE.KeyboardAliveType != KeyboardAliveType.OnCompletedAndButtonClicked)
{
v.HideKeyboard();
}
}
}
}
else
{
EditText.ClearFocus();
if (Element is CustomEntry cE)
{
if (cE.KeyboardAliveType != KeyboardAliveType.OnCompleted &&
cE.KeyboardAliveType != KeyboardAliveType.OnCompletedAndButtonClicked)
{
v.HideKeyboard();
}
}
}
((IEntryController)Element).SendCompleted();
}
return true;
}
}
internal static class CustomEntryRendererExtensions
{
internal static void HideKeyboard(this Android.Views.View inputView, bool overrideValidation = false)
{
if (inputView == null)
throw new ArgumentNullException(nameof(inputView) + " must be set before the keyboard can be hidden.");
using (var inputMethodManager = (InputMethodManager)inputView.Context?.GetSystemService(Context.InputMethodService))
{
if (!overrideValidation && !(inputView is EditText || inputView is TextView || inputView is SearchView))
throw new ArgumentException("inputView should be of type EditText, SearchView, or TextView");
IBinder windowToken = inputView.WindowToken;
if (windowToken != null && inputMethodManager != null)
inputMethodManager.HideSoftInputFromWindow(windowToken, HideSoftInputFlags.None);
}
}
}
In MainActivity.cs
private bool _lieAboutCurrentFocus;
public override bool DispatchTouchEvent(MotionEvent ev)
{
var focused = CurrentFocus;
if (focused?.Parent is CustomEntryRenderer cer)
{
if (cer.Element is CustomEntry cEntry)
{
if (cEntry.KeyboardAliveType == KeyboardAliveType.OnButtonClicked ||
cEntry.KeyboardAliveType == KeyboardAliveType.OnCompletedAndButtonClicked)
{
_lieAboutCurrentFocus = true;
}
}
}
var result = base.DispatchTouchEvent(ev);
_lieAboutCurrentFocus = false;
return result;
}
public override Android.Views.View CurrentFocus
{
get
{
if (_lieAboutCurrentFocus)
{
return null;
}
return base.CurrentFocus;
}
}
Renderer for UWP:
using System;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.System;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Xamarin.Forms;
using Xamarin.Forms.Platform.UWP;
[assembly: ExportRenderer(typeof(CustomEntry), typeof(CustomEntryRenderer))]
public class CustomEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
// Remove the EventHandler set for KeyUp, and add my custom EventHandler.
// Had to do it this way (using WindowsRuntimeMarshal) because the Delegate that
// I want to remove from the KeyUp event is marked private in a different assembly, so no way to access it directly.
// This way I can customize how the keyboard behaves when the Enter key is pressed.
/*Done the best I can for UWP.*/
var keyUpRuntimeEvent = this.Control.GetType().GetRuntimeEvent("KeyUp");
Action<EventRegistrationToken> removeEventHandlerAction =
(Action<EventRegistrationToken>)Delegate.CreateDelegate(typeof(Action<EventRegistrationToken>),
this.Control,
keyUpRuntimeEvent.RemoveMethod);
WindowsRuntimeMarshal.RemoveAllEventHandlers(removeEventHandlerAction);
this.Control.KeyUp += TextBoxOnKeyUp;
this.Control.PreventKeyboardDisplayOnProgrammaticFocus = false;
// Just to make sure that keyboard is up when the Entry is focused.
Control.GotFocus += (sender, args) =>
{
AttemptToForceKeyboardToShow(Control);
};
Control.TextChanged += (sender, args) =>
{
if (Control.FocusState != FocusState.Unfocused)
{
AttemptToForceKeyboardToShow(Control);
}
};
}
}
protected override void Dispose(bool disposing)
{
if (disposing && Control != null)
{
Control.KeyUp -= TextBoxOnKeyUp;
}
base.Dispose(disposing);
}
private void TextBoxOnKeyUp(object sender, KeyRoutedEventArgs args)
{
if (args?.Key != VirtualKey.Enter)
{
return;
}
if (Element.ReturnType == ReturnType.Next)
{
FocusManager.TryMoveFocus(FocusNavigationDirection.Next);
}
else
{
/*Done the best I can for UWP.*/
if (Element is CustomEntry cE)
{
if (cE.KeyboardAliveType != KeyboardAliveType.OnCompleted &&
cE.KeyboardAliveType != KeyboardAliveType.OnCompletedAndButtonClicked)
{
//Hide the soft keyboard; this matches the behavior of Forms on Android/iOS
Windows.UI.ViewManagement.InputPane.GetForCurrentView().TryHide();
}
}
}
((IEntryController)Element).SendCompleted();
}
private void AttemptToForceKeyboardToShow(FormsTextBox control)
{
try
{
var inputPane = InputPane.GetForUIContext(control.UIContext);
var keyboardShowSuccess = inputPane?.TryShow();
if (keyboardShowSuccess == null || !keyboardShowSuccess.Value)
{
Console.WriteLine("Attempt to force Keyboard to show failed on Windows.");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
See here.

Related

Prism RegionAdapter on SfNavigationDrawer

I'm trying to implement a RegionAdapter on the Syncfusion navigation drawer (https://help.syncfusion.com/wpf/navigation-drawer/getting-started).
I saw that it uses a variable (object) ContentView for displaying view so I wrote this :
public class SfNavigationDrawerRegionAdapter : RegionAdapterBase<SfNavigationDrawer>
{
public SfNavigationDrawerRegionAdapter(IRegionBehaviorFactory regionBehaviorFactory) : base(regionBehaviorFactory)
{
}
protected override void Adapt(IRegion region, SfNavigationDrawer regionTarget)
{
if (region == null)
{
throw new ArgumentNullException(nameof(region));
}
if (regionTarget == null)
{
throw new ArgumentNullException(nameof(regionTarget));
}
region.Views.CollectionChanged += (s, e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (FrameworkElement view in e.NewItems) {
regionTarget.ContentView = view;
}
}
// in events Remove is never called but Reset is called after a Add action
};
}
protected override IRegion CreateRegion()
{
return new SingleActiveRegion();
}
}
This snippet does the following behavior : (https://i.imgur.com/kHVFfQh.mp4) when I click back on a View already loaded it doesn't change the View in the ContentView (not calling the CollectionChanged).
The expected behavior is that it should display the View.
How can I manage to do that?
Thank you for reading!

Custom Picker Xamarin Android

I have a picker.Please,tell me how to change color of title, the color of items and remove these lines
I have my custompicker and I could change colors of buttons CANCEL,OK
I tried to remove lines
https://forums.xamarin.com/discussion/78693/how-can-i-remove-the-picker-borders-in-forms-for-android
but it does not work
How to change color of SELECT A CAR and AUDI I do not know
You could implement it by using Custom Renderer
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using App12.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Color = Android.Graphics.Color;
using Orientation = Android.Widget.Orientation;
[assembly: ExportRenderer(typeof(Picker), typeof(MyPickerRenderer))]
namespace App12.Droid
{
public class MyPickerRenderer:PickerRenderer
{
IElementController ElementController => Element;
public MyPickerRenderer(Context context) : base(context)
{
}
private AlertDialog _dialog;
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
Control.Click += Control_Click;
Control.SetHintTextColor(Android.Graphics.Color.Red);
Control.SetSingleLine(true);
Control.SetTypeface(null, TypefaceStyle.Bold);
Control.Gravity = GravityFlags.Center;
var gd = new GradientDrawable();
gd.SetStroke(0, Android.Graphics.Color.Transparent);
Control.SetBackground(gd);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
Control.Click -= Control_Click;
//var picker = (Picker)Element;
//picker.PropertyChanged -= Control_Click;
}
base.Dispose(disposing);
}
private void Control_Click(object sender, EventArgs e)
{
Picker model = Element;
picker.SelectionDividerHeight = 0;
var picker = new TextColorNumberPicker(Context);
if (model.Items != null && model.Items.Any())
{
// set style here
picker.MaxValue = model.Items.Count - 1;
picker.MinValue = 0;
picker.SetDisplayedValues(model.Items.ToArray());
picker.WrapSelectorWheel = false;
picker.Value = model.SelectedIndex;
}
var layout = new LinearLayout(Context) { Orientation = Orientation.Vertical };
layout.AddView(picker);
var titleView = new TextView(Context);
titleView.Text = "Select a car";
titleView.TextSize = 20;
titleView.SetTextColor(Color.Red);
titleView.SetBackgroundColor(Color.White);
ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, true);
var builder = new AlertDialog.Builder(Context);
builder.SetView(layout);
builder.SetTitle(model.Title ?? "");
builder.SetCustomTitle(titleView);
builder.SetNegativeButton("Cancel ", (s, a) =>
{
ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
// It is possible for the Content of the Page to be changed when Focus is changed.
// In this case, we'll lose our Control.
Control?.ClearFocus();
_dialog = null;
});
builder.SetPositiveButton("Ok ", (s, a) =>
{
ElementController.SetValueFromRenderer(Picker.SelectedIndexProperty, picker.Value);
// It is possible for the Content of the Page to be changed on SelectedIndexChanged.
// In this case, the Element & Control will no longer exist.
if (Element != null)
{
if (model.Items.Count > 0 && Element.SelectedIndex >= 0)
Control.Text = model.Items[Element.SelectedIndex];
ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
// It is also possible for the Content of the Page to be changed when Focus is changed.
// In this case, we'll lose our Control.
Control?.ClearFocus();
}
_dialog = null;
});
_dialog = builder.Create();
_dialog.DismissEvent += (ssender, args) =>
{
ElementController?.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
};
_dialog.Show();
Android.Widget.Button btnOk = _dialog.GetButton((int)Android.Content.DialogButtonType.Positive);
btnOk.SetTextColor(Android.Graphics.Color.Blue);
Android.Widget.Button btnCancel = _dialog.GetButton((int)Android.Content.DialogButtonType.Positive);
btnCancel.SetTextColor(Android.Graphics.Color.Gray);
}
}
public class TextColorNumberPicker : NumberPicker
{
public TextColorNumberPicker(Context context) : base(context)
{
}
public override void AddView(Android.Views.View child, int index, ViewGroup.LayoutParams #params)
{
base.AddView(child, index, #params);
UpdateView(child);
}
public void UpdateView(Android.Views.View view)
{
if (view is EditText)
{
((EditText)view).SetTextColor(Color.Red); // set item text color
}
}
}
}
Note : Make sure the Target Framework of the project is the latest stable version (Android Q) .
Override Xamarin.Forms.Platform.Android.AppCompat.PickerRenderer instead of Xamarin.Forms.Platform.Android.PickerRenderer
public class CustomPickerRenderer
: Xamarin.Forms.Platform.Android.AppCompat.PickerRenderer
{
...
}
Reference: https://www.damirscorner.com/blog/posts/20201204-CustomPickerRendererOnAndroid.html

Xamarin Forms picker OK or Cancel button pressed detection

I am developing applications with Xamarin Forms. I have some problems with the picker component.
After picking up with the Picker, I need to determine whether the OK or Cancel button is pressed.
I tried Focus and Unfocus, but it doesn't help me figure out which user is pressing the OK or Cancel button.
Solution:
You should rewrite the toolbar of Picker in CustomRenderer
iOS
[assembly:ExportRenderer(typeof(Picker),typeof(MyPickerRenderer))]
namespace App6.iOS
{
public class MyPickerRenderer:PickerRenderer
{
public MyPickerRenderer()
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if(Control!=null)
{
UIPickerView pickerView = (UIPickerView)Control.InputView;
// get the button Done and rewrite the event
UIToolbar toolbar = (UIToolbar)Control.InputAccessoryView;
UIBarButtonItem done = new UIBarButtonItem("OK", UIBarButtonItemStyle.Done, (object sender, EventArgs click) =>
{
MessagingCenter.Send<Object>(this,"Ok_Clicked");
});
UIBarButtonItem cancel = new UIBarButtonItem(UIBarButtonSystemItem.Cancel, (object sender, EventArgs click) =>
{
MessagingCenter.Send<Object>(this, "Cancel_Clicked");
});
UIBarButtonItem empty = new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace, null);
toolbar.Items = new UIBarButtonItem[] { cancel,empty, done };
}
}
}
}
Android
[assembly: ExportRenderer(typeof(Picker), typeof(MyPickerRenderer))]
namespace App6.Droid
{
public class MyPickerRenderer : PickerRenderer
{
IElementController ElementController => Element as IElementController;
public MyPickerRenderer(Context context) : base(context)
{
}
private AlertDialog _dialog;
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if (e.NewElement == null || e.OldElement != null)
return;
Control.Click += Control_Click;
}
protected override void Dispose(bool disposing)
{
Control.Click -= Control_Click;
base.Dispose(disposing);
}
private void Control_Click(object sender, EventArgs e)
{
Picker model = Element;
var picker = new NumberPicker(Context);
if (model.Items != null && model.Items.Any())
{
picker.MaxValue = model.Items.Count - 1;
picker.MinValue = 0;
picker.SetDisplayedValues(model.Items.ToArray());
picker.WrapSelectorWheel = false;
picker.Value = model.SelectedIndex;
}
var layout = new LinearLayout(Context) { Orientation = Orientation.Vertical };
layout.AddView(picker);
ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, true);
var builder = new AlertDialog.Builder(Context);
builder.SetView(layout);
builder.SetTitle(model.Title ?? "");
builder.SetNegativeButton("Cancel ", (s, a) =>
{
MessagingCenter.Send<Object>(this, "Cancel_Clicked");
});
builder.SetPositiveButton("Ok ", (s, a) =>
{
ElementController.SetValueFromRenderer(Picker.SelectedIndexProperty, picker.Value);
// It is possible for the Content of the Page to be changed on SelectedIndexChanged.
// In this case, the Element & Control will no longer exist.
if (Element != null)
{
if (model.Items.Count > 0 && Element.SelectedIndex >= 0)
Control.Text = model.Items[Element.SelectedIndex];
ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
// It is also possible for the Content of the Page to be changed when Focus is changed.
// In this case, we'll lose our Control.
}
MessagingCenter.Send<Object>(this, "Ok_Clicked");
});
_dialog = builder.Create();
_dialog.DismissEvent += (ssender, args) =>
{
ElementController?.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
};
_dialog.Show();
}
}
}
And you can handle the event in forms by using MessagingCenter.
in contentPage
public MainPage()
{
InitializeComponent();
MessagingCenter.Subscribe<Object>(this, "Ok_Clicked", (sender)=> {
picker.Unfocus();
DisplayAlert("Title", "Ok has been clicked", "cancel");
//do something you want
});
MessagingCenter.Subscribe<Object>(this, "Cancel_Clicked", (sender) => {
picker.Unfocus();
DisplayAlert("Title", "Cancel has been clicked", "cancel");
//do something you want
});
}

How to change TabbedPage Icon when the tab is reselected in Android?

I have an application using Xamarin Forms TabbedPage which has a feature that would allow the user to pause and play a page. Please see the code below.
Shared Code
public partial class MainPage : TabbedPage
{
public MainPage()
{
InitializeComponent();
var homePage = new NavigationPage(new HomePage())
{
Title = "Home",
Icon = "ionicons_2_0_1_home_outline_25.png"
};
var phrasesPage = new NavigationPage(new PhrasesPage())
{
Title = "Play",
Icon = "ionicons_2_0_1_play_outline_25.png"
};
Children.Add(homePage);
Children.Add(phrasesPage);
}
}
In iOS renderer:
public class TabbedPageRenderer : TabbedRenderer
{
private MainPage _page;
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
var tabbarController = (UITabBarController)this.ViewController;
if (null != tabbarController)
{
tabbarController.ViewControllerSelected += OnTabBarReselected;
}
}
void OnTabBarReselected(object sender, UITabBarSelectionEventArgs e)
{
var tabs = Element as TabbedPage;
var playTab = tabs.Children[4];
if (TabBar.SelectedItem.Title == "Play") {
if (tabs != null)
{
playTab.Title = "Pause";
playTab.Icon = "ionicons_2_0_1_pause_outline_22.png";
}
App.pauseCard = false;
}
else {
if (tabs != null) {
playTab.Title = "Play";
playTab.Icon = "ionicons_2_0_1_play_outline_25.png";
}
App.pauseCard = true;
}
}
Android Renderer
public class MyTabbedPageRenderer: TabbedPageRenderer, TabLayout.IOnTabSelectedListener
{
if (e.PropertyName == "Renderer")
{
viewPager = (ViewPager)ViewGroup.GetChildAt(0);
tabLayout = (TabLayout)ViewGroup.GetChildAt(1);
setup = true;
ColorStateList colors = null;
if ((int)Build.VERSION.SdkInt >= 23)
{
colors = Resources.GetColorStateList(Resource.Color.icon_tab, Forms.Context.Theme);
}
else
{
colors = Resources.GetColorStateList(Resource.Color.icon_tab);
}
for (int i = 0; i < tabLayout.TabCount; i++)
{
var tab = tabLayout.GetTabAt(i);
var icon = tab.Icon;
if (icon != null)
{
icon = Android.Support.V4.Graphics.Drawable.DrawableCompat.Wrap(icon);
Android.Support.V4.Graphics.Drawable.DrawableCompat.SetTintList(icon, colors);
}
}
}
void TabLayout.IOnTabSelectedListener.OnTabReselected(TabLayout.Tab tab)
{
var tabs = Element as TabbedPage;
var playTab = tabs.Children[4];
var selectedPosition = tab.Position;
if(selectedPosition == 4)
{
if (playTab.Title == "Play")
{
if (tabs != null)
{
playTab.Title = "Pause";
playTab.Icon = "ionicons_2_0_1_pause_outline_22.png";
}
App.pauseCard = false;
}
else
{
if (tabs != null)
{
playTab.Title = "Play";
playTab.Icon = "ionicons_2_0_1_play_outline_25.png";
}
App.pauseCard = true;
}
}
}
}
This is perfectly working in iOS. But somehow in Android only the Title would change but not the Icon. Anyone knows what Im missing or how it should be done? Also, is this possible to be done in the shared code instead of repeating almost exactly the same lines on code in each platform?
You can do it by using the tab that is being passe to you in the OnTabReselected parameters in the TabRenderer.
You can move your whole logic with this object.
This is my whole renderer file (Android):
[assembly: ExportRenderer(typeof(SWTabSelection.MainPage), typeof(SWTabSelection.Droid.MyTabbedPageRenderer))]
namespace SWTabSelection.Droid
{
public class MyTabbedPageRenderer : TabbedPageRenderer, TabLayout.IOnTabSelectedListener
{
private ViewPager viewPager;
private TabLayout tabLayout;
private bool setup;
public MyTabbedPageRenderer() { }
public MyTabbedPageRenderer(Context context) : base(context)
{
//Use this constructor for newest versions of XF saving the context parameter
// in a field so it can be used later replacing the Xamarin.Forms.Forms.Context which is deprecated.
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == "Renderer")
{
viewPager = (ViewPager)ViewGroup.GetChildAt(0);
tabLayout = (TabLayout)ViewGroup.GetChildAt(1);
setup = true;
ColorStateList colors = GetTabColor();
for (int i = 0; i < tabLayout.TabCount; i++)
{
var tab = tabLayout.GetTabAt(i);
SetTintColor(tab, colors);
}
}
}
void TabLayout.IOnTabSelectedListener.OnTabReselected(TabLayout.Tab tab)
{
// To have the logic only on he tab on position 1
if(tab == null || tab.Position != 1)
{
return;
}
if(tab.Text == "Play")
{
tab.SetText("Pause");
tab.SetIcon(Resource.Drawable.ionicons_2_0_1_pause_outline_25);
App.pauseCard = false;
}
else
{
tab.SetText("Play");
tab.SetIcon(Resource.Drawable.ionicons_2_0_1_play_outline_25);
App.pauseCard = true;
}
SetTintColor(tab, GetTabColor());
}
void SetTintColor(TabLayout.Tab tab, ColorStateList colors)
{
var icon = tab?.Icon;
if(icon != null)
{
icon = Android.Support.V4.Graphics.Drawable.DrawableCompat.Wrap(icon);
Android.Support.V4.Graphics.Drawable.DrawableCompat.SetTintList(icon, colors);
}
}
ColorStateList GetTabColor()
{
return ((int)Build.VERSION.SdkInt >= 23)
? Resources.GetColorStateList(Resource.Color.icon_tab, Forms.Context.Theme)
: Resources.GetColorStateList(Resource.Color.icon_tab);
}
}
}
The only thing that I had with the code above is that the icon was not taking the Tint color so created a function with the same logic you had to set the Tint and I am using it on the Tab Reselection. If you have only one tab in your app you can set a global tint in the Android Theme/Style xml.
Hope this helps.
Custom Renderer is no needed , you can change the Title and Icon directly in Shared code.
Just implement CurrentPageChanged event in TabbedPage
Complete code
public partial class TabbedPage1 : TabbedPage
{
NavigationPage homePage;
NavigationPage phrasesPage;
public TabbedPage1 ()
{
InitializeComponent();
var homePage = new NavigationPage(new Page1())
{
Title = "Home",
Icon = "1.png"
};
var phrasesPage = new NavigationPage (new Page2())
{
Title = "Play",
Icon = "1.png"
};
Children.Add(homePage);
Children.Add(phrasesPage);
this.CurrentPageChanged += (object sender, EventArgs e) => {
var i = this.Children.IndexOf(this.CurrentPage);
if (i == 0)
{
homePage.Title = "HomeChanged";
homePage.Icon = "2.png";
}
else {
phrasesPage.Title = "PlayChanged";
phrasesPage.Icon = "2.png";
}
};
}
}
Result
PS: Make the image files access from a different platform.
iOS - Resources
Android - Resources->drawable
There isn't a way to detect when a tab is reselected in Xamarin.Forms, so we'll have to use custom rederers to detect the logic.
For Android, we'll have to handle two cases: Current Tab Page Changed, and Current Tab Page Reselected.
We'll subscribe to to CurrentPageChanged and in its EventHandler, we'll check to see if the tab selected is PhrasesPage. If it is, we'll update the Icon/Text.
In OnTabReselected, we can check which page is currently selected, and if it's the PhrasesPage, we can update PhrasesPage.Icon and PhrasesPage.Text.
Sample App
https://github.com/brminnick/ChangeTabbedPageIconSample/tree/master
Android Custom Renderer
[assembly: ExportRenderer(typeof(MainPage), typeof(TabbedPageRenderer))]
namespace YourNameSpace
{
public class TabbedPageRenderer : TabbedRenderer, TabLayout.IOnTabSelectedListener
{
//Overloaded Constructor required for Xamarin.Forms v2.5+
public TabbedPageRenderer(Android.Content.Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e)
{
base.OnElementChanged(e);
Element.CurrentPageChanged += HandleCurrentPageChanged;
}
void HandleCurrentPageChanged(object sender, EventArgs e)
{
var currentNavigationPage = Element.CurrentPage as NavigationPage;
if (!(currentNavigationPage.RootPage is PhrasesPage))
return;
var tabLayout = (TabLayout)ViewGroup.GetChildAt(1);
for (int i = 0; i < tabLayout.TabCount; i++)
{
var currentTab = tabLayout.GetTabAt(i);
var currentTabText = currentTab.Text;
if (currentTabText.Equals("Play") || currentTabText.Equals("Pause"))
{
Device.BeginInvokeOnMainThread(() => UpdateTab(currentTabText, currentTab, currentNavigationPage));
break;
}
}
}
void TabLayout.IOnTabSelectedListener.OnTabReselected(TabLayout.Tab tab)
{
System.Diagnostics.Debug.WriteLine("Tab Reselected");
var mainPage = Application.Current.MainPage as MainPage;
var currentNavigationPage = mainPage.CurrentPage as NavigationPage;
if(currentNavigationPage.RootPage is PhrasesPage)
Device.BeginInvokeOnMainThread(() => UpdateTab(currentNavigationPage.Title, tab, currentNavigationPage));
}
void UpdateTab(string currentTabText, TabLayout.Tab tab, NavigationPage currentNavigationPage)
{
if (currentTabText.Equals("Puzzle"))
{
tab.SetIcon(IdFromTitle("Settings", ResourceManager.DrawableClass));
currentNavigationPage.Title = "Settings";
}
else
{
tab.SetIcon(IdFromTitle("Puzzle", ResourceManager.DrawableClass));
currentNavigationPage.Title = "Puzzle";
}
}
int IdFromTitle(string title, Type type)
{
string name = System.IO.Path.GetFileNameWithoutExtension(title);
int id = GetId(type, name);
return id;
}
int GetId(Type type, string memberName)
{
object value = type.GetFields().FirstOrDefault(p => p.Name == memberName)?.GetValue(type)
?? type.GetProperties().FirstOrDefault(p => p.Name == memberName)?.GetValue(type);
if (value is int)
return (int)value;
return 0;
}
}
}
I think you are using the custom render for tabbed page customization. For, Android you should refer the icon from Resource.Drawable. Please try with below code snippet in android renderer.
public class CustomTabRenderer: TabbedRenderer
{
private Activity _act;
protected override void OnModelChanged(VisualElement oldModel, VisualElement newModel)
{
base.OnModelChanged(oldModel, newModel);
_act = this.Context as Activity;
}
// You can do the below function anywhere.
public override void OnWindowFocusChanged(bool hasWindowFocus)
{
ActionBar actionBar = _act.ActionBar;
if (actionBar.TabCount > 0)
{
Android.App.ActionBar.Tab tabOne = actionBar.GetTabAt(0);
tabOne.SetIcon(Resource.Drawable.shell);
}
base.OnWindowFocusChanged(hasWindowFocus);
}
}
Refer this also: https://forums.xamarin.com/discussion/17654/tabbedpage-icons-not-visible-android
Try adding this code to OnElementChanged in TabbedPageRenderer
if (e.PropertyName == "Renderer")
{
ViewPager pager = (ViewPager)ViewGroup.GetChildAt(0);
TabLayout layout = (TabLayout)ViewGroup.GetChildAt(1);
for (int i = 0; i < layout.TabCount; i++)
{
var tab = layout.GetTabAt(i);
var icon = tab.Icon;
if (icon != null)
{
icon = Android.Support.V4.Graphics.Drawable.DrawableCompat.Wrap(icon);
Android.Support.V4.Graphics.Drawable.DrawableCompat.SetTintList(icon, colors);
}
}
}
More info here : https://montemagno.com/xamarin-forms-android-selected-and-unselected-tab-colors/

Only numbers in the text box

if (!(char.IsDigit(e.KeyChar)))
{
e.Handled = true;
}
The above code is not working properly
Below is the image error :
The problem space is "Clipboard"
If this is for WinForms, my suggestion would be to use a MaskedTextBox instead. This is a purpose-built control for allowing only certain kinds of user-input.
You can set the mask through the designer or in code.
For example, for a 5-digit numeric:
maskedTextBox1.Mask = "00000";
maskedTextBox1.ValidatingType = typeof(int);
Yes, this is the typical nemesis for keyboard filtering. The TextBox control doesn't have any built-in events to intercept a paste from the clipboard. You'll have to detect the Ctrl+V keypress yourself and screen Clipboard.GetText().
The logic is tricky to get right. Here's a class that can make all this a little easier. Add a new class to your project and paste the code shown below. Compile. Drop the new control from the top of the toolbox onto a form. Double click it and write the ValidateChar event handler. Like this one, only allowing entering digits:
private void validatingTextBox1_ValidateChar(object sender, ValidateCharArgs e) {
if (!"0123456789".Contains(e.KeyChar)) e.Cancel = true;
}
The code:
using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Text;
[DefaultEvent("ValidateChar")]
class ValidatingTextBox : TextBox {
public event EventHandler<ValidateCharArgs> ValidateChar;
protected virtual void OnValidateChar(ValidateCharArgs e) {
var handler = ValidateChar;
if (handler != null) handler(this, e);
}
protected override void OnKeyPress(KeyPressEventArgs e) {
if (e.KeyChar >= ' ') { // Allow the control keys to work as normal
var args = new ValidateCharArgs(e.KeyChar);
OnValidateChar(args);
if (args.Cancel) {
e.Handled = true;
return;
}
}
base.OnKeyPress(e);
}
private void HandlePaste() {
if (!Clipboard.ContainsText()) return;
string text = Clipboard.GetText();
var toPaste = new StringBuilder(text.Length);
foreach (char ch in text.ToCharArray()) {
var args = new ValidateCharArgs(ch);
OnValidateChar(args);
if (!args.Cancel) toPaste.Append(ch);
}
if (toPaste.Length != 0) {
Clipboard.SetText(toPaste.ToString());
this.Paste();
}
}
bool pasting;
protected override void WndProc(ref Message m) {
if (m.Msg == 0x302 && !pasting) {
pasting = true;
HandlePaste();
pasting = false;
}
else base.WndProc(ref m);
}
}
class ValidateCharArgs : EventArgs {
public ValidateCharArgs(char ch) { Cancel = false; KeyChar = ch; }
public bool Cancel { get; set; }
public char KeyChar { get; set; }
}
Handle TextChanged event or use a MaskedTextBox.
if (textBox1.Text.Count(a => !char.IsDigit(a)) > 0)
{
textBox1.Text = new string(textBox1.Text.Where(a => char.IsDigit(a)).ToArray());
}
I answered a similar question on StackOverflow once.
Here's the link to the question: Best way to limit textbox decimal input in c#
Essentially, you'll have to put my class in your code and apply it to all textboxes you want to restrict data entered.
The TextBoxFilter class I wrote allows you to limit entry to Alphabet, Numerics, AlphaNumerics, Currency and UserSpecified input.
control.TextChanged += (s, a) => {
string value = string.Empty;
foreach (char ch in control.Text.ToCharArray())
{
if (char.IsDigit(ch))
{
value += ch.ToString();
}
}
control.Text = value;
};

Categories