ToDoBox Ver.1.0.1リリース

リリースページ
ToDo Box Ver.1.0.1

変更点は同梱のreadme.txtを参照してください。
本ソフトウェアの使用は自己責任でお願いします。


Copyright
本ソフトウェアでは、SgryさんのAzukiテキストエディタコンポーネントを使用しています。
Azukiはzlib licenseのもと公開されています。
http://azuki.sourceforge.jp/license.html
非常に有益なコンポーネントを公開頂き、この場を借りて感謝いたします。

  • BackLog

ToDoBox v1.0.0

ToDoBox Ver.1.0.0リリース

AzukiControlおよびFormの制御確認のための、叩き台として、ToDoメモの簡易ツールを作成したため、公開します。

ToDo Box Ver.1.0

製作期間1日もないので、バグが潜んでいる可能性大です。
(特にショートカットキー周りは、かなりテキトーです。。:-p)
本ソフトウェアの使用は自己責任でお願いします。


Copyright
本ソフトウェアでは、SgryさんのAzukiテキストエディタコンポーネントを使用しています。
Azukiはzlib licenseのもと公開されています。
http://azuki.sourceforge.jp/license.html
非常に有益なコンポーネントを公開頂き、この場を借りて感謝いたします。



2010/12/13追記
バグが多く、公開に耐えることができないと判断したため、一旦公開を停止しました。
以下、要修正箇所。
エンコードが不正(致命的)
・初回起動後、ToDoファイルを保存せずにオプション画面を開いたとき、保存先パスがまだ作られていないため、警告が表示される(軽微)
・二重起動防止がされていないため、同日付のものを編集すると後勝ちになる(軽微)
・メニューにボタンメニューのアイコンが反映しきれていない(軽微)
・アプリケーションアイコンがデフォルトのまま(軽微)

GoF Iteratorパターン

