Collin Vesel
Collin Vesel

Reputation: 61

Why won't my Xamarin forms custom editor renderer do anything

I am trying to make a custom renderer for an editor that changes the "return" key to a "done" button and fires the Completed event when you tap it instead of typing a newline. The code in OnElementChanged() is being hit, but it's not doing anything. The "return" key is still a "return" key and it still types newlines instead of making the editor go out of focus. What am I doing wrong?

Here is the class for the custom editor (in the .NET project):

using Xamarin.Forms;

namespace Partylist.Custom_Controls
{
    public class ChecklistEditor : Editor
    {

    }
}

Here is the custom renderer for Android:

using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Views.InputMethods;
using Android.Widget;

using Partylist.Custom_Controls;
using Partylist.Droid.Custom_Renderers;

using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(ChecklistEditor), typeof(ChecklistEditorRenderer))]
namespace Partylist.Droid.Custom_Renderers
{
    class ChecklistEditorRenderer : EditorRenderer
    {
        // Constructor because it needs to exist.
        public ChecklistEditorRenderer(Context context) : base(context)
        {

        }

        // This gets overridden so I can change what I want to change.
        protected override void OnElementChanged(ElementChangedEventArgs
            <Editor> e)
        {
            // Make it do what is should normally do so it will exist.
            base.OnElementChanged(e);
            // Make the "Return" button on the keyboard be a "Done" button.
            Control.ImeOptions = ImeAction.Done;
            // Make the thing watch for when the user hits the "Return" button.
            Control.EditorAction += OnEditorAction;
        }

        // This makes the "Return" button fire the "Completed" event 
        // instead of typing a newline.
        private void OnEditorAction(object sender, TextView
            .EditorActionEventArgs e)
        {
            e.Handled = false;
            if (e.ActionId == ImeAction.Done)
            {
                Control.ClearFocus();
                e.Handled = true;
            }
        }
    }
}

Here is the custom renderer for iOS:

using Foundation;
using Partylist.Custom_Controls;
using Partylist.iOS.Custom_Renderers;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

[assembly: ExportRenderer(typeof(ChecklistEditor), typeof(ChecklistEditorRenderer))]
namespace Partylist.iOS.Custom_Renderers
{
    class ChecklistEditorRenderer : EditorRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs
            <Editor> e)
        {
            base.OnElementChanged(e);

            Control.ReturnKeyType = UIReturnKeyType.Done;
        }


        protected override bool ShouldChangeText(UITextView textView, 
            NSRange range, string text)
        {
            if (text == "\n")
            {
                textView.ResignFirstResponder();
                return false;
            }

            return true;
        }
    }
}

The code-behind for the page where I'm using these custom renderers (there's nothing in the XAML that should conflict with it, I think, but I'll add it to the post if people want to make sure):

using Partylist.Custom_Controls;

