Freigeben über


Machen Sie Ihre Komponenten mit dem .NET-Eigenschaftenbrowser von Visual Studio wirklich RAD

 

Shawn Burke
Microsoft Corporation

Aktualisiert im Februar 2002

Zusammenfassung: Dieser Artikel hilft Ihnen, den Microsoft Visual Studio .NET-Eigenschaftenbrowser zu erkunden und seine neuen Features zu nutzen. (23 gedruckte Seiten)

Laden Sie Codesamples.exeherunter.

Inhalte

Einführung
Was kann es tun?
Grundlagen: Verwenden von Attributen zum Anpassen des Browsens
Erweiterbare Eigenschaften und Zeichenfolgenkonvertierung: TypeConverters und der Eigenschaftenbrowser
Bearbeiten und Anzeigen von benutzerdefinierten Typen
Bereitstellen alternativer Eigenschaftenansichten
Und Sie können es auch verwenden!
Zusammenfassung

Einführung

Seit der Einführung von Microsoft® Visual Basic® in der Anfangszeit von Windows® ist der Eigenschaftenbrowser ein entscheidendes Element, um eine wirklich schnelle Anwendungsentwicklung (oft als RAD bezeichnet) zu ermöglichen. In Microsoft Visual Studio® .NET wird diese Tradition mit dem zusätzlichen Vorteil vieler Eigenschaftenbrowserfeatures fortgesetzt, die zuvor nicht verfügbar waren. Wenn Sie Komponenten oder andere Objekte schreiben, die an der .NET-Umgebung von Visual Studio teilnehmen, möchten Sie sicherlich viele dieser neuen Features nutzen, um Ihre Komponenten für Ihre Benutzer optimal zu gestalten.

Was kann es tun?

Frühere Versionen des Eigenschaftenbrowsers verarbeiteten im Wesentlichen COM-Typinformationen und zeigten die darin enthaltenen Eigenschaften an. Die öffentliche API einer COM-Komponente wird in der Regel in der IDL (Interface Definition Language) definiert und verfügt über einen festen Satz von Attributen, z. B. nichtbrowsable, wodurch verhindert wird, dass Eigenschaftenbrowser sie sehen, oder bindbar, wodurch eine Eigenschaft als für die Datenbindung geeignet markiert wird. Andere Browserfeatures, z. B. Standardwertlisten oder kategorisierte Eigenschaften, mussten Komponentenautoren COM-Schnittstellen wie IPerPropertyBrowsing oder ICategorizeProperties implementieren. Der .NET Framework und der Visual Studio .NET-Eigenschaftenbrowser, der mit .NET Framework Windows Forms-Klassen geschrieben wurde, bietet diese Features auf einfachere, einheitlichere Weise und bietet eine Vielzahl neuer und spannender Features.

