Neben dem UITypeEditor und dem TypeConverter ist der Designer die dritte Möglichkeit der Entwurfszeitunterstützung für selbst geschriebene Komponenten. In allen drei Fällen verbindet man eine Entwurfszeit-Klasse mit der Komponente über ein Attribut und beeinflusst damit deren Verhalten in der Visual Studio IDE. Aus diesem Trio sind Designer die mächtigste Schnittstelle, die das .NET Framework zur Steuerung des Entwurfszeitverhaltens bereitstellt. Auch die grafischen Editoren in Visual Studio selbst sind solche Designer und theoretisch könnten Sie auf diese Weise einen eigenen Formular-Designer oder Ähnliches implementieren. Doch wahrscheinlich wollen Sie gar nicht so weit hinaus. Auch die häufiger benutzten Fähigkeiten der Designer-Schnittstelle sind schon interessant genug. Sehen wir uns zuerst einmal die Vererbungshierarchie an (siehe Abb. 1).
Neue Befehle für die Komponente
Sie haben sicher schon bemerkt, dass beispielweise beim DataAdapter das Eigenschaftsfenster nicht nur eine Liste der Eigenschaften inklusive Erklärung für die aktuelle Auswahl enthält, sondern auch Befehle wie Datenadapter konfigurieren..., DataSet generieren..., die als Hyperlinks in einem eigenen Feld auftauchen. Die gleichen Befehle sieht man auch im Kontext-Menü des DataAdapters. Diese Menübefehle stellt der Designer des DataAdapters über die Eigenschaft Verbs bereit. Streng genommen ist ein solches Verb (DesignerVerb) sogar ein bisschen mehr als ein Menübefehl (MenuCommand), weil es im Gegensatz zu diesem auch noch einen eigenen Text für den Menüpunkt definieren kann. Für die Muster-Freunde unter unseren Lesern: An dieser Stelle implementiert das .NET Framework das Command Pattern und erreicht so seine hervorragende Erweiterbarkeit. In den von IDesigner abgeleiteten Klassen kommen zuerst allgemein verwendbare Fähigkeiten für Komponenten hinzu und schließlich spezielle Features für Steuerelemente. Wobei dann schon wieder zwischen Win Forms und Web Forms unterschieden werden muss. In dieser Ausgabe des Komponenten-Breviers konzentrieren wir uns auf die Win Forms und besprechen die wichtigsten Möglichkeiten des ControlDesigners. Als Anschauungsmaterial soll uns diesmal eine eigene Label-Komponente mit verbesserter automatischer Größenanpassung dienen. Das Standard-Label kann ja seine Breite an die Menge des eingetragenen Texts anpassen, nicht jedoch seine Höhe. Deshalb erstellen wir mit Datei | Neu | Projekt...| Visual C#-Projekte | Klassenbibliothek unter dem Namen KB5Components eine neue Assembly. Fügen Sie dem Projekt einen Verweis auf System.Windows.Forms hinzu, benennen Sie die Klasse in AutoHeightLabel um und leiten Sie sie von Label ab. Dann fügen Sie die neue Methode zum Anpassen der Höhe hinzu. Glücklicherweise verfügt die Graphics-Klasse über genau die Methode, die wir brauchen. Mit MeasureString kann die Höhe eines mehrzeiligen Strings bei vorgegebener Breite und Schriftart berechnet werden:internal void ResizeHeight(){Graphics graphics = CreateGraphics();SizeF size = graphics.MeasureString(Text, Font, Width);Height = (int)size.Height;graphics.Dispose();}
public override DesignerVerbCollection Verbs{get {DesignerVerbCollection result = new DesignerVerbCollection();result.Add(new DesignerVerb("Höhe anpassen", new EventHandler(AdjustHeightHandler)));return result;}}
Der TypeDescriptor weiß alles
Dies erreicht man dadurch, dass der Designer seine eigene Methode RaiseComponentChanged aufruft und ihr sowohl die geänderte Eigenschaft als auch deren vorherigen und neuen Wert übergibt. Die Eigenschaft wird an dieser Stelle durch ihre Beschreibung in Form eines PropertyDescriptors angegeben. Ein PropertyDescriptor enthält alle Angaben über eine Eigenschaft wie zum Beispiel den Namen, den Typ, die angegebenen Attribute, den zugeordneten Typ-Konverter und so weiter. Noch interessanter sind die Methoden: Hier kann man unter anderem in generischer Weise den aktuellen Wert abfragen sowie Handler anmelden, die bei einer Änderung des Eigenschaftswerts aufgerufen werden. Diese letzte Fähigkeit werden wir etwas später noch ausnutzen. Für den Augenblick genügt jedoch der PropertyDescriptor als solcher. Man kann ihn sich vom TypeDescriptor abholen, der ja die Grundlage jeglicher Reflektion in .NET ist. MitPropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(Control);PropertyDescriptor pd = pdc.Find("Height", false);
RaiseComponentChanged(pd, oldHeight, MyControl.Height);
|
Eigenschaften aus der Luft gegriffen
Schöner wäre es allerdings noch, wenn sich die Höhe des Labels beim Ändern des Texts automatisch anpassen würde, so wie das auch die geerbte Eigenschaft AutoSize macht. Um diese Anforderung umzusetzen, muss AutoHeightLabel etliche neue Merkmale erhalten:- Eine Eigenschaft, mit der man die automatische Höhenanpassung ein- und ausschalten kann. Anders als bei AutoSize wird diese Eigenschaft nur zur Entwurfszeit existieren.
- Eine visuelle Darstellung der Tatsache, dass das Label gerade im Modus für die Höhenanpassung ist.
- Blockierung der manuellen Größenänderung per Drag&Drop, während die automatische Höhenanpassung eingeschaltet ist.
- Automatische Neuberechnung der Höhe beim Ändern der Text-Eigenschaft
protected virtual void PreFilterEvents(IDictionary events);protected virtual void PostFilterEvents(IDictionary events);protected virtual void PreFilterProperties(IDictionary properties);protected virtual void PostFilterProperties(IDictionary properties);
bool autoAdjustHeight;// Falls true, wird während der Eingabe des Textes die Höhe des Labels angepasst[Category("Design")]public bool AutoAdjustHeight{get {return autoAdjustHeight;}set {autoAdjustHeight = value;MyControl.Invalidate();}}protected override void PreFilterProperties(IDictionary properties)<br></br>{base.PreFilterProperties(properties);properties.Add("AutoAdjustHeight", TypeDescriptor.GetProperties(this).Find("AutoAdjustHeight", false));}
Die Komponente wird verziert
Solche grafischen Zusätze, die nur beim Entwurf zu sehen sind, heißen in .NET Adornment, was so viel wie Verzierung bedeutet. Es wird Sie kaum überraschen, dass auch hierfür der Designer zuständig ist. Jedes Mal wenn das Steuerelement sich zeichnet, ruft die Umgebung anschließend die Designer-Methodeprotected virtual void OnPaintAdornments(PaintEventArgs ps);
protected override void OnPaintAdornments(PaintEventArgs pe){if (autoAdjustHeight) {Pen pen = new Pen(Color.Blue);pe.Graphics.DrawLine(pen, 0, 0, 0, MyControl.Height);pe.Graphics.DrawLine(pen, MyControl.Width-1, 0, MyControl.Width-1, MyControl.Height);}}
public override SelectionRules SelectionRules{get {SelectionRules result = base.SelectionRules;if (autoAdjustHeight) result &= ~(SelectionRules.BottomSizeable | SelectionRules.TopSizeable);return result;}}
Auf Text-Änderungen reagieren
Nun fehlt eigentlich nur noch die Dynamik. Die Höhenanpassung soll ja ausgelöst werden, wenn sich die Eigenschaft Text des Labels ändert. Ein letztes Mal kommt hierzu der PropertyDescriptor zum Einsatz. Der verfügt wie schon erwähnt über ein Methode zum Anmelden von Delegaten. Weist jemand der Eigenschaft einen neuen Wert zu, wird der Delegat aufgerufen - et voilà! Der Rest ist ein Kinderspiel, weil der eigentliche Anpassungs-Mechanismus ja schon steht.public virtual void AddValueChanged(object component, EventHandler handler);
public override void Initialize(IComponent component){base.Initialize(component);TypeDescriptor.GetProperties(MyControl).Find("Text", false).AddValueChanged(MyControl, new EventHandler(TextChangedHandler));}private void TextChangedHandler(object sender, EventArgs e){if (autoAdjustHeight) AdjustHeightHandler(sender, e);}protected override void Dispose(bool disposing){TypeDescriptor.GetProperties(MyControl).Find("Text", false).RemoveValueChanged(MyControl, new EventHandler(TextChangedHandler));base.Dispose(disposing);}
Peter Pohmann ist Geschäftsführer von pohmann & partner, einem Ingenieurbüro für Software-Entwicklung in Niederbayern. Er ist seit über 15 Jahren als Entwickler, Fachautor, Coach, Referent und Berater für Objektorientierte Technologien und Windows-Programmierung tätig. Er freut sich über Feedback zu diesem und verwandten Themen an pohmann@pohmannundpartner.de.






