Visual Studio .NET プロパティ ブラウザーを使用してコンポーネントを実際に RAD にする

 

Shawn Burke
Microsoft Corporation

更新日: 2002 年 2 月

概要: この記事は、Microsoft Visual Studio .NET プロパティ ブラウザーを調べて、その新機能を活用するのに役立ちます。 (23ページ印刷)

Codesamples.exeをダウンロード します

内容

はじめに
何ができますか?
基本: 属性を使用した参照のカスタマイズ
展開可能なプロパティと文字列変換: TypeConverters とプロパティ ブラウザー
カスタム型の編集と表示
代替プロパティ ビューの提供
そして、あなたもそれを使用することができます!
まとめ

はじめに

Windows® の初期に Microsoft® Visual Basic® が導入されて以来、プロパティ ブラウザーは、真に迅速なアプリケーション開発 (RAD と呼ばれる) を可能にする重要な要素となっています。 Microsoft Visual Studio® .NET では、この伝統は、これまで使用できなかった多くのプロパティ ブラウザー機能の追加の利点を引き続き利用できます。 Visual Studio .NET 環境に参加するコンポーネントやその他のオブジェクトを作成する場合は、これらの新機能の多くを利用して、コンポーネントをユーザーに最適なものにしたいと思うでしょう。

何ができますか?

プロパティ ブラウザーの以前のバージョンでは、基本的に COM 型の情報が処理され、そこに含まれていたプロパティが表示されていました。 COM コンポーネントのパブリック API は、通常、インターフェイス定義言語 (IDL) で定義され、固定の属性セットがアタッチされています ( 非ブロック可能など)。これにより、プロパティ ブラウザーで表示できないか、 バインド可能で、プロパティがデータ バインディングの対象としてマークされます。 標準値リストや分類されたプロパティなどの他の参照機能には、コンポーネント作成者が IPerPropertyBrowsingICategorizeProperties などの COM インターフェイスを実装する必要がありました。 .NET Frameworkと、.NET Framework Windows フォーム クラスを使用して記述された Visual Studio .NET プロパティ ブラウザーは、これらの機能をよりシンプルで統一された方法で提供し、新しい魅力的な機能のホストを提供します。

もちろん、Visual Studio .NET プロパティ ブラウザーでは、以前のバージョンと同じ機能が引き続きサポートされています。ITypeInfo から型情報を検査する方法と、上記の拡張参照インターフェイスをサポートする方法を認識しています。 それでも、魅力的な新機能のほとんどにアクセスする場合は、マネージド コードを使用して、.NET Frameworkの上にコンポーネントを記述する必要があります。 次に、これらの優れた新機能とその機能の簡単な一覧を示します。

  • メタデータ属性
    プロパティの属性によって、プロパティ ブラウザーの動作に関する多くの情報が決まります。 コンポーネント作成者は、表示されるプロパティ、プロパティの分類方法、複数選択に含めることができるかどうか、および他のプロパティの値に影響を与えるかどうかを簡単に制御できます。 これらの属性は、簡単に学習して利用できます。
  • 階層的なサポート
    プロパティは、論理organizationのサブプロパティに展開できます。
  • グラフィカルな値表現
    プロパティの値のテキストと共に、選択した色やフォントのサンプルなど、小さなグラフィカル表現を描画できます。
  • カスタム型の編集
    コンポーネントでは、日付型の日付ピッカーや色の拡張カラー ピッカーなど、編集の種類用のカスタム ユーザー インターフェイスが提供される場合があります。 プロパティ ブラウザーでサポートされている型が決定された日は過ぎ去りました。 このタスクは、カスタム型のコンポーネントに任されるようになりました。 フレームワーク自体は、すべての基本的な組み込み型を編集するための機能を提供します。
  • 拡張可能なビュー
    コンポーネントは、プロパティ タブとも呼ばれ、デザイン サーフェイス上の .NET コンポーネントで使用できる標準のプロパティとイベント ビュー以外のビューを追加できます。
  • 再利用可能なコンポーネント
    Visual Studio .NET プロパティ ブラウザーは、主に System.Windows.Forms.PropertyGrid コントロールで構成されており、アプリケーションで再利用できます。

