概要は把握したが、動作するコードは書けなかった。
対象環境
- Raspbierry pi 3 Model B+
- Raspbian stretch(9.0) 2018-06-27
- Mono 5.16.0
- MonoDevelop 7.6 build 711
- Eto.Forms 2.4.1 拡張機能, NuGetパッケージ
- .NET Core 2.2, MonoDevelop参照方法
前回まで
TextAreaで右クリックするとコンテキストメニューが自動で出る。これを削除したいが、TextAreaやTextControlを継承しても解決できなかった。
- http://ytyaru.hatenablog.com/entry/2020/02/27/000000
- http://ytyaru.hatenablog.com/entry/2020/02/28/000000
- http://ytyaru.hatenablog.com/entry/2020/02/29/000000
- http://ytyaru.hatenablog.com/entry/2020/03/03/000000
Eto.Formsでカスタム・コントロールは作れるのか?
英語なので翻訳して読む。
カスタムプラットフォームコントロールを作成することができます。
作ることはできるみたい。
カスタムプラットフォームコントロールを作成することの欠点は、使用する各プラットフォーム用のコントロールのハンドラ実装を作成する必要があることです。
つまり、Windows, Mac, Linux, Androidといった各種プラットフォーム用のコードを書かねばならないと。WindowsならWindowsForm, WPFなどがあり、LinuxならGTK+2, GTK+3があるのだろう。かなり大変そう。
Eto
自作する前にEtoではどのように実装しているのか見てみる。
プラットフォーム
Etoではどんなプラットフォームに対応しているのか。全8種。Windows系4(Direct2D, WinForms, WinRT, Wpf)、Mac系2(Mac, iOS)、Linux系(Gtk)、Android。
ちなみに上記はすべて以下の抽象クラスを継承している。
コントロールを追加している場所
上記リンクの通り、各プラットフォームのディレクトリ配下にPlatform.cs
ファイルがあり、そこでコントロールを追加しているようだ。たとえばEto.Gtk/Platform.csの場合は以下。
namespace Eto.GtkSharp { ... public class Platform : Eto.Platform { ... public Platform() { #if GTK2 if (EtoEnvironment.Platform.IsWindows && Environment.Is64BitProcess) throw new NotSupportedException(string.Format(System.Globalization.CultureInfo.CurrentCulture, "Please compile/run GTK in x86 mode (32-bit) on windows")); #endif AddTo(this); } public static void AddTo(Eto.Platform p) { ... p.Add<TextArea.IHandler>(() => new TextAreaHandler()); ... } } }
TextArea
をPlatform
に追加している。
Etoによる共通インタフェースのラップメソッド内に、プラットフォーム固有の処理を実装している。GTKにおけるTextArea相当のUIはGtk.TextView
らしい。
ソースコード
試しに書いてみたが、エラーが出て失敗する。Gtkが参照できないため作成できない。おそらくGtk
パッケージを追加すればいいのだろうが未確認。
ExtendTextControl.Desktop
Program.cs
using System; using Eto; using Eto.Forms; using Eto.Drawing; using ExtendTextControl; namespace ExtendTextControl.Desktop { class Program { [STAThread] static void Main(string[] args) { // Eto.Gtk.Platform().Add<MyCustomControl.IMyCustomControl>(() => new MyCustomControlHandler() Eto.Platform.Detect.Add<MyCustomControl.IMyCustomControl>(() => new MyCustomControlHandler() // p.Add<TextArea.IHandler>(() => new TextAreaHandler()); Eto.Platform.Detect.Add<TextControl2.IHandler>(() => new TextControl2Handler() new Application(Eto.Platform.Detect).Run(new MainForm()); } } }
ExtendTextControl
MainForm.cs
using System; using Eto.Forms; using Eto.Drawing; namespace ExtendTextControl { public partial class MainForm : Form { public MainForm() { Title = "Extend TextControl"; ClientSize = new Size(400, 350); //TextControl textcontrol = new TextControl(); // abstract class TextControl2 textcontrol2 = new TextControl2(); DynamicLayout layout = new DynamicLayout(); //layout.Add(textcontrol); layout.Add(textcontrol2); Content = layout; } } }
TextControl2.cs
using System; using Eto; using Eto.Forms; using Eto.IO; namespace ExtendTextControl { [Handler(typeof(TextControl2.IHandler))] public class TextControl2 : TextControl { new IHandler Handler { get { return (IHandler)base.Handler; } } static TextControl2() {} public new interface IHandler : TextControl.IHandler { } } }
Gtk/TextControl2Handler.cs
Gtk/TextAreaHandler.csを丸パクリしてTextArea
をTextControl2
に置換した。
しかし、Eto.GtkSharp.Drawing
が存在しない。Gtk.TextView
も参照できない。おそらくEtoプロジェクト内でのみ参照できるのだろう。どうしたものか……。
using System; using Eto.Forms; using Eto.Drawing; using Eto.GtkSharp.Drawing; namespace Eto.GtkSharp.Forms.Controls { public class TextControl2Handler : TextControl2Handler<Gtk.TextView, TextControl2, TextControl2.ICallback> { } public class TextControl2Handler<TControl, TWidget, TCallback> : GtkControl<TControl, TWidget, TCallback>, TextControl2.IHandler where TControl: Gtk.TextView, new() where TWidget: TextControl2 where TCallback: TextControl2.ICallback { int suppressSelectionAndTextChanged; readonly Gtk.ScrolledWindow scroll; Gtk.TextTag tag; public override Gtk.Widget ContainerControl { get { return scroll; } } public override Size DefaultSize { get { return new Size(100, 60); } } public TextControl2Handler() { scroll = new Gtk.ScrolledWindow(); scroll.ShadowType = Gtk.ShadowType.In; Control = new TControl(); Size = new Size(100, 60); scroll.Add(Control); Wrap = true; } public override void AttachEvent(string id) { switch (id) { case TextControl.TextChangedEvent: Control.Buffer.Changed += Connector.HandleBufferChanged; break; case TextControl2.SelectionChangedEvent: Control.Buffer.MarkSet += Connector.HandleSelectionChanged; break; case TextControl2.CaretIndexChangedEvent: Control.Buffer.MarkSet += Connector.HandleCaretIndexChanged; break; default: base.AttachEvent(id); break; } } protected new TextControl2Connector Connector { get { return (TextControl2Connector)base.Connector; } } protected override WeakConnector CreateConnector() { return new TextControl2Connector(); } protected class TextControl2Connector : GtkControlConnector { Range<int> lastSelection; int? lastCaretIndex; public new TextControl2Handler<TControl, TWidget, TCallback> Handler { get { return (TextControl2Handler<TControl, TWidget, TCallback>)base.Handler; } } public void HandleBufferChanged(object sender, EventArgs e) { var handler = Handler; if (handler.suppressSelectionAndTextChanged == 0) handler.Callback.OnTextChanged(Handler.Widget, EventArgs.Empty); } public void HandleSelectionChanged(object o, Gtk.MarkSetArgs args) { var handler = Handler; var selection = handler.Selection; if (handler.suppressSelectionAndTextChanged == 0 && selection != lastSelection) { handler.Callback.OnSelectionChanged(handler.Widget, EventArgs.Empty); lastSelection = selection; } } public void HandleCaretIndexChanged(object o, Gtk.MarkSetArgs args) { var handler = Handler; var caretIndex = handler.CaretIndex; if (handler.suppressSelectionAndTextChanged == 0 && caretIndex != lastCaretIndex) { handler.Callback.OnCaretIndexChanged(handler.Widget, EventArgs.Empty); lastCaretIndex = caretIndex; } } public void HandleApplyTag(object sender, EventArgs e) { var buffer = Handler.Control.Buffer; var tag = Handler.tag; buffer.ApplyTag(tag, buffer.StartIter, buffer.EndIter); } } public override string Text { get { return Control.Buffer.Text; } set { var sel = Selection; suppressSelectionAndTextChanged++; Control.Buffer.Text = value; if (tag != null) Control.Buffer.ApplyTag(tag, Control.Buffer.StartIter, Control.Buffer.EndIter); Callback.OnTextChanged(Widget, EventArgs.Empty); suppressSelectionAndTextChanged--; if (sel != Selection) Callback.OnSelectionChanged(Widget, EventArgs.Empty); } } public virtual Color TextColor { get { return Control.GetForeground(); } set { Control.SetForeground(value); Control.SetTextColor(value); } } public override Color BackgroundColor { get { return Control.GetBase(); } set { Control.SetBackground(value); Control.SetBase(value); } } public bool ReadOnly { get { return !Control.Editable; } set { Control.Editable = !value; } } public bool Wrap { get { return Control.WrapMode != Gtk.WrapMode.None; } set { Control.WrapMode = value ? Gtk.WrapMode.WordChar : Gtk.WrapMode.None; } } public void Append(string text, bool scrollToCursor) { var end = Control.Buffer.EndIter; Control.Buffer.Insert(ref end, text); if (scrollToCursor) { var mark = Control.Buffer.CreateMark(null, end, false); Control.ScrollToMark(mark, 0, false, 0, 0); } } public string SelectedText { get { Gtk.TextIter start, end; if (Control.Buffer.GetSelectionBounds(out start, out end)) { return Control.Buffer.GetText(start, end, false); } return string.Empty; } set { suppressSelectionAndTextChanged++; Gtk.TextIter start, end; if (Control.Buffer.GetSelectionBounds(out start, out end)) { var startOffset = start.Offset; Control.Buffer.Delete(ref start, ref end); if (value != null) { Control.Buffer.Insert(ref start, value); start = Control.Buffer.GetIterAtOffset(startOffset); end = Control.Buffer.GetIterAtOffset(startOffset + value.Length); Control.Buffer.SelectRange(start, end); } } else if (value != null) Control.Buffer.InsertAtCursor(value); if (tag != null) Control.Buffer.ApplyTag(tag, Control.Buffer.StartIter, Control.Buffer.EndIter); Callback.OnTextChanged(Widget, EventArgs.Empty); Callback.OnSelectionChanged(Widget, EventArgs.Empty); suppressSelectionAndTextChanged--; } } public Range<int> Selection { get { Gtk.TextIter start, end; if (Control.Buffer.GetSelectionBounds(out start, out end)) { return new Range<int>(start.Offset, end.Offset - 1); } return Range.FromLength(Control.Buffer.CursorPosition, 0); } set { suppressSelectionAndTextChanged++; var start = Control.Buffer.GetIterAtOffset(value.Start); var end = Control.Buffer.GetIterAtOffset(value.End + 1); Control.Buffer.SelectRange(start, end); Callback.OnSelectionChanged(Widget, EventArgs.Empty); suppressSelectionAndTextChanged--; } } public void SelectAll() { Control.Buffer.SelectRange(Control.Buffer.StartIter, Control.Buffer.EndIter); } public int CaretIndex { get { return Control.Buffer.GetIterAtMark(Control.Buffer.InsertMark).Offset; } set { var ins = Control.Buffer.GetIterAtOffset(value); Control.Buffer.SelectRange(ins, ins); } } public bool AcceptsTab { get { return Control.AcceptsTab; } set { Control.AcceptsTab = value; } } bool acceptsReturn = true; public bool AcceptsReturn { get { return acceptsReturn; } set { if (value != acceptsReturn) { if (!acceptsReturn) Widget.KeyDown -= HandleKeyDown; //Control.KeyPressEvent -= PreventEnterKey; acceptsReturn = value; if (!acceptsReturn) Widget.KeyDown += HandleKeyDown; //Control.KeyPressEvent += PreventEnterKey; } } } void HandleKeyDown(object sender, KeyEventArgs e) { if (e.KeyData == Keys.Enter) e.Handled = true; } static void PreventEnterKey(object o, Gtk.KeyPressEventArgs args) { if (args.Event.Key == Gdk.Key.Return) args.RetVal = false; } public override Font Font { get { return base.Font; } set { base.Font = value; if (value != null) { if (tag == null) { tag = new Gtk.TextTag("font"); Control.Buffer.TagTable.Add(tag); Control.Buffer.Changed += Connector.HandleApplyTag; Control.Buffer.ApplyTag(tag, Control.Buffer.StartIter, Control.Buffer.EndIter); } value.Apply(tag); } else { Control.Buffer.RemoveAllTags(Control.Buffer.StartIter, Control.Buffer.EndIter); } } } public TextAlignment TextAlignment { get { return Control.Justification.ToEto(); } set { Control.Justification = value.ToGtk(); } } public bool SpellCheck { get { return false; } set { } } public bool SpellCheckIsSupported { get { return false; } } public TextReplacements TextReplacements { get { return TextReplacements.None; } set { } } public TextReplacements SupportedTextReplacements { get { return TextReplacements.None; } } } }
所感
Etoのコードを書く前に、Gtk#だけでコードが書けるようになるべき。