studio Odyssey




スタジオ日誌

日誌的なもの

2009.09.28

DataRepeaterコントロールのコード

Written by
しゃちょ
Category
C#
プログラム

 さて、そんな訳で、時間ができたので、VisualBasic PowerPacksに入っている、DataRepeaterコントロールのお話をしよう。
 別に興味ない人は読み飛ばし推奨。

 さて、このコントロール、まぁ、検索でここにたどり着いた人たちは言うまでもない訳で、そういうコントロールです。どういうだよ。っていうか、説明が面倒くせぇよ、説明なしな!

 で、このコントロール、結構、癖があって使いにくいです。
 データバインドされたコンボボックスのデータを表示すると、他の行のコントロールと連動して表示がかわっちゃうとか、DataTableバインドしていて、Not Nullなカラムがあると、次の行に行ったときに、DataErrorが起こるとか、制御がすげぇ面倒くさい。

 とりあえず、使う上で、いくつかのポイントをあげておきます。

  • リストに対してバインドされたコンボボックスは、手動でやるしかない。
  • はっつけたコントロールの、DataBindingsプロパティの要素の一部はコピーされない。
  • ItemTemplateのValidatingは、複数回呼ばれることがあるので、気をつけないとだめ。
  • 追加のためのメソッドであるAddNewは、結構、使いづらい。
  • データバインド後に、ResetBindingsを呼ばないと、いろいろ変なことになったりする。

 みたいなー。

 さて、そんなわけで、簡単なコードをおいつつ、見ていきましょう。

 まずは、「コンボボックスは手動でやるしかない」の話ですが、Listに対してデータバインドされたコンボボックスは、その内容がDataRepeaterのItemCloningでコピーされないようなので、手動でやるしかありません。と言うわけで、そのタイミングは、ItemClonedです。
 実際、ItemTemplateに置かれたコンボボックスのリストにバインドするタイミングにもよりますが、これがLoadのタイミングとかでバインドされているなら、ItemClonedで、データをこぴーすればいいでしょう。サンプルは以下です。動かしていないので、動かないかもしれません。

/// <summary>
/// これ、テンプレートから新しい行にクローンが作成された後に呼ばれる。
/// 実際、クローン側には行かないプロパティだのがあるので、ここで設定してやる
/// </summary>
private void drpMain_ItemCloned(object sender, Microsoft.VisualBasic.PowerPacks.DataRepeaterItemEventArgs e)
{
    //場合によっては、Controls.Find でないととれないこともあるよ
    ComboBox combo = e.DataRepeaterItem.Controls[this.cmbKetsueki.Name] as ComboBox;

    if (combo != null)
    {
        //これ、すげー適当なので、動かないかも > ってか、実際はこういう、コピーのためのメソッド、用意しておく
        combo.DataSource = this.cmbKetsueki.DataSource.Clone();
    }
}

 「はっつけたコントロールの、DataBindingsプロパティの要素の一部はコピーされない」の件ですが、たとえば、データソース側がDateTime型だったりして、AllowDBNull=falseだったりすると、普通は、Parseとか、Formatにイベントをつけるわけですが、これ、つけてもコピーされないのです。まぁ、これは仕方がないので、TextBoxのValidatingのタイミングとかで、必須なら、TextLengthを見て、CancelEventArgsのCancel=trueにするとかしかないかもしれません。まぁ、使いやすいかどうかはともかく。

 「ItemTemplateのValidatingは、複数回呼ばれることがあるので、気をつけないとだめ」は、まぁ、僕の使っているオリジナルのコントロールがいけないんでしょうが、実装の仕方によっては、そういうこともあるかもしれないので注意してね、という話です。まぁ、やり方次第でなんとでも制御できるので、がんばって制御しましょう。

 「追加のためのメソッドであるAddNewは、結構、使いづらい」は、もう、そのまんま。これ、超使いづらいです。っていうか、行追加周りとか、マジ、自分のところの開発者の力量とか、普段使ってるコントロールとかにあわせて拡張した方がいいです。
 Microsoftのコントロールは、基本、万人向けにデザインされているので、ぶっちゃけ、1000万人の人たちに向けて作られたモンは、あなた、またはあなたの会社のメンバーには合わない。
 よい子はこのあたりは拡張して、自前でいろいろ制御した方が、安定したコントロールになると思います。できない事が多いコントロールというのは、ある意味、生産性を向上させる事ができるはずですので。

 「データバインド後に、ResetBindingsを呼ばないと、いろいろ変なことになったりする」は、まぁ、そのまんまです。リストにデータバインドされたコンボボックスとかがあったりすると、データバインドされたあと、項目が再選択されなかったりするので(そりゃそうだ、先にも言ったように、Clonedのタイミングで、要素がコピーされていないんだもの)、呼び出しし直さないと、選択が正しくならなかったりします。

 いや、なかなか難儀なコントロールです。
 使いづらい。

 そんなわけで、DataRepeaterコントロールを、自前で拡張してみます。
 ちょっと、コンパイルして動かす環境がないので(DBないねん)、動かしてはいないですが、実際に僕が作ったコントロールで、使えそうな部分だけ、抜粋して掲載します。
 あ、そういえば、スクロールがどうこうって、検索していた人がいたな。スクロールは、ScrollItemIntoViewですよ。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Globalization;
using System.Windows.Forms;
using Microsoft.VisualBasic.PowerPacks;
using System.Runtime.InteropServices;

