2008年03月27日

二次元配列をもっとビュー 1

前回の 二次元配列をビュー では取り敢えず二次元配列をデータソースとして扱えるビューを作りました。今回はもうちょっと拡張します。長くなりそうなので連載形式で順次改造していくことにしようかと思っています。

データにはソートがつき物です。二次元配列自体はソートできませんが、そのビューならソートできてもおかしくありません。DataTable も行の並び替えはありませんが DataView にはソートが用意されていますし。

前回の TwoDimensionArrayView ではソートをサポートしていませんでした。というかそもそも前回のような実装ではソートの実現は困難です。構成から見直す必要があります。

その前にまず DataGridView などでカラムをクリックしたときなどにどのようにしてソートが実現されるのかを確認しましょう。前回にも書いた、データソースに使用できるインターフェイスを再掲します。

  • IList インターフェイス。1 次元配列を含みます。
  • IListSource インターフェイス。DataTable クラス、DataSet クラスなどがあります。
  • IBindingList インターフェイス。BindingList クラスなどがあります。
  • IBindingListView インターフェイス。BindingSource クラスなどがあります。

TwoDimensionArrayView は IList のみを実装しましたが、IList にはソートなどが用意されていませんでした。IListSource は IList を取得するものですから用途が違います。となると残りの二種類ですね。IBindingList には編集・追加・削除、及びソートと検索に使用されるメンバが用意されています。また IBindingListView にはフィルタ及び複数列ソートのためのメンバが用意されています(IBindingListView は IBindingList から派生するため、IBindingList のメンバも持ちます)。今回はソートできればいいってことで IBindingList を実装することにしましょう。

IBindingList を実装するとして、行の表現はどうすべきでしょうか。ソートということを考えると行の概念は重要になります。前回ではただ表示するだけだったのであまり行と言うものは意識せずに実装していました。今回はもう少し行を意識してみましょう。前回軽く触れましたが、DataRowView は ICustomTypeDescriptor を実装しており、DataRowView の配列でも DataTable と同様にデータソースとして使用できます。これを踏まえ、RowView クラスを作って ICustomTypeDescriptor を実装させることにします。そうなると ITypedList は実装不要になったりしますが、まあ一応残しておきます。DataView も持ってますし。なお挙動をみる限り、DataGridView の DataSource に指定するに当たって、データソースが ITypedList を実装していればその GetItemProperties を使用し、そうでない場合にリストのアイテムの方の ICustomTypeDescriptor.GetProperties を参照するようです。スコープの大きい方優先ってのも奇妙な感じですが、RowView の GetProperties を使用する場合でも全ての RowView について GetProperties を呼び出すことはしないみたいですからまあそんなもんなんでしょう。

ということで取り敢えず雛形を作成しました。次回以降、ソート機能を組み込んでいきます。

今回のコード。C# のみですので、VB の記述がほしい方は Convert C# to VB.NET 辺りをご利用ください。他にも色々変換サービスはあるようです。一度 csc.exe でコンパイルして Reflector でディスコンパイルする、なんて方法もあります。


using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;