Natürlich unterstützt der Visual Studio .NET-Eigenschaftenbrowser weiterhin dieselben Features wie frühere Versionen. Er weiß, wie Typinformationen aus ITypeInfo überprüft werden und wie die oben erwähnten erweiterten Browserschnittstellen unterstützt werden. Wenn Sie jedoch auf die meisten der überzeugenden neuen Features zugreifen möchten, müssen Sie Ihre Komponenten mithilfe von verwaltetem Code auf die .NET Framework schreiben. Im Folgenden finden Sie eine kurze Liste dieser großartigen neuen Features und ihrer Funktionsweise:

  • Metadatenattribute
    Attribute für Eigenschaften bestimmen viel darüber, wie sich der Eigenschaftenbrowser verhält. Sie ermöglichen es dem Komponentenautor, mit Leichtigkeit zu steuern, welche Eigenschaften sichtbar sind, wie sie kategorisiert werden, ob sie in eine Mehrfachauswahl einbezogen werden können und ob sie sich auf die Werte anderer Eigenschaften auswirken. Diese Attribute sind einfach zu erlernen und zu nutzen.
  • Hierarchische Unterstützung
    Eigenschaften können in untergeordnete Eigenschaften für logische organization erweitert werden.
  • Grafische Wertdarstellung
    Zusammen mit dem Text für den Wert einer Eigenschaft kann eine kleine grafische Darstellung gezeichnet werden, z. B. ein Beispiel einer ausgewählten Farbe oder Schriftart.
  • Benutzerdefinierte Typbearbeitung
    Komponenten bieten möglicherweise benutzerdefinierte Benutzeroberflächen für die Bearbeitung von Typen, z. B. eine Datumsauswahl für Datumstypen oder eine erweiterte Farbauswahl für Farben. Vorbei sind die Tage, in denen der Eigenschaftenbrowser ermittelt hat, welche Typen er unterstützt. Diese Aufgabe gilt jetzt für die Komponenten für benutzerdefinierte Typen. Das Framework selbst bietet die Möglichkeit, alle grundlegenden integrierten Typen zu bearbeiten.
  • Erweiterbare Ansichten
    Komponenten, die auch als Eigenschaftenregisterkarten bezeichnet werden, können Ansichten hinzufügen, die über die Standardeigenschaften- und Ereignisansicht hinausgehen, die für .NET-Komponenten auf der Entwurfsoberfläche verfügbar sind.
  • Wiederverwendbare Komponente
    Der Visual Studio .NET-Eigenschaftenbrowser besteht in erster Linie aus dem System.Windows.Forms.PropertyGrid-Steuerelement , das in Ihren Anwendungen wiederverwendet werden kann.

Natürlich gibt es viel zu erkunden über den .NET-Eigenschaftenbrowser. Dieser Artikel wird Ihnen den Einstieg in die Nutzung dieser großartigen neuen Features erleichtern.

Grundlagen: Verwenden von Attributen zum Anpassen des Browsens

Der grundlegende Mechanismus zum Steuern des Browsens entspricht dem, der in IDL-definierten COM-Komponenten verwendet wird, und das Hinzufügen von Metadatenattributen. Das grundlegendste Attribut zum Steuern des Browsens ist das BrowsableAttribute. Standardmäßig zeigt der Eigenschaftenbrowser alle öffentlichen, lesbaren Eigenschaften an, die für ein Objekt definiert sind, und platziert sie dann in einer anderen Kategorie namens "Misc". Im Folgenden ist ein Beispiel für eine einfache Komponente aufgeführt:

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;
         }
      }
   }

So würde diese Komponente im Eigenschaftenbrowser aussehen:

Abbildung 1. Eine einfache Komponente im Eigenschaftenbrowser

In diesem Beispiel verfügt SimpleComponent über zwei Eigenschaften: Data und IsDataValid. Es ist wenig sinnvoll, IsDataValid im Eigenschaftenbrowser zu verwenden, da es schreibgeschützt ist und ein Entwickler seinen Wert zur Entwurfszeit nicht kennen muss. Daher können wir es einfach ausblenden, indem wir das BrowsableAttribute hinzufügen:

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

Der C#-Compiler fügt das Wort "Attribut" automatisch an die Attributklassennamen an, damit wir es weglassen können. Die Eingabe von [BrowsableAttribute(false)] funktioniert jedoch auch. Für Attribute, die nicht explizit für eine Eigenschaft oder Klasse angegeben sind, wird davon ausgegangen, dass die Eigenschaft den Standardwert des Attributs hat. In diesem Fall ist der Standardwert von BrowsableAttributetrue. Dies gilt auch für Code, der in Visual Basic .NET geschrieben wurde. Der einzige wirkliche Unterschied besteht darin, dass Visual Basic .NET-Code anstelle der oben gezeigten eckigen Klammern winkelige Klammern ('<' und '>') um die Attribute herum verwendet.