namespace DataRepeaderSample
{
    /// <summary>
    /// ItemValidating イベントを呼び出すタイミングを指定します。
    /// </summary>
    /// <remarks>
    /// <para>
    /// 正直、行毎にValidatingイベントを呼び出してくれた方が使い勝手がいいので、それを制御するもの。
    /// つーか、行を移動した時とか、すげー制御が面倒なので、まとめてくれよ!
    /// </para>
    /// </remarks>
    public enum RiseItemValidating
    {
        /// <summary>
        /// ItemValidating を呼び出しません。
        /// </summary>
        None,

        /// <summary>
        /// DataRepeaterItem.Validating イベントの最初に呼び出されます。
        /// </summary>
        /// <remarks>
        /// <para>
        /// 可能な限り最初に呼び出されるように調整されます。
        /// </para>
        /// <para>
        /// 内部的にはコンストラクタのタイミングで DataRepeaterItem.Validating に関連づけられます。
        /// </para>
        /// </remarks>
        RiseFirstEvent,

        /// <summary>
        /// <see cref="OrdersDataRepeater.OnItemCloned"/> のタイミングで、イベントを関連づけます。
        /// 大抵の場合、デザイナで指定された DataRepeaterItem.Validating の後です。
        /// </summary>
        RiseItemClonedLast,
    }

    /// <summary>
    /// DataRepeater のサンプルです。
    /// </summary>
    /// <remarks>
    /// <para>
    /// ノークレーム、ノーリターンでお願いします。コンパイルは通してますが、一回も動かしてないので、見た目サンプルとして使ってください。
    /// <note>利用するには、Microsoft.VisualBasic.PowerPacks が必要になります。配布要件に含めてください。</note>
    /// </para>
    /// </remarks>
    public sealed class CustomDataRepeater : Microsoft.VisualBasic.PowerPacks.DataRepeater
    {
        /// <summary>
        /// CustomDataRepeater の新しいインスタンスを生成します。
        /// </summary>
        public CustomDataRepeater()
        {
            this.RiseItemValidating = RiseItemValidating.RiseFirstEvent;

            //くっつけとく > これで、DataRepeaterで、行のValidaingを捕まえられる
            this.ItemTemplate.Validating += new CancelEventHandler(ItemTemplate_Validating);
            this.ItemTemplate.Validated += new EventHandler(DataRepeaterItem_Validated);

            //ちょっと困る制御 > 追加と削除を有効にしてしまうと、すっげー処理が面倒なので、使わせない
            base.AllowUserToAddItems = false;
            base.AllowUserToDeleteItems = false;
        }

        #region RiseItemValidating / ItemValidating イベントを発生させるタイミングを指定します。
        /// <summary>
        /// <see cref="ItemValidating"/> イベントを発生させるタイミングを指定します。
        /// </summary>
        [Browsable(true)]
        [Category("Focus")]
        [Description("DataRepeaterValidating イベントを発生させるタイミングを指定します。")]
        [DefaultValue(typeof(RiseItemValidating), "RiseFirstEvent")]
        public RiseItemValidating RiseItemValidating
        {
            get;
            set;
        }
        #endregion

        //ちょっと困る制御のための隠蔽工作
        #region AllowUserToAddItems / ユーザーが実行時に新しい行を DataRepeater に追加できるかどうかを決定する値を取得または設定します。
        /// <summary>
        /// ユーザーが実行時に新しい行を <see cref="DataRepeater"/> に追加できるかどうかを決定する値を取得または設定します。
        /// </summary>
        /// <value>ユーザーが行を追加できる場合は <c>true</c>。それ以外の場合は <c>false</c>。既定は、<c>false</c> です。</value>
        [Browsable(true)]
        [Bindable(false)]
        [DefaultValue(false)]
        [Category("Behavior")]
        [Description("ユーザーが実行時に新しい行を DataRepeater に追加できるかどうかを決定する値を取得または設定します。")]
        public new bool AllowUserToAddItems
        {
            get
            {
                return base.AllowUserToAddItems;
            }
            set
            {
                base.AllowUserToAddItems = value;
            }
        }
        #endregion

        #region AllowUserToAddItems / ユーザーが実行時に新しい行を DataRepeater に追加できるかどうかを決定する値を取得または設定します。
        /// <summary>
        /// ユーザーが実行時に <see cref="DataRepeater"/> から行を削除できるかどうかを決定する値を取得または設定します。
        /// </summary>
        /// <value>ユーザーが行を削除できる場合は <c>true</c>。それ以外の場合は <c>false</c>。既定は、<c>false</c> です。</value>
        [Browsable(true)]
        [Bindable(false)]
        [DefaultValue(false)]
        [Category("Behavior")]
        [Description("ユーザーが実行時に DataRepeater から行を削除できるかどうかを決定する値を取得または設定します。")]
        public new bool AllowUserToDeleteItems
        {
            get
            {
                return base.AllowUserToDeleteItems;
            }
            set
            {
                base.AllowUserToDeleteItems = value;
            }
        }
        #endregion

        #region OnItemCloned / ItemCloned イベントを発生させます。
        /// <summary>
        /// <see cref="DataRepeater.ItemCloned">ItemCloned</see> イベントを発生させます。
        /// </summary>
        /// <param name="e">イベント データを格納しているオブジェクト。</param>
        protected override void OnItemCloned(Microsoft.VisualBasic.PowerPacks.DataRepeaterItemEventArgs e)
        {
            base.OnItemCloned(e);

            //イベントくっつけておく
            e.DataRepeaterItem.Validating += new CancelEventHandler(DataRepeaterItem_Validating);
        }
        #endregion

