DevTrain

Autor: Klaas Wedemeyer

Reportingtools von der Anwendung entkoppeln

Wer einmal mit .net gedruckt hat, wird schnell die Vorteile eines Reportingtools wie Crystal Report oder List & Label zu schätzen wissen. Das Druckdesign kann im Designer ohne das mühsame Nachzählen der Pixel formatiert werden. Selbst der Anwender kann ohne viel Wissen die Vorlagen an seine Corporate Identity anpassen. Mit wenigen Clicks kann man die Reportingtools in die Anwendung ziehen. Doch das bringt einige Probleme mit sich:

- Die Anwendung wird an vielen Stellen mit dem Tool verheiratet. Das Tool zu wechseln ist dann sehr aufwendig.

- Es ist sehr aufwendig, an allen Stellen das Tool gleich zu implementieren und alle Änderungen an allen Stellen nachzuziehen.

- Alle Entwickler müssen sich mit dem Tool auskennen.

- Alle Entwickler, die am Projekt arbeiten wollen müssen das Tool installieren und benötigen eine Lizenz.

 

Um diese Probleme zu umgehen kann man in der Architektur der Anwendung Drittanbieter Tools in einem eigenen Projekt entkoppeln. Damit wird dann eine eigene Dll aufgerufen. Diese Dll selbst hat eine Schnittstelle, die keine Rückschlüsse auf das Tool zuläßt. So kennt die Anwendung nur die Schnittstelle und benötigt von dem Tool nur die Komponenten, die ausgeliefert werden müssen. Nur auf den Rechnern, auf denen die Dll codiert und compiliert wird, muss das Tool installiert sein. Generell ist dies ein Ansatz aus der mehrschichtigen Software Architektur. Wie man das macht, möchte ich am Beispiel von List & Label zeigen. Bitte beachten Sie die jeweiligen Lizenzbestimmungen unter denen Sie Ihre Software erworben haben.

 

Zuerst muss die Schnittstelle definiert werden. Sie hat eine Funktion fürs Drucken, für die Preview und zum Starten des Designers. Man kann den Report und die Daten übergeben. Die Daten unterscheide ich nach den Werten, die einmal an das Dokument übergeben werden (Values) und den Tabellen. Ich habe mich bei der Übergabe der Daten für Datatables entschieden, weil hier auch in leeren Feldern der Name und der Type des Wertes bekannt ist (Dafür gibt es auch andere Lösungen).

 

public interface IPrintService

{

      bool Print();

      bool PreView();

      bool PreView(IWin32Window owner);

      bool Edit();

      bool Edit(IWin32Window owner);

      DataTable Values { get; set; }

      List<DataTable> Tables { get; set; }

      string Report { get; set; }       

}

 

Der eigentliche Service implementiert das Interface und seine Methoden. Für den richtigen Aufruf des Tools implementiert man das Tool am besten mit dem Visual Studio Designer in einer Testform und übernimmt den so erzeugten Quelltext.

 

public bool Print()

{

      string file = GetReport();

      LL = new ListLabel();

 

      // das List & Label Objekt

      LL = new ListLabel();

      LL.CompressStorage = true;

      LL.Debug = combit.ListLabel9.LlDebug.Enabled;

      LL.DebugLogFilePath = null;

      LL.LicensingInfo = LicensingInfo;

      LL.MaxRTFVersion = 256;

      LL.PreviewControl = null;

 

      // Die Funktionen für die Werte

      LL.DefineVariables+=new DefineVariablesHandler(LL_DefineVariables);

      LL.DefineFields+=new DefineFieldsHandler(LL_DefineFields);

      LL.DefinePrintOptions+=new DefinePrintOptionsHandler(LL_DefinePrintOptions);´

 

// Drucken

      LL.Core.LlSetPrinterInPrinterFile(LlProject.List, file, LlPrinterIndex.AllPages, printDoc.PrinterSettings);

      LL.Print(LlProject.List, file, false, LlPrintMode.Normal, LlBoxType.NormalMeter, "", false, "");

 

      // oder Preview

LL.Print(LlProject.List, file, false, LlPrintMode.Preview, LlBoxType.NormalMeter, "", false, "");

 

// oder Report bearbeiten

LL.Design("Report Designer", LlProject.List, file, false);

 

      return true;

}

 

Die Übergabe der Daten an List & Label geschieht auf die übliche Art. Da mehrere Tabellen gedruckt werden sollen, List & Label das aber nicht kann, müssen bei der Datenübergabe alle Tabellen in einer zusammengefasst werden.

 
A A1 A2 A3
A A2 A2 A3
B B1 B2
B B1 B2
B B1 B2
C C1 C2
C C1 C2

 

