Kross
Kross

Reputation: 333

How to keep keyboard on Xamarin.Forms?

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

Answers (5)

Kikanye
Kikanye

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

Tiger
Tiger

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

Ram&#243;n Esteban
Ram&#243;n Esteban

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

Onur
Onur

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.

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

Upvotes: 0

Paul Karam
Paul Karam

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

Related Questions