public partial class TwoDimensionalArrayView<T>
    : IBindingList, ITypedList,
      IEnumerable<TwoDimensionalArrayRowView<T>> {
#region 非 public メンバ
    // 取り敢えず RowView に直接操作させる必要があったので internal で公開
    internal T[,] array;
    // RowView に公開する意図で internal
    internal PropertyDescriptorCollection ColumnDescriptors {
        get { return new PropertyDescriptorCollection(this.columnDescriptors); }
    }
    private TwoDimensionalArrayColumnDescriptor[] columnDescriptors;
    // 元々の配列と同じ順番を持った RowView の配列
    private TwoDimensionalArrayRowView<T>[] rowViews;
    // ソート済み RowView のリスト
    private List<TwoDimensionalArrayRowView<T>> arrangedViews;
    private bool allowEdit;
#endregion
#region public メンバ
    public TwoDimensionalArrayView(T[,] array) {
        this.array = array;
        int rowCount = array.GetLength(0), columnCount = array.GetLength(1);
        this.columnDescriptors = new TwoDimensionalArrayColumnDescriptor[columnCount];
        for (int i = 0; i < columnCount; i++) {
            this.columnDescriptors[i] = new TwoDimensionalArrayColumnDescriptor(i);
        }
        this.rowViews = new TwoDimensionalArrayRowView<T>[rowCount];
        this.arrangedViews = new List<TwoDimensionalArrayRowView<T>>();
        for (int i = 0; i < rowCount; i++) {
            rowViews[i] = new TwoDimensionalArrayRowView<T>(this, i);
            // 初めはソート済みリストも元のと同じ順番
            arrangedViews.Add(rowViews[i]);
        }
    }
    // rowIndex はソート済みリストのインデックス
    public TwoDimensionalArrayRowView<T> this[int rowIndex] {
        get { return this.arrangedViews[rowIndex]; }
    }
    // rowIndex はソート済みリストのインデックス
    // 値の変更は RowView 越しに行う
    public T this[int rowIndex, int columnIndex] {
        get { return this.arrangedViews[rowIndex][columnIndex]; }
        set { this.arrangedViews[rowIndex][columnIndex] = value; }
    }
    public int Count { get { return this.arrangedViews.Count; } }
    public bool AllowEdit {
        get { return this.allowEdit; }
        set { this.allowEdit = value; }
    }
    // IEnumerable<TwoDimensionalArrayRowView<T>>.GetEnumerator に丸投げ
    public IEnumerator<TwoDimensionalArrayRowView<T>> GetEnumerator() {
        return arrangedViews.GetEnumerator();
    }
#endregion
#region IBindingList の明示的実装群
    // 取り敢えず全て未サポート
    bool IBindingList.SupportsChangeNotification { get { return false; } }
    bool IBindingList.SupportsSearching { get { return false; } }
    bool IBindingList.SupportsSorting { get { return false; } }
    // get/set 両持ちのプロパティを公開するために明示的実装する
    bool IBindingList.AllowEdit { get { return this.AllowEdit; } }
    // 元が二次元配列ゆえに固定長
    bool IBindingList.AllowNew { get { return false; } }
    bool IBindingList.AllowRemove { get { return false; } }
    bool IBindingList.IsSorted { get { return false; } }
    // 未サポートなので全て例外を発生させる
    ListSortDirection IBindingList.SortDirection {
        get { throw new NotSupportedException(); }
    }
    PropertyDescriptor IBindingList.SortProperty {
        get { throw new NotSupportedException(); }
    }
    void IBindingList.AddIndex(PropertyDescriptor property) {
        throw new NotSupportedException();
    }
    void IBindingList.RemoveIndex(PropertyDescriptor property) {
        throw new NotSupportedException();
    }
    object IBindingList.AddNew() {
        throw new NotSupportedException();
    }
    void IBindingList.ApplySort(PropertyDescriptor property, ListSortDirection direction) {
        throw new NotSupportedException();
    }
    int IBindingList.Find(PropertyDescriptor property, object key) {
        throw new NotSupportedException();
    }
    void IBindingList.RemoveSort() {
        throw new NotSupportedException();
    }
    event ListChangedEventHandler IBindingList.ListChanged {
        add    { throw new NotSupportedException(); }
        remove { throw new NotSupportedException(); }
    }
#endregion
#region IList の明示的実装群
    bool IList.IsFixedSize { get { return true; } }
    bool IList.IsReadOnly { get { return !(this.AllowEdit); } }
    // TwoDimensionalArrayRowView<T> を返す
    object IList.this[int index] {
        get { return this[index]; }
        set { throw new NotSupportedException(); }
    }
    // 操作の対象はソート済みリスト
    bool IList.Contains(object value) {
        return ((IList)this.arrangedViews).Contains(value);
    }
    int IList.IndexOf(object value) {
        return ((IList)this.arrangedViews).IndexOf(value);
    }
    // 要素数が変わるのは全て NotSupportedException
    int IList.Add(object value) {
        throw new NotSupportedException();
    }
    void IList.Clear() {
        throw new NotSupportedException();
    }
    void IList.Insert(int index, object value) {
        throw new NotSupportedException();
    }
    void IList.Remove(object value) {
        throw new NotSupportedException();
    }
    void IList.RemoveAt(int index) {
        throw new NotSupportedException();
    }
#endregion
#region ICollection の明示的実装群
    bool ICollection.IsSynchronized { get { return false; } }
    object ICollection.SyncRoot { get { return this; } }
    void ICollection.CopyTo(Array array, int index) {
        ((ICollection)this.arrangedViews).CopyTo(array, index);
    }
#endregion
#region IEnumerable の明示的実装群
    // IEnumerable<TwoDimensionalArrayRowView<T>>.GetEnumerator に丸投げ
    IEnumerator IEnumerable.GetEnumerator() {
        return this.GetEnumerator();
    }
#endregion
#region ITypedList の明示的実装群
    // 適当に
    string ITypedList.GetListName(PropertyDescriptor[] descriptors) {
        return string.Format("{0}View ({1},{2})", typeof(T[,]).Name,
                             this.array.GetLength(0), this.array.GetLength(1));
    }
    PropertyDescriptorCollection ITypedList.GetItemProperties(PropertyDescriptor[] descriptors) {
        return new PropertyDescriptorCollection(this.columnDescriptors);
    }
#endregion
}
public class TwoDimensionalArrayRowView<T> : ICustomTypeDescriptor {
#region private メンバ
    private TwoDimensionalArrayView<T> owner;
    private int originalRowIndex;
#endregion
#region public メンバ
    public TwoDimensionalArrayRowView(TwoDimensionalArrayView<T> owner, int originalRowIndex) {
        this.owner = owner;
        this.originalRowIndex = originalRowIndex;
    }
    public T this[int columnIndex] {
        get { return this.owner.array[this.originalRowIndex, columnIndex]; }
        set { this.owner.array[this.originalRowIndex, columnIndex] = value; }
    }
    // 元の二次元配列における行インデックス
    public int OriginalRowIndex {
        get { return this.originalRowIndex; }
    }
#endregion
#region ICustomTypeDescriptor の明示的実装群
    // 直接関係ないのはスルー
    AttributeCollection ICustomTypeDescriptor.GetAttributes() { return null; }
    string ICustomTypeDescriptor.GetClassName() { return null; }
    string ICustomTypeDescriptor.GetComponentName() { return null; }
    TypeConverter ICustomTypeDescriptor.GetConverter() { return null; }
    EventDescriptor ICustomTypeDescriptor.GetDefaultEvent() { return null; }
    PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty() { return null; }
    object ICustomTypeDescriptor.GetEditor(Type editorBaseType) { return null; }
    EventDescriptorCollection ICustomTypeDescriptor.GetEvents() {
        return EventDescriptorCollection.Empty;
    }
    EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) {
        return EventDescriptorCollection.Empty;
    }
    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() {
        return ((ICustomTypeDescriptor)this).GetProperties(null);
    }
    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes) {
        return this.owner.ColumnDescriptors;
    }
    object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd) {
        return null;
    }