Beachten Sie außerdem, dass der Wert abc in Abbildung 1 fett formatiert ist. Ein fett formatierter Wert bedeutet, dass der Wert von seinem Standardwert geändert wurde und dass der Wert beibehalten wird, wenn Code für ein Formular oder Steuerelement im Designer generiert wird. Es gibt keinen Grund, den Wert einer Eigenschaft beizubehalten, die weiterhin auf den Standardwert festgelegt ist. Dies würde nur Zeit zur Initialisierung der Komponente beim Start und generieren mehr Code in der Datei. Aber wie würde diese SimpleComponent dem Eigenschaftenbrowser mitteilen, welcher "Standardwert" sein sollte, damit der Wert nicht immer im Code gespeichert wird? Zu diesem Zweck können Sie das DefaultValueAttribute verwenden, das ein Objekt in seinem Konstruktor akzeptiert, sodass ein beliebiger Werttyp an das Objekt übergeben werden kann. Wenn der Eigenschaftenbrowser einen Wert anzeigt, vergleicht er den aktuellen Wert mit dem Wert aus DefaultValueAttribute und rendert den Text fett, wenn sich die Werte unterscheiden. In diesem Fall wird der Wert fett formatiert, wenn er über einen anderen Wert als "(none)" verfügt.

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

Sie können ermitteln, ob Ihre Eigenschaft eine komplexere Logik als einfach einen Aktienwert erfordert, indem Sie Ihrer Komponente eine -Methode hinzufügen. Der Name der Methode sollte mit ShouldSerialize beginnen, gefolgt vom Eigenschaftennamen, und die Methode sollte einen Booleschen Wert zurückgeben. In diesem Fall würde die Methode als ShouldSerializeData* bezeichnet.* Das Hinzufügen der unten angegebenen Methode zu SimpleComponent würde als Äquivalent zum Hinzufügen des DefaultValueAttribute zur Data-Eigenschaft dienen, ermöglicht jedoch die Entscheidung bei Bedarf.

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

Häufig ist es für Benutzer viel einfacher, in kategorisierten Eigenschaften zu navigieren. Wie der Name schon sagt, verarbeitet das CategoryAttribute diese Aufgabe. Es wird einfach eine Kategorienamenzeichenfolge verwendet, und der Eigenschaftenbrowser gruppiert Eigenschaften nach Kategoriename. Sie können einen beliebigen Kategorienamen angeben.

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

Eine Aufgabe, die Komponentenentwickler häufig ausführen möchten, ist die Lokalisierung ihrer Kategoriezeichenfolgen. Wenn Sie sich die Klasse System.ComponentModel.CategoryAttribute ansehen, werden Sie feststellen, dass die getLocalizedString-Methode nur diese Fähigkeit zulässt. Um eine lokalisierte Kategoriezeichenfolge zu erstellen, müssen Sie eine eigene Ableitung des Category-Attributs definieren. In diesem Beispiel wird anhand eines Schlüssels in den Manifestressourcen der Komponente nach den Kategorienamen gesucht. Der Schlüssel wird verwendet, anstatt den tatsächlichen Kategorienamen anzugeben, wenn das Attribut auf die Eigenschaft angewendet wird. Wenn dieses Attribut zum ersten Mal nach seiner Kategoriezeichenfolge abgefragt wird, wird die GetLocalizedString-Außerkraftsetzung aufgerufen und erhält diesen Schlüsselwert. Der zurückgegebene Wert wird dann im Eigenschaftenbrowser als Kategoriename angezeigt.

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;
      }
   }

Um dieses Attribut zu verwenden, definieren Sie eine Ressource mit einem Schlüssel, und fügen Sie dieses Attribut in Ihren Eigenschaften ein.

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

