[C#] PropertyGrid verwenden

Jeder kennt das im Visual Studio verwendete Eigenschaftsfenster und das darin liegende Steuerelement für die Manipulation der Objekteigenschaften (Properties). Man findet es in der Toolbox unter “Alle Windows Forms”.

  • Zunächst ein Beispielobjekt, ein Auto 🙂
    using System.ComponentModel;
    
    public enum ECarType
    {
        [Description("ohne Dach")]
        Cabriolet,
        [Description("mit Dach")]
        Stufenheck,
        [Description("mit Rucksack")]
        Combi
    }
    
    // Beispiel-Klassenstruktur für das später im PropertyGrid zu editierende Objekt
    // DefaultPropertyAttribute legt fest, dass Tyres standardmäßig ausgewählt ist
    [DefaultPropertyAttribute("Tyres")]
    public class CCar
    {
        private int iTyres = 4;
        private string sManufacturer = "Horch";
        private string sCarImage = string.Empty;
        private Color clCarColor = Color.Black;
        private List<double-> lConsumption = new List<double->();
        private ECarType eCarType = ECarType.Stufenheck;
    
        // das Auto hat eine bestimmte Anzahl Räder
        // mögliche Attribute für die Steuerung der PropertyGrid-Eigenschaften:
        //
        // Browsable - Objekteigenschaft im PropertyGrid anzeigen?
        // DefaultValue - Standardwert für die Eigenschaft, wenn kein Wert gesetzt
        // Description - Hilfetext für das Hilfefenster
        // Category - Kategorie (Gruppe) für die Sortierung der Eigenschaften
        // ReadOnly - steuert "nur Lesen" Eigenschaft
        // DefaultProperty - Eigenschaft als Standardeigenschaft, wird dann automatisch bei der Anzeige ausgewählt
        // DisplayName - angezeigter Propertyname
        [Browsable(true), DefaultValue(4), Description("Anzahl der Reifen"), Category("Allgemein"), DisplayName("verwendete Reifen")]
        public int Tyres
        {
            get { return iTyres; }
            set { iTyres = value; }
        }
    
        // das Auto hat natürlich einen Hersteller
        [Browsable(true), DefaultValue("Horch"), Description("Name des Herstellers"), Category("Allgemein"), DisplayName("Hersteller des Autos")]
        public string Manufacturer
        {
            get { return sManufacturer; }
            set { sManufacturer = value; }
        }
        
        // das Auto soll ein Bild zugeordnet bekommen
        // zur späteren komfortablen Bildauswahl soll ein Fileauswahl-Dialog angezeigt werden
        // dazu sollte sich eine eigene Klasse 'CFileNameEditorHelper' abgeleitet werden (s.u.)
        [Editor(typeof(CFileNameEditorHelper), typeof(System.Drawing.Design.UITypeEditor))]
        [Browsable(true), DefaultValue(""), Description("Autobild"), Category("Grafik"), DisplayName("kleines Vorschaubild")]
        public string CarImage
        {
            get { return sCarImage; }
            set { sCarImage = value; }
        }
    
        // Farbe des Autos, Farbauswahl wird automatisch angezeigt
        [Browsable(true), Description("Autofarbe"), Category("Grafik"), DisplayName("Farbe des Autos")]
        public Color CarColor
        {
            get { return clCarColor; }
            set { clCarColor = value; }
        }
    
        // Verbrauchstabelle als Liste
        // hier wird ein eigenes Editorfenster benötigt (ConsumptionListEditor), dieser muss extra definiert werden (s.u.)
        // über den TypeConverter wird einfach der Anzeigename der Liste in "Datenwerte" angepasst
        [Editor(typeof(ConsumptionListEditor), typeof(System.Drawing.Design.UITypeEditor))]
        [Browsable(true), Description("Liste mit Verbräuchen"), Category("Kurven"), DisplayName("Verbrauch")]
        [TypeConverter(typeof(MyListConverter))]
        public List<double-> Consumption
        {
            get { return lConsumption; }
            set { lConsumption = value; }
        }
    
        // Beispiel für Verwendung von Enum, Klasse für die Umwandlung bzgl. CarTypeConverter s.u.
        [Browsable(true), Description("beschreibt den Autotyp"), Category("Allgemein"), DisplayName("Typ des Autos")]
        [TypeConverter(typeof(CarTypeConverter))]
        public ECarType CarType
        {
            get { return eCarType; }
            set { eCarType = value; }
        }
    
        public CCar()
        {
        }
    }
    
  • Hilfsklasse für die Bildauswahl
    // zuvor noch Assembly System.Design unter Verweise->Verweis hinzufügen dem Projekt hinzufügen
    using System.Windows.Forms.Design;
    
    // Eigene abgeleitete FileEditor-Klasse für die Bildauswahl
    public class CFileNameEditorHelper : FileNameEditor
    {
        public CFileNameEditorHelper()
        { }
    
        // InitializeDialog überschreiben
        protected override void InitializeDialog(System.Windows.Forms.OpenFileDialog openFileDialog)
        {
            // Basisdialog initialisieren
            base.InitializeDialog(openFileDialog);
    
            // hier die benötigten Properties des Auswahldialoges setzen
            openFileDialog.CheckFileExists = true;
            openFileDialog.ShowReadOnly = false;
            openFileDialog.Title = "Bitte ein Bild auswählen";
            openFileDialog.Filter = "BMP (*.bmp)|*.bmp|JPG (*.jpg)|*.jpg|PNG (*.png)|*.png|GIF (*.gif)|*.gif|Alle Dateien (*.*)|*.*";
            openFileDialog.DefaultExt = "";
            openFileDialog.FileOk += new System.ComponentModel.CancelEventHandler(openFileDialog_FileOk);
        }
        // Eventhandler für das Schließen des File-Dialoges
        void openFileDialog_FileOk(object sender, System.ComponentModel.CancelEventArgs e)
        {
            // sender ist unser Dialog
            System.Windows.Forms.OpenFileDialog fd = sender as System.Windows.Forms.OpenFileDialog;
    
            if (fd != null)
            {
                // hier kann noch etwas mit dem Filedialog veranstaltet werden,
                // z.B. wenn man nur den Filenamen ohne Pfad extrahieren will
                // fd.FileName = Path.GetFileName(fd.FileName);
            }
        }
    }
    
  • Hilfsklasse für den Listeneditor
    // zuvor noch Assembly System.Design unter Verweise->Verweis hinzufügen dem Projekt hinzufügen
    using System.Windows.Forms.Design;
    
    // eigene ListEditor-Klasse für das Editieren einer Liste
    public class ConsumptionListEditor : UITypeEditor
    {
        // Editstyle des Dialoges festlegen
        public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
        {
            return UITypeEditorEditStyle.Modal;
        }
    
        public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
        {
            if ((context != null) && (provider != null))
            {
                IWindowsFormsEditorService svc = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
                if (svc != null)
                {
                    // eigenen Dialog erzeugen und anzeigen
                    // im Beispiel wird im Konstruktor des Dialoges die Referenz der Liste mit übergeben
                    frmDataCurve dialog = new frmDataCurve(value as List<double->);
    
                    // Dialog anzeigen und Result abfragen
                    if (svc.ShowDialog(dialog) == System.Windows.Forms.DialogResult.OK)
                    {
                    }
                }
            }
    
            return value;
        }
    }
    
  • Hilfsklasse für CarTypeConverter
    prublic class CarTypeConverter : EnumConverter
    {
        private Type _enumType;
    
        public CarTypeConverter(Type type) : base(type)
        {
            _enumType = type;
        }
    
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destType)
        {
            return (destType == typeof(string));
        }
    
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type srcType)
        {
            return (srcType == typeof(string));
        }
        
        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destType)
        {
            FieldInfo fi = _enumType.GetField(Enum.GetName(_enumType, value));
    
            DescriptionAttribute dna =(DescriptionAttribute)Attribute.GetCustomAttribute(fi, typeof(DescriptionAttribute)); 
    
            if (dna != null) return dna.Description;
            else return value.ToString();
        }
    
        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            foreach (FieldInfo fi in _enumType.GetFields())
            {
                DescriptionAttribute dna = (DescriptionAttribute)Attribute.GetCustomAttribute(fi, typeof(DescriptionAttribute)); 
    
                if ((dna != null) && ((string)value == dna.Description))  return Enum.Parse(_enumType, fi.Name);
            }
    
            return Enum.Parse(_enumType, (string)value);
        }
    }
    
  • Hilfsklasse für TypeConverter der Liste
    // zuvor noch Assembly System.Design unter Verweise->Verweis hinzufügen dem Projekt hinzufügen
    using System.Windows.Forms.Design;
    
    public class MyListConverter : TypeConverter
    {
        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destType)
        {
            // wenn Liste mit Double-Werten
            if (destType == typeof(string) && value is List<double->)
            {
                return "Datenwerte";
            }
    
            // sonst Standardkonvertierung
            return base.ConvertTo(context, culture, value, destType);
        }
    }
    
  • Anwendungsbeispiel
    // Beispielobjekt erzeugen
    CCar Car = new CCar();
    
    // dem PropertyGrid ein Objekt zum Editieren zuordnen
    // propertyGrid1 - das verwendete PropertyGrid
    // Car - das zu editierende Objekt
    propertyGrid1.SelectedObject = Car;
    // im Propertygrid 'propertyGrid1' werden nun die Properties 'Tyres', 'Manufacturer' und 'CarImage'
    // in den jeweiligen Gruppen 'Allgemein' und 'Grafik' angezeigt
    // für die Auswahl der Property 'CarImage' wird ein FileDialog bereitgestellt
    // Eingaben werden typabhängig geprüft und in das Objekt zurückgeschrieben
    

Eine detailliertere Beschreibung für die Verwendung eines PropertyGrids findet man bei:

Using PropertyGrid control in .NET
Customized display of collection data in a PropertyGrid