読者です 読者をやめる 読者になる 読者になる

Eto.Formsでメモ帳のようなもの

GUIプログラミングといえばテキストエディタ(と個人的には思っているのです)。ということで、Eto.Formsでも簡単なサンプルを作ってみます。

できあがりは次のような感じです。

f:id:mokake:20161227192110p:plain

作成

今回はアプリケーションプロパティでPortable Class LibraryではなくFull .NETを選ぶ点に注意してください。これはファイル操作コードが入っているからです。

f:id:mokake:20161227191317p:plain

using System;
using System.IO;
using System.Text;
using Eto.Forms;
using Eto.Drawing;

namespace EtoSample2
{
    public class MainForm : Form
    {
        TextArea myText = new TextArea();

        public MainForm()
        {
            Title = "Eto notepad";
            ClientSize = new Size(400, 350);

            Content = myText;

           #region Commands

            var openCommand = new Command {
                MenuText = "&Open...",
                ToolBarText = "Open",
                Shortcut = Application.Instance.CommonModifier | Keys.O,
            };
            openCommand.Executed += (s, e) => {
                var dialog = new OpenFileDialog();
                dialog.Filters.Add("text file|.txt");
                dialog.Filters.Add("any file|.*");
                if (dialog.ShowDialog(this) != DialogResult.Ok) return;
                try
                {
                    var text = File.ReadAllText(
                        dialog.FileName, Encoding.UTF8);
                    if (text != null) myText.Text = text;
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Failed. Detail:" + ex.Message);
                }
            };

            var saveAsCommand = new Command {
                MenuText = "Save &As...",
                ToolBarText = "Save",
                Shortcut = Application.Instance.CommonModifier | Keys.S,
            };
            saveAsCommand.Executed += (s, e) => {
                var dialog = new SaveFileDialog();
                dialog.Filters.Add("text file|.txt");
                dialog.Filters.Add("any file|.*");
                if (dialog.ShowDialog(this) != DialogResult.Ok) return;
                try
                {
                    File.WriteAllText(
                        dialog.FileName,
                        myText.Text,
                        Encoding.UTF8);
                }
                catch(Exception ex)
                {
                    MessageBox.Show("Failed. Detail:" + ex.Message);
                }
            };

            var fontCommand = new Command { MenuText = "&Font...", };
            fontCommand.Executed += (s, e) => {
                var dialog = new FontDialog { Font = myText.Font };
                dialog.FontChanged += (s_, e_) => myText.Font = dialog.Font;
                dialog.ShowDialog(this);
            };

            var backgroundColorCommand = new Command {
                MenuText = "&Background Color..."
            };
            backgroundColorCommand.Executed += (s, e) => {
                var dialog = new ColorDialog
                    { Color = myText.BackgroundColor };
                dialog.ColorChanged += (s_, e_) =>
                    myText.BackgroundColor = dialog.Color;
                dialog.ShowDialog(this);
            };

            var wrapCommand = new CheckCommand {
                MenuText = "Wrap Line",
                Checked = myText.Wrap
            };
            wrapCommand.CheckedChanged += (s, e) => {
                myText.Wrap = wrapCommand.Checked;
            };

            var quitCommand = new Command {
                MenuText = "Quit",
                Shortcut = Application.Instance.CommonModifier | Keys.Q
            };
            quitCommand.Executed += (sender, e) => Application.Instance.Quit();

            var aboutCommand = new Command { MenuText = "About..." };
            aboutCommand.Executed += (sender, e) =>
                MessageBox.Show(this, "About my app...");

           #endregion

           #region Menus

            Menu = new MenuBar
            {
                Items =
                {
                    new ButtonMenuItem {
                        Text = "&File",
                        Items = { openCommand, saveAsCommand }
                    },
                    new ButtonMenuItem {
                        Text = "&View",
                        Items = {
                            fontCommand,
                            backgroundColorCommand,
                            wrapCommand
                        }
                    },
                },
                ApplicationItems =
                {
                    new ButtonMenuItem { Text = "&Preferences..." },
                },
                QuitItem = quitCommand,
                AboutItem = aboutCommand
            };

            ToolBar = new ToolBar { Items = { openCommand, saveAsCommand } };

           #endregion
        }
    }
}

行数はやや長いものの、特に難しいことはないので読めば分かることと思います。

実行

実行します。

f:id:mokake:20161227192019p:plain

f:id:mokake:20161227192110p:plain

フォントおよび色選択ダイアログ

フォント選択ダイアログ(FontDialog)、色選択ダイアログ(ColorDialog)は、WPFだと独自実装になります。 WinFormsの名前空間引っ張った方がWindows標準になっていい(WPFが使える環境でWinFormsが使えないケースは事実上存在しない)と思うので、ここは残念。

f:id:mokake:20161227192235p:plain

f:id:mokake:20161227192249p:plain

折り返し設定

さらに折り返し設定も無効にしてみます。

f:id:mokake:20161227192303p:plain

Linux環境で実行

Linux環境では、フォントも色もGTKの標準を使います。

f:id:mokake:20161227192350p:plain

f:id:mokake:20161227192404p:plain

f:id:mokake:20161227192418p:plain

TextArea

複数行を扱えるテキストボックス(1行のみならTextBox)。

TextAreaは、プラットフォームによって結構異なるようです。

  • WPFやWinFormsだと、多段階のUndoが可能。WPFのTextBoxは元々可能だが、WinFormsは本来は1段階なので、追加実装している模様。
  • GTK+2やGTK+3だと、Undoはデフォルトでは不可能。確かにJava/SWTでテキストボックスを作ってGTK環境で動かしてもUndoできないので、デフォルト仕様なのでしょう。

なお、GTK+Fcitxだと、IMEで変換する際、たまに入力部分の文字表示が一瞬だけ乱れます。Java/SWTや、普通にGTK+を使った他のプログラムでも同様なので、おそらくこれも仕様なのだと思います。一瞬だけのことなので、実害はほとんどないと思いますが。

Command

XAMLでも利用できるCommandクラスですが、ここではメニューバーやツールバーで実行する内容を設定しています。ShortcutでCommonModifierを使っているのは主にMac対策です。

MenuTextやToolBarTextで半角「&」を使うと、対応プラットフォームならアクセスキーが定義できます(Windowsなら多くのアプリケーションでキーの[ALT+F]を押下すると「ファイル」メニューが開くなど)。

コモン・ダイアログ

今回使ったのは次の4つです。

  • FontDialog
  • ColorDialog
  • OpenFileDialog
  • SaveFileDialog

FontとColorについては、上でも書いたように、WPFを適用していると独自実装のダイアログになります。 また、API文書にある通り、macOSでは非同期動作となるため、Windows風に if (dialog.ShowDialog(this)==DialogResult.Ok) と書くのは避けておいた方が無難です。

なお、上記コードで使っているFontChangedColorChangedイベントは、WindowsLinuxではOKボタンでダイアログを閉じた直後に発生するので、「選んだ瞬間に変わっちゃう?」などの疑念なく安心して設定処理を書くことができます。

ファイルダイアログは、WPFでも使える関係で、Windows標準のものとなります。 ファイルフィルタは、あいにく読み込み専用プロパティなので、構築後にAddメソッドで追加しなければなりません。 また、フィルタは1回につき1個しか追加できません。

f:id:mokake:20161227192508p:plain

f:id:mokake:20161227192523p:plain