明らかに、.NET プロパティ ブラウザーについて調べるには多くの情報があります。 この記事では、これらの優れた新機能の活用を開始します。

基本: 属性を使用した参照のカスタマイズ

参照を制御するための基本的なメカニズムは、IDL で定義された COM コンポーネントで使用され、メタデータ属性を追加するメカニズムに似ています。 参照を制御するための最も基本的な属性は 、BrowsableAttribute です。 既定では、プロパティ ブラウザーには、オブジェクトに定義されているすべてのパブリックで読み取り可能なプロパティが表示され、"Misc" という名前のその他のカテゴリに配置されます。単純なコンポーネントのサンプルを次に示します。

public class SimpleComponent : System.ComponentModel.Component
   {
      private string data = "(none)";
      private bool   dataValid = false;
      
      public string Data {
         get {
            return data;
         }
         set {
            if (value != data) {
               dataValid = true;
               data = value;   
            }         
}
      }

      public bool IsDataValid {
         get {
            // perform some check on the data 
            //
            return dataValid;
         }
      }
   }

このコンポーネントがプロパティ ブラウザーでどのように表示されるかを次に示します。

図 1. プロパティ ブラウザーの単純なコンポーネント

この例では、 SimpleComponent には DataIsDataValid の 2 つのプロパティがあります。 IsDataValid は読み取り専用であり、開発者はデザイン時にその値を知る必要がないため、プロパティ ブラウザーに IsDataValid を含めるのはほとんど意味がありません。 そのため、 BrowsableAttribute を追加することで簡単に非表示にすることができます。

   [Browsable(false)]
public bool IsDataValid {
      get {
         // perform some check on the data 
         //
         return dataValid;
      }
   }

C# コンパイラは、省略できるように、"Attribute" という単語を属性クラス名に自動的に追加します。 ただし、「 BrowsableAttribute(false)」と 入力しても機能します。 プロパティまたはクラスで明示的に指定されていない属性の場合、 プロパティは属性の既定値を持つものと見なされます。 この場合、 BrowsableAttribute の既定値は true です。 これは、Visual Basic .NET で記述されたコードの場合にも当てはめます。 唯一の違いは、Visual Basic .NET コードでは、上に示した角かっこではなく、属性の周囲に山かっこ ('<' と '>') が使用されていることです。

また、図 1 の値 abc が太字になっていることに注意してください。 太字の値は、値が既定値から変更され、デザイナーでフォームまたはコントロールのコードが生成されるときに値が保持されることを示します。 まだ既定値に設定されているプロパティの値を保持する理由はありません。これにより、起動時にコンポーネントの初期化に時間が追加され、ファイルにさらにコードが生成されます。 しかし、この SimpleComponent は、値が常にコードに永続化されるとは限らないので、プロパティ ブラウザーに "既定値" の値をどのように伝えますか? この目的のために、 DefaultValueAttribute を使用できます。これは、任意の型の値を渡すことができるように、コンストラクター内の オブジェクト を受け取ります。 プロパティ ブラウザーは、値を表示するときに、現在の値と DefaultValueAttribute の値を比較し、値が異なる場合は太字でテキストをレンダリングします。 この場合、"(none)" 以外の値がある場合、値は太字になります。

   [DefaultValue("(none)")]
   public string Data {
      // . . .  
   }

コンポーネントにメソッドを追加することで、単なる株式価値よりも高度なロジックがプロパティに必要かどうかを判断できます。 メソッドの名前は ShouldSerialize で始まり、その後にプロパティ名が続き、メソッドはブール値を返す必要があります。 この場合、メソッドは ShouldSerializeData*.* と呼ばれます。* 以下のメソッドを SimpleComponent に追加すると、 DefaultValueAttribute を Data プロパティに追加するのと同じように機能しますが、必要に応じて決定できます。

private bool ShouldSerializeData() 
{
      return Data != "(none)";
}