        //DataRepeaterItem の Validating イベントは使えないので、使えるようにするためのしかけ
        #region DataRepeaterItem の Validating イベントを捕まえて、DataRepeaterのカスタムイベントに対して送るもの

        #region ItemTemplate_Validating / DataRepeaterItem の Validating イベントを捕まえて、DataRepeaterのカスタムイベントに対して送るもの
        /// <summary>
        /// DataRepeaterItem の Validating イベントを捕まえて、DataRepeaterのカスタムイベントに対して送るもの
        /// </summary>
        private void ItemTemplate_Validating(object sender, CancelEventArgs e)
        {
            if (this.RiseItemValidating == RiseItemValidating.RiseFirstEvent)
            {
                //このメソッドで、呼び出しの回数を1回に制御してる
                this.PreDataRepeaterValidating(sender as DataRepeaterItem, e);
            }
        }
        #endregion

        #region DataRepeaterItem_Validating / Validating イベントが発生した時に呼び出されます。こっちは、Cloned の時ににひもづけられて呼ばれるもの
        /// <summary>
        /// Validating イベントが発生した時に呼び出されます。こっちは、Cloned の時ににひもづけられて呼ばれるもの
        /// </summary>
        private void DataRepeaterItem_Validating(object sender, CancelEventArgs e)
        {
            if (this.RiseItemValidating == RiseItemValidating.RiseItemClonedLast)
            {
                //このメソッドで、呼び出しの回数を1回に制御してる
                this.PreDataRepeaterValidating(sender as DataRepeaterItem, e);
            }
        }
        #endregion

        /// <summary>
        /// 既に呼び出していて、Validating が Cancel = true になっちゃったかのフラグ
        /// </summary>
        private bool _alradyCallValidatingAtCancel;

        #region PreDataRepeaterValidating / イベントを呼ぶ必要があるかを検査して、必要であれば、OnItemValidating を呼び出します。Validate するのもここです。
        /// <summary>
        /// イベントを呼ぶ必要があるかを検査して、必要であれば、<see cref="OnItemValidating"/> を呼び出します。
        /// Validate するのもここです。
        /// </summary>
        /// <param name="repeaterItem">Validating イベントの senderに相当するもの。</param>
        /// <param name="e">Validating イベントの CancelEventArgs。</param>
        /// <remarks>
        /// <para>
        /// 現在のコントロールが DataRepeaterItem でない時は、別のコントロールから呼ばれちゃった時です。
        /// </para>
        /// <para>
        /// この時はどうなっているかというと、元々の DataRepeaterItem の Validating イベントで Cancel = true されて
        /// 他のコントロールが呼び出している時なので、
        /// どうせCancel = true になるので、カスタムイベントは呼ばないで、
        /// Cancel = true にすり替えちゃうところです。
        /// </para>
        /// </remarks>
        private void PreDataRepeaterValidating(DataRepeaterItem repeaterItem, CancelEventArgs e)
        {
            //とりあえず、現在行とか使うので、チェック
            if (this.CurrentItem != null && repeaterItem == this.CurrentItem)
            {
                //これが成立しないときは、既に Validating で Cancel = true になっちゃってるんだよ
                if (this.ActiveControl != null &&
                    this.ActiveControl.GetType() != typeof(DataRepeaterItem) &&
                    this._alradyCallValidatingAtCancel)
                {
                    //
                    e.Cancel = true;
                }
                else
                {
                    //ここで呼ぶ
                    this.OnItemValidating(e);

                    if (e.Cancel)
                    {
                        this._alradyCallValidatingAtCancel = true;
                    }
                }
            }
        }
        #endregion

        #region DataRepeaterItem_Validated / DataRepeaterItem の Validated イベントが発生した時に呼び出されます。
        /// <summary>
        /// DataRepeaterItem の Validated イベントが発生した時に呼び出されます。
        /// </summary>
        private void DataRepeaterItem_Validated(object sender, EventArgs e)
        {
            //ここでフラグを戻しておく
            this._alradyCallValidatingAtCancel = false;
        }
        #endregion

        #endregion

        //events
        #region event CancelEventHandler ItemValidating / DataRepeaterItem コントロールの Validating イベントから呼び出されるイベントです。

        private static readonly object CustomEventItemValidating = new object();

        /// <summary>
        /// <see cref="DataRepeaterItem"/> コントロールの Validating イベントから呼び出されるイベントです。
        /// </summary>
        /// <remarks>
        /// <para>
        /// このイベントは、DataItemRepeater の Validating イベントが、複数回呼び出されてしまうのを抑制するように調整されています。
        /// </para>
        /// <para>
        /// 基本的に入力値のチェック等は、このイベントを利用してください。
        /// <note>ちなみに、このEventHandlerの書き方は、自分のカスタムコントロールで、DataRepeaterにコピーしてもらいたいイベントがあれば、この書き方しないと、コピーされない。</note>
        /// </para>
        /// </remarks>
        [Category("Focus")]
        [Description("DataRepeaterItem コントロールの Validating イベントから呼び出されるイベントです。")]
        public event CancelEventHandler ItemValidating
        {
            add
            {
                this.Events.AddHandler(CustomEventItemValidating, value);
            }
            remove
            {
                this.Events.RemoveHandler(CustomEventItemValidating, value);
            }
        }
        #endregion