Wenn Sie mehrere Komponenten auf der Entwurfsoberfläche auswählen, zeigt der Eigenschaftenbrowser die Schnittmenge oder "Zusammenführung" der Eigenschaften für diese Komponenten basierend auf dem Eigenschaftennamen und -typ an. Wenn dann ein Eigenschaftswert geändert wird, erhalten alle ausgewählten Komponenten diesen Wert für die ausgewählte Eigenschaft. Es ist wenig sinnvoll, einige Eigenschaften in eine Zusammenführung mit anderen einzuschließen. Im Allgemeinen verfügen solche Eigenschaften über Werte, z. B. den Namen einer Komponente, die eindeutig bleiben müssen. Da das Festlegen eines Werts bei Auswahl mehrerer Komponenten versucht, alle Eigenschaften in denselben Wert zu ändern, ist es nützlich, zu verhindern, dass diese Eigenschaften in diese Zusammenführung einbezogen werden. Das MergablePropertyAttribute löst dieses Problem. Fügen Sie einfach dieses Attribut mit dem Wert false zu einer Eigenschaft hinzu, und die Eigenschaft wird ausgeblendet, wenn mehrere Komponenten auf der Entwurfsoberfläche ausgewählt werden.

Einige Eigenschaften können sich auf den Wert anderer Eigenschaften auswirken. Bei datengebundenen Komponenten löscht das Löschen der DataSource-Eigenschaft beispielsweise implizit die DataMember-Eigenschaft , die die Datenzeile angibt, an die die Eigenschaft gebunden ist. Das RefreshPropertiesAttribute behandelt diesen Fall. Der Standardwert ist "None", aber wenn ein anderer Attributwert angegeben wird, wird der Eigenschaftenbrowser automatisch aktualisiert, wenn ein Wert geändert wird, der über dieses Attribut verfügt. Die anderen beiden werte sind Repaint, die angibt, dass der Eigenschaftenbrowser alle Eigenschaften für ihre Werte erneut abfragen und neu streichen soll, und All, was bedeutet, dass das Objekt selbst für seine Eigenschaften erneut in die Warteschlange gestellt werden sollte. Verwenden Sie Alle , wenn die Änderung dazu führt, dass Eigenschaften hinzugefügt oder entfernt werden. Beachten Sie jedoch, dass dies ein komplexeres Verwendungsszenario ist und langsamer ist als eine bloße Neubemalung. RefreshProperteis.Repaint eignet sich für die überwiegende Mehrheit der Fälle. Denken Sie daran, dieses Attribut für die Eigenschaft zu verwenden, die die Änderung verursacht , wenn ihr Wert geändert wird, und nicht für die Eigenschaft, die von der Änderung betroffen ist.

Schließlich sind DefaultPropertyAttribute und DefaultEventAttribute Attribute auf Klassenebene, die angeben, welche Eigenschaft oder welches Ereignis der Eigenschaftenbrowser zunächst für eine Klasse hervorheben soll. Wenn die Auswahl zwischen Komponenten geändert wird, versucht der Eigenschaftenbrowser, eine Eigenschaft mit demselben Namen und Typ auszuwählen, wie sie für die vorherige Komponente ausgewählt wurde. Wenn eine solche Eigenschaft nicht ausgewählt werden kann, wird die im DefaultPropertyAttribute angegebene Eigenschaft ausgewählt. In der Ansicht Ereignisse des Eigenschaftenbrowsers wird der Wert aus defaultEventAttribute verwendet. Beachten Sie außerdem, dass dies das Ereignis ist, für das ein Handler generiert wird, wenn Sie im Designer doppelt auf die Komponente klicken.

Erweiterbare Eigenschaften und Zeichenfolgenkonvertierung: TypeConverters und der Eigenschaftenbrowser

Eines der wichtigsten Features des Visual Studio .NET-Eigenschaftenbrowsers ist die Möglichkeit, geschachtelte Eigenschaften anzuzeigen, wodurch eine differenziertere und logischere Gruppierungsebene als Kategorien ermöglicht wird. Geschachtelte Eigenschaften sind auch im kategorisierten und alphabetischen Sortiermodus verfügbar. Dies trägt dazu bei, die Eigenschaftenlisten kompakt zu halten. Anstelle einer Left- und Top-Eigenschaft wird nur eine Location-Eigenschaft verwendet, die in X und Y für einen separaten Eintrag erweiterbar ist.

Abbildung 2. Verschachtelte Eigenschaften