多くの場合、分類されたプロパティをユーザーが移動する方がはるかに簡単です。 名前が示すように、 CategoryAttribute はこのタスクを処理します。 単にカテゴリ名の文字列を受け取り、プロパティ ブラウザーはカテゴリ名でプロパティをグループ化します。 任意のカテゴリ名を指定できます。

   [DefaultValue("(none)"), Category("Sample")]
   public string Data {
      // . . .  
   }

コンポーネント開発者が一般的に実行するタスクの 1 つは、カテゴリ文字列のローカライズです。 System.ComponentModel.CategoryAttribute クラスを見ると、呼び出された GetLocalizedString メソッドでこの機能だけが許可されていることがわかります。 ローカライズされたカテゴリ文字列を作成するには、category 属性の独自の派生を定義する必要があります。 次の使用例は、コンポーネントのマニフェスト リソースで、キーに基づくカテゴリ名を検索します。 キーは、 属性が プロパティに適用されるときに、実際のカテゴリ名を指定する代わりに使用されます。 この属性のカテゴリ文字列に対して初めてクエリを実行すると、 GetLocalizedString オーバーライドが呼び出され、そのキー値が受け取られます。 返される値は、カテゴリ名としてプロパティ ブラウザーに表示されます。

internal class LocCategoryAttribute : CategoryAttribute {
      public LocCategoryAttribute(string categoryKey) : 
                     base(categoryKey){
      }

      protected override string GetLocalizedString(string key) {
         // get the resource set for the current locale.
         //
         ResourceManager resourceManager = 
new ResourceManager();
         string categoryName = null;

         // walk up the cultures until we find one with
         // this key as a resource as our category name
         // if we reach the invariant culture, quit.
         //
         for (CultureInfo culture = 
               CultureInfo.CurrentCulture;
             categoryName == null && 
               culture != CultureInfo.InvariantCulture;
             culture = culture.Parent) {
             categoryName = (string)
resourceManager.GetObject(key, culture);
         }   
         return categoryName;
      }
   }

この属性を使用するには、キーを使用してリソースを定義し、この属性をプロパティに配置します。

    [LocCategory("SampleKey")]
    public string Data {
      // . . .  
   }

デザイン サーフェイスで複数のコンポーネントを選択すると、プロパティ ブラウザーに、プロパティ名と型に基づいて、それらのコンポーネントのプロパティの交差部分 ("マージ") が表示されます。 次に、プロパティ値が変更されると、選択したすべてのコンポーネントは、選択したプロパティの値を受け取ります。 一部のプロパティを他のプロパティとマージに含めるのはほとんど意味がありません。 一般に、このようなプロパティには、コンポーネントの名前などの一意の値が必要です。 複数のコンポーネントが選択されている場合に値を設定すると、すべてのプロパティが同じ値に変更されるため、それらのプロパティがそのマージに含まれないようにすると便利です。 MergablePropertyAttribute によってこの問題が解決されます。 この属性を false の 値でプロパティに追加するだけで、デザインサーフェイスで複数のコンポーネントが選択されている場合、プロパティは非表示になります。

一部のプロパティは、他のプロパティの値に影響を与える可能性があります。 たとえば、データ バインド コンポーネントでは、 DataSource プロパティをクリアすると、プロパティがバインドされているデータ行を指定する DataMember プロパティが暗黙的にクリアされます。 RefreshPropertiesAttribute は、このケースを処理します。 既定値は "None" ですが、別の属性値が指定されている場合、この属性を持つ値が変更されると、プロパティ ブラウザーは自動的に更新されます。 他の 2 つのそのような値は Repaint です。この値は、プロパティ ブラウザーが値のすべてのプロパティに対してクエリを再実行して再描画することを指定します。 All は、そのプロパティに対してオブジェクト自体を再クエリする必要があることを意味します。 変更によってプロパティが追加または削除される場合は All を使用しますが、これはより高度な使用シナリオであり、単なる再描画よりも遅くなることに注意してください。 RefreshProperteis.Repaint は、ほとんどの場合に適しています。 この属性は、変更の影響を受けるプロパティではなく、値が変更されたときに変更を引き起こすプロパティに配置することを忘れないでください。