        #region OnItemValidating / ItemValidating イベントを発生させます。
        /// <summary>
        /// <see cref="ItemValidating"/> イベントを発生させます。
        /// </summary>
        /// <param name="e">イベントデータ</param>
        private void OnItemValidating(CancelEventArgs e)
        {
            CancelEventHandler handler = (CancelEventHandler)this.Events[CustomEventItemValidating];

            if (handler != null)
            {
                handler(this.CurrentItem, e);
            }
        }
        #endregion

        //いろいろ
        #region HasControl / 指定されたコントロールが、ItemTemplate内に保持されているかを取得します。
        /// <summary>
        /// 指定されたコントロールが、ItemTemplate内に保持されているかを取得します。
        /// </summary>
        /// <param name="control">対象のコントロール</param>
        /// <returns>保持されていれば <c>true</c> 。</returns>
        /// <remarks>
        /// <para>
        /// 複数のコントロールが存在していても、<c>true</c> を返します。
        /// </para>
        /// </remarks>
        public bool HasItemTemplateControl(Control control)
        {
            Control[] ctrls = this.ItemTemplate.Controls.Find(control.Name, true);

            return (ctrls != null && ctrls.Length > 0);
        }
        #endregion

        #region GetCurrentItemControl / 現在のItemの中から、指定されたItemTemplate上のコントロールの実際のインスタンスを取得します。
        /// <summary>
        /// 現在のItemの中から、指定されたItemTemplate上のコントロールの実際のインスタンスを取得します。
        /// </summary>
        /// <param name="control">ItemTemplate 上の、対象のコントロール</param>
        /// <returns>取得した、実際のインスタンス</returns>
        /// <exception cref="ArgumentException">複数のコントロールが同名で存在する時に throw されます。</exception>
        public Control GetCurrentItemControl(Control control)
        {
            return GetItemControl(this.CurrentItem, control);
        }
        #endregion

        #region set get
        // > このへんのメソッドは、あった方が使いやすいのよ。たとえば、コードに対して名称を入れるみたいな処理のとき
        //private void txtCD_Validating(object sender, EventArgs e)
        //{
        //    TextBox txt = (TextBox)sender;
        //
        //    if (txt.TextLength > 0)
        //    {
        //        //もにょもにょして
        //        this.SetText(this.txtMeisho, "なんとか");
        //    }
        //}
        //こうして、同一行の相手コントロールを特定できる
        #region SetText / GetText
        /// <summary>
        /// 指定された <see cref="DataRepeater.ItemTemplate"/> 上のコントロールのインスタンスと対応する、現在の行(<see cref="DataRepeater.CurrentItem"/>)のインスタンスのTextに値を設定します。
        /// </summary>
        /// <param name="control">対象のコントロール</param>
        /// <param name="value">セットする値</param>
        /// <exception cref="ArgumentException">複数のコントロールが同名で存在する時に throw されます。</exception>
        public void SetText(Control control, string value)
        {
            Control target = this.GetCurrentItemControl(control);

            if (target != null)
            {
                target.Text = value;
            }
        }

        /// <summary>
        /// 指定された <see cref="DataRepeater.ItemTemplate"/> 上のコントロールのインスタンスと対応する、現在の行(<see cref="DataRepeater.CurrentItem"/>)のインスタンスのTextを取得します。
        /// </summary>
        /// <param name="control">対象のコントロール</param>
        /// <returns>コントロールの値。存在しない場合は null 。</returns>
        /// <exception cref="ArgumentException">複数のコントロールが同名で存在する時に throw されます。</exception>
        public string GetText(Control control)
        {
            Control target = this.GetCurrentItemControl(control);

            if (target != null)
            {
                return target.Text;
            }

            return null;
        }
        #endregion

        #region SetChecked / GetChecked
        /// <summary>
        /// 指定された <see cref="DataRepeater.ItemTemplate"/> 上のコントロールのインスタンスと対応する、現在の行(<see cref="DataRepeater.CurrentItem"/>)のインスタンスのCheckedに値を設定します。
        /// </summary>
        /// <param name="control">対象のコントロール</param>
        /// <param name="value">セットする値</param>
        /// <exception cref="ArgumentException">複数のコントロールが同名で存在する時に throw されます。</exception>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
        public void SetChecked(CheckBox control, bool value)
        {
            CheckBox target = this.GetCurrentItemControl(control) as CheckBox;

            if (target != null)
            {
                target.Checked = value;
            }
        }

        /// <summary>
        /// 指定された <see cref="DataRepeater.ItemTemplate"/> 上のコントロールのインスタンスと対応する、現在の行(<see cref="DataRepeater.CurrentItem"/>)のインスタンスのCheckedに値を設定します。
        /// </summary>
        /// <param name="control">対象のコントロール</param>
        /// <param name="value">セットする値</param>
        /// <exception cref="ArgumentException">複数のコントロールが同名で存在する時に throw されます。</exception>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
        public void SetChecked(RadioButton control, bool value)
        {
            RadioButton target = this.GetCurrentItemControl(control) as RadioButton;

            if (target != null)
            {
                target.Checked = value;
            }
        }

