Reputation: 333
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)?
Upvotes: 4
Views: 8015
Reputation: 1358
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.
Upvotes: 0
Reputation: 1
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.
Upvotes: 0
Reputation: 936
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
Upvotes: 2
Reputation: 247
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.
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.
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
Upvotes: 0
Reputation: 4210
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.
Upvotes: 0