none
コンポーネントを配置した瞬間を判断したい RRS feed

  • 質問

  • 自作コンポーネントを作っていたら、ちょっと困った事象に出会ってしまい、どうしたもんかという状態で、 
    どなたか助けてください。 

        public partial class Component1 : TextBox
        {
            protected override void OnTextChanged(EventArgs e)
            {
                base.OnTextChanged(e);
    
                MessageBox.Show("tes5t");
            }
        }

    例えばこういったコードのコンポーネントをデザイン上で配置すると、OnTextChanged()が2度走行してしまいます。 
    走行する理由は、コンポーネントを配置した一瞬だけNameと同一値がTextに設定され、その後にText値が空に設定される為のようです。

    デザイン時にも処理を行わせたい為、DesignModeによる条件判断は利用せず、『コンポーネントを配置したその瞬間』 
    だけを制御し、処理させないようにしたいです。 

    今のところ、NameとTextが同一値の場合のみ処理を抜けるようにしていますが、利用者が意図的に 
    NameとTextを同一値に設定した場合に、本来走行して欲しい制御が行われない為、どうにかできないか 
    悩んでいます。 

    どうにかする方法をご存知の方はおりますでしょうか?
    2013年3月22日 2:54

回答

  • とりあえずVS2008,2010,2012では、貼り付けたときのStackTraceに固有の文字が出てきそうなので、

    protected override void OnTextChanged(EventArgs e)
    {
        base.OnTextChanged(e);
        if (Environment.StackTrace.Contains("System.Windows.Forms.Design.ControlDesigner.InitializeNewComponent")) //(DesignMode &&
        {
          return;
        }
        MessageBox.Show(Environment.StackTrace);
    }

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答の候補に設定 佐伯玲 2013年3月25日 0:13
    • 回答としてマーク takiru 2013年3月25日 0:58
    2013年3月22日 10:54

すべての返信

  • とりあえずVS2008,2010,2012では、貼り付けたときのStackTraceに固有の文字が出てきそうなので、

    protected override void OnTextChanged(EventArgs e)
    {
        base.OnTextChanged(e);
        if (Environment.StackTrace.Contains("System.Windows.Forms.Design.ControlDesigner.InitializeNewComponent")) //(DesignMode &&
        {
          return;
        }
        MessageBox.Show(Environment.StackTrace);
    }

    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答の候補に設定 佐伯玲 2013年3月25日 0:13
    • 回答としてマーク takiru 2013年3月25日 0:58
    2013年3月22日 10:54
  • 『コンポーネントを配置したその瞬間』ここだけで判断すると、
    FormのControlAddedイベントで処理するのはいかがでしょうか。
    ※コントロール側からでなくForm側から見る。

    protected override void OnControlAdded(ControlEventArgs e)
    {
    	base.OnControlAdded(e);
    	MessageBox.Show(e.Control.Name);
    	foreach (Control control in GetControls(e.Control))
    	{
    		MessageBox.Show(control.Name);
    	}
    }
    
    /// <summary>
    /// 指定コントロール配下のコントロール一覧を取得します。
    /// </summary>
    /// <param name="parent"></param>
    /// <returns></returns>
    private List<Control> GetControls(Control parent)
    {
    	List<Control> result = new List<Control>();
    	foreach (Control control in parent.Controls)
    	{
    		result.Add(control);
    		result.AddRange(GetControls(control));
    	}
    	return result;
    }
    

    力技臭いですが・・・

    • 回答の候補に設定 佐伯玲 2013年3月25日 0:13
    2013年3月22日 15:29
  • ありがとうございます。
    試してみたところ、思い通りの動作をしてくれました!
    こんな場面でStackTraceが役立つとは・・・。

    2013年3月25日 0:58
  • ありがとうございます。
    Form側での制御となると、コンポーネントとの結合が密すぎる為、独立したコンポーネントとして
    意味がなさなくなってしまいます。
    (制御を実装済みのFormサブクラスを用意したり、利用者がそれを意識しないといけなくなってしまう為)
    gekka様に提示頂いた内容で解決できそうです。ありがとうございました。
    2013年3月25日 1:03
  • 基本的にこのあたりの仕組みが変わることはなさそうなので、今回の例では問題ないと思いますが、この手の実装依存で判断する手法は、将来性に対してリスクがあるのでご注意ください。

    一応、DesignerAttributeControlDesigner といった仕組みは存在するので、頑張ればできるような気はします。
    確実性があるかどうかはわかりませんが、たとえば、こういったコードでも目的のことをある程度満たせそうに見えます。
    ただ、本来の TextBoxDesigner を使えていないので、微妙な挙動を示す場面があるかもしれません。

    public class SampleTextBoxDesigner : ControlDesigner
    {
        public override void InitializeNewComponent(System.Collections.IDictionary defaultValues)
        {
            var targetControl = ((SampleTextBox)Control);
            targetControl._isInitializing = true;
            try
            {
                base.InitializeNewComponent(defaultValues);
                targetControl.Text = string.Empty;
            }
            finally
            { targetControl._isInitializing = false; }
        }
    }
    
    [Designer(typeof(SampleTextBoxDesigner))]
    public class SampleTextBox : TextBox
    {
        internal bool _isInitializing;
    
        protected override void OnTextChanged(EventArgs e)
        {
            base.OnTextChanged(e);
            if (_isInitializing) return;
    
            MessageBox.Show("tes5t");
        }
    
    }

    // これが最良とは思っていません。
    // Site プロパティ周りで、初期化中とかとれないんかなぁと思ったものの、たどり着かず。
    2013年3月25日 14:01
    モデレータ
  • ご回答ありがとうございます。

    将来性に関して全く考えていませんでしたが、ひとまず周りで困っていないのでヨシとしてしまいます(笑)

    ControlDesigner周りについても調べてみましたが、どうもやはりTextBoxBaseDesigner、TextBoxDesignerあたりが
    動いてくれないと元々のTextBoxの挙動と違う部分がちらほらありました。
    (どこで支障を来たすかわからないので単純に実装は難しそうです)

    今回の課題には簡単に適用は出来なさそうですが、大変勉強になりました。ありがとうございました。
    2013年3月27日 8:00