        /// <summary>
        /// 指定された <see cref="DataRepeater.ItemTemplate"/> 上のコントロールのインスタンスと対応する、現在の行(<see cref="DataRepeater.CurrentItem"/>)のインスタンスのCheckedを取得します。
        /// </summary>
        /// <param name="control">対象のコントロール</param>
        /// <returns>コントロールの値。存在しない場合は false 。</returns>
        /// <exception cref="ArgumentException">複数のコントロールが同名で存在する時に throw されます。</exception>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
        public bool GetChecked(CheckBox control)
        {
            CheckBox target = this.GetCurrentItemControl(control) as CheckBox;

            if (target != null)
            {
                return target.Checked;
            }
            return false;
        }

        /// <summary>
        /// 指定された <see cref="DataRepeater.ItemTemplate"/> 上のコントロールのインスタンスと対応する、現在の行(<see cref="DataRepeater.CurrentItem"/>)のインスタンスのCheckedを取得します。
        /// </summary>
        /// <param name="control">対象のコントロール</param>
        /// <returns>コントロールの値。存在しない場合は false 。</returns>
        /// <exception cref="ArgumentException">複数のコントロールが同名で存在する時に throw されます。</exception>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
        public bool GetChecked(RadioButton control)
        {
            RadioButton target = this.GetCurrentItemControl(control) as RadioButton;

            if (target != null)
            {
                return target.Checked;
            }
            return false;
        }
        #endregion

        #region SetSelectedValue / GetSelectedValue
        /// <summary>
        /// 指定された <see cref="DataRepeater.ItemTemplate"/> 上のコントロールのインスタンスと対応する、現在の行(<see cref="DataRepeater.CurrentItem"/>)のインスタンスのSelectedValueに値を設定します。
        /// </summary>
        /// <param name="control">対象のコントロール</param>
        /// <param name="value">セットする値</param>
        /// <exception cref="ArgumentException">複数のコントロールが同名で存在する時に throw されます。</exception>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
        public void SetSelectedValue(ListControl control, object value)
        {
            ListControl target = this.GetCurrentItemControl(control) as ListControl;

            if (target != null)
            {
                target.SelectedValue = value;
            }
        }

        /// <summary>
        /// 指定された <see cref="DataRepeater.ItemTemplate"/> 上のコントロールのインスタンスと対応する、現在の行(<see cref="DataRepeater.CurrentItem"/>)のインスタンスのSelectedValueを取得します。
        /// </summary>
        /// <param name="control">対象のコントロール</param>
        /// <returns>コントロールの値。存在しない場合は null 。</returns>
        /// <exception cref="ArgumentException">複数のコントロールが同名で存在する時に throw されます。</exception>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
        public object GetSelectedValue(ListControl control)
        {
            ListControl target = this.GetCurrentItemControl(control) as ListControl;

            if (target != null)
            {
                return target.SelectedValue;
            }

            return null;
        }
        #endregion

        #endregion