Aber was bestimmt, ob eine Eigenschaft erweiterbar ist oder nicht? Die Antwort liegt nicht in der Eigenschaft selbst, sondern in der Type of-Eigenschaft. In der .NET Framework ist jedem Typ ein TypeConverter zugeordnet. Der TypeConverter für Typen wie Boolean oder string verhindert, dass sie im Eigenschaftenbrowser erweiterbar sind. Es wäre wenig sinnvoll, Untereigenschaften für den Typ Boolean!

TypeConverters führen tatsächlich mehrere Funktionen innerhalb der .NET Framework und insbesondere im Eigenschaftenbrowser aus. Wie der Name schon sagt, bieten TypeConverters eine Standardmethode zum dynamischen Konvertieren von Werten von einem Typ in einen anderen. Der Eigenschaftenbrowser funktioniert z. B. nur direkt mit Zeichenfolgen, sodass er auf TypeConverters angewiesen ist, um diese Zeichenfolgenwerte zu übernehmen und sie in den Typ zu konvertieren, den die Eigenschaft erwartet, und umgekehrt. TypeConverters steuern auch die Erweiterbarkeit und ermöglichen es komplexen Typen, nahtlos mit dem Eigenschaftenbrowser zu arbeiten.

Stellen Sie sich beispielsweise einen Typ namens Person vor, der wie folgt aussieht:

   [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;
         }
      }      
   }

Beachten Sie, dass das TypeConverterAttribute auf die Klasse angewendet wurde, wobei der TypeConverter angegeben wird, der für diesen Typ verwendet wird. Wenn kein TypeConverterAttribute angegeben ist, wird der StandardtypConverter ausgewählt, aber es wird nicht viel bewirkt. Im Fall von PersonConverter haben wir die Methoden GetPropertiesSupported und GetProperties von TypeConverter überschrieben, damit der Typ erweiterbar ist.

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;
      }
   }

Dieser Vorgang ist üblich genug, dass die .NET Framework eine TypeConverter-Ableitung namens ExpandableObjectConverter enthält, die genau diesen Code ausführt. Um einfach erweiterbar zu sein, leiten Sie einfach Ihren TypeConverter von ExpandableObjectoConverter ab. Wir können den TypeConverter jetzt so ändern, dass er einen Person-Typ in und aus einer Zeichenfolge konvertieren kann.

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);
   }   
}

Jetzt funktioniert unser TypeConverter ziemlich gut: Er ist erweiterbar und ermöglicht es dem Benutzer, diesen Typ entweder als Untereigenschaften oder als einzelne Zeichenfolge zu bearbeiten.

Abbildung 3. Erweiterbarer TypConverter

Um die -Eigenschaft wie oben gezeigt zu verwenden, können wir ein UserControl erstellen und den folgenden Eigenschaftencode einfügen:

private Person p = new Person();

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

Bearbeiten und Anzeigen von benutzerdefinierten Typen

Die Bearbeitung von Eigenschaften im Eigenschaftenbrowser kann auf eine von drei Arten erfolgen. Zunächst kann die Eigenschaft als Zeichenfolge bearbeitet werden, und TypeConverters kann diesen Wert (bei Bedarf) in und aus einer Zeichenfolge konvertieren. Zweitens kann ein Dropdownpfeil dazu führen, dass die Bearbeitungsbenutzeroberfläche unterhalb der Eigenschaft angezeigt wird. Schließlich kann eine Schaltfläche mit den Auslassungspunkten eine andere Methode der benutzerdefinierten Benutzeroberfläche öffnen, z. B. ein Dateidialogfeld oder eine Schriftartauswahl. Wir haben bereits die Bearbeitung von Zeichenfolgen behandelt, daher betrachten wir zunächst den Fall eines Dropdown-Editors.

Die .NET Framework enthält mehrere Beispiele für die Dropdownbearbeitung. Die Eigenschaften AccessibleRole, Dock und Color des Steuerelements veranschaulichen, was mit einem Dropdown-Editor getan werden kann.

