C# Report-Möglichkeiten
Aktuell Entwickle ich ein Programm Namens iVBA, welches über eine Access-Datenbank Benutzer, Logins und weiteres speichert.Aus diesen Benutzer- und Login-Daten müssen Reports erstellt werden können. In C# gibt es verschiedene Möglichkeiten, Reports zu erstellen und anzuzeigen.
- Es gibt die normalen "Reports" welche vom Microsoft Report Viewer dargestellt werden können
- Es gibt die sogenannten "CrystalReports" welche vom CrystalReportViewer dargestellt werden können.
Um die Vor- und Nachteile der beiden Varianten habe ich mich nicht gekümmert.
Ich habe mich für die Microsoft-Variante entschieden und mit dem MicrosoftReportViewer gearbeitet.
Da ich meine Datenbank-Verbindung und all meine Abfragen, wie in früheren Posts von mir beschrieben, per Code definiert habe, wollte ich es auch diesmal genau gleich machen.
Ich fand zuerst bloss Anleitungen, wie ich mir mit Hilfe von Visual Studio ein Dataset erstellen und es dem Report zuweisen kann. Dazu hätte ich jedoch eine neue Verbindung zur Datenbank aufbauen müssen, was mir gar nicht gefiel.
Report-Objekte
Anstatt also direkt die Datenbank als Quelle für meine Reports zu nehmen, erstellte ich meine eigenen Klassen, welche die selben Eigenschaften wie die Datenbank-Tabellen enthielten und fügte diese als Datenquellen hinzu.Klassen erstellen:
Wir gehen nun davon aus, dass wir eine Tabelle namens "User" haben, welche folgende Eigenschaften hat:- id
- vorname
- nachname
- inaktiv
- created
In unserem Fall also eine User-Daten-Klasse und eine User-Listen-Klasse, den Inhalt werde ich später erläutern:
public class ReportObjects
{
public class User{}
public class UserList{}
}
Die Klasse User soll nun also die Tabelle darstellen, bzw. die Datensätze darin. Dazu werden in der Klasse folgende Eigenschaften definiert:
private int id;
private string vorname;
private string nachname;
private string email;
private bool inaktiv;
private DateTime created;
Jede dieser Eigenschaften muss natürlich von aussen erreichbar sein, zumindest um diese lesen zu können:
public int Id
{
get { return id; }
}
Dasselbe wird natürlich für jede Eigenschaft wiederholt, ich denke das kennt ihr.
Um die Eigenschaften zu setzten definieren wir einfach einen Konstruktor:
Public User (int id, string vorname, string nachname, string email, bool inaktiv, DateTime created)
{
this.id = id;
this.vorname = vorname;
this.nachname = nachname;
this.email = email;
this.inaktiv = inaktiv;
this.created = created;
}
Nun ist unsere User-Daten-Klasse also fertig.
Also erstellen wir die User-Listen-Klasse. Die Realisierung dieser Klasse ist eigentlich den Bedürfnissen anzupassen. Hier jedoch meine Variante.
Zuerst erhält die klasse folgende Klassen-Variablen:
private List
private DataTable userSource;
Die userList enthält später alle User, die userSource enthält die Datenquelle für die User, also Datensätze aus der User-Tabelle unserer Datenbank.
Zuerst habe ich 2 Konstruktoren definiert. Der 1. ist dafür da, eine Liste zu erstellen, ohne die userSource bereits zu definieren, der 2. um sie bereits zu übergeben.
public UserList()
{
userList = new List
}
public UserList (DataTable userSource)
{
this.userSource = userSource;
userList = new List
AddUsersToList(); <-- Wird noch erklärt/definiert
}
Die Methode AddUsersToList() fügt alle Datensätze in der userSource der UserList hinzu und sieht so aus:
private void AddUsersToList()
{
userList.Clear(); <-- Löscht alle User-Objekte in der UserList
foreach (DataRow zeile in userSource.Rows)
{
userList.Add( new User (
(int)zeile[0],
zeile[1].ToString(),
zeile[2].ToString(),
zeile[3].ToString(),
(bool)zeile[4],
(DateTime)zeile[5]));
}
}
Nun wären also alle User in der Liste. Hätte ich jedoch den 1. Konstruktor verwendet, wäre noch gar nichts als userSource definiert und die AddUseresToList-Methode noch gar nicht ausgeführt.
Deshalb erstellen wir noch die Methode SetUserSource() welche die userSource setzt:
public void SetUserSource(DataTable userSource)
{
this.userSource = userSource;
AddUsersToList(); <-- Fügt sofort alle User der UserList hinzu
}
Sobald wir also entweder den 1. Konstruktor und anschliessend SetUserSource() oder den 2. Konstruktor aufgerufen haben, ist die Liste gefüllt. Um die Liste auch holen zu können müssen wir noch die GetUser-Methode definieren:
public List
{
return userList;
}
Damit kann also auf die Liste in der Klasse zugegriffen werden.
Datenquellen hinzufügen
Kompilieren Sie zuerst ihr Projekt indem sie über "Erstellen" auf "Projektmappe erstellen" klicken.Navigieren Sie Dann zu: Daten --> Neue Datenquelle hinzufügen --> Objekt --> Weiter
Nun erhalten Sie eine Baum-Struktur. Öffnen Sie ihren eigenen Namespace, diesem Untergeordnet einen Weiteren Knoten mit ihre Namespace . Nun sollten Sie den Knoten ReportObjects sehen. Öffnen Sie diesen und wählen sie User aus.
Bericht erstellen & Anzeigen
Sie brauchen natürlich auch noch einen Report und einen "MicrosoftReportViewer" um diesen anzuzeigen.Das Ganze kann natürlich immer etwas anders realisiert werden, jedoch zeige ich hier eine Möglichkeit auf.
Erstellen Sie dazu am besten einen Unterordner und fügen sie ihm einen Bericht hinzu(Endung .rdlc nicht rpt).
Diesen können Sie nun so designen, wie Sie möchten.
Um nun die Daten aus unserer User-Datenquelle anzeigen zu können, müssen Sie bei den Datenquellen (falls Sie diese nicht sehen: Daten/Datenquellen anzeigen) die gewünschte Eigenschaft auswählen und per Drag & Drop auf das Formular ziehen.
Fügen Sie die Felder einfach so ein, wird jedoch nur die Eigenschaft des 1. Objekts in der Datenquelle angezeigt.
Möchten Sie eine Auflistung aller Objekte in der Datenquelle, können Sie über die Toolbox eine Liste hinzufügen und die Eigenschaften per Drag & Drop in die Liste ziehen. Dann wird für jedes Objekt ein Listen-Eintrag generiert.
Sobald Sie die 1. Eigenschaft auf das Formular gezogen haben, wird im Hintergrund ein DataSet für den Bericht definiert. Über diesen werden die Daten beim anzeigen geholt. Das ist sehr wichtig und werde ich später noch genauer erläutern. Wenn ich also vom DataSet des Berichts schreibe, ist genau das gemeint.
Damit Sie den Report anzeigen können, müssen Sie eine neue Form hinzufügen. Der ReportViewer wird später per Code hinzugefügt.
Öffnen Sie nun die Form in der Code-Ansicht.
Fügen sie als KlassenVariable eine UserList hinzu:
private ReportObjects.UserList userlist = new ReportObjects.UserList();
Im Konstruktor fügen sie nun einen MicrosoftReportViewer hinzu, wählen den Report aus der angezeigt werden soll und füllen das DataSet des ausgewählten Reports.
public form ()
{
InitializeComponent();
//Fügt den ReportViewer hinzu.
ReportViewer reportViewer = new ReportViewer();
reportViewer.ProcessingMode = ProcessingMode.Local;
reportViewer.Dock = DockStyle.Fill;
this.Controls.Add(reportViewer);
//Setzt den zu ladenden Report
reportViewer.LocalReport.ReportEmbeddedResource =
"Namespace.Reports.reportname.rdlc";
//Füllen Sie spätestens hier die UserList mit Daten aus der
//Datenbank. Bei mir z.B. so:
userList.SetUserSource(dbHandler.GetNewDataTable(
"SELECT * FROM [User]"))
//Hier wird die Source für das DataSet gesetzt.
//Der Name des ReportDataSource(1. Parameter) ist entscheidend!
//Er besteht immer aus dem Namespace und
//verbunden mit einem _ dem Namen der Datenquelle.
//In unserem Fall "Namespace_User"
//Der 2. Parameter ist die DataSource.
//In unsere, Fall die Liste im userList-Objekt
reportViewer.LocalReport.DataSources.Add(
new ReportDataSource("Namespace_User", userList.GetUser()));
//Zeigt den Report an
reportViewer.RefreshReport();
}
In meinem Fall habe ich das Ganze noch mit Parametern ausgestattet, in dem ich den Report übergebe, welcher geladen werden sollte, sowie meine Datenbank-Verbindung. Natürlich braucht es dann noch andere Auswertungen, welchen DataSet nun mit welchen Daten gefüllt werden muss, aber alles kann ich hier auch nicht beschreiben.
Die Rapporte kann man über die Where-Klausel der SQL-Abfrage Filtern.
Nun müssen Sie nur noch die Form aufrufen und der Report, wie auch immer Sie definieren wollen, welcher es nun sein sollte, wird angezeigt.
Parameter
Es kann sein, dass Sie in ihrem Report einige Parameter verwenden. Diese können Sie in der Eigenschaft ReportParameters des Reports definieren.Diese werden in diesem Fall natürlich auch per Code, spätestens vor dem reportViewer.RefreshReport() gesetzt.
Wir gehen davon aus, dass der Report einen Integer und einen Bool als Parameter braucht.
Um diese zu Übergeben müssen Sie zuerst einen Array von ReportParameter deklarieren:
ReportParameter[] parameter = new ReportParameter[2];
Nun müssen Sie die einzelnen ReportParameter ins Array einfügen:
parameter[0] = new ReportParameter("ParameterName", "53", false);
parameter[1] = new ReportParameter("ParameterName", "True", false);
Äusserst speziell finde ich, dass Parameter bloss als strings übergeben werden können. Deshalb wird der Integer und der Bool hier auch als string übergeben.
Um die Parameter dann doch als Integer bzw. Bool verwenden zu können, müssen Sie im Bericht bei Verwendung des Wertes einfach mit der VB-Syntax einen Ausdruck definieren:
String in Integer: CInt(Parameters!Parametername.Value)
String in Bool: CBool(Parameters!Parametername.Value)
Alle Umwandlungen und weitere Funktionen findet man im Ausdrucks-Generator.
Schlussendlich müssen die Parameter noch dem Report zugewiesen werden:
reportViewer.LocalReport.SetParameters(parameter);
Subreports
Subreports sind etwas sehr nützliches und wurden von mir auch verwendet. Wie ich oben bereits geschrieben habe speichern wir hier Logins. Ein Benutzer hat mehrere Logins auf verschiedene Applikationen.Das heisst ich haben einen User der hat z.B. mehrere Logins auf eines unserer Produkte.
Möchte ich nun für jeden Benutzer den ich habe alle Logins auf dieses Produkt auflisten, erstelle ich einfach genau gleich wie beim Hauptreport in der Klasse ReportObjects eine Daten-Klasse und eine Listen-Klasse für dieses Login.
Ebenfalls füge ich die Datenquelle hinzu und designe einen neuen Report. Diesen werde ich jedoch nun als Subreport verwenden. Als Parameter werde ich im Unterbericht natürlich die UserID definieren, damit ich auch nur die Logins des aktuellen Users ausgebe.
Dazu werde ich aus der Toolbox einen "Unterbericht"-Steuerelement in den Hauptreport ziehen. Natürlich platziere ich dieses wieder in der Liste, da ich für jeden User einen Sub-Report mit dessen Logins ausgeben will.
Mit einem Rechtsklick auf dieses Steuerelement kann ich meinen erstellten SubReport auswählen und die Parameter auswählen. Als Parametername muss ich den korrekten Namen des Parameters angeben, hier also "UserID". Als Parameterwert die ID des aktuellen Users also "=Fields!Id.Value".
Nun wird der Hauptreport zwar versuchen die Subreports zu erstellen, das wird jedoch nicht funktionieren, da wir den DataSet der Subreports keine Datasource zugewiesen haben.
Um das zu tun registrieren wir im Konstruktor einen neuen Event namens SubReportProcessingEvent:
reportViewer.LocalReport.SubreportProcessing +=
new SubreportProcessingEventHandler
(SubreportProcessingEventHandler);
Natürlich müssen wir die Event-Methode definieren:
void SubreportProcessingEventHandler
(object sender, SubreportProcessingEventArgs e)
{
}
In dieser Prozedur muss genau wie oben beim Hauptreport die DataSource fürs DataSet des Reports definiert werden. Wichtig ist das vorher die vorhandenen Daten gelöscht werden.
Per e.Parameters[index].Value[index] kann auf die Parameter-Werte zugegriffen werden:
e.DataSources.Clear()
loginList.SetLoginSource(dbHandler.GetNewDataTable(
"SELECT * FROM Logins WHERE userID=" + e.Parameters[0].Values[0]));
e.DataSources:Add(new ReportDataSource(
"Namespace_Login", loginList.GetLogins()));
Falls mehrere Unterberichte vorhanden sind können die Namen über die Eigenschaft e.ReportPath ausgelesen werden. So weiss man genau für welchen Bericht nun das DataSet gesetzt werden muss.
Keine Kommentare:
Kommentar veröffentlichen