仕事が落ち着いてきたので、やっと再開できそうです。
最近は、デザインパターンの勉強を始めました。
(参考:増補改訂版Java言語で学ぶデザインパターン入門
上、参考書籍ではJavaで書かれているのですが、それをC#で書きなおしてみました。
(URL先のページからサンプルソースをダウンロードすることができます。)

Iterator Pattern

Iteratorは「数え上げ」を行うパターンです。

Aggregateインタフェース

Aggregateとは、集合(体)を意味します。

namespace GoF.Iterator
{
    interface IAggregate
    {
        IIterator iterator();
    }
}
Iteratorインタフェース
using System;

namespace GoF.Iterator
{
    public interface IIterator
    {
        bool hasNext();
        Object next();
    }
}


AggregateとIteratorは対の関係にあります。
集合に対して、それを数え上げるIteratorがいる、それがこのパターンです。
二つに分けるミソは、Iteratorの種類をいくつ作っても、利用する側は(ほぼ)同じ処理で扱うことができるところですね。

Bookクラス

Javaサンプルソースから引用したままですが、集合を構成する最小オブジェクトです。

namespace GoF.Iterator
{
    class Book
    {
        private string name;
        public Book( string name )
        {
            this.name = name;
        }

        public string getName()
        {
            return name;
        }
    }
}
BookShelfクラス

上のBookオブジェクトの集合体です。(:本棚)

using System.Collections.Generic;

namespace GoF.Iterator
{
    class BookShelf : IAggregate
    {
        private List<Book> books;
        public BookShelf()
        {
            this.books = new List<Book>();
        }

        public Book getBookAt( int index )
        {
            return books[index];
        }

        public void appendBook( Book book )
        {
            this.books.Add( book );
        }

        public int getLength()
        {
            return this.books.Count;
        }

        public IIterator iterator()
        {
            return new BookShelfIterator( this );
        }
    }
}
BookShelfIteratorクラス

本棚から本を1冊1冊数え上げる実体のクラスです。

using System;

namespace GoF.Iterator
{
    class BookShelfIterator : IIterator
    {
        private BookShelf bookShelf;
        private int index;

        public BookShelfIterator( BookShelf bookShelf )
        {
            this.bookShelf = bookShelf;
            this.index = 0;
        }

        public bool hasNext()
        {
            if ( index < bookShelf.getLength() )
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public Object next()
        {
            Book book = bookShelf.getBookAt( index );
            index++;
            return book;
        }
    }
}


これらを実際に使用したサンプルは次のとおり。

        private void _BtnShow_Click( object sender, EventArgs e )
        {
            this._RtxtList.Clear();
            StringBuilder sb = new StringBuilder();
            IIterator it = bookShelf.iterator();
            while ( it.hasNext() )
            {
                Book book = (Book)it.next();
                sb.Append( book.getName() + "\n" );
            }
            this._RtxtList.Text = sb.ToString();
        }

        private void _BtnAdd_Click( object sender, EventArgs e )
        {
            InputBoxForm frm = new InputBoxForm();
            frm.ShowDialog();
            string title = frm.Value;
            if ( title.Length > 0 )
            {
                bookShelf.appendBook( new Book( title ) );
            }
        }

(_RtxtListは、フォームに貼り付けてあるリッチテキストコントロールです。Addボタンを押すと本を追加し、Showボタンを押すことによって、現在の本棚にある本のタイトルを列挙します。)


一見すると、すごく回りくどいものに見えるのですが、オブジェクト指向の「再利用」を顕著に表したパターンだと思います。
このサンプルでは、Iteratorインタフェースを継承するクラス(BookShelfIterator)が一つしか無いですが、例えば、BookShelfを逆順に数え上げたり、タイトルを辞書順に数え上げたり、というIteratorを作ったとしても、BookShelfクラスのiteratorメソッドで返すインスタンスを変えるだけで、他は何も変える必要がありません。
また、サンプルソースでは、元々BookShelfはリストではなく、配列で作られていましたが、これをリストに変える過程でも、BookShelfクラスだけを変更するだけで済みました。
C#だとインデクサがあるので、return books[index]に変わりはないんですけど、appendだとか、言語によってはアクセスの仕方が全然変わりますし、やっぱりオブジェクト指向の抽象度の高さは流石だなと思います。
これがベタ書きのプログラムだと、数え上げる処理なんてあちらこちらに分散します。
その点、Iteratorパターンのように、クラスの中だけで完結してしまえば、(AggregateとIteratorの結合度は高いにしても、)外と内をしっかり区別することができます。
「再利用」って聞くと、プロジェクトをまたいで、どんなプログラムにも再利用できる、って思ってしまうんですが、1プロジェクト内に限った話でも、抽象度を高くすることで、随分と保守性が上がります。


ようは、他のクラス(集合の利用側)から見て、その集合がどう実装されているか、なんてどうでも良いんですよね。
これまでのプログラミングだと、どう実装されているかまで知らなくっちゃいけなかったものが、このパターンだと、数えてくれる人がいるので、利用側は「しっかり正確に数え上げます」という責任を持たなくて済むのが一番の利点です、というところでしょうか。(実装の隠匿)

対象.NetFramework変更

LINQ to XMLを利用するため、使用言語をC#(3.0)に引き上げたいと思います。
そのため、Frameworkのバージョンも3.5〜の対応とします。


【理由】
設定ファイルから、内部で使用するデータまで全てXMLを利用するためです。
それだけならFramework2.0のSystem.Xmlのみで十分可能なのですが、LINQを試してみたいという気持ちが強いため、バージョン引き上げに踏み切りました。
勿論、Somaliでは(頻繁に)いくつもの種類のXMLを扱う(予定)ため、一々面倒な書き方をしなければならない+コードの見通しが悪くなることを避ける意図もあります。

DockPanel Suiteの使い方 その4

DockPanelに配置したウィンドウは保存、復元させることができます。
(正しくは、DockPanelに関連付いているDockContent)

using System.Windows.Forms;
using System.IO;
using WeifenLuo.WinFormsUI.Docking;
...

        /// <summary>
        /// レイアウトをXMLファイルに保存
        /// </summary>
        /// <remarks></remarks>
        public void SaveDockLayout()
        {
            //XMLにレイアウト情報を書き出す
            this._dockPanel.SaveAsXml(フォルダパス, 設定ファイル名));
        }

        /// <summary>
        /// XMLファイルからレイアウトを復元
        /// </summary>
        /// <remarks></remarks>
        public void LoadDockLayout()
        {
            //デリゲート生成
            DeserializeDockContent deserializeDockContent
                = new DeserializeDockContent(GetDockContentFromPersistString);

            this._dockPanel.LoadFromXml(設定ファイルパス, deserializeDockContent);
        }

        /// <summary>
        /// PersistStringからDockContentを返却
        /// </summary>
        /// <param name="persistString">DockContentの名前</param>
        /// <returns></returns>
        IDockContent GetDockContentFromPersistString(string persistString)
        {
            //persistStringに応じて、対応するDockContentを生成し、それを返却
            if ( persistString.Equals(typeof(OutlineForm).ToString()) )
            {
                return new OutlineForm();
            }
            else if ( persistString.Equals(typeof(ExplorerForm).ToString()) )
            {
                return new ExplorerForm();
            }
            else
            {
                // 複数ある場合(下記)
                string[] parsedStrings = persistString.Split(new char[] { '|' });
                if ( parsedStrings.Length != 2 )
                    return null;

                if ( parsedStrings[0] != typeof(EditorForm).ToString() )
                    return null;

                EditorForm dummyDoc = new EditorForm();
                if ( parsedStrings[1] != string.Empty )
                    dummyDoc.FilePath = parsedStrings[1];

                return dummyDoc;

            }
        }

保存されるXMLファイルには、下のようにPersistStringという、名前が付けられています。
(デフォルトではクラス名)
XMLファイルからLayoutを復元する際は、このPersistStringを読んでくるため、それに対応したDockContentを返すデリゲートを用意しておきます。

  <Contents Count="2">
    <Content ID="0" PersistString="Somali.EditorForm|none" AutoHidePortion="0.25" IsHidden="False" IsFloat="False" />
    <Content ID="1" PersistString="Somali.ExplorerForm" AutoHidePortion="0.25" IsHidden="False" IsFloat="False" />
  </Contents>


同じDockContentクラスで、複数ある場合は、そのクラスのGetPersistString()をオーバーライドして、区別できる名前にしておく必要があります。
上の場合、EditorFormは複数あるため、次のようにファイルパスを末尾に付けて区別できるようにしてあります。

    public partial class EditorForm : WeifenLuo.WinFormsUI.Docking.DockContent
    {
...

        protected override string GetPersistString()
        {
            //ドキュメントのファイルパスを付けて、ユニークなPersistStringにする
            return GetType().ToString() + "|" + FilePath ;
        }

...
    }

あとは、これをプログラムの開始時点、終了時点に適当に追加すればレイアウトを保持することができます。(FloatWindow含む)
ただし、あくまで保存されるのは、DockPanelに関連付いたDockContentのみであるため、親フォームの状態は別に残しておく必要があります。

Win7対応について

しばらく更新していませんでしたが、開発は継続中です。


新しいPCを購入したため、その環境構築に少し時間がかかりました。
OSはWindows7 Home Premiumにしたため、このSomaliでも、Win7対応を行います。


目下、最初にぶち当たった問題として、Vista/Win7に搭載されているUACの影響がありました。
一般に、アプリケーションはProgram Filesフォルダ配下にインストールされ、アプリの設定ファイルを同フォルダに置くアプリも少なくありません。
私も特に考えず、同じフォルダに置けばいいかな、と思っていましたが、UACにより、Program Filesフォルダにアプリがファイルを書き出すために毎回管理者権限が必要になることを知りました。
そこで、(行儀正しく)ユーザごとのAppDataフォルダに設定ファイルを書き出す必要があります。
そのフォルダパスを知るコードは次の通り。

using System.Windows.Forms;
using System.IO;

        public void savePreferences()
        {
            string strDirPath = String.Format(@"{0}\{1}"
                    , Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
                    , Application.ProductName);
            //フォルダの存在検査
            if ( !Directory.Exists(strDirPath) )
            {
                //存在しない場合、ディレクトリを生成する
                Directory.CreateDirectory(strDirPath);
            }
            //ファイル処理

        }

Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)で、C:\Users\ユーザ名\AppData\Roamingというようなフォルダパスを取得することができます。
詳しくは、アプリケーション設定情報はどこに保存すべきか?:.NET TIPS - @IT参照。