Abbildung 4. Dropdown-Editor

TypeConverters führen auch die Aufgabe einfacher Dropdown-Editoren aus. Wenn Sie sich die Dokumentation für TypeConverter ansehen, sehen Sie drei weitere virtuelle Methoden, um dies zu erreichen: GetStandardValuesSupported(), GetStandardValues()und GetStandardValuesExclusive(). Mit diesen Methoden können Sie eine Liste vordefinierter Werte für Ihre Eigenschaft bereitstellen. Tatsächlich handelt es sich um einen TypeConverter , mit dem der Eigenschaftenbrowser Enum-Werte in einer Dropdownliste anzeigen kann (wie in der linken Beispielgrafik). Der Eigenschaftenbrowser selbst verfügt über keinen Code, der speziell mit Enumerationstypen zu tun hat. Stattdessen wird nur die TypeConverter-Funktionalität verwendet.

Stellen Sie sich beispielsweise eine Komponente namens "FamilyMember" mit einer Eigenschaft namens Relation vor, auf der ein Benutzer auswählen könnte, wie eine Person mit einer anderen verknüpft ist. Um dies zu vereinfachen, verfügt die Eigenschaft über einen Dropdown-Editor, der mit den gängigsten Werten wie Mutter, Vater, Tochter und Schwester gefüllt ist. Angesichts der großen Anzahl möglicher Beziehungen ist es auch wünschenswert, einem Benutzer die Eingabe einer benutzerdefinierten Zeichenfolge zu ermöglichen.

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;
   }
}

Aber wie sieht es mit einer angepassteren Benutzeroberfläche aus? Dazu können wir eine Klasse namens UITypeEditor verwenden. UITypeEditor enthält mehrere Methoden, die vom Eigenschaftenbrowser aufgerufen werden, wenn Eigenschaften gerendert werden und wenn der Benutzer auf eine Schaltfläche für einen Dropdown- oder Popup-Editor klickt.

Einige Eigenschaftstypen wie Image, Color oder Font.Name eine kleine Darstellung des Werts direkt links neben dem Raum zeichnen, in dem der Wert angezeigt wird. Dies wird durch implementieren der UITypeEditorPaintValue-Methode erreicht. Wenn der Eigenschaftenbrowser einen Eigenschaftswert für eine Eigenschaft rendert, die einen Editor definiert, stellt er dem Editor ein Rechteck und ein Graphics-Objekt vor, mit dem er malen soll. Beispielsweise haben wir einen Beispieltyp namens Grade, für den wir eine Darstellung im Eigenschaftenbrowser zeichnen können. Unsere Klasse sieht wie folgt aus:

[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;
      }
   }      
}

Wenn wir im Eigenschaftenbrowser eine Note eingeben, können wir basierend auf dem Wert verschiedene Bitmaps anzeigen.

Abbildung 5. Eingeben einer Note im Eigenschaftenbrowser

Der Code, um dies zu ermöglichen, ist einfach. Beachten Sie das EditorAttribute für den obigen Grade-Typ, der auf die Klasse GradeEditor verweist:

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();
   }
}

Wie oben erwähnt, kann UITypeEditors auch Popup- oder Dropdown-Editoren für Eigenschaftswerte erstellen. Das weiter unten in diesem Artikel enthaltene Beispiel enthält ein Beispiel für diesen Prozess. Weitere Informationen finden Sie unter den Methoden UITypeEditor.GetEditStyle und UITypeEditor.EditValue und der IWindowsFormsEditorService-Schnittstelle .

Bereitstellen alternativer Eigenschaftenansichten

Wenn Sie eine Windows Forms-Anwendung in Visual C# ™ .NET erstellen, bemerken Sie möglicherweise eine zusätzliche Schaltfläche auf der Symbolleiste des Eigenschaftenbrowsers, die wie ein Blitz aussieht. Wenn Sie diese Schaltfläche drücken, werden Sie zu einer Eigenschaftenbrowseransicht weitergeleitet, in der Sie Ereignishandler anstelle von Eigenschaften bearbeiten können. Dies ist eigentlich ein erweiterbarer Mechanismus für Entwickler.