最後に、 DefaultPropertyAttributeDefaultEventAttribute は、プロパティ ブラウザーが最初にクラスに対して強調表示する必要があるプロパティまたはイベントを指定するクラス レベルの属性です。 コンポーネント間で選択が変更されると、プロパティ ブラウザーは、前のコンポーネントで選択したのと同じ名前と種類のプロパティを選択しようとします。 このようなプロパティを選択できない場合は、 DefaultPropertyAttribute で指定されたプロパティが選択されます。 プロパティ ブラウザーの [イベント] ビューでは、 DefaultEventAttribute の値が使用されます。 また、これは、デザイナーでコンポーネントをダブルクリックしたときにハンドラーが生成されるイベントであることにも注意してください。

展開可能なプロパティと文字列変換: TypeConverters とプロパティ ブラウザー

Visual Studio .NET プロパティ ブラウザーの優れた機能の 1 つは、入れ子になったプロパティを表示できることです。これにより、カテゴリよりも詳細で論理的なレベルのグループ化が可能になります。 入れ子になったプロパティは、分類された並べ替えモードとアルファベット順の並べ替えモードの両方でも使用できます。 これは、プロパティ リストをコンパクトに保つのに役立ちます。Left プロパティと Top プロパティの両方ではなく、X と Y に展開可能な Location プロパティだけが個別のエントリに対して行われます。

図 2. 入れ子になったプロパティ

しかし、プロパティが展開可能かどうかは何によって決まりますか? 答えは、プロパティ自体ではなく、プロパティの Type にあります。 .NET Frameworkでは、各型に TypeConverter が関連付けられています。 ブール値文字列などの型の TypeConverter を使用すると、プロパティ ブラウザーで展開できなくなります。 ブール型のサブプロパティを持つことはほとんど意味がありません!

TypeConverters は、実際には、.NET Framework内、特にプロパティ ブラウザー内で複数の関数を実行します。 その名前が示すように、 TypeConverters は、ある型から別の型に値を動的に変換する標準的な方法を提供します。 たとえば、プロパティ ブラウザーは文字列のみを直接操作するため、 TypeConverters に依存してこれらの文字列値を取得し、プロパティが想定する Type に変換します。その逆も同様です。 TypeConverters は、拡張性も制御し、複合型がプロパティ ブラウザーとシームレスに連携できるようにします。

たとえば、次のような Person という型があるとします。

   [TypeConverter(typeof(PersonConverter))]
   public class Person {
      private string firstName = "";
      private string lastName = "";
      private int    age = 0;
   
      public int Age {
         get {
            return age;
         }
         set {
            age = value;
         }
      }
      
      public string FirstName {
         get {
            return firstName;
         }
         set {
            this.firstName = value;
         }
      }

      public string LastName {
         get {
            return lastName;
         }
         set {
            this.lastName = value;
         }
      }      
   }

TypeConverterAttribute が クラスに適用され、この型に使用される TypeConverter が指定されていることに注意してください。 TypeConverterAttribute が指定されていない場合、既定の TypeConverter が選択されますが、それほど多くはありません。 PersonConverter の場合、TypeConverterの GetPropertiesSupported メソッドと GetProperties メソッドをオーバーライドして、型を展開できるようにします。

internal class PersonConverter : TypeConverter {

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

   public override bool GetPropertiesSupported(
            ITypeDescriptorContext context) {
         return true;
      }
   }

この操作は、このコードを正確に実行する ExpandableObjectConverter という TypeConverter 派生が.NET Frameworkに含まれるので十分に一般的です。 簡単に展開するには、ExpandableObjectoConverter から TypeConverter を派生するだけです。 TypeConverter を変更して、Person 型を文字列との間で変換できるようになりました。

internal class PersonConverter : ExpandableObjectConverter {
      
