TextChanged doesn't fire in Editor Custom Renderer EditorRenderer

Paul Long 21 Reputation points
2021-04-23T02:28:33.887+00:00

I'm using an Editor control with my xamarin forms app and I want to set and get selection positions. I understand this is not supported, so I found this solution using a customer rener to get selection positions (https://stackoverflow.com/questions/62875377/xamarin-editor-control-cursorposition-on-android). It works, but now I don't get TextChanged events. If I comment out SetNativeControl, the TextChanged events work, but of course then I don't get selection positions anymore.

How can I fix this problem?

Thanks,
Paul

Developer technologies | .NET | Xamarin
0 comments No comments
{count} votes

Accepted answer
  1. Cole Xia (Shanghai Wicresoft Co,.Ltd.) 6,756 Reputation points
    2021-04-23T06:16:42.05+00:00

    Hello,

    Welcome to Microsoft Q&A!

    You could expand the EditTextSelectChange interface in renderer, add a method TextChanged , and pass the data in BeforeTextChanged and AfterTextChanged event in MyEditText .

    Create new event in custom editor in forms , invoke the event while text changes.

    Custom Renderer
       [assembly: ExportRenderer(typeof(MyEditor), typeof(MyRenderer))]  
       namespace FormsA.Droid  
       {  
           class MyEditText : FormsEditText  
           {  
         
               private int mLastPos = 0;  
               private int mCurPos = 0;  
         
               private EditTextSelectChange editTextSelectChange;  
         
               public void setEditTextSelectChange(EditTextSelectChange editTextSelectChange)  
               {  
                   this.editTextSelectChange = editTextSelectChange;  
               }  
         
               public MyEditText(Context context) : base(context)  
               {  
                   this.BeforeTextChanged += MyEditText_BeforeTextChanged;  
                   this.AfterTextChanged += MyEditText_AfterTextChanged;  
               }  
         
         
               string before, after;  
               private void MyEditText_AfterTextChanged(object sender, Android.Text.AfterTextChangedEventArgs e)  
               {  
                   after = e.Editable.ToString();  
         
                   if (this.editTextSelectChange != null)  
                   {  
                       editTextSelectChange.TextChanged(before, after);  
                   }  
               }  
         
               private void MyEditText_BeforeTextChanged(object sender, Android.Text.TextChangedEventArgs e)  
               {  
                  before = e.Text.ToString();  
               }  
         
               protected override void OnSelectionChanged(int selStart, int selEnd)  
               {  
                   base.OnSelectionChanged(selStart, selEnd);  
                   if (this.editTextSelectChange != null)  
                   {  
                       mCurPos = selEnd;  
                       editTextSelectChange.change(mLastPos, mCurPos);  
                       mLastPos = mCurPos;  
                   }  
         
               }  
         
               public interface EditTextSelectChange  
               {  
                   void change(int lastPos, int curPos);  
         
                   void TextChanged(string oldValue, string newValue);  
         
               }  
           }  
           class MyRenderer : Xamarin.Forms.Platform.Android.EditorRenderer, MyEditText.EditTextSelectChange  
           {  
               Context _context;  
               public MyRenderer(Context context) : base(context)  
               {  
                   _context = context;  
               }  
         
               protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)  
               {  
                   base.OnElementChanged(e);  
         
                   MyEditText myEditText = new MyEditText(_context);  
                   myEditText.Text = Element.Text;  
                   myEditText.setEditTextSelectChange(this);  
                   SetNativeControl(myEditText);  
               }  
         
         
               public void change(int lastPos, int curPos)  
               {  
                   ((MyEditor)Element).SelectionChange(lastPos, curPos);  
               }  
         
               public void TextChanged(string oldValue, string newValue)  
               {  
                   ((MyEditor)Element).TextChange(oldValue, newValue);  
               }  
           }  
       }  
    
    MyEditor
       public class MyEditor : Editor  
           {  
               public static readonly BindableProperty SelectionChangedProperty =  
               BindableProperty.Create("SelectionChanged", typeof(EventHandler), typeof(MyEditor), null);  
         
               public event EventHandler SelectionChanged;  
         
         
               public void SelectionChange(int preIndex, int currentIndex)  
               {  
                   EventHandler eventHandler = this.SelectionChanged;  
                   SelectionEventArgs selectionEventArgs = new SelectionEventArgs() { lastPos = preIndex, curPos = currentIndex };  
                   eventHandler?.Invoke((object)this, selectionEventArgs);  
               }  
         
         
               public static readonly BindableProperty MyTextChangedProperty =  
              BindableProperty.Create("MyTextChanged", typeof(EventHandler), typeof(MyEditor), null);  
         
               public event EventHandler<TextChangedEventArgs> MyTextChanged;  
         
         
               public void TextChange(string oldValue, string newValue)  
               {  
                   EventHandler<TextChangedEventArgs> eventHandler = this.MyTextChanged;  
                   TextChangedEventArgs args = new TextChangedEventArgs(oldValue, newValue);  
                   eventHandler?.Invoke((object)this, args);  
               }  
         
               public class SelectionEventArgs : EventArgs  
               {  
                   public int lastPos { get; set; }  
                   public int curPos { get; set; }  
               }  
           }  
    
    Usage
       <local:MyEditor SelectionChanged="SelectionChange" Text="a" MyTextChanged="MyEditor_TextChanged" />  
         
        private void MyEditor_TextChanged(object sender, TextChangedEventArgs e)  
               {  
         
               }  
    

    Best Regards,
    Cole Xia


    If the response is helpful, please click "Accept Answer" and upvote it.
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    1 person found this answer helpful.
    0 comments No comments