Im List & Label Designer kann man dann mehrere Tabellen anlegen, dort die Spalten der Tabelle auswählen und in der Darstellungsbedingung die Zeilen mit dem richtigen Namen herausfiltern.

 

private void LL_DefineVariables(object sender, DefineElementsEventArgs e)

{

      // Alle Werte übergeben

      foreach (DataColumn col in _Values.Columns)

      {

            string n = col.ColumnName;

            object v = GetValue(_Values.Rows[0], n);

            LL.Variables.Add(n, v);

      }

      e.IsLastRecord = false;

TableIndex = -1;

      RowIndex = -1;

      // Erste Zeile in den Tabellen finden

      for (int i = 0; i < _Tables.Count && RowIndex == -1; i++)

      {

            if (_Tables[i].Rows.Count > 0)

            {

                  TableIndex = i;

                  RowIndex = 0;

                  break;

            }

      }

}

private void LL_DefineFields(object sender, DefineElementsEventArgs e)

{

      // Wenn alle Tabellen leer sind, dann eine Zeile mit Dummywerten übergeben

      if (RowIndex == -1)

      {

            LL.Fields.Add("TableName", "");

            for (int i = 0; i < _Tables.Count; i++)

            {

                  foreach (DataColumn col in _Tables[i].Columns)

                  {

                     &nb sp;  string n = _Tables[i].TableName + "_" + col.ColumnName;

                     &nb sp;  object v = GetNullValue(Tables[i], col.ColumnName);

                     &nb sp;  LL.Fields.Add(n, v);

                  }

            }

            e.IsLastRecord = true;

      }

      else

      {

            // Alle Spalten aller Tabellen übergeben

            LL.Fields.Add("TableName", _Tables [TableIndex].TableName);

            for (int i = 0; i < _Tables.Count; i++)

            {

                  foreach (DataColumn col in _Tables[i].Columns)

                  {

                     &nb sp;  string n;

                     &nb sp;  object v;

                     &nb sp;  if (i == TableIndex)

                     &nb sp;  {

                     &n bsp;  // von der aktuellen Tabelle die Werte übergeben

                     &n bsp;       n = _Tables[i].TableName + "_" + col.ColumnName;

                     &nb sp;       v = GetValue(_Tables[i].Rows[RowIndex], col.ColumnName);

                     &nb sp;  }

                     &nb sp;  else

                     &nb sp;  {

                     &nb sp;       // von allen anderen Dummywerte

                     &nb sp;       n = _Tables[i].TableName + "_" + col.ColumnName;

                     &nb sp;       v = GetNullValue(_Tables[i], col.ColumnName);

                     &nb sp;  }

                     &nb sp;  LL.Fields.Add(n, v);

                  }

            }

      }

      // Nächste Zeile suchen

      RowIndex += 1;

      if (RowIndex < _Tables[TableIndex].Rows.Count)

      {

            // keine Zeile mahr

            e.IsLastRecord = false;

      }

      else

      {

            // nächste Zeile suchen

            RowIndex = 0;

            do

            {

                  TableIndex += 1;

            }

            while (TableIndex < _Tables.Count && _Tables [TableIndex].Rows.Count == 0);

            // wurde eine Zeile gefunden

            e.IsLastRecord = !(TableIndex < Tables.Count);

      }

}

private object GetValue(DataRow row, string col)

{

      if (!row.IsNull(col))

            return row[col];

            if (row.Table.Columns[col].DataType == typeof(string)) return "";

      if (row.Table.Columns[col].DataType == typeof(int)) return 0;

      if (row.Table.Columns[col].DataType == typeof(double)) return 0;

      if (row.Table.Columns[col].DataType == typeof(bool)) return false;

      if (row.Table.Columns[col].DataType == typeof(DateTime)) return new DateTime(>1, 1, 1);

      return row.Table.Columns[col].DataType.ToString();

}

private object GetNullValue(DataTable tab, string col)

{

      if (tab.Columns[col].DataType == typeof(string)) return "";

      if (tab.Columns[col].DataType == typeof(int)) return 0;

      if (tab.Columns[col].DataType == typeof(double)) return >0;

      if (tab.Columns[col].DataType == typeof(bool)) return false;

      if (tab.Columns[col].DataType == typeof(DateTime)) return new DateTime(1, 1, 1);

      return tab.Columns[col].DataType.ToString();

}

 

Und schon ist man fertig.


Erfasst am: 10.08.2006 - Artikel-URL: http://www.devtrain.de/news.aspx?artnr=983
© Copyright 2003 ppedv AG - http://www.ppedv.de