Das ASP.NET DataGrid selbst erweitern
Geschrieben von: Alexander Zeitler Das als Standard im .NET-Framework enthaltene DataGrid-Steuerelement ist bereits ein mächtiges Objekt zur Darstellung und Bearbeitung von Daten in tabellarischer Form. Möchte man das DataGrid allerdings in ein fixes Design implementieren, stört unter Umständen das etwas "sprunghafte Verhalten" des DataGrids, d.h. die Zeilenzahl und damit die Höhe des DataGrids variiert mit der Zahl der anzuzeigenden Datensätze: Geringfügige Abhilfe schafft hier die Verwendung des bereits implementierten Pagings - das vorgenannte Problem verlagert sich damit allerdings nur - in meist umgekehrter Form - auf die letzte anzuzeigende Seite: Um das Problem dauerhaft zu lösen, ist es notwendig, das DataGrid etwas aufzubohren. Und ebendies werden wir in diesem Artikel realisieren. Ordnung muß seinDa wir zur Genüge betrachtet haben, was wir nicht erreichen möchten, wollen wir nun unser tatsächliches Ziel ins Auge fassen: Da die gezeigte Lösung wiederverwendbar sein soll, empfiehlt es sich, sie in ein vom DataGrid abgeleitetes Steuerelement zu packen, welches sich in der Klasse FixedHeightGrid befinden wird. Der Sourcecode der Klasse befindet sich damit konsequenterweise in der Datei FixedHeightGrid.cs. Somit ist auch klar, daß wir die Lösung in C# implementieren werden. Wie bereits erwähnt, stellt unser neues DataGrid eine sog. Ableitung des DataGrids aus der WebControls-Collection des .NET-Frameworks dar. In Code ausgedrückt, sieht das wie folgt aus: public class FixedHeightGrid : System.Web.UI.WebControls.DataGrid Der Doppelpunkt teilt unserer Klasse mit, daß sie alle Eigenschaften der DataGrid-Klasse erben soll, d.h. unser neues DataGrid besitzt bereits jetzt den vollen Leistungsumfang des bekannten DataGrids. Der Weg ist das ZielDer Lösungsansatz an sich dürfte klar sein: Wir benötigen eine Methode, die prüft, wie viele Elemente in der aktuellen Ansicht (sei es mit oder ohne Paging), vorhanden sind. Unsere gewünschte Höhe definieren wir über die bereits im DataGrid implementierte PageSize-Property, welche bisher allerdings nur bei aktiviertem Paging zum Tragen kam. Unsere Methode muß also die Anzahl der tatsächlich vorhandenen Zeilen mit der gewünschten Zeilenzahl vergleichen und ggf. fehlende Zeilen als Leerzeilen anhängen. Nun stellt sich allerdings die Frage, wie wir in das Geschehen bei der Erzeugung des DataGrids, welches sich bisher als Blackbox darstellte, eingreifen können. Vor dem Rendern ist nach dem RendernNichts leichter als das - wie viele Objekte, die von der System.Web.UI.Control-Klasse abgeleitet wurden, besitzt das DataGrid die geschützte OnPreRender-Methode, welche vor dem Rendern (=Erzeugen des HTML-Codes für das DataGrid) aufgerufen wird. In diesem Stadium liegt das DataGrid in seiner wahren und endgültigen Schönheit bereits vor uns - jedoch noch in Form in Objekten - also genau dem, wonach wir suchen. Objekt-OrientierungDaß wir Zeilen ins DataGrid einfügen möchten, haben wir bereits festgestellt. Ebenso ist die Frage geklärt, wann wir die Zeilen einfügen werden. Ungeklärt blieb bisher allerdings die Frage nach dem Wo? Um diese Frage beantworten zu können, ist ein genauerer Blick in den Aufbau des DataGrids notwendig: Wie man in der Darstellung sieht, besteht das DataGrid aus einer Tabelle vom Typ DataGridTable, welche wiederum Tabellenzeilen vom Typ DataGridItem beinhaltet. Diese stellen eine Ableitung der Klasse TableRow dar, welche der HTML-Tag-Kombination <TR></TR> entspricht. Die DataGridItems sind in verschiedene Typen, wie z.B. Pager, Header, Item usw. unterteilt. Wenn es ein Pendant für die <TR></TR>-Tags in der WebControls-Collection gibt, sollte es so etwas wohl auch für die <TD></TD>-Tags, welche das "Innenleben" einer Tabellenzeile darstellen, geben. Und tatsächlich, die Klasse TableCell erledigt genau diese Aufgabe. Die Grafik zeigt auch, daß der Index der Controls durchgehend ist, d.h. der Index bezieht sich auf die gesamte Tabelle (DataGridTable) des DataGrids. Man sollte jedoch tunlichst vermeiden, diesen Index mit dem Wert von this.Items zu verwechseln, dieser liefert nämlich die Anzahl der Datensätze in der aktuellen Ansicht zurück. Um beim Thema Verwechslungen und Mißverständnissen zu bleiben: Das DataGrid erlaubt Header und Footer mittels ShowHeader = True/False bzw. ShowFooter = True/False ein (=True) bzw. auszublenden (=False). Beides hat keinen Einfluß auf die Erzeugung des Indexes, d.h. die Header- und Footer-Items werden trotzdem generiert, aber nicht angezeigt. Ganz anders verhält es sich mit den Pagern: Deren Generierung, d.h. auch der Index der DataGridTable, ist abhängig von dem Attribut "AllowPaging", welches ebenfalls True bzw. False sein kann. Um die Verwirrung komplett zu machen, werden die Pager dann aber nicht konsequenterweise abhängig von dem Attribut PagerStyle-Position (welches "Top", "Bottom" oder "TopAndBottom" lauten kann) erzeugt, sondern immer komplett. Dem Verhalten der Pager müssen wir also ebenfalls Rechnung tragen. Lasst uns endlich Daten (:Taten) sehen!Da wir jetzt genügend Grundlagen für die Umsetzung der Lösung in lauffähigen C#-Code gesammelt haben, können wir uns der Umsetzung widmen. Wie bereits erwähnt, starten unsere Eingriffe in der Methode OnPreRender. Hier rufen wir unsere Methode AddPaddingItems() auf, welche den eigentlichen Einfüge-Vorgang durchführt. protected override void OnPreRender(EventArgs e) { AddPaddingItems(); } In AddPaddingItems() deklarieren wir zunächst einige Variablen: int indexCount; int indexStop = this.PageSize; int indexStart = 1; Der Integer indexCount stellt den Index für unsere DataGridTableItems dar. Der Integer indexStop beinhaltet den Endwert für unseren Index und wird mit der Seitengröße, also der Anzahl der darzustellenen Einträge initialisiert. Der dritte im Bunde ist der Integer indexStart, der angibt, nach welcher Index-Position das erste ListItem erscheint. Die beiden zuletzt genannten Integer-Werte sind abhängig von dem bereits erwähnten Verhalten der Pager. Sind die Pager aktiviert (AllowPaging=True), müssen der Einstiegspunkt (=indexStart) sowie der Endpunkt (=indexStop) um 1 erhöht werden. if(this.AllowPaging==true) { indexStart++; indexStop++; } Nun können wir in einer Schleife, die beginnend an der Summe des Startpunktes und der Anzahl der tatsächlich vorhandenen Listeneinträge beginnt, die neuen Zeilen einfügen lassen: Table myTable = (Table)this.Controls[0]; for(indexCount=indexStart+this.Items.Count;indexCount<=indexStop;indexCount++) { myTable.Controls.AddAt(indexCount,PaddingItem()); } Wie man sieht, ist das Einfügen nur ein einfacher AddAt-Befehl, da es sich bei mytable.Controls um eine abgeleitete Collection handelt. Die Umwandlung von this.Controls[0] in eine Table könnte man sich auch sparen, der besseren Nachvollziehbarkeit des Codes wegen habe ich sie aber implementiert. Allerdings fehlt uns jetzt noch die aufgerufene Funktion PaddingItem(), welche uns ein neues DataGridItem zurückliefern wird. Diese ist ebenso kurz wie einfach: Table myTable = (Table)this.Controls[0]; private DataGridItem PaddingItem() { Table myTable = (Table)this.Controls[0]; int numberOfColumns = myTable.Rows[1].Cells.Count; DataGridItem myItem = new DataGridItem(0,0,ListItemType.Item); for(int indexCount=1;indexCount<=numberOfColumns;indexCount++) { TableCell myCell = new TableCell(); myItem.Cells.Add(myCell); } return myItem; } Zunächst wandeln wir wieder this.Controls[0] in eine Table um. Danach erzeugen wir ein neues DataGridItem vom Typ ListItemType.Item (also ein normales Item). Die ersten beiden Werte für den Index des Items im DataGrid sowie den Index der Daten im DataSet setzen wir kurzerhand auf 0, da beide Werte nicht relevant sind bzw. beim Anfügen an das DataGrid überschrieben werden. Nun durchlaufen wir in einer Schleife mit der Spaltenzahl der Header-Zeile (da diese, wie bereits erwähnt, immer erzeugt wird und somit eine verbindliche Information über die Spaltenzahl liefert) das Erzeugen der Tabellenzellen. Hierbei wird immer eine neue Zelle generiert und an das Ende des DataGridItems angefügt. Nach dem Durchlauf der Schleife liefern wir schließlich das erzeugte DataGridItem zurück an die aufrufende Funktion und haben somit unsere gewünschte Lösung bereits grundlegend realisiert. Neue Attribute braucht das LandUm die eingefügten Zeilen (PaddingItem(s)) nun auch ordentlich formatieren zu können, benötigen wir noch eine Eigenschaft, die die CSS-Style-Definition für diese Zeilen aufnimmt: private TableItemStyle _paddingItemStyle = new TableItemStyle(); public TableItemStyle PaddingItemStyle { get { return _paddingItemStyle; } set { _paddingItemStyle = value; } } Auch hierfür bietet das .NET-Framework bereits eine fast komplette Lösung an. Wir erzeugen einfach ein neues Objekt vom Typ TableItemStyle, welches eine Collection von CSS-Attributen entgegennimmt. Allerdings müssen wir uns noch um die Zuweisung zu unseren PaddingItems kümmern. Wir kehren deshalb nochmals zu unserer Methode PaddingItem() zurück und erweitern diese wie folgt: if(this.PaddingItemStyle.CssClass!=null) { myItem.ApplyStyle(this.PaddingItemStyle); } return myItem; Wir prüfen, ob für die PaddingItems eine CSS-Klasse übergeben wurde. Ist dies der Fall, weisen wir den damit übergebenen PaddingItem-Style an das aktuelle PaddingItem mittels .ApplyStyle zu. Wird nichts übergeben, wird automatisch der Style der normalen Items verwendet, da wir ja ein Item vom Typ ListItemType.Item erzeugt haben. Als weiteres Feature werden wir außerdem noch die Möglichkeit zum Verbinden von Tabellenzellen der PaddingItems implementieren. Dies entspricht dem von HTML bekannten "ColSpan". Unsere Klasse wird also um eine weitere Eigenschaft ergänzt: private bool _mergePaddingColumns; public bool MergePaddingRows { get { return _mergePaddingRows; } set { _mergePaddingRows = value; } } Ebenso erfährt die Methode PaddingItem() eine weitere Änderung: int numberOfColumns = myTable.Rows[1].Cells.Count; if(this.MergePaddingColumns==true) { TableCell myCell = new TableCell(); myCell.ColumnSpan = numberOfColumns; myItem.Cells.Add(myCell); } else { for(int indexCount=1;indexCount<=numberOfColumns;indexCount++) { TableCell myCell = new TableCell(); myItem.Cells.Add(myCell); } } myitem.Cells.Add(mycell); } } Zunächst prüfen wir, ob das Verbinden der Spalten aktiviert wurde. Ist dies der Fall, fügen wir nur eine Tabellenzelle hinzu und setzen die von HTML bekannte Eigenschaft Columnspan auf den Wert der Spaltenzahl der Headerzeile. Schließlich fügen wir die Zelle an das neue DataGridItem an. Das neue DataGrid ist nun bereits einsatzbereit. Allerdings nur, solange wir die Spalten per AutoGenerateColumns erzeugen lassen. Möchten wir das Spaltendesign mittels BoundColumn & Co. selbst beinflussen, erhalten wir folgende Parser-Fehlermeldung: Der Typ learncontrols.BoundColumn in der Assembly learncontrols, Version=1.0.1339.22102, Culture=neutral, PublicKeyToken=null konnte nicht geladen werden. Der Grund hierfür ist, daß wir die BoundColumn mit dem Prefix "aspx" versehen haben. In der Assembly learncontrols (welcher der TagPrefix aspx zugeordnet ist) existiert aber keine Definition für diese Klasse. Die Abhilfe folgt aber auf dem Fuß: wir müssen einfach in unserer Assembly Ableitungen der ursprünglichen Klassen definieren: public class BoundColumn : System.Web.UI.WebControls.BoundColumn { } Damit sind wir auch schon am Ende unseres kleinen, aber wie ich hoffe, interessanten Ausflugs in die Welt der Steuerelement-Entwicklung mit ASP.NET und C# angelangt. Die Verwendung des neuen DataGrids finden Sie in der Datei testfixedheightgrid.aspx bzw. testfixedheightgrid.aspx.cs. SchlußbemerkungDie hier vorgestellte Lösung ist natürlich noch ausbaufähig und erhebt auch keinen Anspruch auf Vollständigkeit. Vielmehr sollen die fast unbegrenzten Möglichkeiten, die das .NET-Framework bietet, aufgezeigt werden und Lust auf mehr machen. Das heute erzeugte DataGrid wird Gegenstand eines weiteren Artikels sein, der die Integration des neuen DataGrids in die Entwicklungsumgebung Visual Studio.NET behandelt, denn z. Zt steht für das neue DataGrid z.b. noch keine IntelliSense im HTML-Editor zur Verfügung. Download des CodesKlicken Sie hier, um den Download zu starten. Verwandte Artikel
Das using Schlüsselwort Wenn Sie jetzt Fragen haben...Wenn Sie Fragen rund um die in diesem Artikel vorgestellte Technologie haben, dann schauen Sie einfach bei uns in den Community Foren der deutschen .NET Community vorbei. Die Teilnehmer helfen Ihnen gerne, wenn Sie sich zur im Artikel vorgestellten Technologie weiterbilden möchten. Eine weitere sehr hilfreiche Resource ist das deutsche ASP.NET Wiki, das als zentrale Anlaufstelle für Tips, Tricks, Know How und alles Nützliche was man in seinem Alltag als (ASP).NET-Entwickler so braucht und entdeckt gedacht ist. Haben Sie Fragen die sich direkt auf den Inhalt des Artikels beziehen, dann schreiben Sie dem Autor! Unsere Autoren freuen sich über Feedback zu ihren Artikeln. Ein einfacher Klick auf die Autor kontaktieren Schaltfläche (weiter unten) und schon haben Sie ein für diesen Artikel personalisiertes Anfrageformular.
Und zu guter Letzt möchten wir Sie bitten, den Artikel zu bewerten. Damit helfen Sie uns, die Qualität der Artikel zu verbessern - und anderen Lesern bei der Auswahl der Artikel, die sie lesen sollten.
©2000-2006 AspHeute.com |