Veröffentlicht: 10. Jul 2006
Von Dino Esposito
In diesem Artikel erstellt Dino Esposito zunächst ein einfaches verbundenes Steuerelement und ergänzt dieses dann um einige angepasste Attribute, damit die Benutzer einen vollständigen Eindruck erhalten.
Klicken Sie hier, um das Codebeispiel zu diesem Artikel herunterzuladen.
Auf dieser Seite
Einführung
Ein Beispiel für ein verbundenes Steuerelement
Verwenden von Attributen als Kommunikationsmittel
Attribute auf Klassenebene
Typkonverter
Erstellen eines benutzerdefinierten Designers
Bereichsmarkierung
Erstellen von Markup zur Entwurfszeit
Aktionslisten und AutoFormats
Letzte Schritte
Der Autor
Einführung
ASP.NET-Steuerelemente weisen zwei unterschiedliche Funktionssätze auf – je nachdem, ob sie zur Laufzeit in einer Seite ausgeführt oder zur Entwurfszeit in einem Hostdesigner verwendet werden. Durch die Laufzeitfähigkeiten wird anhand der Konfiguration das vom Steuerelement ausgegebene Markup festgelegt. Die Entwurfszeitfähigkeiten sind hingegen in Verbindung mit einem visuellen Designer wie Microsoft Visual Studio 2005 nützlich. Durch sie kann der Autor einer Seite das Verhalten des Steuerelements zur Laufzeit auf deklarative Weise und in WYSIWYG-Manier (What-You-See-Is-What-You-Get) konfigurieren.
Alle Standardsteuerelemente von ASP.NET bieten einen Minimalsatz von Entwurfszeitfähigkeiten, die von Basisklassen wie Control oder DataBoundControl bereitgestellt werden. Die Basisfunktionen für die Entwurfszeit sind in ASP.NET 2.0 gegenüber früheren Versionen erheblich umfangreicher und ermöglichen eine nahtlosere Integration sowie Interaktion zwischen Steuerelementen und Host. Benutzerdefinierte Steuerelemente können genau wie integrierte und Standardsteuerelemente darauf ausgelegt sein, in den Hostdesigner integriert zu werden. In diesem Artikel erstelle ich zunächst ein einfaches verbundenes Steuerelement und ergänze dieses dann um einige angepasste Attribute, damit die Benutzer einen vollständigen Eindruck erhalten.
Ein Beispiel für ein verbundenes Steuerelement
Zum Veranschaulichen der Aussagen in diesem Artikel wird ein verbundenes Tool entwickelt, das zwei Hauptbereiche enthält. Der grundlegende Gedanke ist, ein beschriftetes Textfeld zu erstellen. Das Steuerelement entsteht aus einem Label-Steuerelement und einem TextBox-Steuerelement, die miteinander kombiniert werden und somit ein Bearbeitungsfeld mit einem beschreibenden Text darstellen. Dieses LabelBox betitelte Steuerelement erbt von CompositeControl und stellt die in Tabelle 1 aufgeführten Eigenschaften zur Verfügung.
| Tabelle 1: Eigenschaften des "LabelBox"-Steuerelements |
| Eigenschaft | Beschreibung |
| Header | Ruft einen Verweis auf das interne Label-Steuerelement ab. |
| HeaderText | Ruft den Text für das interne Label-Steuerelement ab und legt diesen fest. |
| LabelBoxSettings | Ruft ein Objekt ab, das mehrere visuelle Eigenschaften für das Label-Steuerelement gruppiert. |
| Mode | Ruft den Anzeigemodus des Bezeichnungsfelds ab und legt diesen fest. |
| Text | Ruft den Text für das interne TextBox-Steuerelement ab und legt diesen fest. |
| TextBox | Ruft einen Verweis auf das interne TextBox-Steuerelement ab. |
| TextBoxSettings | Ruft ein Objekt ab, das mehrere visuelle Eigenschaften für das TextBox-Steuerelement gruppiert. |
Die Mode-Eigenschaft wird als Typ LabelBoxMode deklariert – ein enumerierter Typ, der die folgenden Werte zählt: Left, Top, Right und Bottom. Durch den Wert der Mode-Eigenschaft wird die Position des Bezeichnungsfelds in Bezug auf das Textfeld definiert. In der Standardeinstellung wird das Bezeichnungsfeld linksseitig angezeigt.
Als verbundenes Steuerelement überschreibt LabelBox die CreateChildControls-Methode, um eine angepasste Steuerelementstruktur zu erstellen. Im nachstehenden Code wird der endgültige Aufbau veranschaulicht:
protected override void CreateChildControls()
{
base.CreateChildControls();
Controls.Clear();
CreateControlHierarchy();
ClearChildViewState();
}
protected virtual void CreateControlHierarchy()
{
// Build the outermost container table
Table outer = new Table();
// Populate the table based on the display mode
bool vertMode = ((int)Mode) > ((int)LabelBoxMode.Right);
if (vertMode)
CreateTwoRowTable(outer);
else
CreateTwoCellTable(outer);
// Save the control tree
Controls.Add(outer);
}
Als Ergebnis des Markups im Steuerelement ergibt sich eine Struktur in Form einer HTML-Tabelle mit entweder zwei Zeilen oder zwei Zellen, in Abhängigkeit von der Position des Bezeichnungsfelds. Wenn das Bezeichnungsfeld im oberen oder unteren Bereich des Textfelds angeordnet sein soll, weist die Ergebnistabelle zwei Zeilen auf, andernfalls enthält die Tabelle eine Zeile mit zwei Zellen.
Beim Entwerfen eines verbundenen Steuerelements wird im Allgemeinen von den enthaltenen Bereichen ausgegangen. Dabei werden die enthaltenen Bereiche jedoch selten im Code verfolgt. Mit anderen Worten, interne Eigenschaften werden selten als Verweise auf die Struktur im Steuerelement definiert, die einem bestimmten Bereich entspricht. Bereiche werden dann verfolgt, wenn ein relativ komplexer Steuerelementaufbau vorliegt und in Ereignissen und Postbacks häufig auf diese Bereiche zugegriffen werden muss.
Das LabelBox-Steuerelement weist zwei logische Bereiche auf, den Header- und den Bearbeitungsbereich. Der Headerbereich wird von der Tabellenzelle dargestellt, die das Label-Steuerelement enthält, und der Bearbeitungsbereich von der Tabellenzelle, die das TextBox-Steuerelement enthält. Für ein derart einfaches Steuerelement müssen normalerweise Bereiche nicht in internen Membern gespeichert werden. Dies sollte jedoch geschehen, wenn Sie planen, umfangreiche Entwurfszeitfähigkeiten zu erstellen. Auf die Bedeutung von Bereichen wird später in diesem Artikel weiter eingegangen. Im Moment genügt die Feststellung, dass im LabelBox-Steuerelement zwei Member als internal markiert und wie folgt definiert sind:
internal LabelBoxContainer _headerContainer;
internal LabelBoxContainer _textBoxContainer;
Die LabelBoxContainer-Klasse ist eine Hilfsklasse, in der einfach TableCell umbenannt wird. In anderen, umfassenderen Szenarios erweitert eine ähnliche Klasse den Funktionssatz der einfachen TableCell-Klasse.
public class LabelBoxContainer : TableCell
{
}
Zwei Instanzen von LabelBoxContainer werden erstellt, um die jeweilige Steuerelementstruktur für den Header- und den Bearbeitungsbereich aufzunehmen. Jede Instanz wird im entsprechenden internen Member gespeichert.
Dieses Merkmal ist der einzige Aspekt von LabelBox, der eine nähere Betrachtung verdient. In Abbildung 1 ist das aktive LabelBox-Steuerelement in der Visual Studio 2005-Umgebung dargestellt.
Abbildung 1: Das "LabelBox"-Steuerelement
Es ist zu erkennen, dass das Steuerelement im Hostdesigner erwartungsgemäß funktioniert. Es bietet eine ordnungsgemäße Vorschau und wird beim Ändern von Eigenschaftenwerten im Fenster Eigenschaften automatisch aktualisiert. Dies ist bereits ein sehr gutes Ergebnis. Es könnte jedoch noch besser sein. Es sind derartig viele Entwurfszeitfunktionen verfügbar, besonders in ASP.NET 2.0, dass keine davon ausgelassen werden sollte.
Jede ergebnisorientierte Strategie zum Erstellen eines umfassenden Entwurfszeitverhaltens für ASP.NET-Steuerelemente berücksichtigt verschiedene Aspekte:
-
Attribute zum Bereitstellen von Informationen und Hilfestellungen für den Hostdesigner
-
Typkonverter zum Vereinfachen der Bearbeitung komplexer Typeigenschaften
-
Benutzerdefinierte Designer zum Übernehmen der Darstellung für das Steuerelement im Designer
In ASP.NET 2.0 sind durch benutzerdefinierte Designer wesentlich mehr Möglichkeiten vorhanden als in früheren Versionen. Insbesondere ist nun die Übernahme folgender Funktionen möglich:
Im Rest dieses Artikels wird die Implementierung dieser Funktionen im Zusammenhang mit dem LabelBox-Steuerelement in einzelnen Schritten vorgenommen.
Verwenden von Attributen als Kommunikationsmittel
Attribute sind beschreibende Elemente, mit denen Typen und Member optional mit weiteren Merkmalen ausgestattet werden können. Normalerweise stellt ein Attribut allen anfragenden Komponenten zusätzliche Informationen über den Typ oder das Member zur Verfügung. Jeder Codeabschnitt kann den Wert eines vorgegebenen Attributs nach Bedarf abfragen und das eigene Verhalten entsprechend anpassen. Durch Attribute wird das Verhalten des damit ausgestatteten Elements nicht geändert, es kann jedoch beispielsweise zu einem besseren Verhalten eines externen Programms führen.
Eine Steuerelementeigenschaft, die z. B. mit dem Description-Attribut markiert ist, wird ohne zeitliche Nachteile normal ausgeführt. Wenn sich dasselbe Steuerelement jedoch in der IDE von Visual Studio 2005 befindet, wird die dem Attribut zugeordnete Zeichenfolge im Fenster Eigenschaften angezeigt.
In Tabelle 2 sind die wichtigsten Attribute zur Entwurfszeit aufgeführt, mit denen der Autor eines Steuerelements das Verhalten eines Visual Studio 2005-Designers zuverlässig beeinflussen kann.
| Tabelle 2: Wichtige Entwurfszeitattribute für ASP.NET-Steuerelemente |
| Attribut | Beschreibung |
| Bindable | Gibt an, ob und wie eine Eigenschaft an Daten gebunden werden kann. |
| Browsable | Gibt an, ob eine Eigenschaft oder ein Ereignis im Fenster Eigenschaften angezeigt werden sollte. |
| Category | Gibt den Namen der Kategorie an, der eine Eigenschaft oder ein Ereignis zugehört. Auf diese Weise können Eigenschaften und Ereignisse im Fenster Eigenschaften logisch zugeordnet werden. |
| DefaultValue | Wird zum Angeben des Standardwerts einer Steuerelementeigenschaft verwendet. Wenn die Eigenschaft keinen einfachen Typ aufweist, wird ein Typkonverter benötigt. |
| Description | Bietet eine Beschreibung einer Eigenschaft oder eines Ereignisses. Der Text wird im unteren Bereich des Fensters Eigenschaften angezeigt, wenn ein Benutzer die Eigenschaft bzw. das Ereignis auswählt. |
| Editor | Gibt den Editor an, der zum Ändern einer Eigenschaft in einem visuellen Designer verwendet werden soll. |
| Themeable | Gibt an, ob die Eigenschaft einem Steuerelementdesign in einem Design hinzugefügt werden kann. In ASP.NET 1.x nicht unterstützt. |
| TypeConverter | Gibt die Typkonverterklasse an, mit der der Typ der Eigenschaft in Text umgewandelt werden kann. |
Als Beispiel kann die Deklaration der Mode-Eigenschaft im LabelBox-Steuerelement dienen. Der Code enthält die am häufigsten verwendeten Attribute:
[Category("Appearance")]
[DefaultValue(LabelBoxMode.Left)]
[Description("Gets and sets the display mode of the control")]
public LabelBoxMode Mode
{
get
{
object o = ViewState["Mode"];
if (o == null)
return LabelBoxMode.Left;
return (LabelBoxMode)o;
}
set
{
ViewState["Mode"] = value;
}
}
Das Category-Attribut gibt die Kategorie an, unter der die Eigenschaft aufgeführt wird, sofern die Ansicht des Fensters Eigenschaften kategorisiert ist. Das Attribut wird ignoriert, wenn die Ansicht alphabetisch ist. Im Description-Attribut wird der im unteren Bereich des Fensters angezeigte Text festgelegt. (Siehe Abbildung 2.)
Abbildung 2: Auswirkungen der Attribute auf das "LabelBox"-Steuerelement zur Entwurfszeit
Bei der Betrachtung von Abbildung 2 könnte der Eindruck entstehen, alle Eigenschaftenwerte im Raster würden in Fettschrift dargestellt. Genau gesagt, sind nur die Werte in Fettschrift angegeben, deren Wert sich vom Entwurfszeit-Standardwert der Eigenschaft unterscheidet. Dieser wird über das DefaultValue-Attribut festgelegt.
Jeder Eigenschaft wird im get-Accessor ein Laufzeit-Standardwert zugeordnet. Für die Mode-Eigenschaft ist der Laufzeit-Standardwert LabelBoxMode.Left. Es muss nicht betont werden, dass die Standardwerte für die Laufzeit und die Entwurfszeit nicht miteinander verbunden sind. Der Laufzeit-Standardwert gibt den Wert an, der der Eigenschaft zugewiesen wird, wenn Sie ihn nicht ausdrücklich ändern. Der Entwurfszeit-Standardwert gibt einen Prüfwert an, mit dem der im Fenster Eigenschaften angegebene Wert verglichen wird. Unterscheiden sich die Werte, wird der aktuelle in Fettschrift dargestellt, andernfalls werden normale Zeichen verwendet.
In der Standardeinstellung werden alle öffentlichen Eigenschaften eines Steuerelements im Fenster Eigenschaften angezeigt. Beachten Sie, dass öffentliche Felder (d. h. Member ohne get/set-Methoden) stattdessen niemals angezeigt werden. Um zu verhindern, dass eine Eigenschaft im Eigenschaftenraster aufgeführt wird, statten Sie sie mit dem Browsable(false)-Attribut aus.
[Browsable(false)]
public TextBox TextBox
{
get
{
if (_textBox == null)
EnsureChildControls();
return _textBox;
}
}
Normalerweise werden alle Eigenschaften als nicht durchsuchbar markiert, die nur für Zugriffe aus dem Programm ausgelegt sind. Das LabelBox-Steuerelement weist beispielsweise eine Eigenschaft auf, die das interne TextBox-Steuerelement darstellt. Diese Eigenschaft ist eindeutig für den Zugriff durch Code ausgelegt und sollte zur Entwurfszeit nicht sichtbar sein.
Attribute auf Klassenebene
Wenn Sie ein benutzerdefiniertes Serversteuerelement geliefert haben, greifen die Benutzer normalerweise über die Visual Studio 2005-Toolbox darauf zu. Um der Toolbox ein neues Steuerelement hinzuzufügen, muss der Entwickler die Assembly finden und das Steuerelement auswählen. Einige Attribute werden auf Steuerelementklasse angewendet, um Visual Studio 2005 mit Hinweisen auszustatten. Attribute sind in Tabelle 3 aufgeführt.
| Tabelle 3: Attribute auf Klassenebene |
| Eigenschaft | Beschreibung |
| DefaultEvent | Gibt das Standardereignis für das Steuerelement an. Hierbei handelt es sich um das Ereignis, das verarbeitet wird, wenn Benutzer im Designer auf das Steuerelement doppelklicken. Dieses Attribut ist nur in der Klassendeklaration des Steuerelements gültig. |
| DefaultProperty | Gibt die Standardeigenschaft für das Steuerelement an. Die Standardeigenschaft wird im Fenster Eigenschaften ausgewählt, wenn Benutzer im Designer auf das Steuerelement klicken. Dieses Attribut ist nur in der Klassendeklaration des Steuerelements gültig. |
| ToolboxBitmap | Gibt den Namen der Bitmapressource an, die in der Visual Studio 2005-Toolbox als Bitmap des Steuerelements verwendet werden soll. Die Bitmap muss als Ressource in der Assembly des Steuerelements enthalten sein. |
| ToolboxData | Hiermit wird das Standardmarkup angegeben, das Visual Studio der ASPX-Seite hinzufügt, wenn das Stauerelement per Drag & Drop in das Webformular eingefügt wird. Dieses Attribut ist nur in der Klassendeklaration des Steuerelements gültig. |
| ToolboxItem | Gibt an, ob die Klasse in der Toolbox aufgeführt sein soll. |
In der Standardeinstellung werden alle Steuerelementklassen in einer Assembly der Toolbox hinzugefügt. Wenn in Ihrer Assembly Steuerelementklassen definiert sind, die nicht auf einer Seite eingesetzt werden sollen (z. B. Steuerelemente für die interne Verwendung), statten Sie sie mit dem ToolboxItem(false)-Attribut aus, um sie nicht in der Toolbox anzuzeigen.
Seitenautoren ziehen Steuerelemente normalerweise aus der Toolbox in das Webformular. Wenn dies geschieht, wird Markup in der Zielseite erstellt, um auf das Steuerelement zu verweisen. Sie können mit dem ToolboxData-Attribut den ursprünglichen HTML-Inhalt anpassen, der beim Ziehen aus der Toolbox in den Designer platziert wird.
[ToolboxData("<{0}:LabelBox runat=\"server\" />")]
[DefaultProperty("Mode")]
public class LabelBox : CompositeControl
{
:
}
Der {0}-Platzhalter gibt das für das Steuerelement verwendete Präfix an. Das DefaultProperty-Attribut gibt die Eigenschaft an, die im Eigenschaftenfenster automatisch ausgewählt wird, wenn der Seitenautor das Steuerelement im Webformular auswählt. Die DefaultEvent-Eigenschaft gibt stattdessen das Ereignis an (sofern vorhanden), für das automatisch ein Handler erstellt wird, wenn der Seitenautor auf das Steuerelement doppelklickt.
Typkonverter
Die meisten Steuerelementeigenschaften weisen einen einfachen Typ wie string, numbers, date, colors, enums oder similar auf. Bestimmte Eigenschaften müssen jedoch häufig mit einer benutzerdefinierten Klasse dargestellt werden. Wie bearbeiten Sie die Member dieser Klasse zur Entwurfszeit? Wie sorgen Sie für unveränderte Werte dieser Klasse auf der Quellseite (zur Entwurfszeit) und im Ansichtszustand (zur Laufzeit)? Die Antwort ist für beide Fragen dieselbe: Typkonverter.
Ein Typkonverter ist ein Objekt, in dem Logik zum Konvertieren eines Typs in einen anderen implementiert ist. Typkonverter sind nützlich zum Serialisieren des Wertes nicht primitiver Typen (z. B. Font) in ein Zeichenfolgenformat, das in einer ASPX-Datei gespeichert werden kann. Umgekehrt lesen Typkonverter eine Zeichenfolgenbeschreibung eines Wertes und wandeln diese in den ursprünglichen Typ um. Typkonverter sind im Hintergrund tätig, wenn Sie beispielsweise einige Schriftarteigenschaften zur Entwurfszeit über Textattribute angeben und ein Font-Objekt abrufen, das zur Laufzeit sofort verwendet werden kann.
Zur Entwurfszeit werden Typkonverter meist zum Umwandeln eines komplexen Typs in eine Zeichenfolge und umgekehrt verwendet. Zur Laufzeit werden sie verwendet, um denselben komplexen Typ für den Ansichtszustand beizubehalten und aus diesem wiederherzustellen. Die Verwendung eines Typkonverters zum Serialisieren von komplexen Typen ist effizienter als der Einsatz des binären Formatierungsprogramms – der letzten Rettung in .NET zur Serialisierung. Gehen Sie den folgenden Codeabschnitt durch:
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public LabelBoxSettings LabelSettings
{
get
{
if (_headerSettings == null)
_headerSettings = new LabelBoxSettings();
return _headerSettings;
}
}
Die LabelSettings-Eigenschaft fasst einige visuelle Eigenschaften zusammen, die im internen Label-Steuerelement festgelegt werden. Eigenschaften eines komplexen Typs werden in Visual Studio 2005 häufig mithilfe eines Panels zum Erweitern/Reduzieren bearbeitet. In den offiziellen Beispielen finden sich Stil- und Schriftarteigenschaften. Um dasselbe Verhalten in Ihrem benutzerdefinierten Typ (hier: LabelBoxSettings) zu erhalten, müssen Sie einen Typkonverter definieren und die Klasse entsprechend ausstatten:
[TypeConverter(typeof(LabelBoxSettingsConverter))]
public class LabelBoxSettings
{
:
}
Ein Typkonverter für einen Typ einer ASP.NET-Steuerelementeigenschaft ist im Allgemeinen von ExpandableObjectConverter abgeleitet. Er ist eine Klasse, die vier Methoden überschreibt:
-
CanConvertTo
-
CanConvertFrom
-
ConvertTo
-
ConvertFrom
In diesem speziellen Kontext ist der Zieltyp für die Konvertierung string. Grundsätzlich können Sie einen Typkonverter jedoch für jede beliebige Art von Typkonvertierung planen. In der Standardimplementierung wird durch ConvertTo ein kommagetrenntes Array von Zeichenfolgen erstellt, in dem jedes Element der Zeichenfolgendarstellung einer Eigenschaft entspricht. ConvertFrom ist demzufolge für den umgekehrten Vorgang zuständig.
Beispielsweise enthält LabelBoxSettings vier Member: BackColor, ForeColor, FontName und FontSize. Die Zeichenfolgendarstellung dieser Werte liegt in Form eines kommagetrennten Arrays vor, das dem Serialisierungsformat der LabelSettings-Eigenschaft entspricht. In Visual Studio 2005 bearbeiten Sie die Eigenschaft wie in Abbildung 3 dargestellt.
Abbildung 3: Bearbeiten eines komplexen und benutzerdefinierten Typs
Es wird deutlich, dass die verschiedenen Member der LabelBoxSettings-Klasse nun einzeln bearbeitet werden können und in der ASPX-Datei mit dem Format beibehalten werden, das durch das DesignerSerializationVisibility-Attribut vorgegebenen ist. Das Attribut kann jeden der nachstehend aufgeführten Werte annehmen.
| Tabelle 4: Werte für das "DesignerSerializationVisibility"-Attribut |
| Wert | Beschreibung |
| Content | Mit dieser Option wird der Inhalt der Eigenschaft (z. B. LabelSettings) als Ganzes serialisiert. Alle Untereigenschaften werden über eine Ad-hoc-Namenskonvention serialisiert. Beispielsweise wird für die BackColor-Untereigenschaft ein Attribut mit der Bezeichnung LabelSettings-BackColor erstellt. |
| Hidden | Mit dieser Option wird die Eigenschaft vor der Serialisierung verborgen. Die Eigenschaft wird im Markup-Code des Steuerelements nicht beibehalten. |
| Visible | Standardwert: Hiermit wird die Eigenschaft auf der obersten Ebene serialisiert. Wenn diese Option festgelegt ist, wird eine Eigenschaft wie LabelSettings als kommagetrennte Liste von untergeordneten Werten serialisiert. Die Untereigenschaften werden nicht einzeln gespeichert, sondern in einem eindeutigen, alle Werte umfassenden Attribut mit der Bezeichnung LabelSettings. |
Stellen Sie beim Bearbeiten einer komplexen Eigenschaft in Visual Studio 2005 sicher, dass bei Änderungen an den einzelnen Untereigenschaften die übergeordnete Eigenschaft ordnungsgemäß benachrichtigt und das Steuerelement im Designer aktualisiert wird. Um dies zu erreichen, fügen Sie Eigenschaften der LabelBoxSettings-Klasse das NotifyParentProperty-Attribut hinzu:
[TypeConverter(typeof(LabelBoxSettingsConverter))]
public class LabelBoxSettings
{
[NotifyParentProperty(true)]
public string FontName
{
get { return _fontName; }
set { _fontName = value; }
}
:
}
Weitere Einzelheiten zur Implementierung von Typkonvertern finden Sie im begleitenden Quellcode.
Erstellen eines benutzerdefinierten Designers
Die meisten bisher beschriebenen Verfahren sind nicht speziell auf ASP.NET 2.0 bezogen, das Behandeln dieser Themen ist jedoch für ein genaues Verständnis der Entwurfszeitmechanismen notwendig. Im letzten Schritt werden auch die meisten der neuen ASP.NET 2.0-Funktionen verwendet – beim Erstellen von benutzerdefinierten Designern für Serversteuerelemente.
Wenn ein Steuerelement per Drag & Drop in einem Webformular abgelegt wird, fragt Visual Studio 2005 beim Designer des Steuerelements Markup-Code ab, über den Seitenautoren eine ordnungsgemäße Vorschau der Seite erhalten.
Wenn Ihnen Steuerelementdesigner vollkommen unbekannt sind, keine Panik. Alle Serversteuerelemente verfügen seit den Anfangszeiten von ASP.NET über einen Steuerelementdesigner. Der Standard-Steuerelementdesigner ist eine Klasse namens ControlDesigner. Diese Klasse ruft die Render-Methode des Steuerelements auf und erfasst die Ausgabe in einer Zeichenfolge, die dann an Visual Studio 2005 zurückgegeben wird.
Dieses Verhalten weist bei ASP.NET 1.x einige Nachteile auf, in ASP.NET 2.0 funktioniert jedoch alles wie geplant (und besser). Wenn dies Ihrer Ansicht nach für Ihre (benutzerdefinierten) Steuerelemente ausreichend ist, können Sie direkt mit dem Abschluss dieses Artikels fortfahren (Letzte Schritte). Andernfalls sollten Sie weiterlesen und erfahren, wie Sie das Bearbeiten Ihrer ASP.NET 2.0-Steuerelemente zu einer erfreulichen Erfahrung machen können.
Die Änderungen und Verbesserungen sind in drei Hauptbereiche unterteilt:
-
AutoFormat-Menü
-
Smarttags
-
Bereiche
Zum Unterstützen dieser Funktionen erstellen Sie einfach einen Designer, der einige Methoden und Eigenschaften mehr überschreibt als für ASP.NET 1.x erforderlich.
Bereichsmarkierung
Wenn Sie verbundene Steuerelemente entwerfen, die eine in mehrere Blöcke unterteilte Benutzeroberfläche aufweisen, können Sie den Benutzern helfen, indem Sie kontextbezogene Smarttags anbieten, die auf dem derzeit markierten Bereich basieren. Das klingt komplizierter, als es ist. Schauen Sie sich Abbildung 4 an.
Abbildung 4: Bereiche und kontextbezogene Smarttags
Warum ist die Bezeichnung "Customer" in einer anderen Hintergrundfarbe dargestellt? Es liegt nicht daran, dass die Hintergrundfarbe entsprechend festgelegt wurde. Stattdessen wählte ich das Steuerelement zuerst im Formular als Ganzes aus und klickte dann auf den Bezeichnungsbereich. Wenn Sie jetzt auf das Textfeld klicken (den zweiten im LabelBox-Steuerelement definierten Bereich), ändert sich der Hintergrund des Textfelds. Von größerer Bedeutung ist, dass auch die Aufgaben entsprechend des ausgewählten Bereichs geändert werden können.
Wie definieren Sie Entwurfszeitbereiche für ein benutzerdefiniertes Steuerelement? Bevor dies erläutert wird, muss kurz zusammengefasst werden, worum es sich bei einer Designerklasse tatsächlich handelt.
Eine Designerklasse ist eine Klasse, die von einer Basisklasse erbt – in diesem Fall CompositeControlDesigner. Die Klasse muss einige Methoden überschreiben, wie in der nachstehenden Tabelle dargestellt:
| Tabelle 5: Überschreibungen für einen Steuerelementdesigner |
| Member | Beschreibung |
| CreateChildControls | Definiert die Bereiche. |
| Initialize | Bereitet den Designer darauf vor, das Markup des Steuerelements zu erstellen. |
| GetDesignTimeHtml | Gibt das Markup zurück, das in der Visual Studio 2005-IDE angezeigt werden soll. |
| OnClick | Verarbeitet das Klicken auf ein Steuerelement durch einen Benutzer zur Entwurfszeit. |
| ActionLists | Eigenschaft: führt den Inhalt des Smarttags des Steuerelements auf. |
| AutoFormats | Eigenschaft: führt die vordefinierten Formate für das Steuerelement auf. |
In Initialize speichern Sie meist lediglich eine Referenz auf die Steuerelementinstanz, die von Visual Studio 2005 verwendet wird.
public class LabelBoxDesigner : CompositeControlDesigner
{
private LabelBox _labelBoxControl;
public override void Initialize(IComponent component)
{
_labelBoxControl = (LabelBox) component;
base.Initialize(component);
}
:
}
In CreateChildControls definieren Sie die Bereiche, die Sie zur Entwurfszeit verwenden möchten. Ein Entwurfszeitbereich sollte mit dem Inhalt eines logischen Laufzeitbereichs übereinstimmen. Wie bereits erwähnt, weist das LabelBox-Steuerelement zwei logische Bereiche auf, den Header- und den Bearbeitungsbereich. Der Headerbereich entspricht dem Label-Steuerelement, der Bearbeitungsbereich entspricht dem Textfeld. Zwischen Bereichen und Steuerelementen muss nicht immer eine genaue Korrespondenz vorliegen. Häufiger deckt ein Bereich eine ganze Steuerelementstruktur ab. Zur Entwurfszeit stellen Sie jedoch eine Verbindung zwischen einem Bereich und dem Stamm der Steuerelementstruktur des Bereichs her. Beide Bereiche entsprechen allen Steuerelementen in einer Tabellenzelle, und die Tabellenzellen werden in internen Membern des LabelBox-Steuerelements gespeichert. Diese Member verwenden Sie zum Definieren von Bereichen im Designer.
protected override void CreateChildControls()
{
base.CreateChildControls();
if (_labelBoxControl._headerContainer != null)
{
_labelBoxControl._headerContainer.Attributes.Add(
DesignerRegion.DesignerRegionAttributeName,
PROP_HEADERREGION.ToString());
}
if (_labelBoxControl._textBoxContainer != null)
{
_labelBoxControl._textBoxContainer.Attributes.Add(
DesignerRegion.DesignerRegionAttributeName,
PROP_EDITREGION.ToString());
}
}
Um einen Bereich zu definieren, fügen Sie dem HTML-Element, das die als "Bereich" definierte Steuerelementstruktur enthält, ein Attribut hinzu. Hierbei muss beachtet werden, dass dieses zusätzliche Attribut nur zur Entwurfszeit ausgegeben wird. Die Konstanten im vorstehenden Code sind lediglich benutzerdefinierte, aufsteigende Zahlen, mit denen die Bereiche bezeichnet werden. Die Verwendung von Zahlen anstelle von Zeichenfolgen ist eine willkürliche Entscheidung. Sie müssen lediglich eine Methode finden, die Bereiche eindeutig zu bezeichnen.
Der Designer ruft zuerst CreateChildControls auf, um sicherzustellen, dass die Steuerelementstruktur eingerichtet ist und ausgeführt wird, und ruft anschließend GetDesignTimeHtml auf, um das Entwurfszeitmarkup für das Steuerelement abzurufen.
Erstellen von Markup zur Entwurfszeit
In ASP.NET 2.0 weist die GetDesignTimeHtml-Methode der ControlDesigner-Basisklasse zwei Überladungen auf.
public virtual string GetDesignTimeHtml()
public virtual string GetDesignTimeHtml(DesignerRegionCollection regions)
Die erste Überladung ruft einfach das Markup ab, mit dem das Steuerelement zur Entwurfszeit dargestellt wird. Darüber hinaus füllt die zweite Überladung die angegebene Auflistung ebenfalls mit Bereichsinformationen.
public override string GetDesignTimeHtml(DesignerRegionCollection regions)
{
// Add regions
regions.Add(new DesignerRegion(this, HEADERREGION));
regions.Add(new DesignerRegion(this, EDITREGION));
// Set the highlights as needed
if (_regionHighlights[PROP_HEADERREGION])
regions[PROP_HEADERREGION].Highlight = true;
if (_regionHighlights[PROP_EDITREGION])
regions[PROP_EDITREGION].Highlight = true;
return base.GetDesignTimeHtml(regions);
}
Die zum Definieren der Bereiche verwendeten Konstanten sind einfache benutzerdefinierte Konstanten, mit denen jeder Bereich benannt wird. Nach dem Definieren der Bereiche können Sie mit der Visual Studio 2005-IDE den derzeit ausgewählten Bereich markieren. Zu diesem Zweck legen Sie die Highlight-Eigenschaft der DesignerRegion-Klasse als true fest. Durch welches Ereignis wird festgelegt, ob ein Bereich markiert werden sollte? Dies bleibt zum überwiegenden Teil Ihnen überlassen, sie können jedoch nur wenige Ereignisse von der Visual Studio 2005-IDE aus abfangen. In ASP.NET 1.x gibt es überhaupt keine Ereignisse und in ASP.NET 2.0 nur das OnClick-Ereignis. Um die Markierung von Bereichen zu unterstützen, müssen Sie daher auch die OnClick-Methode überschreiben.
protected override void OnClick(DesignerRegionMouseEventArgs e)
{
base.OnClick(e);
// Figure out which region needs to be highlighted
if (e.Region != null)
{
// Highlight Header region
if (e.Region.Name == HEADERREGION)
{
_regionHighlights[PROP_HEADERREGION] =
!_regionHighlights[PROP_HEADERREGION];
_regionHighlights[PROP_EDITREGION] = false;
}
else
{
// Highlight Edit region
if (e.Region.Name == EDITREGION)
{
_regionHighlights[PROP_EDITREGION] =
!_regionHighlights[PROP_EDITREGION];
_regionHighlights[PROP_HEADERREGION] = false;
}
}
// Re-render the control to show the highlight
UpdateDesignTimeHtml();
}
}
Die _regionHighlights-Variable ist ein benutzerdefiniertes boolesches Array, mit dem der Markierungsstatus jedes Bereichs verfolgt wird. Beachten Sie, dass die Instanz der Designerklasse, in der die Variable definiert ist, immer aktiviert ist, solange die Hostseite in der Visual Studio 2005-IDE besteht – es gibt keinen Webanforderungs- oder -antwortmechanismus, mit dem lokale Variablen als ungültig erkannt werden können. Wenn der Benutzer in den Steuerelementbereich klickt, wird die OnClick-Methode aufgerufen und empfängt Informationen zum Bereich (sofern vorhanden), in dem der Klick aufgetreten ist. Die OnClick-Methode aktualisiert interne Informationen und ruft UpdateDesignTimeHtml auf, um das Steuerelement erneut darzustellen. Dabei wird GetDesignTimeHtml aufgerufen, um den ausgewählten Bereich mit einer anderen Hintergrundfarbe darzustellen, wie in Abbildung 4 dargestellt.
In diesem Fall wird die Entwurfszeitdarstellung des Serversteuerelements durch das Standardmarkup festgelegt. Durch das Überschreiben der parameterlosen Version von GetDesignTimeHtml erhalten Sie die vollständige Kontrolle über die zur Entwurfszeit dargestellten Elemente (siehe Abbildung 5).
Abbildung 5: Vollständig benutzerdefinierte Entwurfszeitdarstellung des "LabelBox"-Steuerelements
Der in der Abbildung dargestellte graue Kasten entspricht dem, den einige ASP.NET-Designer für ihre Steuerelemente verwendeten – beispielsweise für Benutzersteuerelemente.
Aktionslisten und AutoFormats
Um die Aktionsliste und das AutoFormat-Menü des Steuerelements zu steuern, überschreiben Sie zwei Eigenschaften der Designerklasse:
public override DesignerActionListCollection ActionLists
{
get
{
DesignerActionListCollection list;
list = new DesignerActionListCollection();
list.AddRange(base.ActionLists);
LabelBoxActionList lbList = new LabelBoxActionList(
this, _regionHighlights[PROP_HEADERREGION])
list.Add(lbList);
return list;
}
}
public override DesignerAutoFormatCollection AutoFormats
{
get
{
DesignerAutoFormatCollection autoFormats;
autoFormats = new DesignerAutoFormatCollection();
autoFormats.Add(new BlueFormat());
autoFormats.Add(new YellowFormat());
autoFormats.Add(new GreenFormat());
return autoFormats;
}
}
Die Aktionsliste bezieht sich auf den Inhalt des Fensters, das angezeigt wird, wenn der Benutzer auf die Smarttag-Schaltfläche klickt, wie in Abbildung 4 dargestellt. Der Inhalt des Fensters entstammt einer steuerelementspezifischen Klasse, die von DesignerActionListCollection erbt.
Die Aktionsliste enthält verschiedene Elementtypen: Header, Eigenschaft und Methode. Das Headerelement erstellt einen beschreibenden Text, wie "Orientation" in Abbildung 4. Das Eigenschaftenelement gibt ein an eine Variable gebundenes Steuerelement aus. Dieses Steuerelement ist vom Typ der Eigenschaft abhängig: Bei einem booleschen Wert ist es ein Kontrollkästchen und bei einem Aufzählungswert ein Dropdown-Menü. Die Eigenschaft stimmt normalerweise mit einer ähnlichen Eigenschaft des Steuerelements überein. Ein Beispiel:
public LabelBoxMode Mode
{
get {return _labelBoxControl.Mode;}
set {
PropertyDescriptor pd;
pd = TypeDescriptor.GetProperties(_labelBoxControl)["Mode"];
if (pd != null)
pd.SetValue(_labelBoxControl, value);
}
}
Die Mode-Eigenschaft der LabelBoxActionList-Klasse ruft den Wert der Mode-Eigenschaft im LabelBox-Steuerelement über das Smarttag-Fenster ab und legt diesen fest. Beachten Sie, dass Sie den Wert der Eigenschaft mit Bedacht wählen sollten, damit die Visual Studio 2005-IDE Menüaktualisierungen wie beim Rückgängigmachen und anderen Aktionen vollständig unterstützen kann.
Schließlich fügt das Methodenelement dem Fenster ein anklickbares Element hinzu, und die angegebene Methode wird als Reaktion ausgeführt. Beispielsweise setzt der "Set Display Mode"-Link in Abbildung 4 die Position der Bezeichnung.
Im Konstruktor der benutzerdefinierten Aktionslistenklasse können Sie alle Informationen übergeben, die Sie für erforderlich halten, um die Aktionsliste bereichsspezifisch zu gestalten. In Abbildung 6 wird das Aufgabenfenster bei ausgewählten Header- und Bearbeitungsbereichen dargestellt (die Änderung ist auf das untere Textfeld beschränkt).
Abbildung 6: Bereichsempfindliche Aktionslisten
Der AutoFormat-Befehl wird automatisch angezeigt, wenn die Designerklasse die AutoFormats-Eigenschaft überschreibt. Sie füllen die AutoFormats-Auflistungseigenschaft mit Instanzen von Klassen, die von der DesignerAutoFormat-Basisklasse abgeleitet sind.
public class BlueFormat : DesignerAutoFormat
{
public BlueFormat() : base("(Blue-ish formatting)")
{
}
public override void Apply(System.Web.UI.Control control)
{
LabelBox _control = control as LabelBox;
if (_control != null)
{
_control.LabelSettings.BackColor = Color.Transparent;
_control.LabelSettings.ForeColor = Color.Blue;
_control.TextBoxSettings.BackColor = Color.Cyan;
_control.TextBoxSettings.ForeColor = Color.Blue;
}
}
}
Eine AutoFormat-Designerklasse überschreibt einfach die Apply-Methode, um einige visuelle Eigenschaften auf vordefinierte Werte festzulegen.
Abbildung 7: Benutzerdefiniertes "AutoFormat"-Fenster
Durch Klicken auf eine der Schaltflächen Apply oder OK werden die in der Apply-Methode definierten Einstellungen an das Steuerelement übergeben, das sich im Visual Studio 2005-Designer befindet, und dann in der ASPX-Quelldatei beibehalten.
Letzte Schritte
Wie binden Sie einen Designer an eine Steuerelementklasse? Die Antwort sollte jetzt offensichtlich sein! Sie verwenden ein Attribut, nämlich das Designer-Attribut.
[Designer(typeof(LabelBoxDesigner))]
public class LabelBox : CompositeControl
{
:
}
Ohne dieses Attribut erbt das Steuerelement den Designer seiner Basisklasse. Wenn Sie ein benutzerdefiniertes Steuerelement erstellen und auch einen benutzerdefinierten Designer erstellen, müssen Sie unbedingt den Designer der Basisklasse als Ausgangspunkt verwenden. In ASP.NET 2.0 sind zahlreiche Designerklassen vorhanden, sodass Sie den am besten zu Ihrem neuen Steuerelement passenden Designer auswählen können.
Das Erstellen eines umfassenden und angenehmen Entwurfszeiterlebnisses ist nicht mehr so schwierig, wie es zum Teil in ASP.NET 1.x war. Wenn Sie Steuerelemente schreiben, die mitunter von anderen Entwicklern verwendet werden, haben Sie nun also keine Entschuldigung mehr.
Der Autor
Dino Esposito ist Mentor von Solid Quality Learning und Autor von Programming Microsoft ASP.NET 2.0 (Microsoft Press, 2005). Dino arbeitet in Italien und ist ein weltweit gefragter Referent bei Branchenveranstaltungen. Sie können ihn in englischer Sprache über cutting@microsoft.com erreichen oder sein englischsprachiges Weblog unter http://weblogs.asp.net/despos besuchen.