   public override bool CanConvertFrom(
         ITypeDescriptorContext context, Type t) {

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

   public override object ConvertFrom(
         ITypeDescriptorContext context, 
         CultureInfo info,
          object value) {

      if (value is string) {
         try {
         string s = (string) value;
         // parse the format "Last, First (Age)"
         //
         int comma = s.IndexOf(',');
         if (comma != -1) {
            // now that we have the comma, get 
            // the last name.
            string last = s.Substring(0, comma);
             int paren = s.LastIndexOf('(');
            if (paren != -1 && 
                  s.LastIndexOf(')') == s.Length - 1) {
               // pick up the first name
               string first = 
                     s.Substring(comma + 1, 
                        paren - comma - 1);
               // get the age
               int age = Int32.Parse(
                     s.Substring(paren + 1, 
                     s.Length - paren - 2));
                  Person p = new Person();
                  p.Age = age;
                  p.LastName = last.Trim();
                  .FirstName = first.Trim();
                  return p;
            }
         }
      }
      catch {}
      // if we got this far, complain that we
      // couldn't parse the string
      //
      throw new ArgumentException(
         "Can not convert '" + (string)value + 
                  "' to type Person");
         
      }
      return base.ConvertFrom(context, info, value);
   }
                                 
   public override object ConvertTo(
            ITypeDescriptorContext context, 
            CultureInfo culture, 
            object value,    
            Type destType) {
      if (destType == typeof(string) && value is Person) {
         Person p = (Person)value;
         // simply build the string as "Last, First (Age)"
         return p.LastName + ", " + 
             p.FirstName + 
             " (" + p.Age.ToString() + ")";
      }
            return base.ConvertTo(context, culture, value, destType);
   }   
}

これで、TypeConverter はかなりうまく機能しています。これは拡張可能であり、ユーザーはこの型をサブプロパティまたは単一の文字列として操作できます。

図 3: 展開可能な TypeConverter

上記のように プロパティを使用するには、 UserControl を作成し、次のプロパティ コードを貼り付けます。

private Person p = new Person();

public Person Person {
   get {
      return p;
   }      
   set {
      this.p = value;
   }
}

カスタム型の編集と表示

プロパティ ブラウザーでのプロパティの編集は、3 つの方法のいずれかで行うことができます。 最初に、プロパティを文字列として編集できます。 TypeConverters は、その値を文字列との間で変換する処理を行うことができます (必要な場合)。 次に、ドロップダウン矢印を使用すると、編集 UI が プロパティの下に表示されることがあります。 最後に、省略記号ボタンを使用すると、ファイル ダイアログやフォント ピッカーなどの他のカスタム UI メソッドを開くことができます。 文字列の編集については既に説明しているので、最初にドロップダウン エディターのケースを見ていきます。

.NET Frameworkには、ドロップダウン編集のいくつかの例が含まれています。 コントロールの AccessibleRoleDockColor の各プロパティは、ドロップダウン エディターで何ができるかを示します。

図 4: ドロップダウン エディター

TypeConverters は、単純なドロップダウン エディターのジョブも実行します。 TypeConverter のドキュメントを見ると、これを実現するための 3 つの仮想メソッドが表示されます。GetStandardValuesSupported()GetStandardValues()GetStandardValuesExclusive())。 これらのメソッドを使用すると、プロパティに定義済みの値の一覧を指定できます。 実際、これは TypeConverter であり、プロパティ ブラウザーで Enum 値をドロップダウンで表示できます (左端の例の図のように)。 プロパティ ブラウザー自体には、Enum 型を具体的に処理するコードはありません。代わりに、 TypeConverter 機能のみを使用します。

たとえば、"FamilyMember" という名前のコンポーネントに Relation というプロパティがあり、ユーザーが他のユーザーとの関係を選択できるとします。 これを簡単にするために、プロパティには、母親、父、娘、姉妹などの最も一般的な値が格納されたドロップダウン エディターがあります。 可能な関係の膨大な数を考えると、ユーザーがカスタム文字列を入力できるようにすることも望ましいです。

public class FamilyMember : Component {

