Glengamoi (Forum) · AspHeute · .NET Heute (RSS-Suche) · AspxFiles (Wiki) · .NET Blogs

Das ASP.NET DataGrid selbst erweitern

Geschrieben von: Alexander Zeitler
Kategorie: ASP.NET

This printed page brought to you by AlphaSierraPapa

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ß sein

Da 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 Ziel

Der 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 Rendern

Nichts 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-Orientierung

Daß 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 Land

Um 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ßbemerkung

Die 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.

This printed page brought to you by AlphaSierraPapa

Download des Codes

Klicken Sie hier, um den Download zu starten.
http://www.aspheute.com/code/20030909.zip

Verwandte Artikel

Das using Schlüsselwort
http:/www.aspheute.com/artikel/20020318.htm
DataGrid - Daten bearbeiten leicht gemacht
http:/www.aspheute.com/artikel/20040929.htm
Einträge numerieren im DataGrid
http:/www.aspheute.com/artikel/20040317.htm
MouseOver-Effekte beim DataGrid
http:/www.aspheute.com/artikel/20040628.htm
Pager- und Footerzeilen des DataGrid erweitern
http:/www.aspheute.com/artikel/20040318.htm
Vergleich von DataGrid, DataList und Repeater-Control - was verwende ich wann?
http:/www.aspheute.com/artikel/20040303.htm

 

©2000-2006 AspHeute.com
Alle Rechte vorbehalten. Der Inhalt dieser Seiten ist urheberrechtlich geschützt.
Eine Übernahme von Texten (auch nur auszugsweise) oder Graphiken bedarf unserer schriftlichen Zustimmung.