Die Ansichten im Eigenschaftenbrowser werden als Eigenschaftenregisterkarten bezeichnet, und daher ist die primäre Klasse, die am Hinzufügen der Ansicht beteiligt ist , System.Windows.Forms.Design.PropertyTab. Eine Eigenschaftenregisterkarte kann einer bestimmten Komponente, einem Designerdokument oder statisch zugeordnet sein, sodass sie immer verfügbar ist. Registerkarten, die sich auf eine Komponente oder ein Dokument beziehen, werden mithilfe von PropertyTabAttribute für eine Klasse angegeben. Dieses Attribut gibt den Typ der zu erstellenden Registerkarte an, und gibt an, wie ihre Sichtbarkeit im Eigenschaftenbrowser über den PropertyTabScope-Parameter von PropertyTabAttribute verwaltet werden soll. Registerkarten mit einer bereichsbezogenen Komponente werden nur angezeigt, wenn die Komponente mit dem PropertyTabAttribute-Element sichtbar ist. Dokumentbereichsregisterkarten bleiben für alle Designerobjekte im aktuellen Designerdokument sichtbar. Der Standardbereich für eine Eigenschaftenregisterkarte ist PropertyTabScope.Component.

Ein Beispiel für PropertyTabs finden Sie im enthaltenen FunkyButton-Projekt . FunkyButton ist ein UserControl , das ein PropertyTab verfügbar macht, mit dem Sie die Form der Schaltfläche in ein nicht rechteckiges Steuerelement bearbeiten können.

Abbildung 6. FunkyButton

Auf der aktuell ausgewählten Eigenschaftenregisterkarte ruft der Eigenschaftenbrowser die Eigenschaften für das ausgewählte Objekt ab. Eigenschaftenregisterkarten erhalten daher die Möglichkeit, den Satz der angezeigten Eigenschaften zu bearbeiten. Die Registerkarte Ereignisse macht dies, indem Ereignisse in einer Weise zurückgegeben werden, die wie Eigenschaften aussieht. In diesem Fall erstellt die Eigenschaftenregisterkarte Eigenschaften, die die Scheitelpunkte unseres Steuerelements darstellen.

Eigenschaften in der .NET Framework werden von einer Klasse namens PropertyDescriptor gekapselt. PropertyDescriptor selbst ist eine abstrakte Basisklasse – special. Ableitungen davon, die im Framework gefunden werden, ermöglichen den Zugriff auf die normalen Eigenschaften, die ein Objekt verfügbar macht. Der Eigenschaftenbrowser arbeitet jedoch für diese PropertyDescriptor-Objekte und nicht direkt für Eigenschaften, sodass wir eigene PropertyDescriptors erstellen können, die spezielle Aufgaben ausführen. In diesem Fall erstellen wir einen, um die Anzahl der Scheitelpunkte in unserem Steuerelement darzustellen, und dann einen anderen, um jeden dieser Scheitelpunkte darzustellen. Beachten Sie erneut, dass wir dem Eigenschaftenbrowser Einträge hinzufügen, die nicht den tatsächlichen Eigenschaften eines Objekts entsprechen.

Wenn der Eigenschaftenbrowser ein PropertyTab nach Eigenschaften fragt, ruft er die GetProperties-Methode auf. Für unser PropertyTab-Beispiel sieht diese Methode wie folgt aus:

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 gibt nur eine Auflistung der Eigenschaftendeskriptoren zurück, die wir zurückgeben möchten. Die PropertyDescriptors selbst sind ziemlich einfach. Sehen Sie sich die Codebeispiele an, um zu sehen, wie sie geschrieben werden.