   private string relation = "Unknown";
   [TypeConverter(typeof(RelationConverter)), 
    Category("Details")]
   public string Relation {
      get {   return relation;}
      set {   this.relation = value;}
   }
}

internal class RelationConverter : StringConverter {

   private static StandardValuesCollection defaultRelations = 
         new StandardValuesCollection(
            new string[]{"Mother", "Father", "Sister", 
                "Brother", "Daughter", "Son", 
                "Aunt", "Uncle", "Cousin"});

   public override bool GetStandardValuesSupported(
                  ITypeDescriptorContext context) {
      return true;
   }

   public override bool GetStandardValuesExclusive(
                  ITypeDescriptorContext context) {
      // returning false here means the property will
      // have a drop down and a value that can be manually
      // entered.      
      return false;
   }

    public override StandardValuesCollection GetStandardValues(
                  ITypeDescriptorContext context) {
      return defaultRelations;
   }
}

しかし、よりカスタマイズされたユーザー インターフェイスはどうでしょうか。 このためには、 UITypeEditor というクラスを使用できます。 UITypeEditor には、プロパティをレンダリングするとき、およびユーザーがドロップダウン エディターまたはポップアップ エディターのボタンをクリックしたときに、プロパティ ブラウザーによって呼び出されるいくつかのメソッドが含まれています。

ImageColorFont.Name などの一部のプロパティの種類では、値が表示されている領域の左側に値の小さな表現が描画されます。 これを実現するには、 UITypeEditorPaintValue メソッドを実装します。 プロパティ ブラウザーは、エディターを定義するプロパティのプロパティ値をレンダリングするときに、四角形と描画に使用する Graphics オブジェクトをエディターに表示します。 たとえば、Grade というサンプル型があり、プロパティ ブラウザーで表現を描画できます。 Grade クラスは次のようになります。

[Editor(typeof(GradeEditor), typeof(System.Drawing.Design.UITypeEditor))]
[TypeConverter(typeof(GradeConverter))]
public struct Grade
{
   private int grade; 
   
   public Grade(int grade)
   {
      this.grade = grade;
   }

   public int Value 
   {
      get 
      {
      return grade;
      }
   }      
}

プロパティ ブラウザーで成績を入力すると、値に基づいてさまざまなビットマップを表示できます。

図 5: プロパティ ブラウザで成績を入力する

これを実現するためのコードは簡単です。 上の Grade 型の EditorAttribute が、 GradeEditor クラスを参照していることに注目してください。

public class GradeEditor : UITypeEditor
{
   public override bool GetPaintValueSupported(
         ITypeDescriptorContext context) 
   {
      // let the property browser know we'd like
      // to do custom painting.
   return true;
   }

   public override void PaintValue(PaintValueEventArgs pe) 
   {
   // choose the right bitmap based on the value
   string bmpName = null;
   Grade g = (Grade)pe.Value;
   if (g.Value > 80) 
   {
      bmpName = "best.bmp";
   }
   else if (g.Value > 60) 
   {
      bmpName = "ok.bmp";
   }
   else 
   {
      bmpName = "bad.bmp";
   }

   // draw that bitmap onto the surface provided.
   Bitmap b = new Bitmap(typeof(GradeEditor), bmpName);
   pe.Graphics.DrawImage(b, pe.Bounds);
   b.Dispose();
   }
}

前述のように、 UITypeEditors では、プロパティ値のポップアップ エディターまたはドロップダウン エディターを実行することもできます。 この記事の後半に含まれるサンプルには、このプロセスの例が含まれています。 詳細については、 UITypeEditor.GetEditStyle メソッドと UITypeEditor.EditValue メソッドと IWindowsFormsEditorService インターフェイスに関するページを参照してください。

代替プロパティ ビューの提供

Visual C# ™ .NET でWindows フォーム アプリケーションを作成すると、プロパティ ブラウザーのツール バーに稲妻のように見える追加のボタンが表示されることがあります。 そのボタンを押すと、プロパティの代わりにイベント ハンドラーを編集できるプロパティ ブラウザー ビューが表示されます。 これは、実際には開発者にとって拡張可能なメカニズムです。