using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace Partylist.Views
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class ChecklistPage : ContentPage
    {
        // Struct for items on the checklist.
        struct Item
        {
            public ChecklistEditor ItemEditor { get; set; }
            public CheckBox ItemCheckbox { get; set; }
        }
        // Create a list of contact structs to populate the ListView.
        ObservableCollection<Item> items;
        // Flag for when an item is added to the list.
        bool itemAdded = false;

        // Constructor.
        public ChecklistPage()
        {
            // Whatever setup stuff it was going to do anyway.
            InitializeComponent();
            // Set the label's BindingContext to the 
            // App class so it can update its text.
            tipLabel.BindingContext = (App)App.Current;
        }

        // Override for OnAppearing().
        protected override void OnAppearing()
        {
            // Makes the page appear.
            base.OnAppearing();
            // Set the page's title to be the name of the selected list.
            Title = App.selectedList.Name;
            // Make a toolbar item appear to access the Main Checklist
            // unless we are already there.
            if (App.selectedList.ListFile.Name.EndsWith(".mchec"))
            {
                ToolbarItems.Remove(MainChecklistButton);
            }
            // Set the binding context of the page to itself.
            BindingContext = this;
            // Start the timer for the tips banner if it is stopped.
            App.tipTimer.Start();
            // Set the banner's text to the current tip's sumamry.
            tipLabel.Text = ((App)App.Current).CurrentTip.Summary;
            OnPropertyChanged("CurrentTip");
            // Subscribe the OnTipUpdate function to the tipUpdate event in the app
            // class.
            App.TipUpdate += OnTipUpdate;

            // Make the ObservableCOllection reference something.
            items = new ObservableCollection<Item>();
            // Open a stream to the list that we want to display.
            using (StreamReader listReader = new StreamReader(App.selectedList
                .ListFile.FullName))
            {
                // Loop through the file and read data into the list.
                while (!listReader.EndOfStream)
                {
                    // Create a blank item.
                    Item newItem = new Item()
                    {
                        ItemEditor = new ChecklistEditor()
                        {
                            Text = listReader.ReadLine(),
                            Placeholder = "New Item",
                            IsTabStop = true,
                            AutoSize = EditorAutoSizeOption.TextChanges,
                            WidthRequest = 310
                        },
                        ItemCheckbox = new CheckBox()
                        {
                            Color = App.selectedList.ListItemColor,
                            IsChecked = bool.Parse(listReader.ReadLine())
                        }
                    };
                    // Subscribe OnCompleted() to the new item's "Completed" 
                    // event.
                    newItem.ItemEditor.Completed += OnCompleted;
                    // Subscribe OnTextChanged() to the new item's 
                    // "TextChanged" event.
                    newItem.ItemEditor.TextChanged += OnTextChanged;
                    // Add the new item to the list.
                    items.Add(newItem);
                    // Make the ListView update.
                    ChecklistView.ItemsSource = items;
                    OnPropertyChanged("contacts");
                }
                // Once everything is loaded, close the file.
                listReader.Close();
            }
        }

        // Override for OnDisappearing().
        protected override void OnDisappearing()
        {
            // Makes the page disappear.
            base.OnDisappearing();
            // Open a stream to the file for the list.
            StreamWriter listWriter = new StreamWriter(App.selectedList
                .ListFile.FullName);
            // Loop through the contacts list to write the contacts to the 
            // file.
            for (int i = 0; i < items.Count; i++)
            {
                // Write each item to the file.
                listWriter.WriteLine(items.ElementAt(i).ItemEditor.Text);
                listWriter.WriteLine(items.ElementAt(i).ItemCheckbox.IsChecked);
            }
            // Close the stream.
            listWriter.Close();
        }

        // Function for when the "Add New Contact" button is clicked.
        private void OnAddNewItemClicked(object sender, EventArgs e)
        {
            // Create a blank item.
            Item newItem = new Item()
            {
                ItemEditor = new ChecklistEditor()
                {
                    Placeholder = "New Item",
                    IsTabStop = true,
                    AutoSize = EditorAutoSizeOption.TextChanges,
                    WidthRequest = 310
                },
                ItemCheckbox = new CheckBox()
                {
                    Color = App.selectedList.ListItemColor,
                    IsChecked = false
                }
            };
            // Subscribe OnCompleted() to the new item's "Completed" 
            // event.
            newItem.ItemEditor.Completed += OnCompleted;
            // Subscribe OnTextChanged() to the new item's 
            // "TextChanged" event.
            newItem.ItemEditor.TextChanged += OnTextChanged;
            // Add the new contact to the list.
            items.Add(newItem);
            // Set the "itemAdded" flag to true.
            itemAdded = true;
            // Make the ListView update.
            ChecklistView.ItemsSource = items;
            OnPropertyChanged("contacts");
            // Select the new item.
            ChecklistView.SelectedItem = items.ElementAt(items.Count - 1);
        }

        // Function for when an item is selected, used to set the focus to
        // a newly added item in the list.
        private async void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
        {
            // Only runs this if an item was added (as opposed to being 
            // read in from the file).
            if (itemAdded)
            {
                if (e.SelectedItem == null) return;
                await Task.Delay(100); // Change the delay time if Focus() doesn't work.
                ((Item)e.SelectedItem).ItemEditor.Focus();
                ChecklistView.SelectedItem = null;
                itemAdded = false;
            }
        }

        // Function for when the user presses "Return" on the keyboard in
        // an editor.
        private void OnCompleted(object sender, EventArgs e)
        {
            // We just want to unfocus the editor.
            ((Editor)sender).Unfocus();
        }

        // Function for when the user types anything in the editor, used 
        // to make sure it resizes.
        private void OnTextChanged(object sender, TextChangedEventArgs e)
        {
            // Makes the cell resize. The cell is the parent of the 
            // StackLayout which is the parent of the ContentView which is
            // the parent of the Editor that fired the event.
            ((ViewCell)((Editor)sender).Parent.Parent.Parent)
                .ForceUpdateSize();
        }
    }
}

Upvotes: 0

Views: 1739

Answers (1)

Junior Jiang
Junior Jiang

Reputation: 12723

In Android , you need to set Single Line for EditTextView , then it will works .

For example :

...
    protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
    {
        base.OnElementChanged(e);
        // set single line will works
        Control.SetSingleLine();

        Control.ImeOptions = ImeAction.Done;
        Control.EditorAction += OnEditorAction;
    }

    private void OnEditorAction(object sender, TextView.EditorActionEventArgs e)
    {
        e.Handled = false;
        if (e.ActionId == ImeAction.Done)
        {
            Control.ClearFocus();
            e.Handled = true;
            InputMethodManager imm = (InputMethodManager)Control.Context.GetSystemService(Context.InputMethodService);
            imm.HideSoftInputFromWindow(Control.WindowToken, 0);
        }
    }
...

The effect :

enter image description here

About iOS to achieve that , you can refer to follow code :

[assembly: ExportRenderer(typeof(Editor), typeof(CustomEditorRenderer))]
namespace AppEntryTest.iOS
{
    class CustomEditorRenderer : EditorRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
        {
            base.OnElementChanged(e);

            Control.ReturnKeyType = UIReturnKeyType.Done;
        }


        protected override bool ShouldChangeText(UITextView textView, NSRange range, string text)
        {
            if (text == "\n")
            {
                textView.ResignFirstResponder();
                return false;
            }

            return true;
        }
    }
}

The effect :

enter image description here

====================================Update================================

If need to wrap text in Android , you can set background for EditTextView

Adding bg_gray_border.xml in Resources/drawable folder :

<?xml version="1.0" encoding="utf-8" ?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
  <solid android:color="#ffffff" />
  <stroke
      android:width="1dp"
      android:color="#DEDEDE" />
  <corners android:radius="6dp" />
</shape>

Used in Renderer class :

...
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
    base.OnElementChanged(e);

    Control.SetSingleLine();
    Control.SetBackgroundResource(Resource.Drawable.bg_gray_border);

    Control.ImeOptions = ImeAction.Done;
    Control.EditorAction += OnEditorAction;
        
}
...

The effect :

enter image description here

Add wapped text in iOS ,

...
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
    base.OnElementChanged(e);

    Control.ReturnKeyType = UIReturnKeyType.Done;

    Control.Layer.BorderColor =UIColor.Gray.CGColor;

    Control.Layer.BorderWidth = 1;

    Control.Layer.CornerRadius = 5;
}
...

The effect :

enter image description here

Here is the sample project .

Upvotes: 1

Related Questions