#endregion
}
public partial class TwoDimensionalArrayView<T> {
    // 列記述子は外部非公開
    private class TwoDimensionalArrayColumnDescriptor : PropertyDescriptor {
        // 列名は列番号そのまま
        public TwoDimensionalArrayColumnDescriptor(int columnIndex)
                : base(columnIndex.ToString(), null) {
            this.columnIndex = columnIndex;
        }
        public int ColumnIndex { get { return this.columnIndex; } }
        private int columnIndex;
        // Index に深い意味は無し。見た目上に過ぎないので適当に
        public override string DisplayName {
            get { return string.Format("Index{0}", this.columnIndex); }
        }
        public override Type ComponentType {
            get { return typeof(TwoDimensionalArrayRowView<T>); }
        }
        public override bool IsReadOnly { get { return false; } }
        public override Type PropertyType { get { return typeof(T); } }
        public override bool CanResetValue(object component) { return false; }
        public override object GetValue(object component) {
            if (component == null) throw new ArgumentNullException("component");
            TwoDimensionalArrayRowView<T> row = component as TwoDimensionalArrayRowView<T>;
            if (row == null) throw new ArgumentException("component");
            return row[this.columnIndex];
        }
        public override void ResetValue(object component) {
            throw new NotSupportedException();
        }
        public override void SetValue(object component, object value) {
            if (component == null) throw new ArgumentNullException("component");
            TwoDimensionalArrayRowView<T> row = component as TwoDimensionalArrayRowView<T>;
            if (row == null) throw new ArgumentException("component");
            row[this.columnIndex] = (T)value;
        }
        public override bool ShouldSerializeValue(object component) {
            return false;
        }
    }
}
posted by Hongliang at 08:15| Comment(0) | TrackBack(0) | .NET | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
この記事へのトラックバックURL
http://blog.seesaa.jp/tb/91148986

この記事へのトラックバック

ここ(hongliang.seesaa.net)で公開しているものについて、利用は自由に行って頂いて構いません。改変、再頒布もお好きになさって下さい。利用に対しこちらが何かを要求することはありません。

ただし、公開するものを使用、または参考したことによって何らかの損害等が生じた場合でも、私はいかなる責任も負いません。

あ、こんなのに使ったってコメントを頂ければ嬉しいです。

×

この広告は1年以上新しい記事の投稿がないブログに表示されております。