プロパティ ブラウザーのビューはプロパティ タブと呼ばれ、その結果、ビューの追加に関連するプライマリ クラスは System.Windows.Forms.Design.PropertyTab になります。 プロパティ タブは、常に使用できるように、特定のコンポーネント、デザイナー ドキュメント、または静的に関連付けることができます。 コンポーネントまたはドキュメントに関連するタブは、クラスの PropertyTabAttribute を使用して指定します。 この属性は、作成するタブの Type と、PropertyTabAttributePropertyTabScope パラメーターを使用してプロパティ ブラウザーでの表示を管理する方法を指定します。 スコープ付きコンポーネントを含むタブは、 PropertyTabAttribute を含むコンポーネントが表示されている場合にのみ表示されます。 ドキュメント スコープのタブは、現在のデザイナー ドキュメント上のすべてのデザイナー オブジェクトに対して表示されたままになります。 プロパティ タブの既定のスコープは PropertyTabScope.Component です

PropertyTabs のサンプルについては、含まれている FunkyButton プロジェクトを参照してください。 FunkyButton は、ボタンの図形を四角形以外のコントロールに操作できるようにする PropertyTab を公開する UserControl です。

図 6: FunkyButton

現在選択されているプロパティ タブは、プロパティ ブラウザーが選択したオブジェクトのプロパティを取得する場所です。 したがって、プロパティ タブには、表示されるプロパティのセットを操作する機会が与えられる。 [イベント] タブでは、プロパティのような方法でイベントを返すことでこれを行います。 この場合、プロパティ タブはコントロールの頂点を表すプロパティを作成します。

.NET Frameworkのプロパティは、PropertyDescriptor というクラスによってカプセル化されます。 PropertyDescriptor 自体は抽象基本クラスです。特殊です。 フレームワークで見つかったの派生は、オブジェクトが公開する通常のプロパティへのアクセスを提供します。 ただし、プロパティ ブラウザーは、プロパティに対して直接ではなく、これらの PropertyDescriptor オブジェクトに対して動作するため、特別なタスクを実行する独自 の PropertyDescriptor を作成できます。 この場合は、コントロール上の頂点の数を表す頂点を作成し、もう 1 つを作成して各頂点を表します。 ここでも、どのオブジェクトの実際のプロパティにも対応しないエントリがプロパティ ブラウザーに追加されることに注意してください。

プロパティ ブラウザーが PropertyTab に プロパティを要求すると、 GetProperties メソッドが呼び出されます。 サンプル の PropertyTab の場合、そのメソッドは次のようになります。

public override PropertyDescriptorCollection 
   GetProperties(ITypeDescriptorContext context, object component, 
         Attribute[] attrs)
{
   // our list of props.
   //
   ArrayList propList = new ArrayList();

   // add the property for our count of vertices
   //
   propList.Add(new NumPointsPropertyDescriptor(this));

   // add a property descriptor for each vertex
   //
   for (int  i = 0; i < ((FunkyButton)component).Points.Count; i++) 
   {
      propList.Add(new VertexPropertyDescriptor(this,i));
   }

   // return the collection of PropertyDescriptors.
   PropertyDescriptor[] props = 
               (PropertyDescriptor[])
         propList.ToArray(typeof(PropertyDescriptor));
   return new PropertyDescriptorCollection(props);
}

GetProperties は、返すプロパティ記述子のコレクションを返すだけです。 PropertyDescriptors 自体はかなり単純です。 コード サンプルを見て、それらがどのように記述されているかを確認してください。

FunkyButton サンプルでは、ドロップダウン エディターの実装も示しています。 各ポイント頂点について、標準の X、Y ポイント値を入力する代わりに、 PropertyTabFunkyButton 図形の表現を表示し、頂点をグラフィカルに配置できるエディターに置き換えます。 これにより、ボタン図形の設定がはるかに簡単になります。

図 7: 頂点をグラフィカルに配置する