        //static > 別にstaticでなくてもいいが
        #region GetItemControl / DataRepeaterItem の中から、対象のコントロールを Find する
        /// <summary>
        /// DataRepeaterItem の中から、対象のコントロールを Find する
        /// </summary>
        /// <param name="repeaterItem">対象のRepeaterItem</param>
        /// <param name="control">検索するコントロール。これはTemplate上にあるもの。</param>
        /// <returns>取得したコントロール。存在しない時は null</returns>
        /// <exception cref="ArgumentException">複数のコントロールが同名で存在する時に throw されます。</exception>
        private static Control GetItemControl(DataRepeaterItem repeaterItem, Control control)
        {
            if (repeaterItem != null && control != null)
            {
                return GetItemControl(repeaterItem, control.Name);
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// DataRepeaterItem の中から、対象のコントロールを Find する
        /// </summary>
        /// <param name="repeaterItem">対象のRepeaterItem</param>
        /// <param name="controlName">検索するコントロールの名前</param>
        /// <returns>取得したコントロール。存在しない時は null</returns>
        /// <exception cref="ArgumentException">複数のコントロールが同名で存在する時に throw されます。</exception>
        private static Control GetItemControl(DataRepeaterItem repeaterItem, string controlName)
        {
            if (repeaterItem != null && !String.IsNullOrEmpty(controlName))
            {
                Control[] ctrls = repeaterItem.Controls.Find(controlName, true);

                if (ctrls == null)
                {
                    return null;
                }
                else if (ctrls.Length > 1)
                {
                    throw new ArgumentException("複数のコントロールが存在します。", "controlName");
                }
                else
                {
                    return ctrls[0];
                }
            }
            else
            {
                return null;
            }
        }
        #endregion

        //AddとRemove
        #region AddNew / 新しいDataRepeaterItem を DataRepeater コントロールに追加します。
        /// <summary>
        /// 新しい <see cref="DataRepeaterItem"/> を <see cref="DataRepeater"/> コントロールに追加します。
        /// </summary>
        /// <exception cref="ReadOnlyException"><see cref="AllowUserToAddItems"/> プロパティが False に設定されています。</exception>
        /// <remarks>
        /// <para>
        /// <note><see cref="CustomDataRepeater"/> コントロールでは、このメソッドを <c>利用しないでください。</c></note>
        /// このメソッドは基本的に変更されていません。思い通りに動かすには、かなり複雑なコードが必要になります。
        /// <see cref="AddNewToScroll(DataView)">AddNewToScroll</see> メソッドを利用してください。
        /// </para>
        /// <para>
        /// <note>この説明は、規定の説明です。</note>
        /// 新しい <see cref="DataRepeaterItem"/> を <see cref="DataRepeater"/> コントロールの下部に表示するには、このメソッドを使用します。
        /// <see cref="DataRepeater.DataSourceChanged">DataSourceChanged</see> イベントまたは <see cref="AddNew()">AddNew</see> メソッドで既定値が指定されない限り、
        /// <see cref="DataRepeaterItem"/> 内のコントロールのデータは空白です。
        /// </para>
        /// <para>
        /// <see cref="AllowUserToAddItems"/> プロパティが <c>true</c> に設定されている場合、<see cref="BindingNavigator"/> コントロール上の
        /// BindingNavigatorAddNewItemToolStripButton をクリックすることによって、
        /// または、<see cref="DataRepeaterItem"/> にフォーカスがある場合は Ctrl + N キーを押すことによって、新しい <see cref="DataRepeaterItem"/> を追加することもできます。
        /// </para>
        /// </remarks>
        [Obsolete("このメソッドは基本的に変更されていません。思い通りに動かすには、かなり複雑なコードが必要になります。AddNewToScroll メソッドで、新規行を追加してください。", false)]
        public new void AddNew()
        {
            //AddNewを普通に使うと、かなり複雑なコードになるので、基本使わせない。
            //ってか、うちの開発者とかだと、面倒見切れない。 > なので、コンパイル時に使っていたら警告するようにしておく。
            this.NewRowAdding();

            base.AddNew();

            this.NewRowAdded(this.CurrentItemIndex);
        }
        #endregion

        #region AddNewToScroll / 新しい DataRepeaterItem を DataRepeater コントロールに追加し、追加した行にスクロール可能であれば、スクロールして、最も若いTabIndexのコントロールにフォーカスをセットします。
        /// <summary>
        /// 新しい <see cref="DataRepeaterItem"/> を <see cref="DataRepeater"/> コントロールに追加し、
        /// 追加した行にスクロール可能であれば、スクロールして、最も若いTabIndexのコントロールにフォーカスをセットします。
        /// </summary>
        /// <returns>
        /// <see cref="DataRepeater.DataSource">DataSource</see> に指定されているコントロールが <see cref="BindingSource"/> で、
        /// その <see cref="BindingSource.List"/> が <see cref="DataView"/> である時は、対象の新規行を検索し返します。
        /// </returns>
        /// <remarks>
        /// <para>
        /// 新しい <see cref="DataRepeaterItem"/> を <see cref="DataRepeater"/> コントロールの下部に表示するには、このメソッドを使用します。
        /// <see cref="DataRepeater.DataSourceChanged">DataSourceChanged</see> イベントまたは <see cref="AddNew()">AddNew</see> メソッドで既定値が指定されない限り、
        /// <see cref="DataRepeaterItem"/> 内のコントロールのデータは空白です。
        /// </para>
        /// <para>
        /// 規定では、<see cref="OrdersDataRepeater"/> コントロールの <see cref="AllowUserToAddItems"/> は <c>false</c> です。このメソッドは追加する前にこのプロパティを操作するため、
        /// <see cref="DataRepeater.AllowUserToAddItemsChanged">AllowUserToAddItemsChanged</see> イベントが2回発生します。
        /// <note><see cref="AllowUserToAddItems"/> が <c>true</c> の時は発生しません。</note>
        /// </para>
        /// </remarks>
        public object AddNewToScroll()
        {
            //で、基本、こっちを使わせるんだけど、使用上の注意があって、
            // 1. DataRepeaterコントロールの DataSource に、ItemTemplate内にある各コントロールにバインドされているBindingSourceが指定されていないといけない
            // 2. そのBindingSource の List が DataView でないといけない
            //という制限がある。
            // > でも、基本、生のDataTableとかをバインドする事はねぇだろうから、これでたいてい、事足りるっしょ。
            // > これくらいの制限をつけておかないと、新規(Addedの状態)のデータDeleteとかがうまく制御しきれない)
            DataView dataView = null;

            BindingSource source = this.DataSource as BindingSource;

            if (source != null)
            {
                dataView = source.List as DataView;
            }

            //実際はここで処理される
            return AddNewToScroll(dataView);
        }

        /// <summary>
        /// 新しい <see cref="DataRepeaterItem"/> を <see cref="DataRepeater"/> コントロールに追加し、
        /// 追加した行にスクロール可能であれば、スクロールして、最も若いTabIndexのコントロールにフォーカスをセットします。
        /// </summary>
        /// <param name="dataView">バインドされている <see cref="BindingSource"/> の <see cref="BindingSource.List">List</see>。</param>
        /// <returns>引数に指定された <paramref name="dataView"/> から、対象の新規行が検索可能な場合は、その新規行。</returns>
        /// <remarks>
        /// <para>
        /// 新しい <see cref="DataRepeaterItem"/> を <see cref="DataRepeater"/> コントロールの下部に表示するには、このメソッドを使用します。
        /// <see cref="DataRepeater.DataSourceChanged">DataSourceChanged</see> イベントまたは <see cref="AddNew()">AddNew</see> メソッドで既定値が指定されない限り、
        /// <see cref="DataRepeaterItem"/> 内のコントロールのデータは空白です。
        /// </para>
        /// <para>
        /// 規定では、<see cref="OrdersDataRepeater"/> コントロールの <see cref="AllowUserToAddItems"/> は <c>false</c> です。このメソッドは追加する前にこのプロパティを操作するため、
        /// <see cref="DataRepeater.AllowUserToAddItemsChanged">AllowUserToAddItemsChanged</see> イベントが2回発生します。
        /// <note><see cref="AllowUserToAddItems"/> が <c>true</c> の時は発生しません。</note>
        /// </para>
        /// </remarks>
        public object AddNewToScroll(DataView dataView)
        {
            //で、ここが実際の追加の処理の部分
            //なので、呼び出しとしては、
            //object obj = this.drpMain.AddNewToScroll();
            //か
            //obj = this.drpMain.AddNewToScroll(this.bndSource.List as DataView);
            //こんな感じになる

            bool changeAllowUserToAddItems = false;

            //追加を許可されていないなら変更する
            if (!base.AllowUserToAddItems)
            {
                changeAllowUserToAddItems = true;
            }

            try
            {
                this.NewRowAdding();

                int newItemIndex = this.ItemCount;

                base.AddNew();

                this.NewRowAdded(newItemIndex);

                if (dataView != null &&
                    this.CurrentItemIndex > -1 &&
                    dataView.Count > this.CurrentItemIndex)
                {
                    DataRowView rowView = dataView[this.CurrentItemIndex];

                    return rowView;
                }
            }
            finally
            {
                //変更したら戻す
                if (changeAllowUserToAddItems)
                {
                    base.AllowUserToAddItems = false;
                }
            }

            return null;
        }
        #endregion

        #region NewRowAdding / 新規行を追加する前によんで
        /// <summary>
        /// 新規行を追加する前によんで
        /// </summary>
        private void NewRowAdding()
        {
            this.SuspendLayout();

            if (this.ItemCount > 0)
            {
                this.CurrentItemIndex = 0;
                this.ScrollItemIntoView(0, true);
            }

            this.ResumeLayout(false);
        }
        #endregion

        #region NewRowAdded / 新規の行を追加した時に呼び出すもの
        /// <summary>
        /// 新規の行を追加した時に呼び出すもの
        /// </summary>
        /// <param name="newItemIndex">新しい行のインデックス</param>
        private void NewRowAdded(int newItemIndex)
        {
            if (this.ItemCount > 0)
            {
                this.ScrollItemIntoView(0, true);
            }

            this.ScrollItemIntoView(newItemIndex, true);

            //最初のTabStop可能なコントロールにフォーカスする
            // > この辺はおまけ機能。どうせ、先輩とか上司とか、営業あたりに言われるだろ、これくらいのこと
            if (this.CurrentItem != null)
            {
                //これ、選択できねーんで、面倒だがー
                Control firstControl = null;

                FindFirstControl(ref firstControl, this.CurrentItem.Controls);

                if (firstControl != null)
                {
                    this.CurrentItem.ScrollControlIntoView(firstControl);

                    firstControl.Focus();
                }
            }
        }

        #region FindFirstControl / TabIndex的に、指定されたコントロールの中の一番若いコントロールをrefする。再帰します。
        /// <summary>
        /// TabIndex的に、指定されたコントロールの中の一番若いコントロールをrefする。再帰します。
        /// </summary>
        /// <param name="firstControl">一番若いコントロール</param>
        /// <param name="controlCollection">検査するコントロールのコレクション</param>
        private static void FindFirstControl(ref Control firstControl, ControlCollection controlCollection)
        {
            if (controlCollection != null)
            {
                foreach (Control ctrl in controlCollection)
                {
                    if (firstControl == null && ctrl.TabStop)
                    {
                        firstControl = ctrl;
                    }

                    if (firstControl != null)
                    {
                        if (ctrl.TabStop &&
                            firstControl.TabIndex > ctrl.TabIndex)
                        {
                            firstControl = ctrl;
                        }
                    }
                    FindFirstControl(ref firstControl, ctrl.Controls);
                }
            }
        }
        #endregion

        #endregion

        #region RemoveAt / 指定したインデックス位置にある DataRepeaterItem を DataRepeater コントロールから削除します。
        /// <summary>
        /// 指定したインデックス位置にある <see cref="DataRepeaterItem"/> を <see cref="DataRepeater"/> コントロールから削除します。
        /// </summary>
        /// <param name="index"><see cref="DataRepeaterItem"/> のインデックス</param>
        /// <exception cref="ArgumentOutOfRangeException">index で指定される値が 0 未満または <see cref="DataRepeater.ItemCount">ItemCount</see> - 1 より大きな値です。</exception>
        /// <remarks>
        /// <para>
        /// <note><see cref="CustomDataRepeater"/> コントロールでは、このメソッドを <c>利用しないでください。</c></note>
        /// このメソッドは何も変更されていません。思い通りに動かすには、かなり複雑なコードが必要になります。
        /// <see cref="RemoveCurrentItem(BindingSource)">RomoveCurrentItem</see> メソッドか、<see cref="RemoveAt(int, BindingSource)">RemoveAt</see> のオーバーロードを利用してください。
        /// </para>
        /// <para>
        /// 実行時に RemoveAt メソッドを呼び出すと、指定された <see cref="DataRepeaterItem"/> が <see cref="DataRepeater"/> から削除されます。
        /// 基になる <see cref="DataSet"/> からレコードは削除されません。
        /// </para>
        /// </remarks>
        [Obsolete("このメソッドは何も変更されていません。思い通りに動かすには、かなり複雑なコードが必要になります。RemoveAt(DataView) メソッドか、RemoveCurrentItem(DataView) で、削除してください。", false)]
        public new void RemoveAt(int index)
        {
            //削除も、普通にやろうとするとすげー大変。(Addedのものの処理とかね)
            //なんで、基本使わせない
            base.RemoveAt(index);
        }
        #endregion

        #region RemoveCurrentItem / 現在の行の DataRepeaterItem を DataRepeater コントロールから削除します。
        /// <summary>
        /// 現在の行の <see cref="DataRepeaterItem"/> を <see cref="DataRepeater"/> コントロールから削除します。
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">index で指定される値が 0 未満または <see cref="DataRepeater.ItemCount">ItemCount</see> - 1 より大きな値です。</exception>
        /// <remarks>
        /// <para>
        /// 実行時に RemoveAt メソッドを呼び出すと、指定された <see cref="DataRepeaterItem"/> が <see cref="DataRepeater"/> から削除されます。
        /// 基になる <see cref="DataSet"/> からレコードは削除されません。
        /// </para>
        /// <para>
        /// この呼び出しでは、基底で <see cref="DataRepeater.DataSource"/> に <see cref="BindingSource"/> が指定されているとして <see cref="RemoveAt(int, BindingSource)">RemoveAt</see> を呼び出します。
        /// </para>
        /// </remarks>
        public void RemoveCurrentItem()
        {
            //これもねカスタムのAddNewToScrollと同じ制限
            BindingSource source = this.DataSource as BindingSource;

            this.RemoveCurrentItem(source);
        }

        /// <summary>
        /// 現在の行の <see cref="DataRepeaterItem"/> を <see cref="DataRepeater"/> コントロールから削除します。
        /// </summary>
        /// <param name="source">バインドされている <see cref="BindingSource"/> 。</param>
        /// <exception cref="ArgumentOutOfRangeException">index で指定される値が 0 未満または <see cref="DataRepeater.ItemCount">ItemCount</see> - 1 より大きな値です。</exception>
        /// <remarks>
        /// <para>
        /// 実行時に RemoveAt メソッドを呼び出すと、指定された <see cref="DataRepeaterItem"/> が <see cref="DataRepeater"/> から削除されます。
        /// 基になる <see cref="DataSet"/> からレコードは削除されません。
        /// </para>
        /// <para>
        /// <paramref name="source"/> が バインドされているとして、対象の行が新規行であれば、CancelEdit を呼び出します。
        /// </para>
        /// </remarks>
        public void RemoveCurrentItem(BindingSource source)
        {
            this.RemoveAt(this.CurrentItemIndex, source);
        }
        #endregion

        #region RemoveAt / 指定したインデックス位置にある DataRepeaterItem を DataRepeater コントロールから削除します。
        /// <summary>
        /// 指定したインデックス位置にある <see cref="DataRepeaterItem"/> を <see cref="DataRepeater"/> コントロールから削除します。
        /// </summary>
        /// <param name="index"><see cref="DataRepeaterItem"/> のインデックス</param>
        /// <param name="source">バインドされている <see cref="BindingSource"/> 。</param>
        /// <exception cref="ArgumentOutOfRangeException">index で指定される値が 0 未満または <see cref="DataRepeater.ItemCount">ItemCount</see> - 1 より大きな値です。</exception>
        /// <remarks>
        /// <para>
        /// 実行時に RemoveAt メソッドを呼び出すと、指定された <see cref="DataRepeaterItem"/> が <see cref="DataRepeater"/> から削除されます。
        /// 基になる <see cref="DataSet"/> からレコードは削除されません。
        /// </para>
        /// <para>
        /// <paramref name="source"/> が バインドされているとして、対象の行が新規行であれば、CancelEdit を呼び出します。
        /// 新規行を削除した場合、内部的な整合性を保つため、再描画が発生します。
        /// </para>
        /// </remarks>
        public void RemoveAt(int index, BindingSource source)
        {
            //新規の行であるかを取得する
            bool isNewRow = false;

            if (source != null)
            {
                DataView dataView = source.List as DataView;

                if (dataView != null &&
                    index > -1 &&
                    dataView.Count > index)
                {
                    isNewRow = dataView[index].IsNew;
                }
            }

            try
            {
                this.SuspendLayout();

                if (isNewRow)
                {
                    //これをやらないと、ドロップダウンとか、いろいろ面倒なことになるのよ
                    this.BeginResetItemTemplate();
                }

                //キャンセルする
                this.CancelEdit();

                if (!isNewRow)
                {
                    //新規でないなら、消さないと
                    base.RemoveAt(index);
                }
            }
            finally
            {
                if (isNewRow)
                {
                    this.EndResetItemTemplate();

                    //再バインドが必要なのよー
                    source.ResetBindings(false);
                }

                this.ResumeLayout(false);
            }
        }
        #endregion
    }
}

 なげぇ!みづれぇ!
 VisualStudioにコピペして読んでください。

 まぁ、こんな感じです。

 あとはがんばれ。ちょーがんばれ。

 ってか、ある程度「できない事」をもうけないと、このコントロールはうまく使えないです。
 何かあれば、コメントなりでどうぞ。わかりそうなら、お答えします。


トラックバックURL

http://blog.studio-odyssey.net/cgi-bin/mt/mt-tb.cgi/134


コメントする