DockPanel Suiteの使い方 その3

DockingMDIとDockingWindowの違いは未だに分からないままですが、ひとまずDockingWindowで進めていこうと思います。

メニュー部には、ToolStripPanelを使用しました。
これは、ToolStripContainerの単体版のようなものらしいです。
標準ではツールボックスに表示されていませんので、ツールボックスで右クリック>”アイテムの選択”から追加します。
ToolStripPanelを使用すると、MenuStripとToolStripを一まとめにして扱うことが可能になります。


次のようなコードで、DockContentフォームを生成することができます。
DockPanelを配置した初期状態では、どのようなDockContentフォームも好きなところに配置できます。
(DockContentFormには、AzukiControlをDock = Fillで配置してあるだけです。)

using WeifenLuo.WinFormsUI.Docking;
...

        private void newToolStripMenuItem_Click(object sender, EventArgs e)
        {
            DockContentForm frm = new DockContentForm();
            frm.Text = "Test";
            frm.Show(this.dockPanel1, DockState.Float);  //Float状態で表示
        }


しかし、これだけではプログラムを終了するときに、例外が発生してしまうようです。

System.NullReferenceException はハンドルされませんでした。
Message="オブジェクト参照がオブジェクト インスタンスに設定されていません。"
Source="Azuki"

どうやら、メインフォームのDispose()にて、AzukiコンポーネントがNULL参照例外に陥っているようです。
フォームのClosingイベントにて、次のような、DockWindowを破棄するコードを追加すると、とりあえずこの現象は起きなくなりました。

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            foreach ( DockWindow x in this.dockPanel1.DockWindows )
            {
                //持っているDockWindow以下全てを問答無用で捨てる
                x.Dispose();
            }

            //Throw ArgumentOutOfRangeException
            //this.dockPanel1.Dispose();

            //Throw System.InvalidOperationException
            //メインフォームを閉じた時、DockContentFormが一つだけ閉じられたように見え、例外発生
            //foreach ( DockContent x in this.dockPanel1.Contents )
            //{
            //    x.Dispose();
            //}
        }


Azukiの問題か、DockPanelの問題か、自分自身の理解不足が問題かは分かりません。
でも、何となく自分のコーディングミスな気がします。
恐らく、DockContentFormを使いまわしたことが原因で、DockPanel+Azukiが捨て切れなかったのかな?と。
(もしかして、DockingWindowにしている関係?とも思ったりします。)

念のため、この時のStackTraceも残しておきます。

続きを読む