Das FunkyButton-Beispiel veranschaulicht auch die Implementierung eines Dropdown-Editors. Für jeden Punktvertex ersetzt das Eigenschaftenregister anstelle der X,Y-Punkt-Standardwerte einen Editor, der eine Darstellung der FunkyButton-Form anzeigt und das grafische Platzieren der Scheitelpunkte ermöglicht. Dies erleichtert das Festlegen der Schaltflächenformen.

Abbildung 7. Grafisches Platzieren der Scheitelpunkte

Da das benutzerdefinierte PropertyTab die Eigenschaften bereitstellt, ist es auch einfach, den Editor für diese Eigenschaft zu überschreiben. Dabei wird einfach die GetEditor-Methode von PropertyDescriptor überschrieben und eine instance des benutzerdefinierten Editors zurückgegeben.

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);
}

Auch das Entwerfen des Editors ist einfach. Der Editor ist einfach ein UserControl, sodass wir ihn wie jeden anderen Typ von WindowsForms-Objekten entwerfen können.

Abbildung 8. Entwerfen des Editors

Wenn der Benutzer schließlich im Eigenschaftenbrowser auf den Pfeil nach unten klickt, hat unser Editor die Möglichkeit, seine Benutzeroberfläche zu erstellen und sie abzulegen. Die Außerkraftsetzung von UITypeEditor.EditValue in PointUIEditor behandelt dies.

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;
}

Und Sie können es auch verwenden!

Schließlich haben wir Ihnen den Kern des Eigenschaftenbrowsers in Visual Studio .NET zur Verwendung in Ihren Anwendungen zur Verfügung gestellt. Das Steuerelement mit dem Namen System.Windows.Forms.PropertyGrid kann Ihrer Toolbox in Visual Studio .NET hinzugefügt werden, indem Sie propertyGrid auf der Registerkarte .NET Framework Komponenten des Dialogfelds Toolbox anpassen auswählen.

PropertyGrid funktioniert wie jedes andere Windows Forms-Steuerelement. Sie können es im Layout verankern oder andocken, seine Farben ändern usw. In der folgenden Liste sind einige der interessanten Eigenschaften aufgeführt, die für PropertyGrid spezifisch sind:

  • Selectedobject
    Bestimmt das Objekt, für das PropertyGrid Eigenschaften anzeigt.
  • SymbolleisteVisible
    Blendet die Symbolleiste am oberen Rand von PropertyGrid ein oder aus.
  • HelpVisible
    Blendet den Hilfetext an der Basis von PropertyGrid ein oder aus.
  • Propertysort
    Bestimmt den Sortiertyp für propertyGrid (kategorisiert, alphabetisch usw.).

Jede dieser Eigenschaften kann zur Entwurfszeit normal festgelegt werden. Zur Laufzeit kann propertyGrid jedoch verwendet werden, um Objekte zu bearbeiten, die durchsucht werden. Im Folgenden finden Sie ein Beispiel für propertyGrid auf einem Formular, das eine Schaltfläche durchsucht. In diesem Fall wurden die PropertyGrid-Symbolleiste und Hilfeinformationen ausgeblendet. Wie bereits erwähnt, können Sie dies mit Eigenschaften für den PropertyGrid-Typ selbst steuern.

Abbildung 9. PropertyGrid mit ausgeblendeter Symbolleiste und Hilfeinformationen

Zusammenfassung

Die .NET Frameworks und Visual Studio .NET haben der neuen Version des Eigenschaftenbrowsers viele Funktionen hinzugefügt. Da der Eigenschaftenbrowser der Kern der RAD-Erfahrung des Entwicklers ist, ermöglichen diese Features noch mehr Flexibilität und behalten gleichzeitig die Benutzerfreundlichkeit bei, die den Eigenschaftenbrowser in Visual Basic so beliebt gemacht hat. Mit der Möglichkeit, PropertyGrid in Ihren Anwendungen zu verwenden, können Sie Ihre Benutzeroberflächendesigns vereinfachen und mehr Zeit damit verbringen, großartige Anwendungen zu schreiben und weniger Zeit für das Entwerfen der Benutzeroberfläche.