none
UserControl 内 Control のプロパティを Form から設定変更したい RRS feed

  • 質問

  • 図のような Form を用意しました。


    +--------------------------+
    | Form                     |
    |                          |
    |  +-----------------+     |
    |  | UserControl     |     |
    |  |                 |     |
    |  | +------------+  |     |
    |  | | LabelEx    |  |     |
    |  | |            |  |     |
    |  | |      +------------+ |
    |  | |      |    Text    | |
    |  | |      +------------+ |
    |  | |            |  |     |
    |  | +------------+  |     |
    |  |                 |     |
    |  +-----------------+     |
    |                          |
    +--------------------------+

    • Form は UserControl を内包
    • UserControl は拡張コントロール LabelEx を内包
    • LabelEx は Label コントロールを拡張したもの
    • LabelEx は Text プロパティを公開
    • UserControl は LabelEx をプロパティとして公開
    • LabeEx は Form からもアクセスできるように UserControl で設定済み

    さて、こういうことをやった目的は、
    UserControl 内にある LabelEx の Text プロパティを Form 上で変更することです。


    ですので、期待としては、

    • Form からは LabelEx の Text のみを表示させたい
    • Form からは LabelEx の Text のみを変更したい
    • Form から変更した Text を反映させたい

    というものだったのですが、現状、

    • Form から見えるのは LabelEx の親である Label のプロパティ全部
    • Form から Text を表面上変更できる
    • 変更内容が実行時に反映されていない

    という結果です。



    確認画像 (1) Label のプロパティが全部見える。Text のみでいい。

     



    確認画像 (2) 変更した値が反映されていない。"labelEx" を "ahaha" に変更していたのに。


     

     

    比較のために、Control をもたないクラスを用意し、同じことをやってみたら、
    期待通りの結果となりました。

     


    確認画像 (3) プロパティ ブラウザで値を設定

     


    確認画像 (4) "label" という文字列が "ahaha" に変更された。

     

     

    Control (Form) をもつものは UserControl 内のプロパティとして設定変更で
    きないのでしょうか?


     

     

     

    以下に、検証に使ったソースを提示します。


    Control をもたないクラスを UserControl に内包させた場合

    A.cs

    Code Snippet

    [TypeConverter(typeof(AConverter))]
    public class A
    {
      public A() { }


      private string _name = "";


      [NotifyParentProperty(true)]
      public string Name
      {
        get { return this._name; }
        set { this._name = value; }
      }
    }


    internal class AConverter : ExpandableObjectConverter
    {
      public override bool CanConvertFrom(

        ITypeDescriptorContext context, Type sourceType)
      {
        if (sourceType == typeof(string))
        {
          return true;
        }
        return base.CanConvertFrom(context, sourceType);
      }


      public override object ConvertFrom(
        ITypeDescriptorContext context,

        System.Globalization.CultureInfo culture, object value)
      {
        if (value is string)
        {
          A a = new A();
          a.Name = value.ToString();
          return a;
        }
        return base.ConvertFrom(context, culture, value);
      }


      public override object ConvertTo(
        ITypeDescriptorContext context, System.Globalization.CultureInfo culture,
        object value, Type destinationType)
      {
        if (destinationType == typeof(string) && value is A)
        {
          A a = (A)value;
          return a.Name;
        }
        return base.ConvertTo(context, culture, value, destinationType);
      }
    }

     


    Container.cs

    Code Snippet

    public partial class Container : UserControl
    {
      public Container()
      {
        InitializeComponent();
      }


      private A _a = new A();


      [Category("internal A"),
        RefreshProperties(RefreshProperties.Repaint),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
      public A A
      {
        get { return this._a; }
        set
        {
          this._a = value;
          this.label.Text = this._a.Name;
        }
      }
    }

     


    Form1.Designer.cs

    Code Snippet

    partial class Form1
    {
      ....
      private void InitializeComponent()
      {
        NoForm.A a1 = new NoForm.A(); ............................................ (1)
        this.container1 = new NoForm.Container();
        this.SuspendLayout();
        //
        // container1
        //
        a1.Name = "ahaha"; ....................................................... (2)
        this.container1.A = a1; .................................................. (3)
        this.container1.BackColor = System.Drawing.SystemColors.GradientActiveCaption;
        this.container1.Location = new System.Drawing.Point(70, 45);
        this.container1.Name = "container1";
        this.container1.Size = new System.Drawing.Size(150, 150);
        this.container1.TabIndex = 0;
        //
        // Form1
        //
        ....
      }


      #endregion


      private Container container1;
    }

     


    この場合、プロパティを設定すると、Form1.Designer.cs に上記 (1)-(3) が自
    動挿入されますが、拡張コントロール LabelEx の場合は挿入されませんでした。
    素の Label コントロールでも挿入されませんでした。

     

    Label の拡張コントロール クラスを UserControl に内包させた場合

    LabelEx.cs

    Code Snippet

    public partial class LabelEx : Label
    {
      public LabelEx()
      {
        InitializeComponent();
      }


      [NotifyParentProperty(true)]
      public override string Text
      {
        get { return base.Text; }
        set { base.Text = value; }
      }
    }


    internal class LabelExConverter : ExpandableObjectConverter
    {
      public override bool CanConvertFrom(

        ITypeDescriptorContext context, Type sourceType)
      {
        if (sourceType == typeof(string))
        {
          return true;
        }
        return base.CanConvertFrom(context, sourceType);
      }


      public override object ConvertFrom(ITypeDescriptorContext context,

        System.Globalization.CultureInfo culture, object value)
      {
        if (value is string)
        {
          LabelEx label = new LabelEx();
          label.Text = value.ToString();
          return label;
        }
        return base.ConvertFrom(context, culture, value);
      }


      public override object ConvertTo(

        ITypeDescriptorContext context, System.Globalization.CultureInfo culture,

        object value, Type destinationType)
      {
        if (destinationType == typeof(string) && value is LabelEx)
        {
          LabelEx label = (LabelEx)value;
          return label.Text;
        }
        return base.ConvertTo(context, culture, value, destinationType);
      }
    }

     


    Container.cs

    Code Snippet

    public partial class Container : UserControl
    {
      public Container()
      {
        InitializeComponent();
      }


      [Category("internal LabelEx"),
        RefreshProperties(RefreshProperties.Repaint),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
      public LabelEx LabelEx
      {
        get { return this.labelEx; }
        set { this.labelEx = value; }
      }
    }

     

     

    - ダウンロード : ソース

     

    参考
    - Visual Studio .NET プロパティ ブラウザによるコンポーネントの本格的な RAD 化
    - Design-Time Integration of Windows Forms Components at C# Online.NET (CSharp-Online.NET)
    - HOW TO:ビジュアルC#.NETを使って、 Web コントロールをデザイナでの展開可能なプロパティで作成します。

    2008年2月24日 9:43

回答

  • TypeConverter って何?という私でしたが、勉強がてらに調べてみました。

     

    今回の問題は、LabelExConverter.ConvertFrom で作成した新しい LabelEx のインスタンスを、Container の LabelEx プロパティのセッターでそのまま代入されているのが原因です。

     

    set { this.labelEx = value; }

     

    この処理により、メンバフィールド this.labelEx の参照は新しい LabelEx のインスタンスになりますが、表示中のラベルのインスタンスは、ユーザーコントロールの InitializeComponent の中で Controls.Add された時点の最初のインスタンスのままです。

     

    ということで、次のように Text プロパティの値だけを採用するようにすれば良いと思います。

     

    Code Snippet

    public LabelEx LabelEx
    {
        get { return this.labelEx1; }
        internal set { this.labelEx1.Text = value.Text; }
    }

     

     

    次に、すべてのプロパティが表示される件ですが、これはコンバーターを ExpandableObjectConverter から派生した場合の仕様です。

    次のようにオーバーライドすれば、Text プロパティだけを表示できます。

     

    Code Snippet

    public override PropertyDescriptorCollection GetProperties(
        ITypeDescriptorContext context,
        object value,
        Attribute[] filter)
    {
        return new PropertyDescriptorCollection(
            new PropertyDescriptor[] { TypeDescriptor.GetProperties(value)["Text"] });
    }

     

     

     

    ただし思ったのですが、今回の場合は TypeConverter などは不要ではないでしょうか?

     

    ユーザーコントロールでは、LabelEx のインスタンスを拡張可能なプロパティとして公開する代わりに、単に次のようなプロパティをユーザーコントロールに実装すれば良いのではないでしょうか?

     

    Code Snippet

    public string InnerText
    {
        get { return this.labelEx.Text; }
        set { this.labelEx.Text = value; }
    }

     

    # あ、ヘルプの件、てっきり Express Edition もスタートメニューは同じようになってると思ってました…すいません

    2008年2月25日 12:11

すべての返信

  • TypeConverter って何?という私でしたが、勉強がてらに調べてみました。

     

    今回の問題は、LabelExConverter.ConvertFrom で作成した新しい LabelEx のインスタンスを、Container の LabelEx プロパティのセッターでそのまま代入されているのが原因です。

     

    set { this.labelEx = value; }

     

    この処理により、メンバフィールド this.labelEx の参照は新しい LabelEx のインスタンスになりますが、表示中のラベルのインスタンスは、ユーザーコントロールの InitializeComponent の中で Controls.Add された時点の最初のインスタンスのままです。

     

    ということで、次のように Text プロパティの値だけを採用するようにすれば良いと思います。

     

    Code Snippet

    public LabelEx LabelEx
    {
        get { return this.labelEx1; }
        internal set { this.labelEx1.Text = value.Text; }
    }

     

     

    次に、すべてのプロパティが表示される件ですが、これはコンバーターを ExpandableObjectConverter から派生した場合の仕様です。

    次のようにオーバーライドすれば、Text プロパティだけを表示できます。

     

    Code Snippet

    public override PropertyDescriptorCollection GetProperties(
        ITypeDescriptorContext context,
        object value,
        Attribute[] filter)
    {
        return new PropertyDescriptorCollection(
            new PropertyDescriptor[] { TypeDescriptor.GetProperties(value)["Text"] });
    }

     

     

     

    ただし思ったのですが、今回の場合は TypeConverter などは不要ではないでしょうか?

     

    ユーザーコントロールでは、LabelEx のインスタンスを拡張可能なプロパティとして公開する代わりに、単に次のようなプロパティをユーザーコントロールに実装すれば良いのではないでしょうか?

     

    Code Snippet

    public string InnerText
    {
        get { return this.labelEx.Text; }
        set { this.labelEx.Text = value; }
    }

     

    # あ、ヘルプの件、てっきり Express Edition もスタートメニューは同じようになってると思ってました…すいません

    2008年2月25日 12:11
  •  TH01 さんからの引用

    TypeConverter って何?という私でしたが、勉強がてらに調べてみました。


    ありがとうございます。


    指示された通りにしてみましたが (そのつもり)、残念ながら期待通りになりま
    せんでした。おっかしいなぁ。


     TH01 さんからの引用

    ただし思ったのですが、今回の場合は TypeConverter などは不要ではないでしょうか?

     

    ユーザーコントロールでは、LabelEx のインスタンスを拡張可能なプロパティ
    として公開する代わりに、単に次のようなプロパティをユーザーコントロール
    に実装すれば良いのではないでしょうか?


    実はこれだけではなく、サイズや位置など他にもあるので、ある程度楽にでき
    るようにしたかったのです。

     

    元ネタ (破棄)
    - Form を開いたデザイナで、Form に配置された UserControl 上の DataGridView のプロパティを変更したい

     


     TH01 さんからの引用

    # あ、ヘルプの件、てっきり Express Edition もスタートメニューは同じよう
    # になってると思ってました…すいません


    いいえ、お気になさらず。問題ありません。

     

    やっぱりローカルファイルは検索が早くて重宝。

    私のネット環境では、検索結果の表示に10秒以上かかってしまいます。

    2008年2月25日 14:13
  • 単純に次のようにContentでいけませんか?

    Code Snippet

    DesignerSerializationVisibility(DesignerSerializationVisibility.Content)

     

     

     

    気づいたきっかけとして、SplitContainerコントロールのメタデータを見たことです。

     

    Code Snippet

    //
    // 概要:
    //     System.Windows.Forms.SplitContainer.Orientation に応じて、System.Windows.Forms.SplitContainer
    //     の左側または上側のパネルを取得します。
    //
    // 戻り値:
    //     System.Windows.Forms.SplitContainer.Orientation が Vertical の場合は System.Windows.Forms.SplitContainer
    //     の左側パネル。System.Windows.Forms.SplitContainer.Orientation が Horizontal の場合は
    //     System.Windows.Forms.SplitContainer の上側パネル。
    [Localizable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public SplitterPanel Panel1 { get; }

     

     

    2008年2月25日 15:24
    モデレータ
  •  Azulean さんからの引用

    単純に次のようにContentでいけませんか?


    Azulean さん、アドバイスありがとうございます。

     

    お、Designer.cs に書き込まれて、実行時に指定した文字列が現れました。
    「永続化」という言われている処理ですかね。


    1つ解決。ありがとう、Azulean さん。


    あと1つ。必要なプロパティのみの表示。

     

    Form1.Designer.cs

    Code Snippet

    private void InitializeComponent()
    {
      this.userControl11 = new TH01.UserControl1();
      this.SuspendLayout();
      //
      // userControl11
      //
      this.userControl11.BackColor = System.Drawing.Color.SandyBrown;
      //
      //
      //
      this.userControl11.LabelEx.AutoSize = true;
      this.userControl11.LabelEx.Location = new System.Drawing.Point(53, 99);
      this.userControl11.LabelEx.Name = "labelEx1";
      this.userControl11.LabelEx.Size = new System.Drawing.Size(35, 12);
      this.userControl11.LabelEx.TabIndex = 0;
      this.userControl11.LabelEx.Text = "ahaha";
      this.userControl11.Location = new System.Drawing.Point(68, 53);
      this.userControl11.Name = "userControl11";
      this.userControl11.Size = new System.Drawing.Size(150, 150);
      this.userControl11.TabIndex = 0;
      //
      // Form1
      //

     

     

    Visible, Content の違いがはっきりと分からず、Visible にしてました。

    2008年2月25日 15:48
  • 全体的にコードが多くて文章が少なく、要点や目的がわかりにくいので、もう少し「目標」を文章で冒頭に明示したほうが、見ている人も食いつきやすいのではないかと思います。
     
    それはおいといて、
     
     custar さんからの引用

    あと1つ。必要なプロパティのみの表示。

     
    LabelEx がどのような用途で作成されたものであるかによるのですが、この UserControl から利用するためだけに作成されていて、かつ LabelEx 側で表示したいプロパティと表示したくないプロパティがあるのであれば、LabelEx のプロパティに BrowsableAttribute を設定していけばよいかと思います。
     
    Code Snippet
    public class LabelEx : Label
    {
        //  : 前略
     
        // Tag プロパティは表示したくないので隠す
        [Browsable(false)]
        public new object Tag
        {
          get { return base.Tag; }
          set { base.Tag = value; }
        }
     
        //  : 後略
    }

     

     
    LabelEx が汎用的なコントロールで、このような設定ができない場合は、TypeConverter を利用してフィルタするか、LabelEx を公開しないで、LabelEx へプロパティを設定するためのクラスを公開するようにすると良いでしょう。
     
     
    2008年2月25日 21:28
  • custar さんのソースをダウンロードしてみました。

     

    ● TypeConverter の指定が漏れてますよ。

    [TypeConverter(typeof(LabelExConverter))]
    public partial class LabelEx : Label

     

    ● LabelEx プロパティのセッターの internal 指定は不要でした。

    LabelEx プロパティへのコードによる代入を避けようと思って internal をつけたのですが、プロパティウィンドウからの変更までもできなくしてしまっていました。

     

    ● Text プロパティのオーバーライドは不要ではないでしょうか。

    NotifyParentProperty 属性も不要だと思います。

     

    # そのプロジェクト名はやめていただければ…

    2008年2月25日 22:08
  • 元のコードを見直しましたが、既にTypeConverterを作られているんですね。

    であれば、LabelExにそれを使用することを属性で宣言すれば良いです。

     

    LabelEx.cs

    Code Snippet

    [TypeConverter(typeof(LabelExConverter))]

    public partial class LabelEx : Label
    {
      public LabelEx()
      {
        InitializeComponent();
      }


      [NotifyParentProperty(true)]
      public override string Text
      {
        get { return base.Text; }
        set { base.Text = value; }
      }
    }


    internal class LabelExConverter : ExpandableObjectConverter
    {
      public override bool CanConvertFrom(

        ITypeDescriptorContext context, Type sourceType)
      {
        if (sourceType == typeof(string))
        {
          return true;
        }
        return base.CanConvertFrom(context, sourceType);
      }


      public override object ConvertFrom(ITypeDescriptorContext context,

        System.Globalization.CultureInfo culture, object value)
      {
        if (value is string)
        {
          LabelEx label = new LabelEx();
          label.Text = value.ToString();
          return label;
        }
        return base.ConvertFrom(context, culture, value);
      }


      public override object ConvertTo(

        ITypeDescriptorContext context, System.Globalization.CultureInfo culture,

        object value, Type destinationType)
      {
        if (destinationType == typeof(string) && value is LabelEx)
        {
          LabelEx label = (LabelEx)value;
          return label.Text;
        }
        return base.ConvertTo(context, culture, value, destinationType);
      }
    }

     

    追記:

    リロードしてなかったら被った…。
    2008年2月25日 22:30
    モデレータ
  •  TH01 さんからの引用

    ● TypeConverter の指定が漏れてますよ。

    [TypeConverter(typeof(LabelExConverter))]
    public partial class LabelEx : Label


    ! 忘れてました。入れてみたら、Text プロパティしか表示されていない。何故?


    GetProperties() の override ですね。調べ直します。

     

    Azulean さんも、ご指摘ありがとうございます。

     

     TH01 さんからの引用

    ● LabelEx プロパティのセッターの internal 指定は不要でした。

    LabelEx プロパティへのコードによる代入を避けようと思って internal をつ
    けたのですが、プロパティウィンドウからの変更までもできなくしてしまって
    いました。


    あ、やっぱりそうですか。

     

     TH01 さんからの引用

    ● Text プロパティのオーバーライドは不要ではないでしょうか。

    NotifyParentProperty 属性も不要だと思います。


    NotifyParentProperty は不要という意見は、今回の見せ方だったら不要ですね。
    元々参考(1) から派生させたものだったので、そのまま残していました。


    これをやると "LabelEx" と表示されているセルの右側のセルの表示も、直ぐ変
    更が効いていたので。


     

    Text プロパティのオーバーライドは不要、という意見はどういう意味でしょう?


    override しなくても、派生元の Label そのもので override されているプロ
    パティなので、派生先 LabelEx ではプロパティとして選択的に表示されるのか
    なぁ? 試してみます。

     

     TH01 さんからの引用

    # そのプロジェクト名はやめていただければ…


    済みません。こちらでの目的が分かりやすかったので。公開時は変更すればよ
    かったですね。ソースならびに画像を消しました。

     


    TH01 さん、Azulean さん、サポートありがとうございます。

    2008年2月26日 0:24
  •  K.Takaoka さんからの引用

    全体的にコードが多くて文章が少なく、要点や目的がわかりにくいので、もう
    少し「目標」を文章で冒頭に明示したほうが、見ている人も食いつきやすいの
    ではないかと思います。


    アドバイスありがとうございます。


    ご指摘の点、自覚しています。自分でも何処に質問部分があるのか見つけにく
    いなぁ、と感じていました。しかもフォーラムで画像が挿入できることが分かっ
    たので、そちらに気が行き過ぎてました。コードも余計だなと感じています。


     K.Takaoka さんからの引用

    LabelEx がどのような用途で作成されたものであるかによるのですが、


    最もシンプルなコントロールだったので、テスト目的で作成しました。


     K.Takaoka さんからの引用

    UserControl から利用するためだけに作成されていて、かつ LabelEx 側で表示
    したいプロパティと表示したくないプロパティがあるのであれば、LabelEx の
    プロパティに BrowsableAttribute を設定していけばよいかと思います。

     

    Code Snippetpublic class LabelEx : Label
    {
        //  : 前略

        // Tag プロパティは表示したくないので隠す
        [Browsable(false)]
        public new object Tag
        {
          get { return base.Tag; }
          set { base.Tag = value; }
        }

        //  : 後略
    }


    なるほど、そんな手がありましたね。Browsable なんて何に使うんだ?と無視
    していましたが、言われればそうですね。しかも、new で隠してる。
    # K.Takaoka さん、発想が柔軟ですねぇ


     K.Takaoka さんからの引用

    LabelEx が汎用的なコントロールで、このような設定ができない場合は、
    TypeConverter を利用してフィルタするか、LabelEx を公開しないで、
    LabelEx へプロパティを設定するためのクラスを公開するようにすると良いで
    しょう。


    おぉ、そうかぁ。フィルタ。
    # 柔軟!


    でも、私の力量不足。msdn 読み返します。


    K.Takaoka さん、アドバイスありがとうございます。

    2008年2月26日 0:44
  •  custar さんからの引用

    Text プロパティのオーバーライドは不要、という意見はどういう意味でしょう?


    TypeConverter 内で以下のように指定しているから不要ってことかな。


    Code Snippet
    public override PropertyDescriptorCollection GetProperties(
      ITypeDescriptorContext context, object value, Attribute[] attributes)
    {
      return new PropertyDescriptorCollection(
        new PropertyDescriptor[] { TypeDescriptor.GetProperties(value)["Text"] });
    }

     

    2008年2月26日 1:40
  • いいえ、少し違います。
    プロパティウィンドウへの表示(および GetProperties のコード)とは関係ないです。
    というのも、Text プロパティは元々パブリックなプロパティであるため、オーバーライドせずともプロパティウィンドウへの表示対象になりますから。
    特に何かの追加処理を行われているわけではないため、オーバーライドされてる目的がわからず、不要なのでは?と思った次第です。

     

    ※追記:
    「パブリックなプロパティだから表示される」と書きましたが、調べると厳密には少し間違ってました。
    Label などは Component を継承し、その Component は IComponent を実装していますが、IComponent には TypeConverter として ComponentConverter が付加されているため、パブリックなプロパティがプロパティウィンドウに表示されるのでした。
    奥が深いですね。間違ってたらご指摘ください。

    2008年2月26日 4:36
  •  TH01 さんからの引用

    Label などは Component を継承し、その Component は IComponent を実装し
    ていますが、IComponent には TypeConverter として ComponentConverter が
    付加されているため、パブリックなプロパティがプロパティウィンドウに表示
    されるのでした。

     

    調査ありがとうございます。紐付けをたどっていくと....何処まで行くのやら。

     

    graphical にドキュメントをたどれるツールがあると、私にとって分かりやす
    く一目瞭然なんですけどね。


    まだ、皆さんの提示された部分を理解できないところがあるので、もう暫く調
    査が続きそうです。でも、本件は一旦ここで閉めたいと思います。


    TH01 さん、Azulean さん、K.Takaoka さん、サポートありがとうございます。

    2008年2月27日 0:38
  • DataGridView の右上にある「タスク ボタン(?)」や、プロパティの Columns
    のボタンを押すと現れるダイアログが CollectionEditor という名前が分かり
    ました。


    これを UserControl から呼び出せるようにすればいいので、調べてみました。


    参考
    (1) フィードバック: PropertyGrid does not show DataGridColumns
    (2) User Control Instance/DataGridView.Columns in Visual Studio Design


    (2) に書かれているサンプルコードで以下のように CollectionEditor を呼び
    出せた上に、Designer.cs に書き込まれることも確認しました。まだ、コード
    の読解は済んでませんが、感心。やるなぁ。

     

    応用できそう

     

     

    + 追記

    ダウンロード : ソース

    2008年3月1日 14:15