こんにちは、SI部の藤沢です。
前回(LinuxでもC#プログラミング(導入編)) にMonoのインストールを行ったので、簡単なGUIアプリケーションの作成をします。
今回のアプリケーションは、社員簿(ID、名前、メールアドレス)で下記を要件とします。

  • データの一覧表示が行える。
  • 名前で検索が行える。
  • 新規登録・編集登録(保存先はXMLファイル)が行える。

作成

まずは、MonoDevelopを起動し、「ファイル」-「新規」-「ソリューションを作成する」からソリューションを作成します。テンプレートは「.Net」-「Gtk# 2.0 プロジェクト」を選択します。プロジェクト名、ソリューション名、保存先を入力します。

プロジェクトの作成が行えたら、XMLファイルを扱うため参照に「System.Xml」を追加しておきます。

社員情報XML

まずは、社員情報のXMLの操作を行うクラスから作成していきます。ソリューションから新しいファイルの作成で「空のクラス」を選択します。
今回は、EmployeesModelでXMLの読書を行います。EmployeeModelが社員1名の情報になります。

using EmployeesBook;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System;
using System.Xml;
using System.Xml.Serialization;

/// <summary>
/// 社員情報XMLクラス
/// </summary>
namespace EmployeesBook
{
    [XmlRoot("Employees")]
    public class EmployeesModel
    {
        [XmlElement("Employee")]
        public List<EmployeeModel> Models { get; set; }

        /// <summary>
        /// 社員情報XMLファイル名
        /// </summary>
        private static readonly string filename = "Employee.xml";

        /// <summary>
        /// XMLを読み込みます。
        /// </summary>
        public static EmployeesModel Read()
        {
            var xml = Path.Combine (Directory.GetCurrentDirectory (), filename);
            EmployeesModel models = new EmployeesModel ();
            using (var fs = new FileStream (@xml, FileMode.Open)) {
                var serializer = new XmlSerializer (typeof(EmployeesModel));
                models = (EmployeesModel)serializer.Deserialize (fs);
            }

            return models;
        }

        /// <summary>
        /// XMLに書き込みます。
        /// </summary>
        /// <param name="value">Value.</param>
        public static void Write(EmployeesModel value)
        {
            var xml = Path.Combine (Directory.GetCurrentDirectory (), filename);

            using (var fs = new FileStream (@xml, FileMode.Create))
            using (var sw = new StreamWriter (fs, Encoding.UTF8)) {
                var ns = new XmlSerializerNamespaces ();
                ns.Add (string.Empty, string.Empty);
                var serializer = new XmlSerializer (typeof(EmployeesModel));
                serializer.Serialize (sw, value, ns);
            }
        }
    }

    /// <summary>
    /// 社員情報(1レコード)のクラスです
    /// </summary>
    public class EmployeeModel
    {
        [XmlElement("ID")]
        public string ID{ get; set; }

        [XmlElement("Name")]
        public string Name{ get; set; }

        [XmlElement("Mail")]
        public string Mail{ get; set; }

        #region コンストラクタ
        /// <summary>
        /// コンストラクタ
        /// </summary>
        public EmployeeModel()
            : this(string.Empty,string.Empty,string.Empty)
        {
        }
        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="id">Identifier.</param>
        /// <param name="name">Name.</param>
        /// <param name="mail">Mail.</param>
        public EmployeeModel(string id,string name,string mail)
        {
            ID = id;
            Name = name;
            Mail = mail;
        }
        #endregion

        /// <summary>
        /// オブジェクトが初期値か否か?
        /// </summary>
        /// <returns><c>true</c> if this instance is empty; otherwise, <c>false</c>.</returns>
        public bool IsEmpty()
        {
            return string.IsNullOrEmpty (ID + Name + Mail);
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents the current <see cref="EmployeesBook.EmployeeModel"/>.
        /// </summary>
        /// <returns>A <see cref="System.String"/> that represents the current <see cref="EmployeesBook.EmployeeModel"/>.</returns>
        public override string ToString ()
        {
            return string.Format ("[EmployeeModel: ID={0}, Name={1}, Mail={2}]", ID, Name, Mail);
        }
    }
}

初期データとして社員情報XMLを作成します。ファイル名は「EmployeesModelのfilename」で設定した名称になります。
また、ファイルの出力設定を行います。

<?xml version="1.0" encoding="UTF-8" ?>
<Employees>
    <Employee>
        <ID>00001</ID>
        <Name>Casley Taro</Name>
        <Mail>taro@casley.com</Mail>
    </Employee>
    <Employee>
        <ID>00002</ID>
        <Name>Casley Jiro</Name>
        <Mail>jiro@casley.com</Mail>
    </Employee>
    <Employee>
        <ID>00003</ID>
        <Name>Casley Hanako</Name>
        <Mail>hanako@casley.com</Mail>
    </Employee>
</Employees>

メイン画面

続いて、メイン画面を作成します。「MainWindow.cs」を開き画面の作成を行います。各widget下記のような配置になります。

・MainWindow
  └  boxMain(VBox)
      ├  boxSearch(HBox)
      │  ├  lblSearch(Label)
      │  ├  txtSearch(Entry)
      │  │     OnTxtSearchChanged(Change Event)
      │  └  btnNew(Button)
      └  GtkSerolledWindow
          └  viewEmployees(Node View)

using Gtk;
using System.Linq;
using System;
using EmployeesBook;

/// <summary>
/// メインウィドウ
/// </summary>
public partial class MainWindow: Gtk.Window
{
    #region メンバ変数
    // フィルタ
    TreeModelFilter modelFilter;
    #endregion

    /// <summary>
    /// コンストラクタ
    /// </summary>
    public MainWindow () : base (Gtk.WindowType.Toplevel)
    {
        Build ();
        initTreeView ();
        initData ();

    }

    /// <summary>
    /// Raises the delete event event.
    /// </summary>
    /// <param name="sender">Sender.</param>
    /// <param name="a">The alpha component.</param>
    protected void OnDeleteEvent (object sender, DeleteEventArgs a)
    {
        Application.Quit ();
        a.RetVal = true;
    }

    #region 初期化処理
    /// <summary>
    /// TreeViewの初期化処理をします。
    /// </summary>
    private void initTreeView()
    {
        // 各カラム
        var colId = new TreeViewColumn ();
        colId.Title = "ID";
        var colName = new TreeViewColumn ();
        colName.Title = "名前";
        var colMail = new TreeViewColumn ();
        colMail.Title = "メールアドレス";

        // 各セル
        var cellId = new CellRendererText();
        colId.PackStart (cellId, true);
        colId.AddAttribute (cellId, "text", 0);
        var cellName = new CellRendererText ();
        colName.PackStart (cellName, true);
        colName.AddAttribute (cellName, "text", 1);
        var cellMail = new CellRendererText ();
        colMail.PackStart (cellMail, true);
        colMail.AddAttribute (cellMail, "text", 2);

        viewEmployees.AppendColumn (colId);
        viewEmployees.AppendColumn (colName);
        viewEmployees.AppendColumn (colMail);

        colId.SetCellDataFunc (cellId, new TreeCellDataFunc (
            delegate(TreeViewColumn tree_column, CellRenderer cell, TreeModel tree_model, TreeIter iter) {
                var emp = (EmployeeModel)tree_model.GetValue(iter, 0);
                (cell as CellRendererText).Text = emp.ID;
        }));

        colName.SetCellDataFunc (cellName, new TreeCellDataFunc (
            delegate(TreeViewColumn tree_column, CellRenderer cell, TreeModel tree_model, TreeIter iter) {
                var emp = (EmployeeModel)tree_model.GetValue(iter, 0);
                (cell as CellRendererText).Text = emp.Name;
        }));

        colMail.SetCellDataFunc (cellMail, new TreeCellDataFunc (
            delegate(TreeViewColumn tree_column, CellRenderer cell, TreeModel tree_model, TreeIter iter) {
                var emp = (EmployeeModel)tree_model.GetValue(iter, 0);
                (cell as CellRendererText).Text = emp.Mail;
        }));

        viewEmployees.CursorChanged += (object sender, EventArgs e) => {
            var selection = (sender as TreeView).Selection;
            TreeModel model;
            TreeIter iter;

            if(selection.GetSelected(out model, out iter))
            {
                var emp = (EmployeeModel)model.GetValue(iter, 0);
                showEditDialog(emp);
            }
        };
    }

    /// <summary>
    /// データの初期を行う。
    /// </summary>
    private void initData()
    {
        var store = new TreeStore (typeof(EmployeeModel));
        foreach (var emp in EmployeesModel.Read().Models.OrderBy(o => o.ID)) {
            store.AppendValues (emp);
        }

        modelFilter = new TreeModelFilter (store, null);
        modelFilter.VisibleFunc = new TreeModelFilterVisibleFunc (
            delegate(TreeModel model, TreeIter iter) {
                var emp = (EmployeeModel)model.GetValue(iter, 0);
                if(string.IsNullOrEmpty(txtSearch.Text))
                {
                    return true;
                }

                if(emp.Name.IndexOf(txtSearch.Text) > -1)
                {
                    return true;
                }
                else
                {
                    return false;
                }
        });

        viewEmployees.Model = modelFilter;
    }
    #endregion

    #region 編集画面
    /// <summary>
    /// 編集画面を表示します。
    /// </summary>
    /// <param name="employee">Employee.</param>
    private void showEditDialog(EmployeeModel employee)
    {
        var edit = new EmployeeDialog (employee);
        edit.Run ();
        edit.Destroy ();
        initData ();
    }

    /// <summary>
    /// 検索をします。
    /// </summary>
    /// <param name="sender">Sender.</param>
    /// <param name="e">E.</param>
    protected void OnTxtSearchChanged (object sender, EventArgs e)
    {
        modelFilter.Refilter ();
    }

    /// <summary>
    /// 新規登録ボタン処理をします。
    /// </summary>
    /// <param name="sender">Sender.</param>
    /// <param name="e">E.</param>
    protected void OnBtnNewClicked (object sender, EventArgs e)
    {
        showEditDialog (new EmployeeModel ());
    }
    #endregion
}

編集ダイアログ

続いて編集ダイアログを作成します。新しいダイアログの作成から、「ダイアログ」を選択します。
MainWindowと同様にwidgetは下記のようになります。

・EmployeesBook.EmplyeeDialog
  ├  boxMain(VBox)
  │  └  tableDetail(Table)
  │      ├  lblTitle(Label,RightAttach:2)
  │      ├  lblID(Label)
  │      ├  txtID(Entry)
  │      ├  lblName(Label)
  │      ├  txtName(Entry)
  │      ├  lblMail(Label)
  │      └  txtMail(Entry)
  └  ActionArea(HButtonBox)
      ├  btnCancel(Button)
      └  btnOk(Button)

using System;
using System.Linq;
using Gtk;

/// <summary>
/// 編集ダイアログ
/// </summary>
namespace EmployeesBook
{
    /// <summary>
    /// 編集ダイアログクラス
    /// </summary>
    public partial class EmployeeDialog : Gtk.Dialog
    {
        #region メンバ変数
        /// <summary>
        /// 編集中か?
        /// </summary>
        private bool editing = false;
        #endregion

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public EmployeeDialog (EmployeeModel employess)
        {
            this.Build ();
            initData (employess);
        }

        #region 初期化
        /// <summary>
        /// 画面の初期化をします。
        /// </summary>
        /// <param name="employee">Employee.</param>
        private void initData(EmployeeModel employee)
        {
            txtID.Text = employee.ID;
            txtName.Text = employee.Name;
            txtMail.Text = employee.Mail;

            if (!employee.IsEmpty ()) {
                lblTitle.Text = "編集登録";
                txtID.IsEditable = false;
                txtID.CanFocus = false;
                txtName.IsFocus = true;
                editing = true;
            }
        }
        #endregion

        /// <summary>
        /// 登録できるか?
        /// </summary>
        /// <returns><c>true</c>登録可能<c>false</c> 登録不可</returns>
        private bool canRegister()
        {
            if (string.IsNullOrEmpty (txtID.Text)) {
                showErrorMeesage ("IDを入力してください。");
                return false;
            }

            if (!editing && EmployeesModel.Read ().Models.Any (w => w.ID.Equals (txtID.Text))) {
                showErrorMeesage ("IDは登録されています。");
                return false;
            }

            if (string.IsNullOrEmpty (txtName.Text)) {
                showErrorMeesage ("名前を入力してください。");
                return false;
            }

            if (string.IsNullOrEmpty (txtMail.Text)) {
                showErrorMeesage ("メールアドレスを入力してください。");
                return false;
            }

            return true;
        }

        /// <summary>
        /// エラーメッセージを表示します。
        /// </summary>
        /// <param name="msg">Message.</param>
        private void showErrorMeesage(string msg)
        {
            var dialog = new MessageDialog (this, DialogFlags.Modal, MessageType.Error, ButtonsType.Ok, msg);
            dialog.Run ();
            dialog.Destroy ();
        }

        /// <summary>
        /// データを登録します。
        /// </summary>
        private void register()
        {
            var emp = new EmployeeModel ();
            emp.ID = txtID.Text;
            emp.Name = txtName.Text;
            emp.Mail = txtMail.Text;

            var employees = EmployeesModel.Read ();
            var find = employees.Models.FirstOrDefault (w => w.ID.Equals (emp.ID));
            if (find != null) {
                find.Name = emp.Name;
                find.Mail = emp.Mail;
            } else {
                employees.Models.Add (emp);
            }

            EmployeesModel.Write (employees);
        }

        /// <summary>
        /// OKボタン処理をします。
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">E.</param>
        protected void OnBtnOkClicked (object sender, EventArgs e)
        {
            if (canRegister ()) {
                register ();
                OnClose ();
            }
        }

        /// <summary>
        /// キャンセルボタン処理をします。
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">E.</param>
        protected void OnBtnCancelClicked (object sender, EventArgs e)
        {
            OnClose ();
        }
    }
}

以上でソースの作成が完了しました。

実行

さいごに

前回の続きでC#を使用してLinuxのGUIアプリケーションを作成してみました。
WindowsFormと似ているため、WindowsFormを作ったことのある人ならば、すぐに理解が出来たかと思います。
LinuxでGUIアプリケーションを作成する機会は少ないですが、.Netを使用してる方は試してみてください。

最後までご高覧頂きまして有難うございます。