1 additional answer

Sort by: Most helpful
  1. Paul Long 21 Reputation points
    2021-04-23T22:31:28.717+00:00

    Thanks Cole, that did work. However, I realize now that I want to use the Editor Text property instead of a callback event. So I tried to use your idea to modify the property but when I do OnElementPropertyChanged is called, which calls the base.OnElementPropertyChanged which changes it back to the original value!?! Obviously I'm doing something wrong here. Also, I wonder in the back of my head why I have to reproduce the code that works without my renderer, but if that's way, I'm fine with it. But how can I get this working. Keep in mind my object names are different than the code I pointed you to.

    Here's my XAML:

            <controls:EditorExtension
                x:Name="LyricEditor" 
                Text="{Binding CurrentLyric, Mode=TwoWay}"
                EndSelection="{Binding EndSel, Mode=TwoWay}"
                StartSelection="{Binding StartSel, Mode=TwoWay}"
                VerticalOptions="StartAndExpand"
                AutoSize="TextChanges"
                FontSize="Large"
                Placeholder="Add or paste your lyrics here.  Use Split to move unselected text to new section"
                SelectionChanged="SelectionChanged"
                />
    

    And here's the renderer:

    [assembly: ExportRenderer(typeof(EditorExtension), typeof(EditorRendererExtended))]
    namespace HarmApp.Droid.UIExtensions
    {
        public class EditorRendererExtended : EditorRenderer , MyEditText.EditTextSelectChange
        {
            Context _context;
            public EditorRendererExtended(Context context) : base(context)
            {
                _context = context;
            }
    
            protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
            {
                base.OnElementChanged(e);
    
                if(e.OldElement != null)
                {
                    int x = 0;
                }
    
                MyEditText myEditText = new MyEditText(_context);
                myEditText.Text = Element.Text;
                myEditText.setEditTextSelectChange(this);
    
                SetNativeControl(myEditText);
            }
    
            public void change(int startPos, int selEnd)
            {
                ((EditorExtension)Element).SelectionChange(startPos, startPos);
            }
    
            protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                base.OnElementPropertyChanged(sender, e);
                if (Control != null)
                {
                    Control.RequestFocus();
    
                    var element = (EditorExtension)Element;
    
                    if(e.PropertyName=="Text")
                    {
                        element.EndSelection = element.StartSelection;
                    }
                    else
                    {
    
                    }
    
                    Control.SetSelection(element.StartSelection, element.EndSelection);
                }
            }
    
            public void TextChanged(string oldValue, string newValue)
            {
                ((EditorExtension)Element).Text = newValue;
                Control.SetSelection(((EditorExtension)Element).StartSelection + 1, ((EditorExtension)Element).EndSelection + 1);
            }
        }
    
        class MyEditText : FormsEditText
        {
    
            private EditTextSelectChange editTextSelectChange;
    
            public MyEditText(Context context) : base(context)
            {
                this.BeforeTextChanged += MyEditText_BeforeTextChanged;
                this.AfterTextChanged += MyEditText_AfterTextChanged;
            }
    
            public void setEditTextSelectChange(EditTextSelectChange editTextSelectChange)
            {
                this.editTextSelectChange = editTextSelectChange;
            }
    
            protected override void OnSelectionChanged(int selStart, int selEnd)
            {
                base.OnSelectionChanged(selStart, selEnd);
                if (this.editTextSelectChange != null)
                {
                    editTextSelectChange.change(selStart, selEnd);
                }
            }
    
            string before, after;
            private void MyEditText_AfterTextChanged(object sender, Android.Text.AfterTextChangedEventArgs e)
            {
                after = e.Editable.ToString();
                if (this.editTextSelectChange != null)
                {
                    editTextSelectChange.TextChanged(before, after);
                }
            }
    
            private void MyEditText_BeforeTextChanged(object sender, Android.Text.TextChangedEventArgs e)
            {
                before = e.Text.ToString();
            }
    
            public interface EditTextSelectChange
            {
                void change(int lastPos, int curPos);
                void TextChanged(string oldValue, string newValue);
            }
        }
    }
    

    And here's my editor

    namespace HarmApp.UIExtensions
    {
        public class EditorExtension : Editor
        {
            public static BindableProperty EndSelectionProperty
                = BindableProperty.Create(nameof(EndSelectionProperty), typeof(int), typeof(EditorExtension), defaultBindingMode: BindingMode.TwoWay);
    
            public int EndSelection
            {
                get { return (int)GetValue(EndSelectionProperty); }
                set { SetValue(EndSelectionProperty, value); }
            }
    
            public static BindableProperty StartSelectionProperty
                = BindableProperty.Create(nameof(StartSelectionProperty), typeof(int), typeof(EditorExtension), defaultBindingMode: BindingMode.TwoWay);
    
            public int StartSelection
            {
                get { return (int)GetValue(StartSelectionProperty); }
                set { SetValue(StartSelectionProperty, value); }
            }
    
            public static readonly BindableProperty SelectionChangedProperty =
                        BindableProperty.Create("SelectionChanged", typeof(EventHandler), typeof(EditorExtension), null);
    
            public event EventHandler SelectionChanged;
    
            public void SelectionChange(int startIndex, int endIndex)
            {
                EventHandler eventHandler = this.SelectionChanged;
                SelectionEventArgs selectionEventArgs = new SelectionEventArgs() { startPos = startIndex, endPos = endIndex };
                eventHandler?.Invoke((object)this, selectionEventArgs);
            }
    
            public class SelectionEventArgs : EventArgs
            {
                public int startPos { get; set; }
                public int endPos { get; set; }
            }
    
            //public static readonly BindableProperty MyTextChangedProperty =
            //        BindableProperty.Create("MyTextChanged", typeof(EventHandler), typeof(EditorExtension), null);
    
            //public event EventHandler<TextChangedEventArgs> MyTextChanged;
    
            //public void TextChange(string oldValue, string newValue)
            //{
            //    EventHandler<TextChangedEventArgs> eventHandler = this.MyTextChanged;
            //    TextChangedEventArgs args = new TextChangedEventArgs(oldValue, newValue);
            //    eventHandler?.Invoke((object)this, args);
            //}
    
            public static readonly BindableProperty EditorTextProperty =
                    BindableProperty.Create("Text", typeof(string), typeof(EditorExtension), defaultBindingMode: BindingMode.TwoWay);
    
            public string Text
            {
                get { return (string)GetValue(EditorTextProperty); }
                set { SetValue(EditorTextProperty, value); }
            }
        }
    }
    

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.