カスタム PropertyTab はプロパティを提供しているため、このプロパティのエディターをオーバーライドすることも簡単です。 これを行うには、PropertyDescriptorGetEditor メソッドをオーバーライドし、カスタム エディターのインスタンスを返す必要があります。

public override object GetEditor(Type editorBaseType) 
{
   // make sure we're looking for a UITypeEditor.
   //
   if (editorBaseType == typeof(System.Drawing.Design.UITypeEditor)) 
   {
      // create and return one of our editors.
      //
      if (editor == null) 
      {
         editor = new PointUIEditor(owner.target);
      }
      return editor;
   }
   return base.GetEditor(editorBaseType);
}

エディターの設計も簡単です。 エディターは単に UserControl であるため、他の種類の WindowsForms オブジェクトと同様に設計できます。

図 8: エディターの設計

最後に、ユーザーがプロパティ ブラウザーで下向き矢印をクリックすると、エディターはユーザー インターフェイスを作成してドロップダウンする機会を得られます。 PointUIEditorUITypeEditor.EditValue のオーバーライドによって、これを処理します。

public override object EditValue(
      ITypeDescriptorContext context, 
      IServiceProvider sp, object value) 
{
   // get the editor service.
   IWindowsFormsEditorService edSvc =          
   (IWindowsFormsEditorService)
sp.GetService(typeof(IWindowsFormsEditorService));

   // create our UI
   if (ui == null) 
   {
      ui = new PointEditorControl();
   }
         
   // initialize the ui with the settings for this vertex
   ui.SelectedPoint = (Point)value;
   ui.EditorService = edSvc;
   ui.Target = (FunkyButton)context.Instance;
         
   // instruct the editor service to display the control as a 
   // dropdown.
   edSvc.DropDownControl(ui);
   
   // return the updated value;
   return ui.SelectedPoint;
}

そして、あなたもそれを使用することができます!

最後に、Visual Studio .NET のプロパティ ブラウザーのコアをアプリケーションで使用できるようにしました。 System.Windows.Forms.PropertyGrid と呼ばれるコントロールは、[ツールボックスのカスタマイズ] ダイアログの [.NET Framework コンポーネント] タブから PropertyGrid を選択することで、Visual Studio .NET のツールボックスに追加できます。

PropertyGrid は、他のWindows フォーム コントロールと同様に機能します。 レイアウトに固定またはドッキングしたり、色を変更したりできます。 次の一覧は、 PropertyGrid に固有の興味深いプロパティの一部を示しています。

  • SelectedObject
    PropertyGrid でプロパティを表示するオブジェクトを指定します。
  • ToolbarVisible
    PropertyGrid の上部に表示されるツール バーの表示と非表示を切り替えます。
  • HelpVisible
    PropertyGrid の基になるヘルプ テキストを表示または非表示にします。
  • PropertySort
    PropertyGrid の並べ替えの種類 (分類、アルファベット順など) を決定します。

これらの各プロパティは、デザイン時に通常どおりに設定できます。 ただし、実行時に PropertyGrid を使用して、参照しているオブジェクトを操作できます。 次に、ボタンを参照するフォームの PropertyGrid の例を示します。 この場合、 PropertyGrid ツール バーとヘルプ情報は非表示になっています。 前述のように、 PropertyGrid 型自体のプロパティを使用してこれを制御できます。

図 9: 非表示のツール バーとヘルプ情報を含む PropertyGrid

まとめ

.NET Framework と Visual Studio .NET では、プロパティ ブラウザーの新しいバージョンに多くの機能が追加されました。 プロパティ ブラウザーは開発者の RAD エクスペリエンスの中核であるため、これらの機能により、Visual Basic でプロパティ ブラウザーが非常に人気のある使いやすさを維持しながら、さらに柔軟性が向上します。 アプリケーションで PropertyGrid を使用する機能を使用すると、ユーザー インターフェイスの設計を簡略化し、優れたアプリケーションの作成に多くの時間を費やし、ユーザー インターフェイスをレイアウトする時間を短縮できます。