やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

Eto.Forms.TextAreaのコードを読んでみる

 デフォルト右クリックの処理を削除するためにはどうすればいいかを探るため。

対象環境

参考

 コントロールの継承関係は以下。

Control

 ControlのコードにMouseDownイベント定義があった。

        public const string MouseDownEvent = "Control.MouseDown";
        protected virtual void OnMouseDown(MouseEventArgs e)
        {
            Properties.TriggerEvent(MouseDownEvent, this, e);
        }
        public event EventHandler<MouseEventArgs> MouseDown
        {
            add { Properties.AddHandlerEvent(MouseDownEvent, value); }
            remove { Properties.RemoveEvent(MouseDownEvent, value); }
        }
        public new interface ICallback : Widget.ICallback
        {
            ...
            EventLookup.Register<Control>(c => c.OnMouseDown(null), Control.MouseDownEvent);
        protected class Callback : ICallback
        {
            ...
            public void OnMouseDown(Control widget, MouseEventArgs e)
            {
                using (widget.Platform.Context)
                    widget.OnMouseDown(e);
            }

予想1: -=

 これから察するに、以下のようにすればデフォルトのMouseDownイベントを削除できる?

TextArea ta = new TextArea().
ta.MouseDown -= ta.OnMouseDown;

 と思ったが、TextAreaのインスタンスからOnMouseDownを参照することができなかった。

予想2: protected

 削除する方法は上記で合っているはず。右クリックメニューを表示するイベントハンドラ(デリゲート)はMouseDownで、その処理を定義したメソッドはOnMouseDown。だからMouseDown -= OnMouseDownとすることで、右クリックメニューのデフォルト表示処理を削除できるはず。

 問題はOnMouseDownが参照できないことだ。原因はアクセス修飾子がprotectedだから。OnMouseDownTextAreaクラスの内部クラスCallback内で定義されており、Callbackクラスのアクセス修飾子はprotectedprotectedは継承クラスまでしか公開されないため、TextAreaインスタンスからは参照できない。
 OnMouseDownを参照するためには、TextAreaを継承し、this.Callback.OnMouseDownとする必要がある。つまり、以下のようにすれば解決できる?

using Eto.Forms;

class TextAreaPlain : Eto.Forms.TextArea {
    public TextAreaPlain() : base() {
        // デフォルトの右クリックメニュー表示処理を削除する
        this.MouseDown -= this.Callback.OnMouseDown;
    }
}

 しかしやってみると、なぜかCallbackを参照できない。thisbaseにしても同様。あ、そうか、protected CallbackControlクラスの定義であってTextAreaではない。TextAreaControlを継承しているので参照できるだろうが、TextAreaを継承したクラスでは見れないと思う。ダメだ、打つ手なし。

            // 参照できない
            //this.MouseDown -= this.Callback.OnMouseDown;

 また、base.MouseDown -= base.OnMouseDown;としても怒られた。なぜ? Eto.Formsにおけるイベント周りの実装を把握する必要がありそう。

            // エラー: No overload for 'OnMouseDown' matches delegate 'EventHandler<MouseEventArgs>'
            //base.MouseDown -= base.OnMouseDown; // protectedのため参照不可

予想3: virtual, override

 Controlで定義されるOnMouseDownメソッドはvirtualである。これをoverrideすれば処理を上書きできないか?

        protected virtual void OnMouseDown(MouseEventArgs e)
        {
            Properties.TriggerEvent(MouseDownEvent, this, e);
        }
using System;
using Eto;
using Eto.Forms;

namespace TextAreaMouseDownRemove
{
    public class TextAreaPlain : TextArea
    {
        public TextAreaPlain() : base() { }
        protected override void OnMouseDown(MouseEventArgs e) {
            MessageBox.Show("クリックイベント");
            //new ContextMenu().Show(this);
            return;
        }
    }
}

 実行してテキストエリアを右クリックすると以下の順で表示された。

  1. overrideした処理(クリックイベントのメッセージボックス表示)
    f:id:ytyaru:20181221102953p:plain
  2. デフォルトの右クリックメニュー表示
    f:id:ytyaru:20181221103010p:plain

 前回とは順序が逆になっただけの違いである。結局、デフォルト処理は消えていない。

 イベント処理がスタックされているのだろう。以降のイベントを実行せず中断するにはどうすればいいのか? ずっと前の.NET Frameworkではreturnすれば良かったような記憶がぼんやりとあるが。

予想4: EventLookup

 Controlのコードを見る限り、EventLookupでイベントを登録しているように見える。こいつを参照できれば、イベントを消すこともできるのではないか?

        public new interface ICallback : Widget.ICallback
        {
            ...
            EventLookup.Register<Control>(c => c.OnMouseDown(null), Control.MouseDownEvent);

 EventLookupソースコードがどこにあるか迷った。名前空間Eto内にあった。Eto.Formsではない。

 EventLookupのコードを見てみる。

namespace Eto
{
    static class EventLookup
    {
        static readonly Dictionary<Type, List<EventDeclaration>> registeredEvents = new Dictionary<Type, List<EventDeclaration>>();
        static readonly Assembly etoAssembly = typeof(EventLookup).GetAssembly();
        static readonly Dictionary<Type, string[]> externalEvents = new Dictionary<Type, string[]>();

        struct EventDeclaration
        {
            public readonly string Identifier;
            public readonly MethodInfo Method;

            public EventDeclaration(MethodInfo method, string identifier)
            {
                Method = method;
                Identifier = identifier;
            }
        }
        public static void Register<T>(Expression<Action<T>> expression, string identifier)
        {
            var method = ((MethodCallExpression)expression.Body).Method;
            var declarations = GetDeclarations(typeof(T));
            declarations.Add(new EventDeclaration(method, identifier));
        }
        static List<EventDeclaration> GetDeclarations(Type type)
        {
            List<EventDeclaration> declarations;
            if (!registeredEvents.TryGetValue(type, out declarations))
            {
                declarations = new List<EventDeclaration>();
                registeredEvents.Add(type, declarations);
            }
            return declarations;
        }

 以下のようにしてイベント削除できないだろうか。

using Eto;
EventLookup.registeredEvents["Control.MouseDown"].Remove();

 早速using Eto;して参照しようとしたが、参照できない……。

using Eto;
using Eto.Forms;

class TextAreaPlain : Eto.Forms.TextArea {
    public TextAreaPlain() : base() {
            // 参照できない
            //Eto.EventLookup.
    }
}

予想5: Properties

 ControlMouseDownイベントハンドラを見ると処理内容はPropertiesによりAdd, Removeしている。ならTextAreaを継承してPropertiesを参照すれば、MouseDownイベント処理を削除できないか?

        public event EventHandler<MouseEventArgs> MouseDown
        {
            add { Properties.AddHandlerEvent(MouseDownEvent, value); }
            remove { Properties.RemoveEvent(MouseDownEvent, value); }
        }

 以下のようなコードを書いてみた。

using System;
using Eto;
using Eto.Forms;

namespace TextAreaMouseDownRemove
{
    public class TextAreaPlain : TextArea
    {
        public TextAreaPlain() : base()
        {
            Properties.Remove(Control.MouseDownEvent);
        }
        protected override void OnMouseDown(MouseEventArgs e) {
            MessageBox.Show("クリックイベント");
            //new ContextMenu().Show(this);
            return;
        }
    }
}

 実行してテキストエリアを右クリックしてみたが、予想3と同じ結果になった。

  1. overrideした処理(クリックイベントのメッセージボックス表示)
    f:id:ytyaru:20181221102953p:plain
  2. デフォルトの右クリックメニュー表示
    f:id:ytyaru:20181221103010p:plain

 これでも消せないのか……。

予想6:

 もうTextAreaを丸ごと上書きしちゃおう。MouseDownイベントだけ消せないか?

 コードをコピペしてビルドすると以下エラー。

/tmp/work/Projects/TextAreaMouseDownRemove/TextAreaMouseDownRemove/TextAreaMouseDownRemove/TextAreaWithoutMouseDown.cs(13,13): Error CS0122: 'EventLookup' is inaccessible due to its protection level (CS0122) (TextAreaMouseDownRemove)

 EventLookupにアクセスできない。これはEto.Formsプロジェクトを丸ごと持ってこないとダメな予感……。そこまでやるなら、Eto.Toolkitをビルドできないかを試したい。

結論

 Eto.Forms.TextAreaの右クリックメニューは消せない。

 本当かよ……少なくとも私の技量では見つけられなかった。

おまけ

削除できたら次に考えること

 コンテキストメニューの参照と設定をできるようにすると親切。

    public ContextMenu ContextMenu { get; set; }
    public TextAreaPlain() : base() {
        ContextMenu = new ContextMenu();
        MouseDown += OnMouseDown;
    }
    public void OnMouseDown() {
        this.ContextMenu.Show();
    }

 OnMouseDownは公開したい。TextAreaに倣ってprotectedにすべきかもしれないが、もしMouseEventの挙動をコンテキストメニューの表示以外にしたいときにイベント削除したくなるはず。今回のように。今回のように!
 イベントに汎用性を持たせたいなら、処理を定義しているメソッドは公開すべき。他に削除できる手段があるならいいが、見つけられない。

 テキストエリアにおいて、他にどんなマウスイベントが考えられるか? たとえば手書き入力の実装がある。ペイントツールで字を書くように、マウスで字を書く。左クリックで書き、右クリックで確定させ、1文字を入力する。
 似たようなものに画像認識(OCR)がある。カメラや画像ファイルから文字を読み取り、テキストに変換してTextArea.Textに追記する。また、音声認識もある。たとえば右ダブルクリックで音声を拾い、右クリックで終了。解析したらテキストエリアに表示する。これらをマウスジェスチャで実行させるようにすることも考えられる。

 このように、右クリックイベント処理がコンテキストメニューの表示だけとは限らないため、イベント処理を公開して削除できるようにしたい。

ソースコード

 エラーだらけのクソコードを一応載せておく。

MainForm.cs

using System;
using Eto.Forms;
using Eto.Drawing;

namespace TextAreaMouseDownRemove
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            Title = "TextArea MouseDown override";
            ClientSize = new Size(400, 350);

            //TextArea textarea = new TextArea();
            //textarea.MouseDown -= textarea.OnMouseDown; // protectedのため参照不可
            //textarea.MouseDown -= TextArea.RegisterEvent<TextArea.MouseDownEvent>.OnMouseDown;
            //textarea.MouseDown -= TextArea.RegisterEvent<>.OnMouseDown;
            //textarea.MouseDown -= TextArea.RegisterEvent.OnMouseDown;

            TextAreaPlain textarea = new TextAreaPlain();
            DynamicLayout layout = new DynamicLayout();
            layout.Add(textarea);
            Content = layout;
        }
    }
}

TextAreaPlain.cs

using System;
using System.Collections.Generic;
using System.Linq;
using Eto;
using Eto.Forms;

namespace TextAreaMouseDownRemove
{
    public class TextAreaPlain : TextArea
    {
        public TextAreaPlain() : base()
        {
            // エラー: No overload for 'OnMouseDown' matches delegate 'EventHandler<MouseEventArgs>'
            //base.MouseDown -= base.OnMouseDown; // protectedのため参照不可
            // 参照できない
            //this.MouseDown -= this.Callback.OnMouseDown;
            //this.MouseDown -= Eto.Forms.Control.OnMouseDown;
            // 参照できない
            //Eto.EventLookup.
            // 変化なし
            Properties.Remove(Control.MouseDownEvent);
            //Properties.RemoveEvent(Control.MouseDownEvent); // (object key, Delegate) 正体不明の引数

            // Properties.Keysのobjectが何なのかわからない
            // [ERROR] FATAL UNHANDLED EXCEPTION: System.InvalidCastException: Specified cast is not valid.
            //this.Text += Properties.ToString();
            this.Text += Properties.Keys.Count.ToString() + "\n";
            this.Text += Properties.Keys.ToString() + "\n";

            //        while (null != Properties.Keys.GetEnumerator().Current) {
            //Properties.Keys.GetEnumerator().Current;
            //Properties.Keys.GetEnumerator().MoveNext();
            //}
            //KeyValuePair<object, object> pair = Properties.Keys.First();
            object key = Properties.Keys.First();
            this.Text += key.ToString() + "\n";
            //this.Text += (string)key + "\n";

            //foreach (KeyValuePair<object, object> pair in Properties.Keys)
            //{
            //}
            //foreach (KeyValuePair<object, object> pair in Properties.Keys.First())
            //{
            //}
            //foreach (KeyValuePair<object, object> pair in Properties.Keys)
            //{
            //}
            
            //List<string> keys = new List<string>(Properties.Keys);
            //foreach (string key in keys)
            //{
            //    this.Text += key;
            //}

            //foreach (Dictionary<object, object> key in Properties.Keys)
            //{
            //}
            //foreach (Dictionary<object, object>.KeyCollection key in Properties.Keys)
            //{
            //    this.Text += key.ToString();
            //    //foreach (Dictionary<object, object> k in key.Keys)
            //    //{
            //    //    this.Text += k.key.ToString();
            //    //}
            //}
            
        }
        //public override void OnMouseEvent(MouseEventArgs e) { }
        protected override void OnMouseDown(MouseEventArgs e) {
            MessageBox.Show("クリックイベント");
            //new ContextMenu().Show(this);
            return;
        }
    }
}

TextAreaPlain.cs

 予想6のコード。TextAreaを丸パクリしたが、ビルドできずコメントアウト

////using System;
////namespace TextAreaMouseDownRemove
////{
////    public class TextAreaWithoutMouseDown
////    {
////        public TextAreaWithoutMouseDown()
////        {
////        }
////    }
////}
//using Eto.Drawing;
//using System;
//using System.ComponentModel;

//namespace Eto.Forms
//{
//    /// <summary>
//    /// Text replacement options when entering text
//    /// </summary>
//    [Flags]
//    public enum TextReplacements
//    {
//        /// <summary>
//        /// Do not perform any automatic replacements based on user input
//        /// </summary>
//        None = 0,
//        /// <summary>
//        /// Perform text replacements, such as shortcuts
//        /// </summary>
//        Text = 1 << 0,
//        /// <summary>
//        /// Perform replacements of straight quotes to 'curly' quotes
//        /// </summary>
//        Quote = 1 << 1,
//        /// <summary>
//        /// Perform replacements of dashes '-' to em dash '—'.
//        /// </summary>
//        /// <remarks>
//        /// Note that some platforms may do this automatically with a single dash, some may require the user to enter 
//        /// double dashes.
//        /// </remarks>
//        Dash = 1 << 2,
//        /// <summary>
//        /// Perform automatic spelling correction
//        /// </summary>
//        Spelling = 1 << 3,
//        /// <summary>
//        /// All replacements enabled.
//        /// </summary>
//        All = Text | Quote | Dash | Spelling
//    }

//    /// <summary>
//    /// Control for multi-line text
//    /// </summary>
//    /// <remarks>
//    /// This differs from the <see cref="TextBox"/> in that it is used for multi-line text entry and can accept <see cref="Keys.Tab"/>
//    /// and <see cref="Keys.Enter"/> input.
//    /// </remarks>
//    [Handler(typeof(TextArea.IHandler))]
//    public class TextArea : TextControl
//    {
//        new IHandler Handler { get { return (IHandler)base.Handler; } }

//        #region Events

//        /// <summary>
//        /// Identifier for handlers when attaching the <see cref="SelectionChanged"/> event.
//        /// </summary>
//        public const string SelectionChangedEvent = "TextArea.SelectionChanged";

//        /// <summary>
//        /// Occurs when the <see cref="Selection"/> is changed.
//        /// </summary>
//        public event EventHandler<EventArgs> SelectionChanged
//        {
//            add { Properties.AddHandlerEvent(SelectionChangedEvent, value); }
//            remove { Properties.RemoveEvent(SelectionChangedEvent, value); }
//        }

//        /// <summary>
//        /// Raises the <see cref="SelectionChanged"/> event.
//        /// </summary>
//        /// <param name="e">Event arguments.</param>
//        protected virtual void OnSelectionChanged(EventArgs e)
//        {
//            Properties.TriggerEvent(SelectionChangedEvent, this, e);
//        }

//        /// <summary>
//        /// Identifier for handlers when attaching the <see cref="CaretIndexChanged"/> event.
//        /// </summary>
//        public const string CaretIndexChangedEvent = "TextArea.CaretIndexChanged";

//        /// <summary>
//        /// Occurs when the <see cref="CaretIndex"/> has changed.
//        /// </summary>
//        public event EventHandler<EventArgs> CaretIndexChanged
//        {
//            add { Properties.AddHandlerEvent(CaretIndexChangedEvent, value); }
//            remove { Properties.RemoveEvent(CaretIndexChangedEvent, value); }
//        }

//        /// <summary>
//        /// Raises the <see cref="CaretIndexChanged"/> event.
//        /// </summary>
//        /// <param name="e">Event arguments.</param>
//        protected virtual void OnCaretIndexChanged(EventArgs e)
//        {
//            Properties.TriggerEvent(CaretIndexChangedEvent, this, e);
//        }

//        #endregion

//        static TextArea()
//        {
//            EventLookup.Register<TextArea>(c => c.OnSelectionChanged(null), TextArea.SelectionChangedEvent);
//            EventLookup.Register<TextArea>(c => c.OnCaretIndexChanged(null), TextArea.CaretIndexChangedEvent);
//        }

//        /// <summary>
//        /// Gets or sets a value indicating whether this <see cref="Eto.Forms.TextArea"/> is read only.
//        /// </summary>
//        /// <remarks>
//        /// A read only text box can be scrolled, text can be selected and copied, etc. However, the user
//        /// will not be able to change any of the text.
//        /// This differs from the <see cref="Control.Enabled"/> property, which disables all user interaction.
//        /// </remarks>
//        /// <value><c>true</c> if the control is read only; otherwise, <c>false</c>.</value>
//        public bool ReadOnly
//        {
//            get { return Handler.ReadOnly; }
//            set { Handler.ReadOnly = value; }
//        }

//        /// <summary>
//        /// Gets or sets a value indicating whether text will wrap if lines are longer than the width of the control.
//        /// </summary>
//        /// <remarks>
//        /// Typically, a platform will word wrap the text.
//        /// </remarks>
//        /// <value><c>true</c> to wrap the text; otherwise, <c>false</c>.</value>
//        [DefaultValue(true)]
//        public bool Wrap
//        {
//            get { return Handler.Wrap; }
//            set { Handler.Wrap = value; }
//        }
//        /// <summary>
//        /// Gets or sets the selected text.
//        /// </summary>
//        /// <remarks>
//        /// When setting the selected text, the text within the <see cref="Selection"/> range will be replaced with
//        /// the new value.
//        /// </remarks>
//        /// <value>The selected text.</value>
//        public string SelectedText
//        {
//            get { return Handler.SelectedText; }
//            set { Handler.SelectedText = value; }
//        }

//        /// <summary>
//        /// Gets or sets the range of selected text.
//        /// </summary>
//        /// <remarks>
//        /// When setting the selection, the control will be focussed and the associated keyboard may appear on mobile platforms.
//        /// </remarks>
//        /// <seealso cref="SelectAll"/>
//        /// <value>The text selection.</value>
//        public Range<int> Selection
//        {
//            get { return Handler.Selection; }
//            set { Handler.Selection = value; }
//        }

//        /// <summary>
//        /// Selects all text.
//        /// </summary>
//        /// <remarks>
//        /// When setting the selection, the control will be focussed and the associated keyboard may appear on mobile platforms.
//        /// </remarks>
//        public void SelectAll()
//        {
//            Handler.SelectAll();
//        }

//        /// <summary>
//        /// Gets or sets the index of the insertion caret.
//        /// </summary>
//        /// <remarks>
//        /// When setting the caret, the control will be focussed and the associated keyboard may appear on mobile platforms.
//        /// </remarks>
//        /// <value>The index of the insertion caret.</value>
//        public int CaretIndex
//        {
//            get { return Handler.CaretIndex; }
//            set { Handler.CaretIndex = value; }
//        }

//        /// <summary>
//        /// Gets or sets a value indicating whether the tab key is inserted into the text area, or if it should be ignored by this control and used
//        /// for navigating to the next control.
//        /// </summary>
//        /// <value><c>true</c> if the TextArea accepts tab key characters; otherwise, <c>false</c>.</value>
//        [DefaultValue(true)]
//        public bool AcceptsTab
//        {
//            get { return Handler.AcceptsTab; }
//            set { Handler.AcceptsTab = value; }
//        }

//        /// <summary>
//        /// Gets or sets a value indicating whether the return key is inserted into the text area, or if it should be ignored by this control.
//        /// </summary>
//        /// <value><c>true</c> if the TextArea accepts the return key; otherwise, <c>false</c>.</value>
//        [DefaultValue(true)]
//        public bool AcceptsReturn
//        {
//            get { return Handler.AcceptsReturn; }
//            set { Handler.AcceptsReturn = value; }
//        }

//        /// <summary>
//        /// Gets or sets the horizontal alignment of the text.
//        /// </summary>
//        /// <value>The horizontal alignment.</value>
//        public TextAlignment TextAlignment
//        {
//            get { return Handler.TextAlignment; }
//            set { Handler.TextAlignment = value; }
//        }

//        /// <summary>
//        /// Gets or sets the horizontal alignment of the text.
//        /// </summary>
//        /// <value>The horizontal alignment.</value>
//        [Obsolete("Since 2.1: Use TextAlignment instead")]
//        public HorizontalAlign HorizontalAlign
//        {
//            get { return Handler.TextAlignment; }
//            set { Handler.TextAlignment = value; }
//        }

//        /// <summary>
//        /// Gets or sets a value indicating whether this <see cref="Eto.Forms.TextArea"/> will perform spell checking.
//        /// </summary>
//        /// <remarks>
//        /// When <c>true</c>, platforms will typically show misspelled or unknown words with a red underline.
//        /// This is a hint, and is only supported by the platform when <see cref="SpellCheckIsSupported"/> is true.
//        /// When not supported, setting this property will do nothing.
//        /// </remarks>
//        /// <value><c>true</c> if spell check; otherwise, <c>false</c>.</value>
//        public bool SpellCheck
//        {
//            get { return Handler.SpellCheck; }
//            set { Handler.SpellCheck = value; }
//        }

//        /// <summary>
//        /// Gets a value indicating whether the <see cref="SpellCheck"/> property is supported on the control's platform.
//        /// </summary>
//        /// <value><c>true</c> if spell check is supported; otherwise, <c>false</c>.</value>
//        public bool SpellCheckIsSupported
//        {
//            get { return Handler.SpellCheckIsSupported; }
//        }

//        /// <summary>
//        /// Gets or sets a hint value indicating whether this <see cref="Eto.Forms.TextArea"/> will automatically correct text.
//        /// </summary>
//        /// <remarks>
//        /// On some platforms, autocorrection or text replacements such as quotes, etc may be default.
//        /// Set this to <see cref="Eto.Forms.TextReplacements.None"/> to disable any text replacement.
//        /// 
//        /// Note this is only supported on OS X currently, all other platforms will be ignored.
//        /// </remarks>
//        /// <value>Type of replacements to enable when entering text..</value>
//        public TextReplacements TextReplacements
//        {
//            get { return Handler.TextReplacements; }
//            set { Handler.TextReplacements = value; }
//        }

//        /// <summary>
//        /// Gets the text replacements that this control supports on the current platform.
//        /// </summary>
//        /// <remarks>
//        /// You can use this to determine which flags in the <see cref="TextReplacements"/> will take effect.
//        /// </remarks>
//        /// <value>The supported text replacements.</value>
//        public TextReplacements SupportedTextReplacements
//        {
//            get { return Handler.SupportedTextReplacements; }
//        }

//        /// <summary>
//        /// Append the specified text to the control and optionally scrolls to make the inserted text visible.
//        /// </summary>
//        /// <remarks>
//        /// This is an optimized way of inserting text into a TextArea when its content gets large.
//        /// </remarks>
//        /// <param name="text">Text to insert.</param>
//        /// <param name="scrollToCursor">If set to <c>true</c>, scroll to the inserted text.</param>
//        public void Append(string text, bool scrollToCursor = false)
//        {
//            Handler.Append(text, scrollToCursor);
//        }

//        static readonly object callback = new Callback();

//        /// <summary>
//        /// Gets an instance of an object used to perform callbacks to the widget from handler implementations
//        /// </summary>
//        /// <returns>The callback instance to use for this widget</returns>
//        protected override object GetCallback()
//        {
//            return callback;
//        }

//        /// <summary>
//        /// Callback interface for the <see cref="TextArea"/>
//        /// </summary>
//        public new interface ICallback : TextControl.ICallback
//        {
//            /// <summary>
//            /// Raises the selection changed event.
//            /// </summary>
//            void OnSelectionChanged(TextArea widget, EventArgs e);

//            /// <summary>
//            /// Raises the caret index changed event.
//            /// </summary>
//            void OnCaretIndexChanged(TextArea widget, EventArgs e);
//        }

//        /// <summary>
//        /// Callback implementation for handlers of the <see cref="TextArea"/>
//        /// </summary>
//        protected new class Callback : TextControl.Callback, ICallback
//        {
//            /// <summary>
//            /// Raises the selection changed event.
//            /// </summary>
//            public void OnSelectionChanged(TextArea widget, EventArgs e)
//            {
//                using (widget.Platform.Context)
//                    widget.OnSelectionChanged(e);
//            }

//            /// <summary>
//            /// Raises the caret index changed event.
//            /// </summary>
//            public void OnCaretIndexChanged(TextArea widget, EventArgs e)
//            {
//                using (widget.Platform.Context)
//                    widget.OnCaretIndexChanged(e);
//            }
//        }

//        /// <summary>
//        /// Handler interface for the <see cref="TextArea"/>
//        /// </summary>
//        public new interface IHandler : TextControl.IHandler
//        {
//            /// <summary>
//            /// Gets or sets a value indicating whether this <see cref="Eto.Forms.TextArea"/> is read only.
//            /// </summary>
//            /// <remarks>
//            /// A read only text box can be scrolled, text can be selected and copied, etc. However, the user
//            /// will not be able to change any of the text.
//            /// This differs from the <see cref="Control.Enabled"/> property, which disables all user interaction.
//            /// </remarks>
//            /// <value><c>true</c> if the control is read only; otherwise, <c>false</c>.</value>
//            bool ReadOnly { get; set; }

//            /// <summary>
//            /// Gets or sets a value indicating whether text will wrap if lines are longer than the width of the control.
//            /// </summary>
//            /// <remarks>
//            /// Typically, a platform will word wrap the text.
//            /// </remarks>
//            /// <value><c>true</c> to wrap the text; otherwise, <c>false</c>.</value>
//            bool Wrap { get; set; }

//            /// <summary>
//            /// Append the specified text to the control and optionally scrolls to make the inserted text visible.
//            /// </summary>
//            /// <remarks>
//            /// This is an optimized way of inserting text into a TextArea when its content gets large.
//            /// </remarks>
//            /// <param name="text">Text to insert.</param>
//            /// <param name="scrollToCursor">If set to <c>true</c>, scroll to the inserted text.</param>
//            void Append(string text, bool scrollToCursor);

//            /// <summary>
//            /// Gets or sets the selected text.
//            /// </summary>
//            /// <remarks>
//            /// When setting the selected text, the text within the <see cref="Selection"/> range will be replaced with
//            /// the new value.
//            /// </remarks>
//            /// <value>The selected text.</value>
//            string SelectedText { get; set; }

//            /// <summary>
//            /// Gets or sets the range of selected text.
//            /// </summary>
//            /// <seealso cref="SelectAll"/>
//            /// <value>The text selection.</value>
//            Range<int> Selection { get; set; }

//            /// <summary>
//            /// Selects all text.
//            /// </summary>
//            /// <remarks>
//            /// When setting the selection, the control will be focussed and the associated keyboard may appear on mobile platforms.
//            /// </remarks>
//            void SelectAll();

//            /// <summary>
//            /// Gets or sets the index of the insertion caret.
//            /// </summary>
//            /// <remarks>
//            /// When setting the caret, the control will be focussed and the associated keyboard may appear on mobile platforms.
//            /// </remarks>
//            /// <value>The index of the insertion caret.</value>
//            int CaretIndex { get; set; }

//            /// <summary>
//            /// Gets or sets a value indicating whether the tab key is inserted into the text area, or if it should be ignored by this control and used
//            /// for navigating to the next control.
//            /// </summary>
//            /// <value><c>true</c> if the TextArea accepts tab key characters; otherwise, <c>false</c>.</value>
//            bool AcceptsTab { get; set; }

//            /// <summary>
//            /// Gets or sets a value indicating whether the return key is inserted into the text area, or if it should be ignored by this control.
//            /// </summary>
//            /// <value><c>true</c> if the TextArea accepts the return key; otherwise, <c>false</c>.</value>
//            bool AcceptsReturn { get; set; }

//            /// <summary>
//            /// Gets or sets a hint value indicating whether this <see cref="Eto.Forms.TextArea"/> will automatically correct text.
//            /// </summary>
//            /// <remarks>
//            /// On some platforms, autocorrection or text replacements such as quotes, etc may be default.
//            /// Set this to <see cref="Eto.Forms.TextReplacements.None"/> to disable any text replacement.
//            /// 
//            /// Note this is only a hint and not all (or any) of the replacements may apply on some platforms.
//            /// </remarks>
//            /// <value>Type of replacements to enable when entering text..</value>
//            TextReplacements TextReplacements { get; set; }

//            /// <summary>
//            /// Gets the text replacements that this control supports on the current platform.
//            /// </summary>
//            /// <remarks>
//            /// You can use this to determine which flags in the <see cref="TextReplacements"/> will take effect.
//            /// </remarks>
//            /// <value>The supported text replacements.</value>
//            TextReplacements SupportedTextReplacements { get; }

//            /// <summary>
//            /// Gets or sets the horizontal alignment of the text.
//            /// </summary>
//            /// <value>The horizontal alignment.</value>
//            TextAlignment TextAlignment { get; set; }

//            /// <summary>
//            /// Gets or sets a value indicating whether this <see cref="Eto.Forms.TextArea"/> will perform spell checking.
//            /// </summary>
//            /// <remarks>
//            /// When <c>true</c>, platforms will typically show misspelled or unknown words with a red underline.
//            /// This is a hint, and is only supported by the platform when <see cref="SpellCheckIsSupported"/> is true.
//            /// When not supported, setting this property will do nothing.
//            /// </remarks>
//            /// <value><c>true</c> if spell check; otherwise, <c>false</c>.</value>
//            bool SpellCheck { get; set; }

//            /// <summary>
//            /// Gets a value indicating whether the <see cref="SpellCheck"/> property is supported on the control's platform.
//            /// </summary>
//            /// <value><c>true</c> if spell check is supported; otherwise, <c>false</c>.</value>
//            bool SpellCheckIsSupported { get; }
//        }
//    }
//}