WWS Synergie-Analyse

Shared by: UUzgpZav
Categories
Tags
-
Stats
views:
44
posted:
11/26/2011
language:
German
pages:
163
Document Sample
scope of work template
							Best Practices
                     Entwicklung mit VisualRPG.NET



                                               Version 2.9 vom 16.6.2008




                                                                           Autor:
                                                                           Christian Neißl




                                                                   -1-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
INHALTSVERZEICHNIS


1.            Ziel ....................................................................... 9
2.            Einstellungen in VS.......................................... 10
2.1.          Zeilennummern ............................................................. 10
2.2.          Herausheben von Strings und Schriftgröße einstellen . 10

3.            Einführung in DataGate ................................... 11
3.1.          Anlegen einer Datenbank ............................................. 11
3.2.          Arbeiten mit der Datenbank .......................................... 12
3.3.          Anlegen einer Bibliothek ............................................... 13
3.4.          Anlegen von Physischen Files ...................................... 13
3.4.1. Automatisches Generieren von DDS......................................... 14
3.5.          Anlegen von Logischen Files ........................................ 14
3.6.          Erfassen/Bearbeiten von Daten .................................... 15
3.7.          Einrichten der Bibliotheksliste ....................................... 15

4.            Windows-Projekte ............................................ 16
4.1.          Anlegen eines Projekts ................................................. 16
4.2.          Einstellen des Startprogramms .................................... 17
4.3.          Bearbeiten der Oberfläche ........................................... 19
4.3.1. Hauptmenü erstellen ................................................................. 19
4.3.2. Funktionstasten zuordnen ......................................................... 19
4.3.3. Eigenschaften der Seuerelemente einstellen ............................ 20
4.4.          Der Einstieg in den ProgrammCode ............................. 20
4.5.          Start des Programms im Debug-Modus ....................... 21
4.6.          Treiben wir’s bunt ......................................................... 22
4.7.          Bunt ist gut aber … ....................................................... 23

5.            Ein Projekt planen ............................................ 24


                                                                   -2-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
INHALTSVERZEICHNIS

5.1.          Konzepte....................................................................... 24
5.1.1. Schichtenmodell........................................................................ 24
5.1.2. Klassen ..................................................................................... 24
5.1.3. Namespaces ............................................................................. 24
5.2.          Programmieren ............................................................. 25
5.2.1. Dokumentation .......................................................................... 25
5.2.2. Coderegionen ........................................................................... 25
5.2.3. Programmparameter ................................................................. 26
5.2.4. Datenbankverbindung zur Laufzeit erstellen ............................. 26
5.2.5. Gültigkeit und Lebensdauer von Objekten ................................ 27
5.3.          Übergabe von Parametern, Pointer in .NET ................. 29
5.4.          Funktionen und Subroutinen ........................................ 30
5.4.1. Funktionsaufruf ......................................................................... 30
5.4.2. Aufruf einer Subroutine ............................................................. 31
5.4.3. Wann verwende ich Funktionen, wann Subroutinen ................. 31

6.            Ein Projekt umsetzen ....................................... 32
6.1.          Hinzufügen einer Klasse zu einem Projekt ................... 32
6.2.          Design von Klassen ...................................................... 33
6.2.1. Organisation ............................................................................. 33
6.2.2. Benennung ............................................................................... 34
6.3.          Organisieren des Programmcodes ............................... 34
6.4.          Eine Klasse codieren .................................................... 35
6.5.          Datenbankverbindung und Klassen deklarieren ........... 38
6.6.          Objekte im UserInterface programmatisch erzeugen ... 40
6.7.          Die TAG-Eigenschaft .................................................... 40
6.8.          Casten eines Objekts ................................................... 41
6.9.          Startzeit des Programms optisch verkürzen ................. 41
6.9.1. AssemblyInfo ............................................................................ 42


                                                                   -3-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
INHALTSVERZEICHNIS

6.9.2. Integration des SplashScreens ................................................. 42
6.10. Application.DoEvents ................................................... 43
6.11. Grids füllen.................................................................... 43
6.12. Grids abfragen .............................................................. 44
6.13. MemoryFile – der neue Subfile ..................................... 45
6.14. Die RANGE-Befehle ..................................................... 46
6.15. Eigene Ereignisse erstellen und abfragen .................... 47
6.16. Inhalte in Grids selektieren ........................................... 48
6.17. Contextmenü erstellen und abfragen ........................... 49
6.18. Formulare aufrufen ....................................................... 49
6.19. Combobox- WertListe füllen ......................................... 50
6.20. Defaultwerte in Comboboxen ....................................... 51
6.21. Cursorreihenfolge festlegen ......................................... 53
6.22. Komfortable Datenerfassung ........................................ 53
6.23. Steuerung aktive TabPage ........................................... 54
6.24. DefaultButton für Eingabetaste festlegen ..................... 54
6.25. Tastencode abfragen .................................................... 55
6.26. Array mit Werten füllen ................................................. 55
6.27. Array aus String füllen .................................................. 55
6.28. DataGridView oder DataRow mit Werten füllen ........... 56
6.29. Werte in DataGridView berechnen ............................... 56
6.30. Combo- und Checkboxen als DataGridView-Spalten ... 57
6.31. Felder an MemoryFile anhängen .................................. 59
6.32. Daten in einer DataTable sortieren ............................... 59
6.33. Das TreeView ............................................................... 60
6.34. Kalenderwochenberechnung, Rekursionen .................. 62
6.35. Überladen von Funktionen (OO-Technik) ..................... 63


                                                                   -4-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
INHALTSVERZEICHNIS

6.36. Programmaufrufe .......................................................... 64
6.37. AS/400-API’s aufrufen .................................................. 64
6.37.1.          Benutzer des DG-Servicejobs auslesen ................................. 64
6.37.2.          DTAQ auslesen ..................................................................... 64
6.38. Datenstrukturen als iSeries-Programmparameter ........ 64
6.38.1.          Lösungsbeispiel Schnittstellenprogramme ............................. 65
6.39. Datenzugriff .................................................................. 67
6.40. Routinen aus iSeries-Programmen .............................. 67
6.41. F-Spec’s........................................................................ 68
6.42. Feld- und KeyDeklarationen ......................................... 68
6.43. Datenstrukturen ............................................................ 68
6.44. Datenstrukturen als Key – Teilkeys mit %KDS ............. 69
6.45. Datenstrukturen als Programmparameter .................... 71
6.46. Hilfsfunktionen .............................................................. 74
6.47. Gültigkeitsprüfungen ..................................................... 75
6.48. Drucken ........................................................................ 76
6.48.1.          RPG.NET – Printfiles ............................................................. 76
6.48.2.          Druckschleife ......................................................................... 77
6.49. Einbinden von 3rd-Party Komponenten ........................ 77
6.49.1.          Grafikdarstellung .................................................................... 78
6.50. Embedded SQL und offener Datenzugriff .................... 81
6.50.1.          Namespaces und Deklarationen ............................................ 82
6.50.2.          Event-Subroutinen ................................................................. 82
6.50.3.          ODBC .................................................................................... 83
6.50.4.          SQL-Server ............................................................................ 84
6.50.5.          OLEDB .................................................................................. 85

7.            Microsoft Office einbinden .............................. 86



                                                                   -5-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
INHALTSVERZEICHNIS

7.1.          Einfache Excel-Ausgabe .............................................. 86
7.2.          Performante Ausgabe einer Tabelle in Excel ............... 88
7.3.          Excel automatisieren .................................................... 88
7.4.          Beispielanwendung mit Excel ....................................... 89
7.5.          Lassen wir uns von Excel helfen .................................. 90
7.6.          Excel sieht Daten anders .............................................. 90
7.7.          Der Standard von Office-Produkten ............................. 90
7.8.          XML in Office ................................................................ 91
7.9.          Beispielanwendung mit Excel ....................................... 91
7.9.1. Verarbeitungsschritte ................................................................ 91
7.9.1.1.         Einlesen .................................................................................................. 91
7.9.1.2.         Prüfen ..................................................................................................... 92
7.9.1.3.         Update .................................................................................................... 92
7.9.2. Programmcode ......................................................................... 92
7.9.2.1.         Referenzen ............................................................................................. 92
7.9.2.2.         Deklarationen.......................................................................................... 92
7.9.2.3.         Constructor ............................................................................................. 94
7.9.2.4.         Events ..................................................................................................... 95
7.9.2.5.         Logik ....................................................................................................... 96
7.10. Office-Funktionen herausfinden ................................. 105
7.10.1.          Makro einsetzen................................................................... 105
7.10.2.          Vorsicht bei Formeln ............................................................ 106
7.11. Integration von Word und iSeries mit RPG.NET ........ 107
7.11.1.          Konzept ............................................................................... 107
7.11.2.          Umsetzung........................................................................... 108
7.11.2.1.        Kopf- und Fussinformationen ................................................................ 108
7.11.2.2.        Artikelpositionen ................................................................................... 109
7.11.3.          Das Ergebnis ....................................................................... 113
7.11.4.          Technische Voraussetzungen .............................................. 114




                                                                   -6-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
INHALTSVERZEICHNIS

7.11.5.          Programmcode Word ........................................................... 114
7.11.6.          Text in RTF-Box formatieren ................................................ 117

8.            Webseiten ....................................................... 122
8.1.          Anlegen eines Projekts ............................................... 122
8.2.          Anlegen einer Masterpage ......................................... 123
8.3.          Anlegen von Webseiten .............................................. 124
8.4.          Startseite festlegen ..................................................... 125
8.5.          Seitengröße festlegen ................................................ 125
8.6.          Seiten zeichnen .......................................................... 126
8.7.          Projektorganisation ..................................................... 126
8.8.          Programmierung ......................................................... 127
8.9.          Gestaltung .................................................................. 129
8.10. Global.ASAX ............................................................... 130

9.            WebServices erstellen ................................... 130
9.1.          Programmierung ......................................................... 131
9.2.          Bilder in XML-übertragen ............................................ 132

10.           WebServices nutzen ...................................... 133
11.           ASNA-Datagate Bibliotheksfunktionen ........ 135
11.1. DG-Datenbanken auslesen ........................................ 135
11.2. iSeries-Bibliotheksliste auslesen ................................ 136
11.3. Objektliste der iSeries-Bibliotheken auslesen ............ 137
11.4. Dateifeldbeschreibung auslesen ................................ 138

12.           Windows-Dienste programmieren ................ 140
12.1. Was ist ein Windows-Dienst ? .................................... 140
12.2. PROGRAM.VR - Basis des Dienstes ......................... 141



                                                                   -7-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
INHALTSVERZEICHNIS

12.3. ProcessInstaller.VR – Constructor ............................. 142
12.4. EnCryption.VR – Hier wird gearbeitet ......................... 143
12.5. MD5EnCryption.VR – Crypt nach MD5 ...................... 144
12.6. Den Windows-Dienst installieren / deinstallieren ........ 145

13.           Hintergrundverarbeitung ............................... 147
13.1. Start des BackGroundWorker-Jobs ............................ 148
13.2. Der Programmaufruf für den Worker-Job ................... 149
13.3. Der Programmende des Worker-Job’s ....................... 149

14.           DataGate aus VB oder C# verwenden .......... 150
14.1.1.          Deklarationen in VB – Vergleich RPG .................................. 150
14.1.2.          Sequentieller Zugriff in VB – Vergleich RPG ........................ 151
14.1.3.          Direktzugriff in VB – Vergleich RPG ..................................... 153
14.2. WinDialog – Aufruf von iSeries-Programmen ............. 154
14.2.1.          Deklaration von iSeries-Programmen in VB ......................... 154
14.2.2.          Aufruf von iSeries-Programmen in RPG............................... 155
14.2.3.          Aufruf von iSeries-Programmen in VB ................................. 155

15.           Die Beispielanwendung WinDialog in C#.NET ...... 156
16.           Programminstallation .................................... 159
16.1. Versionskonflikte zwischen DLL-Versionen lösen ...... 159

17.           Interfaces ........................................................ 160
17.1. Beispielanwendung UseInterfaces ............................. 161
17.2. Referenzstrukturen ..................................................... 163




                                                                   -8-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES


1. Ziel
Dieser Leitfaden hat das Ziel Entwickler mit Beispielen und Empfehlungen zu
unterstützen.

In DotNet haben wir alle das Problem aus den vielfältigen Lösungsansätzen zu einer
Aufgabenstellung den bestmöglichen Weg zu finden. Wie in RPG auch, gibt es nicht
immer ‚den besten Weg’ oder ‚die ideale Lösung’ sondern ein Spektrum an Ansätzen.

Neben grundlegenden Informationen finden Sie auch Anregungen und Empfehlungen
die Ihnen helfen mit RPG.NET in kurzer Zeit produktiv zu werden.

Die Beispiele stammen aus verschiedenen Projekten sind aber zum Thema in sich
abgeschlossen.

Gerne stehe ich für weitere Informationen zur Verfügung, ich freue mich aber auch über
Ihre Kritik und Verbesserungsvorschläge.




                                                                   -9-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
2. Einstellungen in VS


2.1. Zeilennummern

                                               Menü EXTRAS – OPTIONEN – TEXTEDITOR – ALLE
                                               SPRACHEN.

                                               Hier
                                               Zeilennummern
                                               anhaken.




2.2. Herausheben von Strings und Schriftgröße einstellen
Unter SCHRIFTARTEN und FARBEN – für String Farbe ROT einstellen. Auch die
Schriftart und die Größe wird hier eingestellt.




                                                                  -10-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
3. Einführung in DataGate
DataGate ist die Datenbankschicht
die zwischen RPG.NET und den
Datenbanken der iSeries, SQL-
Server und der ASNA-eigenen
Windows-Datenbank Acceler8DB
liegt.

Wir werden in diesem Projekt auf
DataGate und der Acceler8DB
aufbauen da sie ebenso arbeitet
wie die iSeries-DB.
Es kann somit lokal entwickelt
werden, die Anwendung kann dann
per Definition gegen iSeries, SQL-
Server oder Acceler8DB gefahren
werden.




3.1. Anlegen einer Datenbank
Mit den DB-Wizard legen wir uns eine Projektdatenbank an.




Mithilfe des Browserfensters kann                                  ein
Verzeichnis für die DB gewählt werden.


                                                                  -11-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
Das Verzeichnis muss ANGELEGT und LEER sein.




3.2. Arbeiten mit der Datenbank
Mit     ‚DATABASE-OPEN’
werden die vorhandenen
Datenbanken angezeigt.
Im       Fenster   ‚Select
Database Names’ finden
wir die Datenbanknamen.

Hier können iSeries-DB,
SQL-Server und lokale DB
ausgewählt werden. Es gibt
für den DB-Manager keinen
Unterschied da er sich über
alle Datenbanken legt und
sie alle verwalten kann.




                                                   Mit Hilfe der Optionen wird beim ersten Öffnen die
                                                   Standardeinstellung für die Sicht gewählt. Die neue DB
                                                   enthält nur die QTEMP.




                                                                  -12-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
3.3. Anlegen einer Bibliothek
Über ‚Objekt – Create Library’ wird eine Bibliothek angelegt.




3.4. Anlegen von Physischen Files
Über    ‚Definition  –    Create
Database File – Physical’ werden
physische Dateien angelegt.




Die Arbeit mit den Datenfeldern ist in der WorkArea des DB-Managers zu machen.
ACHTUNG – AS/400 und Acceler8DB – Datenbanken erlauben keine NULL-Felder.




Mit ADD werden die Felder hinzugefügt,
gespeichert wird mit DONE. Keyfelder werden mit
ADDKEY bestimmt.


                                                                  -13-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES

Um den File in der Bibliothek zu
erstellen genügt es die Datei mit der
Maus von der WORKAREA in die
gewünschte Bibliothek zu ziehen und
die Erstellung zu bestätigen.




3.4.1.         Automatisches Generieren von DDS
ACHTUNG – für Files die Sie auf der iSeries erstellen kann automatisch eine DDS
generiert werden. Beachten Sie die Einstellung in VIEW – OPTIONS.




3.5. Anlegen von Logischen Files




Logische Files werden über Physischen erstellt.
Die Bearbeitung von Keyfeldern erfolgt wie bei
den PF in der WorkArea.




                                                                  -14-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
3.6. Erfassen/Bearbeiten von Daten
Unter der Bibliothek wird der File dargestellt,
innerhalb des Files finden wir die Members.
Durch Links-Klick auf Member wird eine
Browser-Übersicht angezeigt, mit Rechts-Klick
öffnet sich ein KontextMenü über das die
Bearbeitung der Daten ausgewählt werden
kann.




3.7. Einrichten der Bibliotheksliste

Bei geöffneter Datenbank                         kann        die      Bibliotheksliste
eingerichtet werden.


Unter der Karte INITIAL
die Bibliotheksliste Zeile
für Zeile eingeben und
OK drücken.




                                                                   -15-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
4. Windows-Projekte


4.1. Anlegen eines Projekts
Nach     Start   von
VisualStudio2005
das Menü DATEI-
NEU-PROJEKT
auswählen.




                                                                               Das Verzeichnis ‚Workshop* unter
                                                                               Laufwerk C anlegen.




Projekttyp ‚WindowsApplication’                         und
Name ‚WWS’ angeben und                                  OK
drücken.




                                                                           Das Ergebnis ist ein leeres Projekt.




                                                                  -16-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
4.2. Einstellen des Startprogramms
Wir wollen ein Projekt mit mehreren Formularen
erstellen. Das Hauptformular soll nicht wie
Standardmäßig angelegt ‚Form1’ heißen sondern
‚frmMain’.
Deswegen löschen wir das Form1 per Mausklick-
Rechts mit ENTFERNEN und LÖSCHEN.
Damit wird das Formular unwiderruflich entfernt.




Danach legen wir mit HINZUFÜGEN eine neue
Windows-Form mit dem Namen ‚frmMAIN’ an.




                                                                  -17-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES

                                       Das   Startformular für   das Projekt wird in   den
                                       PROJEKTEIGENSCHAFTEN festgelegt. Wir stellen    das
                                       Formular WWS.frmMAIN ein.




Der Text wird auf ‚WWS-
Hauptprogramm
festgelegt.

Unter ICON wählen wir
die DISC aus dem ICON-
Verzeichnis.

(Liegt auf CD bei)




                                                                  -18-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
4.3. Bearbeiten der Oberfläche


4.3.1.         Hauptmenü erstellen

Durch KLICK und Ziehen des
Symbols wird das Control auf
der Oberfläche des Formulars
erstellt.




Unsere Menüstruktur sollte am Ende so
aussehen wie hier gezeigt.
Den Unterstreichungsstrich, zugleich auch
der Tastencode wird durch ein ‚&’ vor dem
gewünschten Zeichen erreicht.
Den Strich zwischen EINSTELLUNGEN
und ENDE bekommt man durch Eingabe
eines ‚-‚ im Text.



4.3.2.         Funktionstasten zuordnen

Jedes Windows-Programm
sollte auch ohne Maus
problemlos bedienbar sein.

Darum werden wir in den
Eigenschaften unseren
Menüeinträgen F-Tasten
zuordnen.

Der Menüeintrag bietet dazu
die Eigenschaft
SHORTCUTKEYS.
Für ENDE stellen wir F3 ein




                                                                  -19-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
4.3.3. Eigenschaften der Seuerelemente einstellen

Jedes Steuerelement bekommt einen eindeutigen
Namen wie ein Feld in einem Bildschirmformat.

Der Name wird von VS generiert, wir möchten natürlich
die Namen selbst festlegen.

Neben dem Namen können jede Menge Eigenschaften
eingestellt werden. Jedes Steuerelement hat auf Grund
seiner Eigenheiten auch bestimmte Eigenschaften die
in dem EIGENSCHAFTEN-Dialog von VS zur
ENTWICKLUNGSZEIT eingerichtet werden.

Alle Eigenschaften können auch während der Laufzeit
des Programms festgelegt werden. Beispiele folgen.

Wir fahren mit der Benennung der Steuerelemente                            fort
und benennen die horizontal angelegten Einträge                            mit
dem Prefix ‚mnu’ und Ihrem Namen da sie                                     ein
Hauptmenü, die vertikalen Einträge als ‚itm’ da                             sie
einen Eintrag darstellen.

Also: mnuDatei, mnuBelege, mnuHilfe,
itmEinstellungen, itmEnde.

Im DropDown des Eigenschaften-Editors
finden  wir alle  Controls   unseres
Formulars.




4.4. Der Einstieg in den ProgrammCode
Jedes Steuerelement hat nicht nur Eigenschaften sondern auch Ereignisse. VS stellt uns
ein ‚Default’-Ereignis zur Verfügung. Durch DoppelKlick auf das Steuerelement kommen
wir in den Code und werden in eine sog. Ereignis-Subroutine befördert.


Der ‚Blitz’ in den Steuerelementen wechselt
von der Eigenschaften- in die Ereignissicht.
Wie wir sehen hat VS die Ereignissubroutine
bereits erstellt und zeigt sie als itmEnde_Click
in den Ereignissen an.




                                                                  -20-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
In diese
Programmroutine
tragen wir den Befehl

*this.Close()

ein.

Damit haben wir für
den Menüeintrag eine
Anweisung zum
schließen des
Programms
festgelegt.



4.5. Start des Programms im Debug-Modus
Das Programm ist nun ausführbar. Wir starten mit F5 oder dem Pfeil wie in den
Symbolen vom guten alten Kassettenrekorder.




Das Programm fährt hoch, das Menü lässt sich
bedienen, durch Menüklick oder F3 endet das
Programm
Wir haben ja auch sonst noch nichts
programmiert, aber immerhin fragen wir bereits F3
ab.

Vergleichen Sie das mit dem Aufwand den Sie haben um das in einem iSeriesRPG-
Programm zu machen.

                                                                  -21-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
4.6. Treiben wir’s bunt
Was wären Windows-Programme ohne Icons? Gar Nichts.
Wir werden nun den Menüs möglichst sinnige Icons zuordnen. Auf der CD ist ein
Verzeichnis mit Icons, wir nehmen für die Menüeinträge die im Verzeichnis 16x16.

Im Eigenschaften-Editor klicken wir auf die
Eigenschaft IMAGE.

Mit dem Auswahlbutton öffnen wir ein Fenster in
dem     wir  auf     LokaleRessource       und
IMPORTIEREN drücken.




                                                                           Wir     kommen        in    einen
                                                                           OpenFileDialog und öffnen dort das
                                                                           Verzeichnis mit den Icons 16x16
                                                                           für ENDE wählen wir das Icon
                                                                           DELETE_16.ico




ACHTUNG – im Dropdown des Öffnen-
Dialogs zuerst ‚ALLE DATEIEN’
einstellen.




Weisen Sie den Menüelementen Icons nach Ihrem
Geschmack zu.




                                                                  -22-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
4.7. Bunt ist gut aber …
Bedenken Sie bei Ihren Projekten dass Sie sich in der Windows-Welt befinden in der
sich Moden und Zeitgeist widerspiegeln.
Sie können mit VS jedes Programm einfärben wie es Ihnen gefällt, sie verlieren aber
dadurch die Möglichkeit den Look&Feel von Betriebssystemversionen zu erben.

Windows-Programme passen sich im Erscheinungsbild dem Betriebssystem automatisch
an. Wenn nun ein Benutzer ein besonderes Farbschema oder ein neues Betriebssystem
eine besondere Standardfarbe für Steuerelemente wählt bleibt die Darstellung Ihres
Programms auf dem von Ihnen gesetzten Standard.
Die Konsequenz davon ist dass Ihre Programme in kürzeren Abständen überarbeitet
werden müssen als vom Programmcode her nötig.

Halten Sie folgende Regeln für Bildschirmdialoge ein:

         Weisen Sie Farben für Steuerelemente nur im Ausnahmefall zu
         Orientieren Sie sich an der Optik von Microsoft-Programmen
         Geben Sie Standard-Microsoft-Steuerelementen den Vorzug vor zugekauften
          oder geschenkten 3rd-Party Tools
         Umso einfacher und selbsterklärender ein Programm ist umso weniger Probleme
          haben Sie




                                                                  -23-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
5. Ein Projekt planen


5.1. Konzepte


5.1.1.         Schichtenmodell
Die wichtigste Regel ist dass die Anwendung in Schichten aufzubauen ist. Es geht
darum die Zugriffe auf die Daten von der Dialogschicht zu trennen.
Der Vorteil ist dass die Logik als Basis erhalten bleibt während die Dialoge dem Wandel
der Zeiten stärker Rechnung tragen müssen und kürzeren Veränderungszyklen
unterworfen sind.

Ein weiterer Vorteil dieses Schichtenmodells unter DotNet ist dass die Anwendungen die
Logik in RPG.NET verwenden nicht zwingend in RPG geschrieben sein müssen.

Sehr oft werden in RPG.NET nur die Logikmodule erstellt, die Dialoge werden in VB oder
C# geschrieben.

In diesem Modell sehen Sie wie eine Windows- und eine Webanwendung ein
Logikmodul verwenden.




                                                           Logikmodul




                                                           Datenbank



5.1.2.         Klassen
In DotNet ist alles ‚Klasse’ – dem entsprechend muss auch unsere Anwendung ein
sauberes Klassenmodell mitbringen.
Das Verstehen des Klassenmodells ist für den RPG-Programmierer kein Problem sofern
er mit strukturierter Programmierung vertraut ist.


5.1.3.         Namespaces
Die Funktionsbibliotheken werden in DotNet in ‚Namespaces’ organisiert. Um eine gute
Anwendungsarchitektur zu entwickeln werden unsere Programme auch mit Namespaces
arbeiten.

                                                                  -24-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
5.2. Programmieren


5.2.1.         Dokumentation
Wie allgemein üblich sollte
man           Programme
beschreiben.
In RPG.NET wird mit // eine
Zeile auskommentiert.

Ich habe mir angewöhnt
meine      Dokumentationen
mit /// zu markieren da sie
dadurch       von     einer
auskommentierten
Programmzeile
unterschieden       werden
kann.


5.2.2.         Coderegionen
VS bietet mit den Coderegionen eine sehr praktische Möglichkeit Code der nicht
gebraucht wird aus der Optik zu nehmen.

Mit den Coderegionen werden Bereiche im Sourcecode thematisch gegliedert. Durch
Klick auf den Knoten werden die Bereiche angezeigt oder versteckt.




                                                                  -25-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
5.2.3. Programmparameter
DotNet bietet ein organisiertes Umfeld, dazu gehört auch dass mit Programmparametern
in einer bestimmten Art und Weise umgegangen wird.

Die APP.CONFIG ist eine XML-Datei die für den Zweck vorgesehen ist, die Parameter
werden mit dem AppSettingsReader eingelesen, siehe nächstes Kapitel.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>

<!--  Eigene Einstellungen
          Platz für Programmparameter
    -->
<appSettings>

          <!--    Parameter für DB-Verbindung zur Laufzeit -->
          <add   key="DBName" value="*Public/MAC"/>
          <add   key="Server" value="192.168.1.6"/>
          <add   key="Label" value="DB2"/>
          <add   key="Port" value="5042"/>
          <add   key="User" value="MAC"/>
          <add   key="PW" value="MAC"/>
          <add   key="Description" value="for demonstration purposes"/>
          <add   key="Libl" value="ZMODERN;"/>

          <!--    ProgrammParameter -->
          <add   key="Fehlermail" value="mailto:c.neissl@niceware.at?Subject=Fehlermeldung-MT800Win"/>
          <add   key="WartungsTimeOutSek" value="30"/>
          <add   key="PicturePath" value="C:\NiceWare\ASNA\Interessenten\MACSchweiz\Bilder\"/>
          <add   key="PictureExtension" value=".jpg"/>
          <add   key="Vertrag" value="C:\NiceWare\ASNA\Interessenten\MACSchweiz\Vorlagen\Vertrag.doc"/>

          <add key="ListenTitelBild" value="C:\NiceWa..\ASNA\Intere..\MACSch..\Graf..\Mac-Logo.bmp"/>

  </appSettings>

</configuration>



5.2.4.         Datenbankverbindung zur Laufzeit erstellen
Um am Client möglichst wenig installieren zu müssen kann man das Programm so
konzipieren dass die DB-Verbindung die zur Laufzeit aus Parametern erstellt wird.

Diese DB-Verbindung wird dann an die Logikmodule weitergegeben dass die
Anwendungsprogramme nur mehr die Logikmodule benötigen und sich nicht um die DB-
Verbindung kümmern müssen.

Die Parameter werden bei Programmstart aus der APP.CONFIG eingelesen, dann wird
die DB-Verbindung aufgebaut.
Wenn unter dem Usernamen der Sonderwert *PROMPT eingestellt ist muss sich der
Benutzer in einem Login-Fenster anmelden. Es kann natürlich ein Standarduser
festgelegt werden.
/// wird bei Programmstart ausgeführt
BegSr Form1_Load Access(*Private) Event(*this.Load)
       DclSrParm sender Type(*Object)
       DclSrParm e Type(System.EventArgs)

          /// Hilfsfelder für Bibliotheksliste
          DclFld cSplit Type(*Onechar) Inz(";")
          DclFld strLibl *string



                                                                  -26-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
          /// DB-Verbindungsparameter aus APP.CONFIG einlesen
          With dbSession
          .DBName        = asrApp.GetValue("DBName", Type.GetType("System.String")) *as String
          .Server        = asrApp.GetValue("Server", Type.GetType("System.String")) *as String
          .Label         = asrApp.GetValue("Label", Type.GetType("System.String")) *as String
          .Port          = asrApp.GetValue("Port", Type.GetType("System.String")) *as String
          .User          = asrApp.GetValue("User", Type.GetType("System.String")) *as String
          .Password      = asrApp.GetValue("PW", Type.GetType("System.String")) *as String
          .Text          = asrApp.GetValue("Description", Type.GetType("System.String")) *as String
          strLibl        = asrApp.GetValue("Libl", Type.GetType("System.String")) *as String
          /// Bibliotheksliste aufbereiten
          .InitialLibl= strLibl.Split(cSplit)
          EndWith

          /// DB-Verbindung aufbauen mit strukturierter Fehlerbehandlung
          Try
                 stb.Text = "Baue Verbindung mit " + dbSession.DBName + " auf ..."
                 Connect dbSession
          Catch Name(dgEx) Type(ASNA.DataGate.Common.dgException)
                 MsgBox Msg("Datagate meldet: " + %Unicode(13) + dgEx.Message) +
                 Icon(*Stop) Title("Datenbankverbindung zu " + dbSession.DBName + " fehlerhaft")
                 LeaveSr
          Catch Err Type(System.Exception)
                 MsgBox Msg(Err.Message) Icon(*Stop) Title ("Verbindung zu Datenbank " +
                 bSession.DBName + " fehlerhaft")
                 LeaveSr
          EndTry

…
EndSr



5.2.5.          Gültigkeit und Lebensdauer von Objekten
Grundregeln:
   o Objekte nur so lange bestehen zu lassen solange es unbedingt nötig ist
   o je weniger ein Benutzer einer Klasse über die internen Abläufe wissen muss umso
      besser ist es

Die Lebensdauer des Objekts beginnt mit der Instanzierung und endet mit dem
Programmende bzw. Verlassen des Gültigkeitsbereiches des Objekts.

Die Gültigkeit von Objekten kann über die Eigenschaft Access eingestellt werden.
Es gibt hier folgende Möglichkeiten:

 Access
          Optional. The type of access to the field. *PRIVATE is the default.

                *PRIVATE (default) - access to the field is accessible only from within their declaration
                 context, including from members of any nested types, for example from within a nested
                 procedure or from an assignment expression in a nested enumeration.

                *PUBLIC - access to the field is accessible from anywhere within the same project, from other
                 projects that reference the project, and from an assembly built from the project.

                *PROTECTED - access to the field is accessible only from within the same class, or from a
                 class derived from this class.

                *INTERNAL - access to the field is accessible from within the same project, but not from
                 outside the project.


*PRIVATE

                                                                  -27-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
Gilt im Kontext der Deklaration und darunter.
Wird ein Objekt mit *PRIVATE deklariert ist es von außen nicht sichtbar. Wird ein Objekt
an oberster Ebene einer Klasse deklariert ist es innerhalb dieser Klasse überall
verfügbar. Wird es einer Subroutine deklariert so lebt das Objekt (im Normalfall) von der
Instanzierung bis zum Ende der Subroutine.

*PUBLIC
Gilt überall, ist auch aus jeder Klasse die das Objekt verwendet sichtbar.

*PROTECTED
Objekt ist nur in dieser Klasse oder einer abgeleiteten Klasse sichtbar.

*INTERNAL
Objekt ist nur in dieser Assembly sichtbar, ähnlich wie *PUBLIC, aber nicht von außen.




                                                                                      Gelten in ganzer Klasse
                                                                                      da sie mit *PRIVATE
                                                                                      (Default) an oberster
                                                                                      Ebene in der Klasse
                                                                                      deklariert sind.




                                                                           Gelten nur in dieser Subroutine und
                                                                           werden bei nächstem Aufruf wieder
                                                                           neu initialisiert.




                                                                  -28-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
5.3. Übergabe von Parametern, Pointer in .NET
Es gibt wie in jeder modernen Entwicklungsumgebung die Möglichkeit Parameter als
Wert oder Verweis (Pointer auf ein Objekt) zu übergeben.
Diese Vorgangsweise nennt man Call by Value (Wertübergabe) oder Call by Reference
(Verweis mit Pointer).
Hier sei angemerkt dass es .NET keine Pointeroperationen anbietet da es gegen die
Plattformunabhängigkeit ist. Natürlich arbeitet .NET intern mit Pointer.

In unserem Beispiel sieht der Aufruf der Logon-Methode so aus:
                    If cUser.Logon(*ByRef cUserDaten, txtKennwort.Text)


Hier werden also 2 Parameter übergeben, das Objekt cUserDaten per Reference und
das Kennwort per Value.

Auf der Empfänger-Seite werden die Parameter so übernommen:
          BegFunc Logon Type(*boolean) Access(*Public)
                 DclSrParm     cUserDaten             Type(UserDaten)        By(*REFERENCE)
                 DclSrParm     cKennwort              Type(*char)    Len(20)



Das heißt dass die UserDaten nicht wirklich übertragen werden sondern über einen
Pointer das Objekt des Aufrufers direkt bearbeitet wird.



  Aufrufer:                                                         Klasse User
                                                                    BegFunc Logon   Type(*boolean) Access(*Public)


                                       UserDaten                    DclSrParm     cUserDaten      Type(UserDaten)
                                                                           By(*REFERENCE)

                                                                    DclSrParm      cKennwort      Type(*char)
                                                                           Len(20)
                                       Kennwort
                                                                                       Kennwort
  If cUser.Logon(*ByRef cUserDaten,
  txtKennwort.Text)




Hier wird deutlich was *ByRef bewirkt. Die aufgerufene Methode arbeitet mit dem
Original-Objekt, im anderen Fall wird eine Kopie des aufrufenden Objekts erstellt.

Ale Empfehlung kann gesagt werden dass ein CallByValue zu bevorzugen ist wenn die
übergebenen Daten verändert werden und nicht an den Aufrufer zurückgegeben werden
müssen.

CallByRef verwende ich in diesem Beispiel weil ich das Objekt UserDaten bearbeite und
die Daten an den Aufrufer übergeben möchte.
Da die Funktion mir außerdem über den Rückgabewert noch mitteilt ob die Operation
erfolgreich war oder nicht verwende ich CallByRef.
                                                                  -29-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
5.4. Funktionen und Subroutinen
In RPG.NET wird zwischen Funktionen und Subroutinen unterschieden. Funktionen
geben einen Wert an den Aufrufer zurück, Subroutinen nicht. Parameter können von
beiden übernommen werden.


5.4.1.         Funktionsaufruf
          BegFunc Logon Type(*boolean) Access(*Public)
                 DclSrParm     cUserDaten             Type(UserDaten)        By(*REFERENCE)
                 DclSrParm     cKennwort              Type(*char)    Len(20)


Der Name der Funktion ist ‘Logon’, der Rückgabewert ist vom Typ ‘*boolean’, also Wahr
oder Falsch. In RPG wären das die Indicator (*INxx) mit ‚1’ für Wahr und ‚0’ für Falsch.
Hier werden zwei Parameter übergeben.

Der Aufruf dieser Funktion ist eigentlich eine Abfrage
                    If cUser.Logon(*ByRef cUserDaten, txtKennwort.Text)

                               …

                    Else

                               …

                    Endif


Da die Funktion einen Wert zurückgibt der aussagt ob die Operation erfolgreich war oder
nicht kann im aufrufenden Code direkt auf den Rückgabewert Bezug genommen werden.

Eine umständlichere aber ebenso mögliche Variante wäre:
                    DclFld bWarAufrufOK Type(*boolean)
                    bWarAufrufOK = cUser.Logon(*ByRef cUserDaten, txtKennwort.Text)
                    If bWarAufrufOK = *true
                               …
                    Else
                               …
                    Endif


Jeder Funktionsaufruf muss seinen Rückgabewert an den Aufrufer zurückliefern. Das
übernimmt der Befehl LeaveSr.
Im Programmcode muss der Typ deklariert und der Wert zugewiesen werden.
          ///------------------------------------------------------------------
          /// Controller über das Einlesen der OPDaten, Übergabe per Reference
          ///------------------------------------------------------------------
          BegFunc GetOPData     Type(*boolean) Access(*Public)
                 DclSrParm      cUserDaten     Type(UserDaten)       By(*REFERENCE)
                 DclSrParm      cOPDaten       Type(OPDaten)         By(*REFERENCE)

                    DclFld               bOK                  Type(*boolean)   Inz(*false)

                    ...

                    LeaveSr bOK

          EndFunc




                                                                  -30-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
5.4.2. Aufruf einer Subroutine
In dieser Programmzeile wird die Subroutine ‚GetLinkTable’ aufgerufen der ein
Parameter per Reference übergeben wird.
                                         /// Einlesen der Linktabelle
                                         GetLinkTable(*ByRef cUserDaten)


Hier ist der Beginn der Subroutine mit der Übernahme des Parameters.
          ///------------------------------------------------------------------
          /// Einlesen der Linktabelle für das Portal über den iSeries -
          /// Usernamen - wird nichts gefunden bleibt die Tabelle *NOTHING
          ///------------------------------------------------------------------
          BegSR GetLinkTable
                 DclSrParm      cUserDaten     Type(UserDaten)       By(*REFERENCE)




5.4.3.         Wann verwende ich Funktionen, wann Subroutinen
Muss jeder für sich entscheiden, generell verwende ich gerne Funktionen die einen
Bool’schen Wert zurückgeben damit ich im aufrufenden Programm erkenne ob der Aufruf
erfolgreich war oder nicht.
Es hängt von der Aufgabe ab ob ich Parameter übergebe oder global gültige Variable
verwende.

Generell gilt soviel wie möglich zu kapseln und eindeutige Schnittstellen zwischen den
Programmteilen festzulegen.

Umso mehr global gültige Variablen im Programm vorhanden sind umso schwieriger ist
es zu warten.




                                                                  -31-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6. Ein Projekt umsetzen


6.1. Hinzufügen einer Klasse zu einem Projekt
Wie wir bereits im Beispiel mit dem
Formular gesehen haben können
verschiedenste Elemente zu einem
Projekt hinzugefügt werden.
In unserem Beispiel brauchen wir eine
Klasse die Business-Logik enthält um
den Datenzugriff von der Oberfläche zu
trennen.

Die KLASSE wird direkt angeboten,
also nehmen wir sie auch aus dem
Kontextmenü.




Wir benennen Sie
clsParameter.vr und drücken
HINZUFÜGEN.

Das Ergebnis ist die leere
Klasse.




                                                                  -32-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.2. Design von Klassen


6.2.1.         Organisation
Klassen haben die Aufgabe Business-Logik zu kapseln und Ergebnisse zu liefern.
Umso weniger ein Benutzer einer Klasse über die darin enthaltene Logik wissen
muss umso besser ist es.

Klassen sollten außerdem themenbezogen und weniger datenbezogen sein. Es geht
darum einen ‚BusinessCase’ abzudecken.
Ein solcher kann das zur Verfügungstellen von Belegdaten sein die in einer Klasse
übergeben werden.
Die Datenklasse die zwischen Oberfläche und Logik hin- und her gereicht wird kann
Daten aus den unterschiedlichsten Quellen enthalten, wichtig ist dass die Daten aus
einem logischen Gesichtspunkt zusammengefasst werden.




                                                     Datenklasse             Logikmodul




                                                                             Datenbank

Beispiel Belegdaten:

          Die Datenklasse enthält: Belegkopf
                                   Belegpositionen


                                         Die Logik bietet Funktionen wie: LeseBeleg()
                                                                          SchreibeBeleg()
                                                                          DruckeBeleg()


Um diese Funktionen anbieten zu können müssen verschiedenste Dateien verarbeitet
werden.
Wenn Sie nun eine Datenklasse für Kopfdaten und eine weitere für Positionsdaten
anbieten würden so müsste der Benutzer der Klasse wissen welche Daten
zusammengehören um eine Funktion ausführen zu können.

Beachten Sie dass es nicht immer so sein wird dass die Oberfläche von Programmen
von Entwicklern erstellt wird denen die Funktionen des Programms klar sind und die aus
diesem Verständnis heraus arbeiten.



                                                                  -33-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.2.2. Benennung
Klassen und deren Eigenschaften und Funktionen sollen sprechend benannt werden.
Auch hier gilt es daran zu denken dass einem Entwickler die Bezeichnung ‚ARIKELNR’
mehr sagt als BPATNR.
Zum einen ist dem Benutzer einer Klasse die Herkunft des Datenfeldes völlig egal da er
sich nur um die Datenklasse kümmern braucht.
Zum anderen muss er bei dieser Benennung entweder einen Entwickler fragen oder in
der Projektdokumentation nachlesen.
All das kann man sich ersparen wenn man die Schnittstellen so entwirft dass es kaum
Fragen zu Feldinhalten oder Funktionen gibt.

Hier sind die Leiter der Entwicklungsabteilungen gefordert Richtlinien auszuarbeiten an
die sich die Entwickler zu halten haben.



6.3. Organisieren des Programmcodes
Nachdem wir bereits in den Code reingesehen
haben wollen wir auch gleich mal Ordnung
machen.
Mit dem Codefenster können wir die Codesicht
auswählen.
Wir sehen dass VS in unser Projekt bereits
nach dem erstellen eines Projekts etliche
Codezeilen produziert hat.
Mit den Coderegionen können wir unser Projekt
ideal strukturieren.
Achten Sie darauf dass die Region ‚Ereignisse’
am Ende des Codes steht da VS die
Ereignisroutinen immer am Ende erstellt.




                                                                  -34-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.4. Eine Klasse codieren
Die Parameterklasse hat
die Aufgabe Parameter
einzulesen. Auch hier
werden wir eine
Datenklasse als
Schnittstelle verwenden.

Wie jedes Programm
sollte auch die Klasse
eine Dokumentation im
Programmkopf haben.



Die Deklarationen für die Klasse werden direkt unterhalb der BEGCLASS gemacht.

                                                                                  DB-Verbindungen zur
                                                                                  Umwandlungszeit und zur Laufzeit
          /region Deklaration der Arbeitsfelder

          /// Datenbankverbindung
          DclDB          Name(dbRuntime)                      DBName("")
          DclDB          Name(dbCompile)                      DBName("*Public/Workshop")

          /// iSeries-Tabellen
          DclDiskFile          Name(PFINH)                                 File("*LIBL/PFINH")      Type(*Input)
          Org(*Indexed) DB(dbCompile) ImpOpen(*NO)

          /// Datenfelder für Properties
          DclFld         Name(strErrorMessage) Type(*string)

          /// Keylists und Keyfelder                                                               File-Deklarationen
          DclKlist       k2PFINH                                Fehlerinfo als Datenfeld
                 DclKFld kfFIRM                                 für die Property
                 DclKfld kfPARM

          /// Keylists und Keyfelder
          DclKlist       k3PFINH
                 DclKfld kfFIRM
                                                          KeyListen
                 DclKfld kfPARM
                 DclKfld kfKEY

          /// Keyfelder deklarieren
          DclFld kfFIRM         Like(PFFIRM)
          DclFld kfPARM         Like(PFPARM)
          DclFld kfKEY          Like(PFKEY)
                                                                              Keyfelder von DB-Felder ableiten
          /// Parameterwerte - Tabelle
          DclFld dtParameterWerte      Type(DataTable)


                                                     Datentabelle für Parameterwerte




                                                                  -35-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
              Datenklasse für Transport der Daten
              zwischen Dialog und Business-Logik

          /// Parameterklasse
          BegClass       clsParmData                          Access(*Public)

                    /// Firmennummer
                    DclFld         Firma                                   Type(*string)                Access(*Public)
                    DclFld         Parameter                               Type(*string)                Access(*Public)
                    DclFld         Key                                     Type(*string)     Schlüsselfelder für Zugriff
                                                                                                        Access(*Public)

                    /// Wertliste in DataTable
                                                                                             auf Parameterdatei
                    DclFld         ParameterWerte                          Type(DataTable)               Access(*Public)

                    /// Anzahl der Datensätze
                    DclFld         Anzahl                                  Type(*integer4)               Access(*Public)

          EndClass
                                                                Rückgabe der Parameter
              Satzanzahl in Tabelle                             in Form einer Tabelle

                                             Eigenschaft ErrorMessage
                                             deklarieren

          /// Properties wie ErrorMessage
          BegProp        ErrorMessage   Type(*string)                             Access(*Public)
                  BegGet
                         LeaveSr strErrorMessage
                  EndGet
          EndProp

          /endregion


Der Konstruktor übernimmt die DB-Verbindung, erstellt die Parametertabelle und öffnet
die Parameterdatei. Die CLOSE-Methode schließt die Datei.
          /region Konstruktor
          BegConstructor Access(*Public)
                 DclSrParm      db      Type(ASNA.VisualRPG.Runtime.Database)

                    /// dbVerbindung speichern
                    dbRuntime = db

                    /// Datentabelle erstellen
                    dtParameterWerte = *new DataTable()
                    dtParameterwerte.Columns.Add("Name", Type.GetType("System.String"))
                    dtParameterwerte.Columns.Add("Key", Type.GetType("System.String"))
                    dtParameterwerte.Columns.Add("Wert", Type.GetType("System.String"))
                    dtParameterwerte.Columns.Add("Bezeichnung", Type.GetType("System.String"))

                    Try
                           Open    PFINH DB(dbRuntime)
                    Catch ex Exception
                           strErrorMessage = ex.Message
                    EndTry

          EndConstructor

          BegSr     Close      Access(*Public)
                    Try
                           Close PFINH
                    Catch ex Exception
                           strErrorMessage = ex.Message
                    EndTry
          EndSr
          /endregion


                                                                  -36-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
Die Methode ‚LESEPARAMETER’ liest Daten aus der Parameterdatei und übergibt sie in
die Tabelle der Datenklasse. ACHTUNG – die Übergabe der Datenklasse erfolgt PER
REFERENCE – d.h. es wird nur ein Verweis auf das Objekt im Speicher des
Aufrufenden Programms übergeben aber nicht das tatsächliche Objekt.
          /region Subroutinen

          /// Einlesen der Parameter
          BegFunc LeseParameter                    Type(*boolean)        Access(*Public)
                  DclSrParm cParmData                     Type(clsParmData)             By(*reference)

                    /// DataRow deklarieren
                    DclFld dr      Type(DataRow)

                    /// Rückgabewert deklarieren
                    DclFld bOK     *boolean

                    /// Datentabelle immer mit leerer Tabelle überschreiben
                    cParmData.ParameterWerte      = dtParameterWerte.Copy()
                    cParmData.Anzahl              = *zero


                    /// Schlüssel setzen und Lesen
                    kfFIRM = cParmData.Firma
                    kfPARM = cParmData.Parameter
                    kfKEY = cParmData.Key


                    /// Fehler abfangen
                    Try

                               /// wenn Key leer ganzen Bereich einlesen
                               If kfKEY = *blank
                                      ReadRange      PFINH          FirstKey(k2PFINH)           LastKey(k2PFINH)
                               Else
                                      Chain          PFINH          Key(k3PFINH)
                               Endif

                               /// Schleife
                               DoWhile not %eof

                                         /// Zeile in Tabelle erstellen und mit Werten fülllen
                                         dr = cParmData.ParameterWerte.NewRow()
                                         dr.Item("Name")        = PFPARM
                                         dr.Item("Key")         = PFKEY
                                         dr.Item("Wert")        = PFWERT
                                         dr.Item("Bezeichnung") = PFBEZ

                                         /// Zeile in Datentabelle anhängen
                                         cParmData.ParameterWerte.Rows.Add( dr )

                                         /// nächsten Satz lesen
                                         Read   PFINH

                               EndDo

                               bOK = *true

                    Catch ex Exception

                               strErrorMessage = ex.Message
                               bOK = *false

                    EndTry


                    LeaveSr bOK

          EndFunc

          /endregion




                                                                  -37-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.5. Datenbankverbindung und Klassen deklarieren
Im Hauptprogramm, frmMain sollten keine Dateideklarationen zu finden sein.
Die Datenbankverbindung aber muss im Hauptprogramm angelegt werden da sie
ausgehend vom Hauptprogramm an die Klassen weitergegeben werden soll.

Der DBName wird aus der Parameterdatei APP.CONFIG eingelesen.
Dafür wird auch ein Objekt, der APPSETTINGSREADER erstellt.

Damit mit der Paramerterklasse gearbeitet werden kann werden die Klasse selbst und
die Datenklasse angelegt. Dazu gehört auch eine Konstante mit dem Key für unsere
Belegtypen.
          /region Eigene Deklarationen

          /// Datenbankverbindung
          DclDB Name(dbRuntime)

          /// Objekt zum Einlesen der Parameter aus App.Config
          DclFld asrApp Type(System.Configuration.AppSettingsReader) new()
                                                                                        Objekt deklarieren
          /// Parameter aus DB einlesen                                                 (Objekt im Speicher
          DclFld cParameter             Type(clsParameter)
          DclFld cParamBelege           Type(clsParameter.clsParmData)                  anlegen)
          /// Konstante Felder
          DclConst       conBelegtyp                          Value("BELEGTYP")

          /endregion


Der Konstruktor eröffnet die DB-Verbindung, die Routine dazu liegt in der Region
SUBROUTINEN.
          BegConstructor Access(*Public)
                 //
                 // Required for Windows Form Designer support
                 //
                 InitializeComponent()

                    //
                    // TODO: Add any constructor code after InitializeComponent call
                    //

                    /// wenn es Probleme bei der DB-Verbindung gibt Programm beenden
                    if not GetDBConnection()
                           LeaveSr
                    Endif

          EndConstructor




          /region Subroutinen
                                                                           Auslesen DB-Name aus APP.CONFIG
          /// erstellen der DB-Verbindung
          BegFunc GetDBConnection       Type(*boolean)                     und erstellen der Verbindung
                    /// DB-Verbindungsparameter aus APP.CONFIG einlesen
                    dbRuntime.DBName = asrApp.GetValue("DBName", Type.GetType("System.String"))
                           *as String

                    /// DB-Verbindung aufbauen mit strukturierter Fehlerbehandlung
                    Try

                               Connect dbRuntime



                                                                  -38-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
     RPG.NET BESTPRACTICES
                         Catch Name(dgEx) Type(ASNA.DataGate.Common.dgException)
                                MsgBox Msg("Datagate meldet: " + %Unicode(13) + dgEx.Message) +
                                               Icon(*Stop) Title("Datenbankverbindung zu " +
                                               dbRuntime.DBName + " fehlerhaft")
                                LeaveSr *false
                         Catch Err Type(System.Exception)
                                MsgBox Msg(Err.Message) Icon(*Stop) Title ("Verbindung zu Datenbank " +
                                               dbRuntime.DBName + " fehlerhaft")
                                LeaveSr *false
                         EndTry
                                                                                        Objekt instanzieren
                         /// Objekt für Parameterhandling erzeugen                      (erstellen des Objekts)
                         cParameter     = *new clsParameter( dbRuntime )

                         /// Datenobjekt für Belegdaten erzeugen
                         cParamBelege                  = *new clsParameter.clsParmData()
                         cParamBelege.Firma            = asrApp.GetValue("Firma",
                                                       Type.GetType("System.String")) *as String
                         cParamBelege.Parameter        = conBelegtyp

                         /// Parameter per Reference übergeben                             Eigenschaften des
                         If cParameter.LeseParameter(*ByRef cParamBelege)                  Objekts zuweisen
                                    /// Schleife über die Tabelle
                                    ForEach dr Type(DataRow) Collection(cParamBelege.ParameterWerte.Rows)

                                              /// BelegItem erstellen
Methode des Objekts                           DclFld itm     Type(ToolStripMenuItem)                    Schleife   über
ausführen und Parameter                       itm            = *new ToolStripMenuItem()                 Tabelle
                                              itm.Text       = dr.item("Wert").tostring().trim()
per REFERENCE                                 itm.Tag        = dr.item("Key").Tostring().trim()
übergeben                                     itm.Name       = "mnu" + dr.item("Key").Tostring().trim()
                                              Try
(erstellen des Objekts)                               LOADPICTURE
                                                             File(asrApp.GetValue("BelegIcon",
                                                                      Type.GetType("System.String")) *as String)
                                                             Target(itm.Image)
                                              Catch ex Exception
                                              EndTry                                  MenüEintrag
                                                                                    programmatisch erzeugen
                                              /// Menühandler deklarieren
                                              Addhandler     SourceObject(itm)
                                                             SourceEvent(Click)   HandlerSr(BelegClick)
    MenüEintrag an
    Belegmenü anhängen                        /// BelegItem in Belegmenü hängen
                                                                                      EreignisHandler
                                              mnuBelege.DropDownItems.Add(itm)
                                                                                      programmatisch erzeugen
                                    EndFor

                         Endif

                         LeaveSr *true

               EndFunc

               /endregion




     Hier sind die programmatisch erzeugten
     Menüeinträge.




                                                                       -39-
     Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.6. Objekte im UserInterface programmatisch erzeugen
In den meisten Fällen macht es Sinn die Oberfläche mit dem Designer zu erzeugen.
Manchmal aber ist es praktisch wenn man die Oberfläche zur Laufzeit verändern kann.

In unserem Fall geht es darum eine Standard-Belegbearbeitung zu erzeugen. Die
Belegtypen sollen frei definierbar sein.
D.h. das Programm muss datengesteuert und nicht logikgesteuert arbeiten.

Wir legen daher in der Parametertabelle fest welche Belegtypen wir haben.




Das Programm muss nun diese Belege im Menü anbieten. Dazu die im vorigen Kapitel
beschriebene Routine.
Nun müssen wir noch herausfinden welchen Beleg der Benutzer gemeint hat wenn er
Click’t.


6.7. Die TAG-Eigenschaft
Jedes Objekt hat eine TAG-Eigenschaft. Der Typ des TAG ist OBJEKT.
D.h. der Entwickler kann jedes beliebige Objekt in der TAG-Eigenschaft mit einem
anderen Objekt verbinden.

Im Menüeintrag weisen wir einfach den KEY zu. D.h. im Tag-Objekt des Menüeintrags
findet sich das Kürzel für den Belegtyp.
                    /// BelegItem erstellen
                    DclFld itm     Type(ToolStripMenuItem)
                    itm            = *new ToolStripMenuItem()
                    itm.Text       = dr.item("Wert").tostring().trim()
                    itm.Tag        = dr.item("Key").Tostring().trim()


Beim CLICK-Ereignis müssen wir die TAG-Eigenschaft auslesen.
          BegSr     BelegClick Access(*Private)
                    DclSrParm sender Type(*Object)
                    DclSrParm e Type(System.EventArgs)

                    DclFld itm           Type(ToolStripMenuItem)

                    itm = sender *as ToolStripMenuItem

                    MsgBox "Beleg " + itm.Tag
          EndSr



                                                                  -40-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.8. Casten eines Objekts
In dieser häufig gebrauchten Funktion geht es darum den Typ eines Objekts zuzuweisen
oder wiederherzustellen.
In jeder Ereignisroutine werden 2 Parameter übergeben, SENDER und E.
Der Sender ist vom Typ *OBJECT, d.h. es ist kein bestimmter Typ. E ist von einem
bestimmten Typ, nämlich SYSTEM.EVENTARGS.
          BegSr     BelegClick Access(*Private)
                    DclSrParm sender Type(*Object)
                    DclSrParm e Type(System.EventArgs)


Mit der Deklaration des Feldes itm als TOOLSTRIPMENUITEM wird ein Feld mit dem
richtigen Typ erzeugt.
                    DclFld itm           Type(ToolStripMenuItem)

Mit der Zuweisung *AS wird das Objekt SENDER in den entsprechenden Typ
umgewandelt und zugewiesen.
                    itm = sender *as ToolStripMenuItem


Nachdem das Objekt den richtigen Typ hat kann die TAG-Eigenschaft verwendet
werden.
                    MsgBox "Beleg " + itm.Tag




6.9. Startzeit des Programms optisch verkürzen
Beim Laden eines Programms werden gezwungenermaßen Abläufe ausgeführt die Zeit
brauchen da sich das Programm ja gleich brauchbar darstellen soll. Deswegen macht es
Sinn einen sog. SPLASH-SCREEN der Anzeige des Hauptprogramms vorzuschalten.
Dadurch wird die Wartezeit für den Anwender optisch verkürzt. Generell ist es sinnvoll
den Anwender immer mit ‚einer guten Show’ zu unterhalten, so kommt er nicht auf den
Gedanken dass etwas ‚lange dauert’.

Der Screen ist eine rahmenlose
Form mit 2 PictureBoxen und 2
Labels.
Im einen Label ist ein
Informationstext, im anderen Label
werden Programm und
Versionsnummer angezeigt.




                                                                  -41-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.9.1. AssemblyInfo
Über den Splash-Screen kommen wir zur Assembly-Info.
In diesem XML-File der in jedem Projekt existiert sind die Statusinformationen des
Programms enthalten.




Diese Informationen finden wir in den
Eigenschaften des Programmes in der Sicht des
Betriebssystems wieder.



6.9.2.         Integration des SplashScreens
Nach der Deklaration des Formulars wird der SplashScreen im Construktor aktiviert.
Wichtig ist die Zeile APPLICATION.DOEVENTS() da erst hier die Anzeige erfolgt.
          BegConstructor Access(*Public)
                 //
                 // Required for Windows Form Designer support
                 //
                 InitializeComponent()

                    //
                    // TODO: Add any constructor code after InitializeComponent call
                    //

                    /// SplashScreen anzeigen
                    frmSplash.Show()
                    Application.DoEvents()

                    /// wenn es Probleme bei der DB-Verbindung gibt Programm beenden
                    if not GetDBConnection()
                           LeaveSr
                    Endif

          EndConstructor

                                                                  -42-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
Die Maske wird wieder gelöscht wenn das Formular angezeigt wird.
          /// wenn Dialog angezeigt wird kann der SplashScreen geschlossen werden
          BegSr frmMain_Shown Access(*Private) Event(*this.Shown)
                 DclSrParm sender Type(*Object)
                 DclSrParm e Type(System.EventArgs)

                    frmSplash.Close()

          EndSr




6.10.Application.DoEvents
Unter dem Application-Objekt finden wir einige wichtige Informationen zur Anwendung
und zum Umfeld und auch die Methode DOEVENTS.

Der Sinn davon ist dass andere Anwendungen (Threads) auch Prozessorzeit brauchen
und die Anwendung diese Zeit zur Verfügung stellt.

Die Anwendung die wir hier programmieren wird am Computer des Anwenders laufen
und soll sich ‚gut benehmen’.

Dazu gehört auch dass sie anderen Threads Verarbeitungszeit gibt. Die Anwendung
selbst braucht diesen DOEVENTS da ohne DOEVENTS der Splashscreen erst
angezeigt wird wenn der Programmcode bewältigt und die Hauptmaske sowieso schon
da steht.

Bedenken Sie bei zeitaufwändigen Routinen unbedingt DOEVENTS einzubauen.


6.11.Grids füllen
Unser Formular hat inzwischen ein TabControl mit TabPages erhalten.
Innerhalb jeder Page finden wir einen Menübalken mit Funktions-Auswahlen, ein Feld
zum Aufsetzen und ein Grid.




                                                                  -43-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
Die Inhalte für die Grids kommen aus den Klassen, die Daten werden aus Tabellen
übernommen und einfach ins Grid eingestellt.
Das Grid übernimmt automatisch alle Tabellen des Memoryfiles und stellt die
entsprechenden Spalten zur Verfügung.

Natürlich können die Spalten auch programmatisch definiert werden.


6.12.Grids abfragen
In unserem Beispiel wird die komplette Zeile
eines Grid per Click oder Cursor ausgewählt.
Das wird festgelegt durch die Einstellung des
Selection-Modes.


Nun wollen wir eine Aktion ausführen zu der wir die Lieferantennummer oder die
Teilenummer des gewählten Artikels brauchen.
Dazu müssen wir die Zelle in der sich diese Information befindet lokalisieren.
          BegSr itmBearbeiten_Click Access(*Private) Event(*this.itmBearbeiten.Click)
                 DclSrParm sender Type(*Object)
                 DclSrParm e Type(System.EventArgs)

                    DclFld dgvr          Type(DataGridViewRow)

                    If tabMAin.SelectedTab = tpLieferanten

                               dgvr = dgvLieferanten.SelectedRows(0)
                               MsgBox "Bearbeite Lieferant " + dgvr.Cells(2).value.Tostring()

                    Else

                               dgvr = dgvTeile.SelectedRows(0)
                               MsgBox "Bearbeite Teil " + dgvr.Cells(1).value.Tostring()

                    Endif

          EndSr




Ist die Selektion auf CELLSELECT eingestellt kann mit DGV.CURRENTCELL der Wert
der aktuell geklickten Zelle abgefragt werden.




                                                                  -44-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.13.MemoryFile – der neue Subfile
Der MemoryFile ist das RPG.NET-Gegenstück zum Subfile. Er übernimmt die Struktur
einer Datei, die kann aber auch aus einem XML-File abgeleitet werden.

In unserer Lieferantenklasse nehmen wir den Aufbau der Basisdatei für den MemoryFile
          /// iSeries-Tabellen
          DclDiskFile          Name(LISTAP)                                File("*LIBL/LISTAP")          Type(*Update)
          Org(*Indexed) Addrec(*Yes)  DB(dbCompile)                        ImpOpen(*NO)

          ///    Memoryfile
          DclMemoryFile Name(LISTAPm) FileDesc("*LIBL/LISTAP")                                    DBDesc(dbCompile)
          ImpOpen(*NO)      RnmFmt(memLief)


Die Datenklasse der Lieferantenliste enthält eine Tabelle für den MemoryFile.
          /// Datenklasse für Lieferantenliste
          BegClass       clsLieferantenliste                               Access(*Public)

                    /// Listendaten
                    DclFld         Firma                                   Like(FIRNR)                   Access(*Public)
                    DclFld         AbNummer                                Like(LIFNR)                   Access(*Public)
                    /// Anzahl der Datensätze in Liste
                    DclFld         Anzahl                                  Type(*integer4)               Access(*Public)

                    /// Kopfdaten als Tabelle
                    DclFld         Liste                                   Type(DataTable)               Access(*Public)

          EndClass


Das Auslesen erfolgt über eine einfache Schleife. Es werden alle Felder ausgegeben da
sie denselben Namen wie die Datenfelder der Inputdatei haben.
          /// Einlesen der Belegliste
          BegFunc LeseLieferantenliste                        Type(*boolean)                             Access(*Public)
                  DclSrParm cLieferantenliste                        Type(clsLieferantenliste)           By(*reference)

                    /// Rückgabewert deklarieren
                    DclFld bOK     *boolean

                    /// MemoryFile rücksetzen
                    LISTAPm.DataSet.Clear()

                    /// Schlüssel setzen und Lesen
                    kfFIRNR = cLieferantenliste.Firma
                    kfLIFNR = cLieferantenliste.AbNummer

                    /// Fehler abfangen
                    Try

                               /// wenn Key leer ganzen Bereich einlesen
                               SETLL LISTAP Key(klLISTA)
                               READ   LISTAP Access(*nolock)

                               /// Schleife
                               DoWhile not %eof

                                         /// Zeile in MemoryFile ausgeben
                                         Write LISTAPm

                                         /// Anzahl abfragen
                                         If     cLieferantenliste.Anzahl > *zero

                                                   If LISTAPm.DataSet.Tables(0).Rows.Count =
                                                          cLieferantenliste.Anzahl

                                                              Leave



                                                                  -45-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
                                                   Endif
                                         Endif

                                         /// nächsten Satz lesen
                                         Read   LISTAP

                               EndDo

                               /// Datentabelle übergeben
                               cLieferantenliste.Liste = LISTAPm.DataSet.Tables(0)

                               bOK = *true

                    Catch ex Exception

                               strErrorMessage = ex.Message
                               bOK = *false

                    EndTry

                    LeaveSr bOK

          EndFunc


Mit der Zuweisung der TABLES(0) des MemoryFile-Dataset wird die Tabelle an die
Datenklasse übergeben.



6.14.Die RANGE-Befehle
In der Routine wird ein Befehl verwendet der im Standard-Befehlsumfang von
iSeriesRPG nicht existiert. READRANGE und seine Verwandten DELETERANGE,
SETRANGE wurden von ASNA entwickelt um den Netzwerkverkehr zu optimieren.
Mit den Range-Befehlen werden komplette, von Beginn- und EndeKey begrenzte
Datenblöcke eingelesen und verarbeitet.




                                                                  -46-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.15.Eigene Ereignisse erstellen und abfragen
Durch die Oberfläche vorgegeben ist der Programmablauf nicht mehr wie im iSeriesRPG
prozedural sondern ereignisgesteuert.
Natürlich können wir in RPG.NET eigene Ereignisse erstellen.

Ereignisse müssen deklariert werden
          /// Ereignisse       deklarieren
          DclEvent             FillGrids                                   Access(*Public)
          DclEvent             FillLieferanten                             Access(*Public)
          DclEvent             FillTeile                                   Access(*Public)


In der Startroutine werden die HANDLER erstellt die eine Ereignisbehandlung festlegen.
…
/// Lieferanten
cLieferanten                   = *new clsLieferanten( dbRuntime )        Erstellt            ein Ereignis und weist eine
cLieferantenListe              = *new clsLieferanten.clsLieferantenliste()
                                                                                       Subroutine zu die aufgerufen wird
/// Teile
cTeile                                   = *new clsTeile( dbRuntime )
                                                                                       wenn das Ereignis eintritt
cTeileListe                              = *new clsTeile.clsTeileliste()

AddHandler          SourceObject(*this)            SourceEvent(FillGrids)       HandlerSr(FillAllGrids)
AddHandler          SourceObject(*this)            SourceEvent(FillLieferanten) HandlerSr(FillLieferantenGrid)
AddHandler          SourceObject(*this)            SourceEvent(FillTeile)       HandlerSr(FillTeileGrid)

*this.FillGrids()



Die Ereignisse werden unter *THIS angeboten.




Hier die Subroutinen die die Ereignisse behandeln.
/// das Ereignis abfangen
BegSr FillAllGrids                                   Das Ereignis das alle Grids füllt löst
                                                     seinerseits die Einzelnen
          *this.FillLieferanten()
          *this.FillTeile()                          Ereignisse für die Grids aus
EndSr

/// das Ereignis abfangen
BegSr FillLieferantenGrid

          /// Lieferanten aktualisieren
          cLieferantenListe.Firma              = asrApp.GetValue("Firma",
                  Type.GetType("System.String")) *as String
          cLieferantenListe.Anzahl      = asrApp.GetValue("DefaultAnzahl",
                  Type.GetType("System.String")) *as String
          Try
                 cLieferantenListe.AbNummer    = txtAbLieferanten.Text
          Catch ex Exception
                 cLieferantenliste.AbNummer    = *zero                                          Zuweisen der
          EndTry                                                                                DataTable ans Grid
          If cLieferanten.LeseLieferantenliste(*byRef cLieferantenliste)
                 dgvLieferanten.DataSource     = cLieferantenliste.Liste
          Endif



                                                                  -47-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
    RPG.NET BESTPRACTICES
    EndSr

    /// das Ereignis abfangen
    BegSr FillTeileGrid

              /// Lieferanten aktualisieren
              cTeileListe.Firma     = asrApp.GetValue("Firma", Type.GetType("System.String")) *as String
              cTeileListe.Anzahl    = asrApp.GetValue("DefaultAnzahl", Type.GetType("System.String"))
                                            *as String
              Try
                     cTeileListe.AbNummer = txtAbTeile.Text
              Catch ex Exception
                     cTeileListe.AbNummer = *zero
              EndTry

              If cTeile.LeseTeileliste(*byRef cTeileliste)
                     dgvTeile.DataSource    = cTeileListe.Liste
              Endif

    EndSr




    6.16.Inhalte in Grids selektieren
    DataGrids werden mit DataTable’s gefüllt. Es ist praktisch den Inhalt zu filtern ohne dass
    die Daten der Tabelle neu eingelesen werden müssen. Mit der SELECT-Methode der
    DataTable kann wie bei einem SQL-Select ein Filter gesetzt werden.
    Der Select erzeugt ein Array von Datensätzen aus dem eine neue Tabelle erstellt wird.
              /// Füllen des Grids mit Auftragsdaten
              BegSr FillAuftraege

                        /// Abhängig von der Einstellung der Combobox - Stahlsorte
                        If cboStahlsorte.Text = *blank

                                   /// alle Daten
                                   dgv.DataSource = cAuftragsliste.dtAuftraege

                        Else

                                   /// Filter auf      Stahlsorte
                                   DclArray            foundRows      Type(DataRow)   Rank(1)
                                   DclFld dt           Type(DataTable)
                                   DclFld dr           Type(DataRow)
                                   DclFld i            *integer4                      Array für Ergebnis des
                                   DclFld strKey       *string                        SELECT vorbereiten
                                   /// Stahlsorte lesen
                                   strKey = cboStahlsorte.Text.Substring(0,2)

                                   /// Filter auf Sätze
                                   foundRows = cAuftragsliste.dtAuftraege.Select("S2GUET = '" + strKey + "'")

                                   /// Tabelle clonen
                                   dt = cAuftragsliste.dtAuftraege.clone()                      SELECT ausführen
                                   dt.Clear()

Neue Tabelle mit                   /// Alle gefilterten Sätze lesen und in Tabelle übergeben
selbem Format                      do fromval(i) toval( foundRows.GetUpperBound(0) )
                                          /// Neuen Satz anlegen, mit Werten Füllen und in Tabelle übergeben
erzeugen                                  dr = dt.NewRow()
                                          dr.ItemArray = foundRows(i).ItemArray
                                          dt.Rows.Add( dr )
                                   Enddo

                                   /// gefilterte Daten in Grid anzeigen               Schleife über
                                   dgv.DataSource = dt                                 Selektionsergebnis mit
                        endif                                                          füllen der Ausgabetabelle


                                                                      -48-
    Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
                    /// in Gridanzeige wechseln
                    tabMAin.SelectedTab = tpAuftraege
                    /// Satzanzahl in Messageline anzeigen
                    stbMessage.Text = dgv.Rows.Count.ToString() + " Sätze angezeigt"

          EndSr


In diesem Beispiel ist die Basistabelle die dtAuftraege des Objekts cAuftragsliste.
Aus ihr wird eine neue Tabelle selektiert die dem Grid zugewiesen wird.



6.17.Contextmenü erstellen und abfragen
In der Startroutine wurden Belegtypen aus der Parameterdatei ausgelesen. Für jeden
Belegtyp wird ein Eintrag im Contextmenü gemacht, das Contextmenü wird an das Grid
gebunden.
          …
          /// Menühandler deklarieren
          Addhandler     SourceObject(itm)                    SourceEvent(Click)   HandlerSr(BelegClick)
          /// BelegItem in Contextmenü hängen
          ctmBelege.Items.Add(itm)

       EndFor
/// Contextmenü an Grid binden
dgvLieferanten.ContextMenuStrip = ctmBelege


Hier ist die Ereignisbehandlung für das Contextmenü.
          /// Ereignisse des Contextmenü's abfragen
          BegSr BelegClick Access(*Private)
                 DclSrParm sender Type(*Object)
                 DclSrParm e Type(System.EventArgs)

                    /// ausgewählte Zeile im Grid einlesen
                    DclFld dgvr    Type(DataGridViewRow)
                    dgvr = dgvLieferanten.SelectedRows(0)

                    /// den Menüpunkt feststellen                             Aus dem Menüobjekt wird der Inhalt
                    DclFld itm     Type(ToolStripMenuItem)                    der TAG-Eigenschaft, aus dem Grid
                    itm = sender *as ToolStripMenuItem
                                                                              wird die LieferantenNr übergeben
                    /// das BelegObjekt erstellen und anzeigen
                    DclFld myBeleg        Type(frmBeleg)
                    /// Parameter in den Constructor übergeben
                    myBeleg = *new frmBeleg( itm.Tag.ToString(), dgvr.Cells(2).Value.Tostring() )
                    myBeleg.Show()

          EndSr




6.18.Formulare aufrufen
Im vorigen Beispiel wurde das Formular aufgerufen. In der Instanzierung wurden dem
Constructor Parameter übergeben.
                    /// das BelegObjekt erstellen und anzeigen
                    DclFld myBeleg        Type(frmBeleg)
                    /// Parameter in den Constructor übergeben
                    myBeleg = *new frmBeleg( itm.Tag.ToString(), dgvr.Cells(2).Value.Tostring() )
                    myBeleg.Show()


Im aufgerufenen Formular sieht das so aus:


                                                                  -49-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
          BegConstructor Access(*Public)
                 DclSRParm      strBeleg       *string
                 DclSRParm      strNr          *string
                 //
                 // Required for Windows Form Designer support
                 //
                 InitializeComponent()

                    //
                    // TODO: Add any constructor code after InitializeComponent call
                    //
                    *this.Text = *this.Text + " " + strBeleg + " " + strNr

          EndConstructor


Der         Constructor
verlangt diese beiden
Parameter und übergibt
sie    hier   in   den
Formularkopf.


In der Menüleiste stellen sich nun beide Formulare so dar.



Wir wollen das Formular als komplett eigenständig agierendes
Objekt einbinden. Dem Formular sollen daher alle Objekte
mitsamt der DB-Verbindung übergeben werden die es braucht.

Der Sinn ist dass sich eine Anwendung entwickelt die mehrere
von einander unabhängige Unterformulare beinhalten kann.
Dadurch ist der Benutzer in der Lage beliebig viele
Stammdatensätze, Belege, was die Formulare auch immer
darstellen gleichzeitig und voneinander unabhängig zu
bearbeiten.


6.19.Combobox- WertListe füllen
Die Listenwerte in den DropDown-Boxen werden über die Parameterklasse gefüllt.
Wenn die Datenklasse mit LESEPARAMETER erstellt wird so wird auch gleichzeitig eine
Liste von Werten gefüllt die an die ComboBox übergeben wird.

Hier die Erstellung des Parameterobjekts und die Zuweisung der Liste an die
ComboBox:
                    oVART          = *new clsParameter.clsParmData()
                    oVART.Firma           = strFirma
                    oVART.Parameter       = "VART"
                    oParameter.LeseParameter(*ByRef oVART)
                    cboVersandart.Items.AddRange(oVART.KeyListe.ToArray())

Im Parameterobjekt wird die Liste so deklariert:
/// KeyListe für DropDowns
DclFld KeyListe       Type(System.Collections.ArrayList)                   Access(*Public)




                                                                  -50-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
Hier ist die Routine LESEPARAMETER:
                    /// Schleife
                    DoWhile not %eof

                               /// Zeile in Tabelle erstellen und mit Werten fülllen
                               dr = cParmData.ParameterWerte.NewRow()
                               dr.Item("Name")        = PFPARM
                               dr.Item("Key")         = PFKEY
                               dr.Item("Wert")        = PFWERT
                               dr.Item("Bezeichnung") = PFBEZ

                               /// Zeile in Datentabelle anhängen
                               cParmData.ParameterWerte.Rows.Add( dr )

                               /// Feld in Keyliste hängen
                               cParmData.KeyListe.Add(PFKEY)
                                                                           Feldinhalt in Liste
                                                                           übergeben
                               /// nächsten Satz lesen
                               Read   PFINH

                    EndDo




6.20.Defaultwerte in Comboboxen
Die      Anwender
schätzen      eine
vorbelegte
DropDown, es ist
auch kein Problem
diesen Service zu
bieten.

In unserem Beispiel
wird            eine
Parameterdatei
durchgegangen
und
Parametertabellen
und Listen gefüllt.

Ebenso können wir
einen Defaultwert
einstellen, in der
Tabelle wird ein
Feld eingefügt, hier
mit dem Namen
PFDFT, Typ CHAR
Len 1




                                                                  -51-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES

In dieses Feld werden beim
gewünschten Satz *ON-
Merker eingefüllt.




In der Klassenroutine sieht das so aus:
                               /// Schleife
                               DoWhile not %eof

                                         …
                                         /// Feld in Keyliste hängen
                                         cParmData.KeyListe.Add(PFKEY)

                                         /// Standardwert abfragen und setzen
                                         If PFDFT = *ON
                                                cParmData.DefaultKey = PFKEY
                                         Endif

                                         /// nächsten Satz lesen
                                         Read   PFINH

                               EndDo


Hier die Zuweisung an das DropDown.
                    cboVersandart.Text             = oVART.DefaultKey


Das Ergebnis:




                                                                  -52-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.21.Cursorreihenfolge festlegen
In Windows-Programmen kann
auf die Positionierung des
Cursors festgelegt werden.
Dazu hat jedes Control die
Eigenschaft TABINDEX.
Dieser startet bei 0.
Üblicherweise wird während der
Entwicklung am UI gearbeitet,
wenn das UI fertig ist sollte
auch der TABINDEX gesetzt
werden.
Natürlich zählen nur Controls
die auch einen Cursor
annehmen können.


6.22.Komfortable Datenerfassung
Programme werden für Benutzer geschrieben, den sollte man auch so gut wie möglich
unterstützen, man lebt ja davon dass er mit der Arbeit die der Entwickler leistet zufrieden
ist.
Ein wichtiger Punkt ist dass das Eingabefeld nicht mehr Zeichen annehmen kann als das
Datenfeld zulässt.

Eine Textbox ist per Standard auf die MAXLENGTH von 32767 Zeichen eingestellt.

Deswegen sollte für jede Textbox die Feldlänge eingestellt werden. Sie ersparen sich
und dem Anwender jede Menge Ärger.

Bei der Arbeit mit dem Programm ist es praktisch wenn der Feldinhalt in das der Cursor
springt automatisch markiert wird sodass ein einfaches Erfassen möglich ist.

Hier ist ein Weg dem Benutzer die Arbeit leicht zu machen.

Es genügt eine EventRoutine in der das ENTER-Ereignis aller TextBoxen bearbeitet
wird.

          BegSr txtNAME1_Enter Access(*Private) Event(*this.txtNAME1.Enter, *this.txtNAME2.Enter,
                         *this.txtSTRASSE.Enter, *this.txtLand.Enter, *this.txtPLZ.Enter,
                          *this.txtORT.Enter, *this.txtProjekt.Enter)
                 DclSrParm sender Type(*Object)
                 DclSrParm e Type(System.EventArgs)
                                                                                   TextBox aus
                    DclFld txtBox Type(TextBox)
                    txtBox = sender *as TextBox                                    Ereignisparameter
                                                                                   übernehmen
                    txtBox.Text = txtBox.Text.Trim()
                    txtBox.SelectAll()

          EndSr                                                            Mögliche Leerstellen
                                     Alle Zeichen                          abschneiden
                                     markieren.

                                                                  -53-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES


So macht das Programm einen
professionellen Eindruck.

Das funktioniert natürlich auch für
die ComboBoxen und
NumericUpDown.




6.23.Steuerung aktive TabPage
Die aktive TabPage kann natürlich programmatisch gesteuert werden. In diesem Fall soll
bei verlassen des Feldes Projekt die Positions-Page aktiviert und der Cursor auf ein
bestimmtes Feld positioniert werden.




Der LEAVE-Event wird ausgelöst wenn der
Focus das Feld verlässt.
/// Tabsprung soll Positionserfassung öffnen
BegSr txtProjekt_Leave Access(*Private) Event(*this.txtProjekt.Leave)
       DclSrParm sender Type(*Object)
       DclSrParm e Type(System.EventArgs)

          /// stellt Positionserfassung in den Vordergrund
          tabMain.SelectedTab = tpPos
          /// Setzt Focus auf ArtikelNr
          txtArtikelNr.Focus()

EndSr




6.24.DefaultButton für Eingabetaste festlegen
Abhängig von der aktiven TabPage soll bei drücken der Eingabe-Taste ein jeweils
anderer Button ausgelöst werden.
Gernerell wird die Eingabetaste vom ‚Accept’-Button des Formulars abgefangen.
Der wird vom Programm gesetzt wann auch immer sich die aktive TabPage ändert.
          /// wenn sich die aktuelle tabPage ändert wird der Default für die Eingabetaste festgelegt
          BegSr tabMain_SelectedIndexChanged Access(*Private)
                          Event(*this.tabMain.SelectedIndexChanged)


                                                                  -54-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
                    DclSrParm sender Type(*Object)
                    DclSrParm e Type(System.EventArgs)

                    If tabMAin.SelectedTab = tpTeile
                           *this.AcceptButton     = btnBelegSpeichern
                    Else
                           *this.AcceptButton     = btnPosOk
                    Endif

          EndSr




6.25.Tastencode abfragen
Die Key-Events von Windows-Programmen kommen mit einem Parameter der
Argumente enthält, diese Argumente können mit einer Enumeration der Keys – Klasse
oder direkt mit dem Tastencode abgefragt werden.
In diesem Fall wird die ENTER-Taste abgefragt.
          BegSr dgv_KeyPress Access(*Private) Event(*this.dgv.KeyPress)
                 DclSrParm sender Type(*Object)
                 DclSrParm e Type(System.Windows.Forms.KeyPressEventArgs)

                    If e.KeyChar = U'000D'

                               *this.AuftragAnzeigen()

                    Endif

          EndSr




6.26.Array mit Werten füllen
Eine Array wird wie folgt mit Werten gefüllt:
/// Array mit Werten initialisieren
DclArray       aWerte         Type(*string)        Rank(1)
aWerte = *new System.String[] { "0" , txtArtikelNr.Text, txtBezeichnung.Text,
       txtMengeneinheit.Text, nudMenge.Text, nudEinzelpreis.Text, "0", txtPositionstext.Text }


Die Werte innerhalb der geschwungenen Klammer werden an das Array übergeben,
durch *NEW wird es mit einem Typ initialisiert.


6.27.Array aus String füllen
Hier ist ein Beispiel indem ein Array aus einem String gefüllt wird der eine Zeile von
Daten getrennt durch ein bestimmtes Zeichen enthält. Verwendet wird die SPLIT-
Funktion vom Datentyp STRING
lblKW.text = „2006/35“
          If lblKW.text <> *blank
                 DclArray       aKW                           Type(*string)    Rank(1)
                 DclFld         oc                            Type(*onechar)
                 oc = "/"
                 aKW = lblKW.Text.Split(oc)
                 If aKW.length = 2
                         dr.Item("BPLJJ")                     = %int(aKW[0])
                         dr.Item("BPLWW")                     = %int(aKW[1])
                 Endif
          Endif


                                                                  -55-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.28.DataGridView oder DataRow mit Werten füllen
Eine Datenzeile wird durch eine Array an Werten dargestellt. Sie kann einfach an die
Rows-Collection angehängt werden.
/// Array mit Werten initialisieren
DclArray       aWerte         Type(*string)        Rank(1)
aWerte = *new System.String[] { "0" , txtArtikelNr.Text, txtBezeichnung.Text,
         txtMengeneinheit.Text, nudMenge.Text, nudEinzelpreis.Text, "0", txtPositionstext.Text }

/// Zeile an DataGridView anhängen
dgvPos.Rows.Add( aWerte )




6.29.Werte in DataGridView berechnen
Nach Änderungen/Ergänzungen/Löschungen wird diese Routine aufgerufen die
Positionssummen errechnet und die Werte im Grid updatet.
          BegSr     RechneWerteInGrid

                    DclFld     i *integer4
                    DclFld     zSumme *decimal
                    DclFld     zMenge Type(*zoned)            Len(10,3)
                    DclFld     zPreis Type(*zoned)            Len(13,3)
                    DclFld     zTotal Type(*zoned)            Len(13,3)

                    /// um das Durchlaufen der Schleife bei Folgeereignissen abzublocken
                    If bGridUpdate

                               bGridUpdate = *false

                               /// Schleife über die Datenzeilen
                               ForEach dr     Type(DataGridViewRow) Collection(dgvPos.Rows)

                                         /// Werte initialisieren
                                         zMenge = 0
                                         zPreis = 0
                                         dr.Cells(0).Value     = i+1

                                         /// Rechenfelder einlesen
                                         Try
                                                zMenge = dr.Cells("colMenge").Value *as System.String
                                         Catch Ex Exception
                                                dr.Cells("colMenge").Value = 0
                                         EndTry

                                         Try
                                                zPreis = dr.Cells("colEinzelpreis").Value *as System.String
                                         Catch Ex Exception
                                                dr.Cells("colEinzelpreis").Value = 0
                                         EndTry

                                         /// berechnen und updaten
                                         zSumme = zMenge * zPreis
                                         dr.Cells("colPreis").Value = zSumme
                                         zTotal += zSumme

                               EndFor

                               /// Summe zuweisen
                               lblWert.Text   = zTotal
                               stbLabel.Text = "Gesamtwert " + zTotal

                               /// Bearbeitung wieder freigeben
                               bGridUpdate = *true

                    Endif

          EndSR


                                                                  -56-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.30. Combo- und Checkboxen als DataGridView-Spalten
Hier ein Beispiel das die Verwendung von Funktionsspalten im DataGridView erläutert.




                dclfld           dgvChk           Type(System.Windows.Forms.DataGridViewCheckBoxColumn) new()
                dclfld           dgvCbo           Type(System.Windows.Forms.DataGridViewComboBoxColumn)

        BegSr dgvDatenErstellen
                // Spalte 1
                dgvDaten.Columns.Add("Spalte1", "Normale Spalte 1")
                // Spalte 2 - ideal für J/N - Richtig / Falsch
                dgvChk.Name = "Check"
                dgvChk.HeaderText = "Check J/N"
                dgvDaten.Columns.Add(dgvChk)

              // Spalte 3
              dgvCbo      = *new System.Windows.Forms.DataGridViewComboBoxColumn()
              dgvCbo.Name = "Spalte3"
              dgvCbo.HeaderText = "ComboBox Spalte 1"
              dgvCbo.Items.Clear()
              dgvCbo.Items.Addrange("Karl", "Franz", "Sepp")
              dgvDaten.Columns.Add(dgvCbo)
        //    ==> Hier möchte ich z.B. "J" auf selected setzen!
        /// so gehts halt nicht - musst im Erstellungsevent machen

        EndSr

        BegSr Form1_Load Access(*Private) Event(*this.Load)
              DclSrParm sender *Object
              DclSrParm e System.EventArgs
                // Occurs when form is first loaded.
                // Put form "startup" code here (ie open files).
                dgvDatenErstellen()
        EndSr
        BegSr Form1_FormClosing Access(*Private) Event(*this.FormClosing)
              DclSrParm sender Type(*Object)
              DclSrParm e Type(System.Windows.Forms.FormClosingEventArgs)
                // Occurs when form is closing.
                // Put form "housecleaning" code here (ie close files).
        EndSr




                                                                  -57-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
          BegSr dgvDaten_RowsAdded Access(*Private) Event(*this.dgvDaten.RowsAdded)
               DclSrParm sender *Object
               DclSrParm e System.Windows.Forms.DataGridViewRowsAddedEventArgs
                /// absichern da bei erstem Durchlauf noch keine Spalten existieren
                /// die werden erst im FormLoad erstellt
                try

                         /// um Abwechslung ins Leben zu bringen
                         if e.Rowindex     < 3
                                 /// bei nur 2 Auswahlmöglichkeiten könnte man auch eine Checkbox nehmen
                                 dgvDaten.Rows.Item( e.RowIndex ).cells("Check").Value = *true
                                 /// man kann einfach den Wert eingeben
                                 dgvDaten.Rows.Item( e.RowIndex ).cells("Spalte3").Value = "Sepp"
                         else
                                 dgvDaten.Rows.Item( e.RowIndex ).cells("Check").Value = *false
                                 /// oder einen Wert aus der Tabelle nehmen
                                 dgvDaten.Rows.Item( e.RowIndex ).cells("Spalte3").Value =
                                              dgvcbo.Items(1).Tostring()
                         Endif
                catch ex Exception
                endTry

          EndSr




                                                                  -58-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.31. Felder an MemoryFile anhängen
Manchmal muss man an einen Memoryfile zur Laufzeit Felder anzuhängen, so wird’s
gemacht.
Die Funktion ‚NeuerBeleg’ erzeugt eine Instanz einer Datenklasse, an den MemoryFile
wird die Spalte Text angehängt.
          BegFunc NeuerBeleg                       Type(clsBelegDaten)                   Access(*Public)

                    DclFld cBelegDaten     Type(clsBelegDaten)
                    cBelegDaten    = *new clsBelegDaten()
                    cBelegDaten.Positionen = BPPOSm.DataSet.Tables(0)

                    /// Spalte für Texte im Dataset anfügen
                    DclFld dcText         Type(DataColumn)      new()
                    dcText.ColumnName = "Text"
                    dcText.DataType = Type.GetType("System.String")
                    cBelegDaten.Positionen.Columns.Add(dcText)

                    LeaveSr cBelegDaten

          EndFunc




6.32.Daten in einer DataTable sortieren
Es gibt einige Wege, hier finden wir den Weg über die DataView, ein anderer wäre einen
PrimaryKey über die Tabelle zu legen.
          /// eine DataView erzeugen, die Spalte über den Index angeben – ASC - Ascending
          DclFld dv      Type( DataView )
          dv = *new DataView( dtMain, "", dtMain.Columns( e.ColumnIndex ).ColumnName + " ASC",
                  DataViewRowState.CurrentRows)
          /// die sortierte view in die neue Tabelle ausgeben
          dtSort = dv.ToTable()
          /// Listbox-Inhalt löschen
          lb.Items.Clear()
          /// Spalte in Grid ausgeben
          ForEach dr     Type( DataRow )                                   Collection( dtSort.Rows )
                    /// Spalte in Listbox ausgeben
                    lb.Items.Add( dr.item( e.ColumnIndex ).Tostring() )
          EndFor




                                                                  -59-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.33.Das TreeView
Ist ein sehr interessantes Control das viele Möglichkeiten bietet. Hier sehen Sie wie das
TreeView gefüllt wird:




        /// -------------------------------------------------------------------
        /// Zweck:            baut die Baumstruktur für die Anzeige auf
        ///     Gültigkeit:   lokal - innerhalb dieser Klasse
        ///     Parameter:    keine
        /// Hinweise: TreeView, Nodes, Objekte, Ereignisse, Methoden,
        ///                   Datenzugriff, Try..Catch,
        /// -------------------------------------------------------------------
     BegSr FillTree

                    DclFld    tn                   Type(TreeNode) /// Hauptknoten
                    DclFld    ts                   Type(TreeNode) /// Subknoten
                    DclFld    dtGA                 Type(*date)           /// Hilfsfeld für Datum glt-ab
                    DclFld    dtGB                 Type(*date)           /// dto. gültig-bis

                    /// Änderungen nicht gleich anzeigen
                    tvNews.BeginUpdate()

                    /// löschen des Baumes
                    tvNews.Nodes.Clear()
                    /// löschen der DropDown-Boxen
                    cboKeyHNGRN1.Items.Clear()
                    cboHNGRN1.Items.Clear()

                    /// initialisieren des GW-Kriteriums
                    lstHNGRN1      = *blank

                    /// Aufsetzen Dateibeginn und Readschleife bis EndOfFile
                    SETLL NEWS2L01        Key(*start)
                    READ   NEWS2L01       Access(*nolock)
                    DoWhile not %eof

                               /// bei GW in Knotengruppe neuen Hauptknoten erstellen
                               If     HNGRN1 <>      lstHNGRN1
                                      /// Schlüsselfeld füllen
                                      kfFLIAFI = HNGRN1
                                      CHAIN FLDVAL01         Key(klFLDVAL01)       Err(*extended) +
                                              Access(*nolock)
                                      If not %Error and not %eof

                                                   /// neuen Hauptknoten als Objekt erstellen
                                                   tn = *new TreeNode()
                                                   tn.Text = HNGRN1 + " " + TXT1FI
                                                   tn.Tag = HNGRN1
                                                   /// tvNews ist das Steuerelement, darunter wird /// tn als
                                                   Hauptknoten angehängt
                                                   tvNews.Nodes.Add(tn)

                                                   /// Gruppe auch in die Liste der Komboboxen
                                                   cboHNGRN1.Items.Add(HNGRN1)


                                                                  -60-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
                                                   cboKeyHNGRN1.Items.Add(HNGRN1)

                                                   /// GW-Kriterium setzen
                                                   lstHNGRN1      = HNGRN1

                                         Endif

                               Endif

                               /// Problemen bei Datumsformat vorbeugen
                               Try
                                      dtGA = GLTAN1
                               Catch ex Exception
                                      /// bei Fehler aktuelles Datum verwenden
                                      dtGA = DateTime.Now()
                               EndTry

                               Try
                                      dtGB = GLTBN1
                               Catch ex Exception
                                      dtGB = DateTime.Now()
                               EndTry

                               /// für jeden Datensatz der NEWS2L01 einen Knoten erstellen
                               ts = *new TreeNode()
                               ts.Text = %trim(HNGRN1) + " " + %EDITC(SEITN1,"4") + " " +
                                        TEXTN1 + dtGA.ToShortDateString() + " " + DVBKN1 + " " +
                                        dtGB.ToShortDateString()
                               /// TAG-Eigenschaft beinhaltet den Schlüssel
                               ts.Tag = %trim(HNGRN1) + %EDITC(SEITN1,"4")

                               /// Knoten als Subknoten zum jeweiligen Hauptknoten (Gruppe)
                               tn.Nodes.Add(ts)

                               /// nächsten Satz aus NEWS2L01 lesen
                               Read   NEWS2L01       Access(*nolock)
                    Enddo

                    /// nun wird der Baum angezeigt
                    tvNews.EndUpdate()

                    /// Format mit Baumstruktur öffnen
                    tabNews.SelectedTab = tabStruktur

     EndSr




                                                                  -61-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.34.Kalenderwochenberechnung, Rekursionen
Die Berechnung der Kalenderwoche ist nicht wirklich trivial, hier ist ein Beispiel in dem
eine Rekursion verwendet wird.
          /// Kalenderwochenberechnung gibt Woche als string zurück, bekommt Datum übergeben
          BegFunc Kalenderwoche Type(*string)
                 DclSrParm      dDatum *date

                    /// ISO-Norm 8601
                    /// Kalenderwoche beginnt immer an einem Montag
                    /// Erste Kalenderwoche eines Jahres ist die in der der 4.Januar liegt.


                    /// Rechenfeld deklarieren
                    DclFld i2Week *integer2

                    /// Beginn der ersten Woche im Jahr feststellen
                    DclFld dVierterJanuar *date
                    dVierterJanuar = dDatum.Parse("04.01." + dDatum.Year.ToString())

                    /// Anzahl der Wochen rechnerisch feststellen
                    i2Week = (dDatum.DayOfYear - dVierterJanuar.DayOfWeek + 1) / 7 + 1

                    /// wenn die Woche 0 ist fällt sie noch ins alte Jahr
                    If i2Week = *zero
                           /// Funktion rekursiv mit 31.12. des Vorjahres aufrufen
                           LeaveSr Kalenderwoche( dDatum.AddDays( (dDatum.DayOfYear + 1) * -1 ) )
                    Endif

                    LeaveSr i2Week.ToString()

          EndFunc




                                                                  -62-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.35.Überladen von Funktionen (OO-Technik)
Die OO-Technologie des Überladens ermöglicht uns eine Funktion mit mehreren
unterschiedlichen Parametern anzusprechen.
In unserem Textausgabe-Beispiel werden beliebige Texte (Artikel, Beleg-Kopfdaten,
Positionstexte, …) in einer Tabelle gespeichert.
Das Handling wird über eine Klasse gemacht die eine Lese- und eine Schreibfunktion
anbietet. Abhängig von den Parametern werden die Keys befüllt.
Damit sich der Nutzer der Klasse keine Gedanken über die Befüllung machen muss und
somit auch weniger Fehler machen kann werden unterschiedliche Parameterlisten
angeboten. Darunter laufen für jede Parameterliste dieselben Funktionen.

Parameterliste 1 - BelegTexte




Parameterliste 2 – ArtikelTexte




Codiert wird das so:
          /// Einlesen der Texte für einen Beleg
          BegFunc LeseText              Type(*String)         Access(*Public)
                  DclSrParm     Firma          Type(*char)           Len(2)
                  DclSrParm     BelegTyp       Type(*char)           Len(3)
                  DclSrParm     BelegNummer    Type(*char)           Len(10)
                  DclSrParm     PositionNr     Type(*Integer2)

                    kfFIRM     =   Firma
                    kfTYP      =   BelegTyp
                    kfBENR     =   BelegNummer
                    kfPOSN     =   PositionNr
                    kfATNR     =   *blank

                    LeaveSr HoleTextAusDaten()

          EndFunc

          /// Einlesen der ArtikelTexte
          BegFunc LeseText              Type(*String)                      Access(*Public)
                  DclSrParm     Firma          Type(*char)                        Len(2)
                  DclSrParm     ArtikelNr      Type(*char)                        Len(18)

                    kfFIRM     =   Firma
                    kfTYP      =   *BLANK
                    kfBENR     =   *ZERO
                    kfPOSN     =   *zero
                    kfATNR     =   ArtikelNr

                    LeaveSr HoleTextAusDaten()

          EndFunc


Über den gleichen Namen bietet VS die Parameterliste an. Der Entwickler kann sich für
eine Liste entscheiden ohne wissen zu müssen was dahinter abläuft.




                                                                  -63-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.36.Programmaufrufe
Wie im RPG üblich werden Programme mit CALL aufgerufen, dieser Call geht gegen ein
iSeries-Programm.
                               Move 'LNDC' CCNAME
                               Move EALNDC CCVAL
                               Call Pgm( '*LIBL/MB102'            ) DB( dbRuntime )
                                      DclParm CCPARM
                               *IN30 = *OFF
                               If( CCPRZ = 1 )
                                      *IN30 = *ON
                               EndIf




6.37.AS/400-API’s aufrufen


6.37.1. Benutzer des DG-Servicejobs auslesen

                    DclFld     Receiver            Type(*char)                    Len(100)
                    DclFld     Length              Type(*binary)                  Len(4,0)      inz(100)
                    DclFld     Format              Type(*char)                    Len(8)        Inz("JOBI0100")
                    DclFld     JobName             Type(*char)                    Len(26)       inz("*")
                    DclFld     JobID               Type(*char)                    Len(16)
                    Call       Pgm("QUSRJOBI")                       DB(ProdDB)
                               DclParm Receiver               DBDirection(*output)
                               DclParm Length                 DBDirection(*input)
                               DclParm Format                 DBDirection(*input)
                               DclParm JobName                DBDirection(*input)
                               DclParm JobID                  DBDirection(*input)
                    txtJob.Text           = Receiver.Substring( 8, 10)
                    txtUser.Text   = Receiver.Substring( 18, 10)
                    txtJobnum.Text = Receiver.Substring( 28, 6)




6.37.2. DTAQ auslesen
                    CALL       Pgm('*LIBL/QRCVDTAQ')                       DB(dbBGLesen) Err(*extended)
                               DclParm DtaQName                                   DBDirection(*input)
                               DclParm DtaQLib                                    DBDirection(*input)
                               DclParm DtaQLen                                    DBDirection(*output)
                               DclParm DSData                                     DBDirection(*output)
                               DclParm DtaQWait                                   DBDirection(*input)
                               DSLOAD Source( DSData )                     Name( DSMPAR )




6.38.Datenstrukturen als iSeries-Programmparameter
Um Datenstrukturen mit gepackten Feldern zwischen der iSeries und dem Client
übertragen zu können empfehle ich Schnittstellenprogramme zu schreiben deren
Schnittstelle keine gepackten Felder enthält.
Generell gibt es in RPG.NET keine Probleme mit gepackten Feldern, sie können ohne
Einschränkungen verwendet werden. Befinden sich gepackte Felder innerhalb eines

                                                                  -64-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
Strings so gibt es dadurch Probleme dass Hex’00’ in der ASCII-Welt als Code für
StringEnde verwendet wird.

Beispiel:
VALUE IN HEX
       '404040404040F1F2F3F4F5F640C140F8F7F6F5F4F3F2F140404040404040F1848484F584848484F0'X
  41   'F1848484F584848484F0F1848484F584848484F0F1848484F584848484F0F1848484F584848484F0'X
  81   'F1848484F584848484F0F1848484F584848484F0F1848484F584848484F0F1848484F584848484F0'X
  121 'F1848484F584848484F0F1848484F584848484F0F1848484F58400000000001F8400000000001DF0'X


In diesem String finden Sie auf Stelle 147 und 154 je ein gepacktes Feld mit Feldwert 11,
im ersten Feld positiv, im 2. Feld negativ.

Wenn das Programm direkt angesprochen wird gehen alle Daten ab Stelle 147
unweigerlich verloren. Die Ursache ist dass die EBCDIC zu ASCII Konvertierung auf
dem Host-System stattfindet.


EBCDIC zu ASCII – Konvertierung am Host-System




Bezeichnung                      Type            Länge
Mode                             Char            4                     Ab dem ersten Hex’00’ in Stelle 147
Data                             Char            3000
Message format                   Char            1
Error Msg details                Char            2000
Return code                      Char            1




6.38.1. Lösungsbeispiel Schnittstellenprogramme
Die Programme die Datenstrukturen mit gepackten Feldern verwenden werden über eine
Schnittstelle aufgerufen die entweder wie im Beispiel nur die benötigten Felder enthält
oder alle Felder als Datenstruktur in welcher die gepackten Felder durch gezonte Felder
ersetzt werden.
       H              J
       I*****************************************************************
       I**‚ Schnittstelle zu Kundenstamm wegen gepackter Felder
       I**
       IPKDN      E DSSFWKDN
       I**

                                                                  -65-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
       I**‚ DATENSTRUKTUREN FÜR *LDA
       I**
       ISYSDTA    EUDSSYSDTADS
       I**
       I              '*LIBL/#KDN'          C         #PGM
       I**
       I*****************************************************************
       C**
       C           *LIKE     DEFN KDN01     #DN01
       C           *LIKE     DEFN KDN02     #DN02
       C           *LIKE     DEFN KDN23     #DN23
       C           *LIKE     DEFN XMCF03    #MCF03
       C**
       C*****************************************************************
       C**‚ PROGRAMM-ÜBERGABE-PARAMETER
       C**
       C           *ENTRY    PLIST
       C                     PARM           #DN01
       C                     PARM           #DN02
       C                     PARM           #DN23
       C                     PARM           #MCF03
       C**
       C**‚ Programmaufruf
       C**
       C                     MOVE #DN01     KDN01
       C                     MOVE #DN02     KDN02
       C                     CALL #PGM
       C                     PARM           PKDN
       C**
       C**‚ Returncode einlesen
       C**
       C           *NAMVAR   DEFN *LDA      SYSDTA
       C                     IN   SYSDTA
       C                     DUMP
       C**
       C**‚PROGRAMM BEENDEN
       C**
       C                     MOVE KDN23     #DN23
       C                     MOVE XMCF03    #MCF03
       C                     MOVE '1'       *INLR
       C**
       C*****************************************************************




                                                                  -66-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.39.Datenzugriff
Die Logik wird wie in
ILERPG geschrieben.




6.40.Routinen aus iSeries-Programmen
Hier ist eine
Routine aus dem
Originalprogramm
die zeigt dass es
keine
wesentlichen
Änderungen im
Verständnis des
RPG braucht um
Logik zu
schreiben.




                                                                  -67-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.41.F-Spec’s
Natürlich kennt RPG.NET keine F-Spezifikationen mehr, an Stelle der F’specs steht
DclDiskFile.




6.42.Feld- und KeyDeklarationen

Variablen können von Datenbankfeldern
abgeleitet werden, Keylists werden
deklariert wie in RPG gewohnt.




6.43.Datenstrukturen
Datenstrukturen
werden wie auf
der iSeries
deklariert. Die
Felder reihen sich
aneinander
sodass Lücken
mit Dummyfelder
geschlossen
werden müssen.

Überlappende
Felder werden mit
OVERLAY
deklariert.




                                                                  -68-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.44.Datenstrukturen als Key – Teilkeys mit %KDS
Die BuiltInFunction %KDS erlaubt eine Datenstruktur als Key anzugeben, hier ist ein
Beispiel:




Umso weniger keyfelder eingegeben werden umso mehr Datensätze zeigt das
Programm. Das ist ein typischer Fall von Aufsetzen mit Teilkeys – hier ist eine
Lösungsansatz.

Die Grid-Füll-Routine ruft eine mehrfach überladene Funktion in der Datenklasse auf.
          BegSr     FillGrid
                    DclFld KundenNr                Type( *decimal )
                    DclFld Jahr                    Type( *decimal )
                    DclFld Art                            Type( *decimal )
                    Select
                               When      txtJahr.Text   = *blank
                                         KundenNr       = txtKunde.Text *as *decimal
                                         dgv.DataSource = cKundenUmsatz.Liste( KundenNr )
                               When      txtArt.Text           = *blank
                                         Jahr           = txtJahr.Text *as *decimal
                                         KundenNr       = txtKunde.Text *as *decimal
                                         dgv.DataSource = cKundenUmsatz.Liste( KundenNr, Jahr )
                               Other
                                         Art                   = txtArt.Text *as *decimal
                                         Jahr           = txtJahr.Text *as *decimal
                                         KundenNr       = txtKunde.Text *as *decimal
                                         dgv.DataSource = cKundenUmsatz.Liste( KundenNr, Jahr, Art )
                    EndSl

          EndSr


Die Klasse ‚Kundenumsatz‘ löst die Aufgabe so:
Using System
Using System.Text
Using System.Data
BegClass KundenUmsatz Access(*Public)

          DclDb         dbLocal        DBName("*public/DG NET LOCAL")
          DclDiskFile   CSMasterL1     Type(*input) Org(*indexed) DB(dbLocal)
                  File("Examples/CSMasterL1")
          DclMemoryFile        CSMaster            DBDesc(dbLocal) FileDesc("Examples/CSMaster")
          /// Key-Datenstruktur          erstellen
          DclDs kyDS
                 DclDsFld                kyKundenNr           like(CSCustNo)
                 DclDsFld                kyJahr               like(CSYear)
                 DclDsFld                kyArt                like(CSType)

                                                                  -69-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
          /// Listenaufrufe mit verschiedenen Paramtern bearbeiten
          BegFunc        Liste Type(DataTable)        Access(*public)
                  DclSrParm     KundenNr       like(CSCustNo)
                    kyKundenNr           = KundenNr
                    kyJahr               = *zero
                    kyArt                = *zero
                    LEaveSr LeseListe( 1 )
          EndFunc

          BegFunc          Liste         Type(DataTable)       Access(*public)
                    DclSrParm            KundenNr       like(CSCustNo)
                    DclSrPArm            Jahr           like(CSYear)
                    kyKundenNr           = KundenNr
                    kyJahr               = Jahr
                    kyArt                = *zero
                    LEaveSr LeseListe( 2 )
          EndFunc

          BegFunc          Liste         Type(DataTable)       Access(*public)
                    DclSrParm            KundenNr       like(CSCustNo)
                    DclSrPArm            Jahr           like(CSYear)
                    DclSrPArm            Art            like(CSType)
                    kyKundenNr           = KundenNr
                    kyJahr               = Jahr
                    kyArt                = Art
                    LEaveSr LeseListe( 3 )
          EndFunc

          /// hier wird die Liste erstellt - die Anzahl der Keyfelder wird
          /// als Parameter übergeben
          BegFunc        LeseListe             Type(DataTable)
                  DclSrParm     KeyFelder      Type(*integer2)
                    csMaster.ClearFileData(0)
                    /// Aufsetzen und lesen ersten Satz
                    Select
                           When KeyFelder = 1
                                   SETLL CSMasterL1                        Key( %kds( kyDS, 1) )
                                   READ   CSMasterL1
                           When KeyFelder = 2
                                   SETLL CSMasterL1                        Key( %kds( kyDS, 2) )
                                   READ   CSMasterL1
                           When KeyFelder = 3
                                   SETLL CSMasterL1                        Key( %kds( kyDS, 3) )
                                   READ   CSMasterL1
                    EndSl
                    /// Leseschleife
                    DoWhile NOT %EOF
                               /// Ausgabe des MemoryFiles
                               WRITE CSMaster
                               /// READE mit Keystruktur
                               Select
                                      When KeyFelder = 1
                                              READE CSMasterL1                    key( %kds( kyDS, 1 ) )
                                      When KeyFelder = 2
                                              READE CSMasterL1                    key( %kds( kyDS, 2 ) )
                                      When KeyFelder = 3
                                              READE CSMasterL1                    key( %kds( kyDS, 3 ) )
                               EndSl
                    Enddo
                    /// Ausgabe der Tabelle
                    LEaveSr CSMaster.DataSet.Tables(0)
          EndFunc

EndClass


                                                                  -70-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.45.Datenstrukturen als Programmparameter
Das Problem mit numerischen Zeichen in Datenstrukturen beruht vor allem darauf dass
Datenstrukturen auf der AS/400 als Strings behandelt werden und daraufhin vom
Entwickler erwartet wird dass das in anderen Umgebungen auch so ist.

Folgende Probleme treten auf:
     1. Enthält der String gepackte Felder beginnen die meistens mit H‘00‘ – was Stringende auf ASCII-
        basierenden Systemen bedeutet. Da die EBCDIC/ASCII-Konvertierung von der AS/400 gemacht
        wird gibt es mit allen anderen Produkten außer DataGate das Problem dass mit dem ersten H‘00‘
        der String zuende ist.
     2. Enthält der String gezonte numerische Werte ergeben sich Probleme mit negativen Werten da sie
        durch alpha-Character dargestellt werden. Das ist überall außer mit DataGate so.

 Auch mit DataGate ergeben sich diese Probleme wenn man Datenstrukturen als String
übergibt. Werden die Datenstrukturen als Externe DS beschrieben und als solche
übergeben gibt es auch kein Problem – weder mit gepackten Feldern noch mit gezonten
numerischen Daten.

Beispiel:

Es wird ein AS/400 – Programm aufgerufen das Daten in Datenstrukturen übergibt. Im
ILE-RPG und auch im RPG.NET werden diese EDS deklariert und als Parameter
übergeben:
          /// hier werden die Strukturen deklariert - und schlampigerweise public freigegeben
          DclDs dsCMASTNEW      DBDesc( dbLocal )      ExtDesc( *yes )
                         FileDesc( "NXTGEN/CMASTNEW" )                Access( *Public )
          DclDs dsCSMASTER      DBDesc( dbLocal )      ExtDesc( *yes )
                         FileDesc( "NXTGEN/CSMASTER" )                Access( *Public )

          BegFunc GetEDS Type( *boolean )                     Access( *Public )
                  DclSrParm     NR                            Like( CMCUSTNO )
                  DclSrParm     DUMP                          Like( CMACTIVE )
                  DclSrParm     VZU                           Like( CMACTIVE )
                    /// hier wird das Programm aufgerufen - die Strukturen werden direkt übergeben
                    CALL   Pgm( "NXTGEN/GETEDS" ) DB( dbLocal )
                           DCLPARM        NR
                           DCLPARM        DUMP
                           DCLPARM        VZU
                           DCLPARM        dsCMASTNEW
                           DCLPARM        dsCSMASTER
                    LeaveSr (NOT %error )
          EndFunc




                                                                  -71-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
Hier ist das ILE-RPG das die Parameter übernimmt und füllt:

         H DEBUG(*YES)

         FCMASTNEWL1IF                  E                      K DISK
         FCSMASTERL1IF                  E                      K DISK

         D CUSTDS                       E DS                               EXTNAME(CMASTNEW)
         D SALEDS                       E DS                               EXTNAME(CSMASTER)

         C           *ENTRY                     PLIST
         C                                      PARM                              NR
         C                                      PARM                              DUMP
         C                                      PARM                              VZUMK
         C                                      PARM                              CUSTDS
         C                                      PARM                              SALEDS

         C           *LIKE                      DEFINE             CMCUSTNO       NR
         C           *LIKE                      DEFINE             *INLR          DUMP
         C           *LIKE                      DEFINE             *INLR          VZUMK

         C                                      IF                 (DUMP = *ON)
         C           'BEFORE'                   DUMP
         C                                      ENDIF

         C           NR                         CHAIN              CMASTNEWL1
90
         C           NR                         CHAIN              CSMASTERL1
91

         C                                      IF                 (VZUMK = *ON)
         C                                      EVAL               CSSALES01 = CSSALES01   *   -1
         C                                      EVAL               CSSALES02 = CSSALES02   *   -1
         C                                      EVAL               CSSALES03 = CSSALES03   *   -1
         C                                      EVAL               CSSALES04 = CSSALES04   *   -1
         C                                      EVAL               CSSALES05 = CSSALES05   *   -1
         C                                      EVAL               CSSALES06 = CSSALES06   *   -1
         C                                      EVAL               CSSALES07 = CSSALES07   *   -1
         C                                      EVAL               CSSALES08 = CSSALES08   *   -1
         C                                      EVAL               CSSALES09 = CSSALES09   *   -1
         C                                      EVAL               CSSALES10 = CSSALES10   *   -1
         C                                      EVAL               CSSALES11 = CSSALES11   *   -1
         C                                      EVAL               CSSALES12 = CSSALES12   *   -1
         C                                      ENDIF

         C                                      IF                 (DUMP = *ON)
         C           'AFTER'                    DUMP
         C                                      ENDIF

   C               MOVE        *ON           *INLR




                                                                  -72-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
Hier ist das Ergebnis nachdem alle Felder in die Listboxen übernommen wurden.




                                                                  -73-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.46.Hilfsfunktionen
In diesem Dialog wurden Tooltips und F1-Hilfe ebenso wie Gültigkeitsprüfungen
integriert.


Hier ein ToolTip




Hier die Logik für die ToolTips, sie wird üblicherweise in die Initialisierungsroutine
gesetzt.
                    /// ToolTip setzen
                    tt.SetToolTip(nudLNR,"Eindeutiger Schlüssel des Datensatzes")
                    tt.SetToolTip(txtSNAM,"Eindeutiger Suchbegriff des Datensatzes")
                    tt.SetToolTip(txtName,"Lieferantenname")
                    tt.SetToolTip(txtORT,"Ort des Lieferanten")




                                                 Hier ein Hilfetext, er kann mit F1 auf dem Feld oder
                                                 dem Fragezeichen zur Anzeige gebracht werden.



/// Hilfe setzen
hp.SetHelpString(nudLNR,"Geben Sie hier den Schlüssel des Datensatzes an, der Begriff muss
eindeutig sein")
hp.SetHelpString(txtSNAM,"Geben Sie hier einen eindeutigen Suchbegriff für den Datensatz an um den
Lieferanten später einfach finden zu können; Maximal 5 Zeichen")
hp.SetHelpString(txtName,"Der Name des Lieferanten; Max. 50 Zeichen")
hp.SetHelpString(txtORT,"Der Ort des Lieferanten; Max 50 Zeichen")




                                                                  -74-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.47.Gültigkeitsprüfungen
DotNet bietet einen sog.
ErrorProvider der neben dem
Feld einen roten Hinweispunkt
anzeigt.     Der     Hilfetext
erscheint wenn man mit der
Maus auf den Punkt fährt.




Das ist die Logik die dafür nötig ist.




                                                                  -75-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.48.Drucken


6.48.1. RPG.NET – Printfiles
Mit ASNA-Printfiles kann man unter Windows ebenso drucken wie auf der iSeries. ASNA
hat eigene PrintControls erstellt, es können aber auch Windows-Controls verwendet
werden.




Vor Eröffnen des
Printfiles werden
wie auf der iSeries
Attribute
überschrieben,
hier werden die
Druckvorschau
und die
Druckerauswahl
eingestellt.




                                                                  -76-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.48.2. Druckschleife

Wie auf der iSeries
wird in einer Schleife
über eine Datei
gedruckt.

 READRANGE ist
ein neuer Befehl von
ASNA der einen
Bereich zwischen
zwei Schlüssel
eingrenzt.

Die Datensätze
werden in den
Printfile wie gewohnt
ausgegeben. Wenn
die Datenfelder des
Printfiles gleich wie
die DB-Felder
benannt sind braucht
auch keine
Zuweisung erfolgen.




6.49.Einbinden von 3rd-Party Komponenten
Für die Darstellung der Werte in einer Grafik wird eine Komponente verwendet die ASNA
mitliefert. Der GraphicServer ist eine frei verfügbare DotNet-Komponente die Ihre
Wurzeln schon in der Vorläufer-Welt (COM) hatte.

Wir finden am Markt jede Menge von Komponenten die, oft auch kostenlos, angeboten
werden.
Meine persönliche Einstellung dazu ist dass ich gerne jede Komponente von Microsoft
einbinde da ich davon ausgehen kann das MS dieselbe oder eine erweiterte
Komponente auf der nächsten Version anbietet.
Diese Situation hatten wir mit dem DataGrid in den Versionen 2003 und 2005.

Auf 2003 gab es ein Grid das seine Aufgabe zwar erfüllte aber Wünsche offen ließ.
Es war nicht einfach möglich verschiedene Controls wie Buttons oder Dropdowns
einzubauen, auch konnten die Zellen nicht beliebig gefärbt werden.

Auf 2005 gibt es einen Nachfolger, das DataGridView das alle diese Ansprüche erfüllt.
Beim Öffnen eines 2003-Projekts mit VS2005 hilft ein Upgrade-Assistent das Projekt
beim Upgrade. Dieser lässt aber das Grid unangetastet und es bleibt auch von der Optik
her das was es auf 2003 war.

                                                                  -77-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
Natürlich kann mit ein paar Eingriffen vom Grid auf das DataGridView umgestellt werden,
das geschieht aber nicht automatisch.

An der Optik der Steuerelemente erkennt man die Generation aus der sie stammen.
Solange man vorwärts geht oder vorne ist sollte das kein Problem sein, bindet man aber
eine Komponente ein von der es keine weiterentwickelte Version gibt so kann es sein
dass man einen gewohnten Vorteil verliert.

Entscheiden Sie selbst ob Sie Fremdkomponenten einsetzen wollen, bedenken Sie aber
auch immer dass Sie sich langfristig gesehen davon abhängig machen.


6.49.1. Grafikdarstellung
Unsere Grafikkomponente ist bereits mit der Vorläuferwelt von ASNA geliefert worden
und ist auch in dieser Welt verfügbar. Da unsere Anwendung nicht davon abhängt
können wir sie guten Gewissens einbinden.

Die Anzeige erfolgt
in einem eigenen
Formular das neben
der Grafik auch ein
Grid mit den Daten
beinhaltet. Bei Klick
auf den Spaltenkopf
wird die Spalte zur
Anzeige der Daten
gewählt.


Hier ist die Ereignisroutine im Hauptformular die dieses Formular aufruft.
          /// prinzipiell selber Vorgang wie beim Excel-Button
          BegSr btnArtikelGraph_Click Access(*Private) Event(*this.btnArtikelGraph.Click, +
                                        *this.btnKundenGraph.Click, *this.btnUmsatzGraph.Click)
                 DclSrParm sender Type(*Object)
                 DclSrParm e Type(System.EventArgs)

                    *this.Cursor = Cursors.WaitCursor

                    DclFld btn     Type(Button)
                    btn = sender *as Button


                    DclFld fGraph Type(frmGraph)
                    fGraph = *new frmGraph()


                    DclFld ds      Type(DataSet)
                    ds = btn.tag *as DataSet

                    /// hier werden Parameter für die Grafikdaten festgelegt
                    /// die Textspalte und die Datenspalte für den Start
                    DclFld strTitel       Type(*string)
                    SELECT
                           When btn = btnArtikelGraph
                                   strTitel = "Artikelstatistik"
                                   fGraph.TextColumnNumber = 0
                                   fGraph.DataColumnNumber = 1
                                   fGraph.SummenInLetzterZeile = *true


                                                                  -78-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
                               When btn = btnKundenGraph
                                      strTitel = "Kundenstatistik"
                                      fGraph.TextColumnNumber = 2
                                      fGraph.DataColumnNumber = 3
                                      fGraph.SummenInLetzterZeile = *false
                               When btn = btnUmsatzGraph
                                      strTitel = "Umsatzstatistik"
                                      fGraph.TextColumnNumber = 1
                                      fGraph.DataColumnNumber = 4
                                      fGraph.SummenInLetzterZeile = *false
                    ENDSL

                    /// zuweisen des DataSets für die Grafik
                    fGraph.GraphData = ds

                    /// zuweisen des Formulartitels
                    fGraph.Text = fGraph.Text + " " + strTitel

                    /// ungebundenes Formular anzeigen
                    fGraph.Show()

                    /// Sanduhr zurückstellen
                    *this.Cursor = Cursors.Default

          EndSr


Hier wird also per Property festgelegt welche Spalten als Text- und die Datenspalte zu
verwenden ist und wie viele Datenzeilen verarbeitet werden sollen. Die letzte Zeile der
Artikelstatistik ist eine Summenzeile und die sollte nicht in der Grafik angezeigt werden.
          BegProp GraphData     Type(DataSet) Access(*Public)
                  BegSet
                         /// zugewiesene Daten verwenden
                         ds = *propval
                         /// füllen der Combobox mit den Tabellennamen
                         ForEach dt Type(DataTable)    Collection(ds.Tables)
                                cboTables.Items.Add(dt.TableName)
                         EndFor
                         /// einstellen des ersten Elements - dadurch Event auslösen
                         cboTables.SelectedIndex = 0
                  EndSet
          EndProp
Hier werden die Events von der ComboBox und dem Klick auf den Spaltenheader
abgefangen und die Anzeige der Grafik aufgerufen.
          /region Events

          /// wenn andere Tabelle gewählt die Anzeige akutalisieren
          BegSr cboTables_SelectedIndexChanged Access(*Private) +
                                        Event(*this.cboTables.SelectedIndexChanged)
                 DclSrParm sender Type(*Object)
                 DclSrParm e Type(System.EventArgs)

                    /// nur wenn Daten vorhanden sind
                    if cboTables.Items.count > 0

                               Try
                                      /// Datenquelle für Grid festlegen
                                      dgv.DataSource = ds.Tables(cboTables.SelectedIndex)
                                      /// Grafik anzeigen
                                      ShowGraph()
                               Catch Ex Exception
                                      MsgBox Msg("Bitte gültige Tabelle auswählen") Icon(*information) +
                                              Title("Auswahl Tabellen")
                               EndTry

                    Endif

          EndSr


          /// bei Klick auf Spalte Graphic mit neuen Daten laden und anzeigen


                                                                  -79-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
          BegSr dgv_ColumnHeaderMouseClick Access(*Private) Event(*this.dgv.ColumnHeaderMouseClick)
                 DclSrParm sender Type(*Object)
                 DclSrParm e Type(System.Windows.Forms.DataGridViewCellMouseEventArgs)

                    iDataCol = e.ColumnIndex
                    ShowGraph()

          EndSr

          /endregion


Hier ist das befüllen der Eigenschaften des Grafik-Controls:
          /region Subroutines

          /// Grafik anzeigen
          BegSr ShowGraph
                 DclFld i      *integer2
                 DclFld iMax   *integer2
                 DclFld strColName    *string

                    /// Spaltenname für Datenfelder herausholen
                    strColName = ds.Tables( cboTables.SelectedIndex ).Columns( iDataCol ).ColumnName

                    /// Maximum auf Summenzeile einstellen
                    If bSummenInLetzterZeile
                           iMax = ds.Tables( cboTables.SelectedIndex ).Rows.Count - 2
                    Else
                           iMax = ds.Tables( cboTables.SelectedIndex ).Rows.Count - 1
                    Endif

                    /// Datenklassen neu intialisieren
                    Data = *new Series()
                    Labels = *new Series()

                    /// Schleife über Datenspalte des DataSets ( Summenzeile wird nicht übernommen)
                    Do FromVal(0) Toval(iMax) Index(i)

                               /// Datensatz aus Tabelle lesen
                               DclFld dr      Type(DataRow)
                               dr = ds.Tables( cboTables.SelectedIndex ).Rows(i)

                               /// Arbeitsfelder initialisieren
                               DclFld dData Type(Decimal)
                               DclFld strText Type(*string)

                               /// mögliche Fehler bei Textspalten abfangen
                               Try
                                      dData = dr.item( iDataCol ).Tostring()
                               Catch ex Exception
                                      dData = 0
                               EndTry

                               /// Text der angegebenen Spalte einsetzen
                               strText = dr.item( iTextCol ).tostring()

                               /// Objekte füllen
                               Data.SetValue( SeriesComponent.Y, i, dData )
                               Labels.SetValue( SeriesComponent.Label, i, strText)

                    EndDo

                    /// das Chart-Objekt mit Parametern füllen
                    With gsChart.Chart
                           .Grid.AxisPie.AxisMode    = AxisMode.Category
                           .Grid.AxisPie.LabelSeries = Labels
                           .RemoveAllSeries()
                           .AddSeries( Data )
                           .ChartTitle.text = strColName // cboTables.Text

                               .ChartEventsToEnable.EnableMarkerMouseClickEvent = *True
                               .GetSeriesDrawing(0).PieLabelLineLength     = 25
                               .GetSeriesDrawing(0).PieLabelLocation       = PieLabelLocation.Outside
                               .GetSeriesDrawing(0).markerlabelProperties.Font = *new Font( "Tahoma", 6 )
                               // explode first datapoint 40%


                                                                  -80-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
                               .GetSeriesDrawing(0).SetDataPointExplode( 0, 40 )
                               .Grid.AxisPie.LabelFormatMask = "C"                                  // currency
                               .GetSeriesDrawing(0).PieUnits = PieUnits.Value
                               .ChartType = ChartType.Pie3D
                    EndWith

          EndSr

          /endregion




6.50.Embedded SQL und offener Datenzugriff
Da VisualRPG.NET auf DotNet                                aufsetzt        können   wir   alle   Datenbanken      und
Zugriffsmodelle verwenden.

In diesem binden wir die iSeries über ODBC, SQL-Server über ManagedProvider und
Access über OLEDB ein.




                                                                  -81-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.50.1. Namespaces und Deklarationen
Using   System.Data
Using   System.Data.ODBC
Using   System.Data.OleDb
Using   System.Data.SqlClient

…

/Region MyDeclaratives

DclConst coniSConnection     Value("Connection=ODBC;DSN=ATNICE;UID=NE;PWD=TUBA")
DclConst coniSSelection      Value("SELECT * FROM Petroplus.F4211LA where sddoco=18841")
DclConst conSQLConnection    Value("Integrated Security=SSPI;Persist Security Info=False;Data
       Source=NICENOTE2005;Packet Size=4096;Workstation ID=NICENOTE2005;Initial
       Catalog=Petroplus;")
DclConst conSQLSelection     Value("SELECT * FROM F4211")
DclConst conAccessConnection         Value("Provider=Microsoft.Jet.OLEDB.4.0;Data
       Source=C:\NiceWare\XMPL\XMPL.mdb;")
DclConst conAccessSelection          Value("SELECT * FROM Artikel")

/Endregion


In diesen Deklarationen werden Konstante angelegt aus denen die Textfelder befüllt
werden mit denen die Datenbank-Verbindung und die Abfrage ausgeführt werden.
Natürlich sollten diese Strings in einem ‚ordentlichen Projekt aus der APP.CONFIG
einelesen werden.


6.50.2. Event-Subroutinen
Bei Klick auf Radio-Buttons werden die Felder belegt, bei Start wird abhängig von der
ausgewählten Datenbank eine entsprechende Routine ausgeführt.

    /region Events

          BegSr rbISeries_Click Access(*Private) Event(*this.rbISeries.Click)
                 DclSrParm sender Type(*Object)
                 DclSrParm e Type(System.EventArgs)

                    txtConnection.Text = coniSConnection
                    txtSelection.Text = coniSSelection

          EndSr

          BegSr rbSQLServer_Click Access(*Private) Event(*this.rbSQLServer.Click)
                 DclSrParm sender Type(*Object)
                 DclSrParm e Type(System.EventArgs)

                    txtConnection.Text = conSQLConnection
                    txtSelection.Text = conSQLSelection

          EndSr

          BegSr rbAccess_Click Access(*Private) Event(*this.rbAccess.Click)
                 DclSrParm sender Type(*Object)
                 DclSrParm e Type(System.EventArgs)

                    txtConnection.Text = conAccessConnection
                    txtSelection.Text = conAccessSelection

          EndSr




                                                                  -82-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
          BegSr btnStart_Click Access(*Private) Event(*this.btnStart.Click)
                 DclSrParm sender Type(*Object)
                 DclSrParm e Type(System.EventArgs)

                    DclFld ds            Type(DataSet)

                    stb.Text = *blank
                    dg.DataSource = *nothing

                    Select

                               When rbISeries.Checked
                                      ds = iSODBC(txtConnection.Text, txtSelection.text)

                               When rbSQLServer.Checked
                                      ds = ManagedSQL(txtConnection.Text, txtSelection.text)

                               When rbAccess.Checked
                                      ds = OLEDB(txtConnection.Text, txtSelection.text)


                    EndSl

                    Try
                           dg.DataSource = ds.Tables(0)
                    Catch ex Exception
                           stb.Text = "keine Daten gefunden"
                    EndTry

          EndSr

/Endregion




6.50.3. ODBC
Als Datenbank-Standard hat kann man auf die meisten Datenbanken zugreifen. Natürlich
auch auf die iSeries. Zugriff über ODBC auf die iSeries ist zudem gratis.
Ich empfehle aber diese Funktion wirklich nur für den Zugriff über SQL zu nutzen und
Daten wann immer möglich über Schlüssel zu lesen und die RPG-OpCodes zu
bearbeiten.
Dasselbe gilt für den Zugriff auf iSeries-Programme. Man kann zwar über ODBC aus
StoredProcedures zugreifen, ein Zugriff über ASNA DataGate ist ungleich einfacher und
sicherer.
BegFunc       iSODBC Type(DataSet)
       DclSrParm     strConnection                            *string
       DclSrParm     strSQL                                   *string

          DclFld     conn                Type(Odbc.OdbcConnection) new()
          DclFld     cmdData             Type(Odbc.OdbcCommand)     new()
          DclFld     daData              Type(Odbc.OdbcDataAdapter) new()
          DclFld     dsData              Type(DataSet)              new()

           conn.ConnectionString = strConnection

           // Verbindung zur iSeries erstellen
           Try
               conn.Open()
           Catch ex Exception
               MsgBox Msg("Verbindung zu iSeries über " + strConnection +
                  " konnte nicht hergestellt werden") Title("Connection") Icon(*exclamation)
                LEaveSR dsData

                                                                  -83-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
           EndTry

           Try
                 cmdData.Connection = conn
                 cmdData.CommandText = strSql
                 cmdData.CommandType = CommandType.Text
                 daData.SelectCommand = cmdData
                 daData.Fill(dsData)
          Catch ex Exception
                 MsgBox Msg("Daten konnten mit Command '" + strSQL +
                 "' nicht eingelesen werden") Title("SQL-Fehler") Icon(*exclamation)
                 LEaveSR dsData
          EndTry

          LEaveSR dsData

EndFunc



6.50.4. SQL-Server
ManagedProvider sind die DotNet-native – Methode um auf Daten zuzugehen.
Auch hier empfehle ich den Zugriff über DataGate da es für den iSeries-Programmierer
einfacher ist auf Satzebene updates auszuführen als sie in ADO.NET zu behandeln.
BegFunc       ManagedSQL    Type(DataSet)
       DclSrParm     strConnection        *string
       DclSrParm     strSQL               *string

          DclFld    conn                 Type(SqlConnection)               new()
          DclFld    cmdData              Type(SqlCommand)                  new()
          DclFld    daData               Type(SQLDataAdapter)              new()
          DclFld    dsData               Type(DataSet)                     new()

          conn.ConnectionString = strConnection

           // Verbindung zum SQLServer erstellen
           Try
                  conn.Open()
           Catch ex Exception
                  MsgBox Msg("Verbindung zum SQLServer über " + strConnection +
                  " konnte nicht hergestellt werden") Title("Connection") Icon(*exclamation)
                  LEaveSR dsData
           EndTry

           Try
                  cmdData.Connection = conn
                  cmdData.CommandText = strSQL
                  daData.SelectCommand = cmdData
                  daData.Fill(dsData)
           Catch ex Exception
                  MsgBox Msg("Daten konnten mit Command '" + strSQL +
                  "' nicht eingelesen werden") Title("SQL-Fehler") Icon(*exclamation)
                  LEaveSR dsData
           EndTry

          LEaveSR dsData

EndFunc




                                                                  -84-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
6.50.5. OLEDB
Für OLEDB gibt es kein DataGate, es gibt aber jede Menge Datenbanken die über
OLEDB angesprochen werden können.
Der Zugriff auf die iSeries ist zwar möglich da über ClientAccess der nötige Driver
mitgeliefert wird, er ist aber lizenzpflichtig.
Für die meisten Datenbanken sind OLEDB-Driver zu bekommen die nicht immer
lizenzpflichtig sind.
Unser Beispiel mit Access steht symbolisch für alle Datenbanken die über OLEDB
angesprochen werden können.
BegFunc       OLEDB Type(DataSet)
       DclSrParm    strConnection                             *string
       DclSrParm    strSQL                                    *string

          DclFld conn                              Type(OleDBConnection)    new()
          DclFld daData                            Type(OleDBDataAdapter)   new()
          DclFld dsData                            Type(DataSet)            new()

          conn.ConnectionString = strConnection

          // Verbindung zum SQLServer erstellen
          Try
                 conn.Open()
          Catch ex Exception
                 MsgBox Msg("Verbindung zu Access über " + strConnection +
                 " konnte nicht hergestellt werden") Title("Connection") Icon(*exclamation)
                 LEaveSR dsData
          EndTry

          Try
                 daData = *new OleDbDataAdapter( strSQL, conn )
                 daData.Fill(dsData)
          Catch ex Exception
                 MsgBox Msg("Daten konnten mit Command '" + strSQL +
                 "' nicht eingelesen werden") Title("SQL-Fehler") Icon(*exclamation)
                 LEaveSR dsData
          EndTry

          LEaveSR dsData

EndFunc




                                                                  -85-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
7. Microsoft Office einbinden


7.1. Einfache Excel-Ausgabe
Hier wird eine selbst erstellte Excel-Schnittstelle eingesetzt die Daten aus einem DataSet
in Arbeitsblätter von Excel ausgibt. Hier ist der Aufruf aus der Ereignisroutine die alle
Excel-Buttons bearbeitet.
          /// diese Routine verarbeitet die Events aller Excel-Buttons
          /// da die Daten an den Button gebunden sind braucht hier nicht
          /// extra noch auf den Button abgefragt und die richtigen Daten
          /// gesucht werden
          BegSr btnArtikelExcel_Click Access(*Private) Event(*this.btnArtikelExcel.Click, +
                                         *this.btnKundenExcel.Click, *this.btnUmsatzExcel.Click)
                 DclSrParm sender Type(*Object)
                 DclSrParm e Type(System.EventArgs)

                    /// einstellen der Sanduhr
                    *this.Cursor = Cursors.WaitCursor

                    /// das Button-Objekt übernehmen
                    DclFld btn     Type(Button)
                    btn = sender *as Button

                    /// das DataSet initialisieren
                    DclFld ds      Type(DataSet)

                    /// die Datenklasse instanzieren
                    DclFld cXLS Type(clsXLS)
                    cXLS = *new clsXLS()

                    /// das DataSet aus der TAG-Eigenschaft des Buttons holen und an die
                    /// Excel-Klasse übergeben
                    ds = btn.tag *as DataSet
                    cXLS.DataSet = ds
                    cXLS.LoadExcel()

                    /// Sanduhr zurückschalten
                    *this.Cursor = Cursors.Default

          EndSr


Hier wird eine Klasse clsXLS mit einem DataSet ausgestattet und danach die Funktion
LoadExcel ausgeführt.

          /// ================================================================================
          /// Methode LoadExcel ist als einzige Metholde öffentlich verfügbar
          /// ================================================================================
          //
          /// Wie von Microsoft empfohlen werden die Daten die in Excel ausgegeben werden
          /// sollen im ClipBoard, also im Arbeitsspeicher aufbereitet.
          ///
          /// Zuerst wird allerdings das Excel-Objekt und darin ein Worksheet erstellt
          ///
          /// Dann werden die Daten im Clipboard aufgebaut und ins Excel geladen
          ///
          /// ist das geschehen wird Excel sichtbar gemacht
          ///
          /// Mit Autofit werden die Spalten in der idealen Breite präsentiert
          ///
          BegFunc LoadExcel Type(*boolean) Access(*public)
                 DclFld         i                     Type(*integer2)
                 DclFld         bOK                   Type(*boolean) INZ(*true)
                 DclFld         xlApp       Type(Excel.Application)
                 DclFld         xlBook      Type(Excel.Workbook)
                 DclFld         xlRange     Type(Excel.Range)

                                                                  -86-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
                    DclFld               xlSheet          Type(Excel.Worksheet)
                    DclFld               Clip                Type( System.Windows.Forms.Clipboard )

                    SetMousePtr *HourGlass                                        /// Mauspointer auf Sanduhr stellen

                    xlApp = *New Excel.Application()                              /// Instazieren von Excel

                    xlBook               = xlApp.Workbooks.Add( *Noparm )                /// Neues Workbook anlegen
                    xlApp.Visible        = *True                                         /// Excel anzeigen

                    /// für jede Tabelle ein Worksheet erstellen
                    i = 0
                    ForEach dt Type(DataTable)    Collection(Prop_DataSet.Tables)
                           i += 1
                           If i > 3
                                   xlBook.Sheets.Add(*noparm, *noparm, *noparm, *noparm)
                           Endif
                           xlSheet = xlbook.Sheets[i] *As Excel.Worksheet /// das Worksheet ansprechen
                           xlSheet.Name   = Prop_DataSet.Tables(i-1).TableName /// Namen festlegen

                               Clip.Clear()
                               Clip.SetDataObject(TableToClip(Prop_DataSet.Tables(i-1)))   /// Clip füllen
                               xlSheet.Paste(*NoParm, *NoParm )            /// Sheet aus Clipboard füllen

                               xlRange = xlSheet.Columns                   /// Alle Spalten in den Arb.Bereich holen
                               xlRange.AutoFit()                           /// auf ideale Breite formatieren
                    EndFor


                    SetMousePtr *dft                                       /// Mauspointer zurückschalten

                    LeaveSR bOK                                            /// Routine verlassen

          EndFunc


Für jede übergebene Tabelle wird ein eigenes Excel-Worksheet angelegt.




                                                                  -87-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
7.2. Performante Ausgabe einer Tabelle in Excel
Die vorangegangene Version der Excel-Ausgabe ist bei großen Datenmengen sehr
langsam. Hier ist eine Version die eine Tabelle befüllt und dann die Tabelle auf einmal in
Excel ausgibt.
          /// Datentabelle aus Memoryfile laden
          DclFld dtUmsatz               Type(DataTable)
          dtUmsatz =     mCustSales.DataSet.Tables(0)

          /// Arbeitsfelder für Zeile und Spalte
          DclFld rowIndex              Type(*integer4)                                   inz(1)
          DclFld columnIndex           Type(*integer4)
          /// Array deklarieren - mit CreateInstance instanzieren - verlangt Typ, Zeilen und Spalten
          DclFld aTable Type(System.Array)
          aTable = System.Array.CreateInstance( Type.GetType("System.Object"),
                  dtUmsatz.rows.count+1, dtUmsatz.Columns.Count)

          /// Spaltentitel in erste Zeile ausgeben
          ForEach        dc     Type(DataColumn)                           Collection(dtUmsatz.columns)

                    columnindex += 1
                    aTable.SetValue( dc.ColumnName.tostring(), 0, columnIndex-1 )
          EndFor
          /// Datenzeilen ausgeben
          ForEach        dr     Type(DataRow)                 Collection(dtUmsatz.rows)
                    rowindex += 1
                    columnindex = 0
                    /// Werte für jede Zeile eintragen
                    ForEach dc     Type(DataColumn)                        Collection(dtUmsatz.columns)
                               columnindex += 1
                               aTable.SetValue( dr[ columnindex-1 ].tostring(), rowIndex-1, columnIndex-1 )
                    EndFor
          endFor

                         /// Ausgabebereich festlegen und Tabelle einkopieren
          xlRange=xlSheet.Range[ xlSheet.Cells[1,1],
                         xlSheet.Cells[dtUmsatz.rows.count+1,dtUmsatz.Columns.Count] ]
          xlRange.Value2 = aTable




7.3. Excel automatisieren
Die Einbindung von iSeries-Daten und –Programmen in Microsoft-Office birgt großes
Rationalisierungspotential.

Alle Microsoft-Office-Produkte können über VisualRPG.NET gesteuert werden. Das
Ergebnis ist eine wesentliche Hilfestellung für den Anwender und somit eine Ersparnis
an Zeit und Steigerung der Effizienz an vielen Arbeitsplätzen.

iSeries-Daten in Excel auszugeben haben wir alle ja schon vor Jahren geschafft. Die
Basislösung war dem Anwender eine Datei im IFS zur Verfügung zu stellen. Diese Datei
enthielt die gewünschten Werte in CSV- oder TAB-Delimited – Format und musste vom
Anwender abgeholt werden.

Mit VisualRPG.NET können wir:
    o Daten von der iSeries oder anderen Quellen einlesen
    o Excel öffnen
                                                                  -88-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
     o    Arbeitsblätter benennen
     o    Überschriften, Daten und Summenzeilen einfügen
     o    bestimmte Spalten oder ganze Bereiche farblich hervorheben
     o    Schriften und –Attribute festlegen
     o    iSeries-Programme aufrufen und Ergebnisse in Excel-Zellen ablegen
     o    Daten aus Excel an die iSeries zurückgeben, prüfen, aktualisieren, …
     o    u.v.m.

Alle Funktionen die manuell ausgeführt werden setzt MS-Office in Funktionsaufrufe um
die wir auch mit VisualRPG.NET ausführen können. Dieser Leitfaden zeigt am Beispiel
Excel wie Sie als VisualRPG.NET – Programmierer Microsoft-Office-Anwendungen
auftomatisieren können und weist Sie auf mögliche Gefahren und Fallen in den Office-
Produkten hin.




7.4. Beispielanwendung mit Excel
Wir erstellen ein Windows-Dialogprogramm das die Steuerung des Ablaufs übernimmt.
Eingegeben werden Schlüsselwerte der gewünschten Daten sowie Felder die farblich
hervorzuheben oder zu summieren sind.

Schritt 1:
Das Programm öffnet Excel, gibt Überschriften, Daten und Summenzeilen aus, formatiert
das Sheet.

Schritt 2:
Der Benutzer bearbeitet die Daten.

Schritt 3:
Unser Programm prüft die Daten auf Plausibiliät und gibt entsprechende Meldungen im
Sheet aus.

                                                                  -89-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
Schritt 4:
Update der iSeries-Daten aus dem Worksheet.



7.5. Lassen wir uns von Excel helfen
Für uns iSeries-Programmierer ist es kein Problem Daten aus der iSeries zu verarbeiten
oder Programme aufzurufen. Die Herausforderung besteht darin Excel zu erklären was
wir erreichen wollen.
Dazu gibt es eine einfache Methode, man startet den Makro-Recorder, führt die
gewünschten Schritte manuell aus und liest das Ergebnis aus dem aufgezeichneten
Material aus. So lässt man sich von Excel helfen und hat den schnellsten Weg zum
Erfolg gefunden.

Beachten Sie aber dass uns Excel nicht nur hilft sondern auch behindern kann.


7.6. Excel sieht Daten anders
Für Excel sind Daten generell numerisch. Wenn wir nun ein Datenfeld das 5 Nullen
enthält und vielleicht auch noch ein Schlüsselfeld ist ausgeben bleibt in der Zelle nur
eine Null über.
                   iSeries-Datenfeld –Alpha 5-Stellen    Excel-Zelle
                                 00000                       0

Das lässt sich verhindern indem man Excel erklärt dass dieses Feld ‚alpha’ ist und ein
Hochkomma vor den Wert ausgibt.


7.7. Der Standard von Office-Produkten
Wir als Programmierer sind es gewohnt Programme auf den verschiedensten
Betriebssystem-Versionen zu installieren und erwarten dass neuere Betriebssystem-
Versionen im Mindesten den Funktionsumfang der Vorgänger enthalten.
Dieses Verhalten nennt man ‚Kompatibilität’ den wir von Office in nicht allzu hohem
Maße erwarten sollten.
Es gibt Unterschiede in den Formeln zwischen deutschen und anderssprachigen
Versionen, man sollte auch nicht davon ausgehen dass die Versionen untereinander
kompatibel sind.
Über lange Zeit sind die Formeln und Funktionen von Excel gleich geblieben. Auch
Office2003 ist in der Lage Funktionen seiner Vorgänger zu erfüllen, es bringt aber einen
neuen Ansatz mit.




                                                                  -90-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
7.8. XML in Office
Ein Urproblem in der Datenverarbeitung ist die Mischung von Daten und Programmen
innerhalb eines Objekts. Wir sehen das in Excel, wie auch in gänzlich anderen Bereichen
wie ASP, dem Vorläufer von ASPX der Programmcode in HTML-Code ‚einpackt’.

In unserem Excel-Worksheet haben wir Daten, im Hintergrund liegen Formeln oder
Makro’s die wissen was mit den Daten geschehen soll. All das wird in einem Objekt
gespeichert.
Solange es keine Änderung im Werkzeug gibt ist das auch kein Problem, Excel war
ursprünglich konzipiert um Dienste eines Arbeitsblattes (Worksheet) zu übernehmen.

Microsoft hat mit DotNet ein Bekenntnis zu XML abgelegt. XML ist die Sprache in der
Daten beschrieben werden, erfunden von IBM, standardisiert vom W3C und wie so oft
erfolgreich kommerziell genutzt von Microsoft.

Office2003 – Professional setzt auf eine neue Strategie mit Daten umzugehen. Daten
werden von Darstellung und Verarbeitung getrennt was viele Vorteile bietet. Dieselben
Daten können nicht nur in Excel sondern auch in Word oder im Internet dargestellt
werden ohne mehrfach abgespeichert zu sein.


7.9. Beispielanwendung mit Excel
Eingabefelder für Titel, Firmennummer und
Bestellnummer.
Drop-Down-Listen zur Auswahl von Datenfeldern
für Spalten die besonders hervorzuheben und zu
summieren sind.
Das Programm liest die Dateibeschreibung aus
füllt alle Feldnamen in die Liste für die zu
markierenden Daten, die numerischen Felder
werden für die Summierung angeboten.



7.9.1.         Verarbeitungsschritte
Die eingegebene Bestellnummer wird nach der Eingabe auf vorhanden geprüft, erst bei
positiver Feldprüfung erfolgt die Freigabe des ‚Einlesen’-Buttons.

7.9.1.1.       Einlesen

Wurden die erfassten Daten erfolgreich geprüft wird der Einlesen-Button freigegeben.
  o Einlesen der Daten von der iSeries in einen ‚Memoryfile’.
  o Ein Feld eines jeden Datensatzes enthält ein julianisches Datum und wird mit
     einem iSeries-Programm in ein anderes Format umgewandelt das dann in das
     Worksheet ausgegeben wird. (Natürlich könnte man das auch mit DotNet-
     Funktionen, es geht darum den Aufruf eines iSeries-Programmes zu zeigen.)
  o Erstellen einer Überschrift für das Arbeitsblatt

                                                                  -91-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
     o Feldnamen und Feldlänge als Überschriften in Zeile 2 und 3
     o Formatieren des Überschriftsbereichs mit Hintergrundfarbe und Schriftstärke
       ‚Bold’
     o Festlegen der Darstellung des Datenbereiches mit Hintergrundfarben
     o Festlegen der Summenfelder
     o Füllen der Daten ins Worksheet

7.9.1.2.       Prüfen

   o Inhalt jeder Zelle gegen Feldtyp und Feldlänge prüfen
   o Inhalt der Schlüsselfelder auf vorhanden prüfen
   o Bei Fehler Fehlermeldung ausgeben
   o Wenn kein Fehler Merker *Update oder *Neuanlage in Spalte 1 ausgeben
7.9.1.3. Update

     o Anhand des Merkers in Spalte 1 Aktion gegen Datenbank ausführen
     o Erfolg in ‚Bemerkung’-Spalte ausgeben



7.9.2.         Programmcode
7.9.2.1.       Referenzen

Bevor wir mit Excel arbeiten können muss eine Referenz auf Excel hinzugefügt werden.
Sie finden im Leitfaden AVRWindows unter 7.3.9 eine Beschreibung wie das gemacht
wird. Im Programm importieren wir den Excel-Namespace mit
Using Excel


7.9.2.2.       Deklarationen

Wir beweisen in diesem Beispiel dass wir mit DataGate ohne Codeänderung mit jeder
Datenbank arbeiten können. Deswegen sind hier 3 Datenbank-Objekte deklariert von
denen natürlich nur eines aktiv sein kann.
// Datenbank zur Umwandlungszeit
       DclDB          Name( dbProduction )                    DBname( "ASNA SQL DB" )
//     DclDB          Name( dbProduction )                    DBname( "*public/DG NET LOCAL" )
//     DclDB          Name( dbProduction )                    DBname( "ATNICE" )

Hier sind die File-Deklarationen, der MemoryFile ist für den RPG-Programmierer eine Art
Subfile. In DotNet nennt man das DataSet.
          // Deklaration Eingabedaten
          DclDiskFile           Name               ( F4211LA     )       Type( *Update )                +
                                                   DB     ( dbProduction )              +
                                                   File   ( "*Libl/F4211LA" )                           +
                                                   Org    ( *Indexed            )                       +
                                                   Addrec ( *yes                )                       +
                                                   ImpOpen ( *No                )

          DclMemoryFile                  Name      ( memF4211 )            DBDesc         ( dbProduction )   +
                                                   FileDesc                ( "*Libl/F4211" )     +
                                                   RnmFmt                  ( I4211,R4211 )       +
                                                   ImpOpen                 ( *No                 )




                                                                  -92-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
Die Excel-Objekte und Datenfelder die wir zur Verarbeitung von Excel brauchen
deklarieren wir global am Programmbeginn.
// Excel-Objekte und Datenfelder
       DclFld         xlApp                        Type(Excel.Application)
       DclFld         xlBook                       Type(Excel.Workbook)
       DclFld         xlRange                      Type(Excel.Range)
       DclFld         xlSheet                      Type(Excel.Worksheet)
       DclFld         IX                                          Type( *Integer4 )
       DclFld         IR                                          Type( *Integer4 )
       DclFld         NumRecs                      Type( *Integer4 )
       DclFld         @Cancel                             Type( *Boolean )
       DclFld         @CRLF                               Type( *String )
       DclFld         iFieldCount                         Type( *Integer4 )
       DclFld         iEndHeader                          Type( *Integer4 )
       DclFld         iStrData                            Type( *Integer4 )
       DclFld         iEndData                            Type( *Integer4 )
       DclFld         iMarkColumn                         Type( *Integer4 )
       DclFld         iMsgColumn                          Type( *Integer4 )
       DclFld         iKeyColumn                          Type( *Integer4 )


          // Konstante zur Aufbereitung von Excel
          DclConst       @TAB                  Value( U'0009')
          DclConst       @NEW                  Value("*Neuanlage")
          DclConst       @UPD                  Value("*Update")
          DclConst       @DEL                  Value("*DELETE")

Der Datenzugriff läuft über Dateien mit Schlüssel. Wir leiten die Schlüsselfelder im
Programm von Datenfeldern der Datei ab und deklarieren einige Variablen die Verweise
auf die Spaltennummer enthalten.
          // Keyfelder
          DclFld               kfVON               Like(SDDOCO)

          // Keylist anlegen
          DclKlist klUpdate
                 DclKfld kfSDDOCO
                 DclKfld kfSDDCTO
                 DclKfld kfSDKCOO
                 DclKfld kfSDLNID

          // Schlüsselfelder für Key anlegen
          DclFld kfSDDOCO       Like(SDDOCO)
          DclFld kfSDDCTO       Like(SDDCTO)
          DclFld kfSDKCOO       Like(SDKCOO)
          DclFld kfSDLNID       Like(SDLNID)

          // Konstante für Schlüsselfelder
          DclConst       fnSDDOCO       Value("SDDOCO")
          DclConst       fnSDDCTO       Value("SDDCTO")
          DclConst       fnSDKCOO       Value("SDKCOO")
          DclConst       fnSDLNID       Value("SDLNID")

          // Spalten für Schlüsselfelder
          DclFld ikSDDOCO       *integer2
          DclFld ikSDDCTO       *integer2
          DclFld ikSDKCOO       *integer2
          DclFld ikSDLNID       *integer2




                                                                  -93-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
Um ein iSeries-Programm aufzurufen und Daten auszutauschen kann man, wie in RPG
üblich, Parameterlisten deklarieren. Hier sind die Felder und die Deklarationen.
          // Parameterfelder deklarieren
          DclFld parm#SIDAT     Type(*char)                   Len(6)
          DclFld parm#EDAT      Type(*char)                   Len(8)
          DclFld parm#FFMT      Type(*char)                   Len(7)
          DclFld parm#TFMT      Type(*char)                   Len(7)
          DclFld parm#SEP       Type(*char)                   Len(7)
          DclFld parm$ERTST     Type(*char)                   Len(1)
          DclFld parm$CTRY      Type(*char)                   Len(2)
          DclFld parm#FJPN      Type(*char)                   Len(1)
          DclFld parm#TJPN      Type(*char)                   Len(1)
          DclFld parm#EDAT2     Type(*char)                   Len(10)
          DclFld parm#SIDT2     Type(*char)                   Len(8)

          // Parameterliste anlegen
          DclPlist       plX0028
                 DclParm parm#SIDAT
                 DclParm parm#EDAT
                 DclParm parm#FFMT
                 DclParm parm#TFMT
                 DclParm parm#SEP
                 DclParm parm$ERTST
                 DclParm parm$CTRY
                 DclParm parm#FJPN
                 DclParm parm#TJPN
                 DclParm parm#EDAT2
                 DclParm parm#SIDT2

          // Spalte für Summenfeld
          DclFld iSUMFLD *integer2

          /Endregion


7.9.2.3.       Constructor

Der Konstruktor ist der Einsprungpunkt für das Programm. Mit InitializeComponent
werden die Controls initialisert. Es macht Sinn hier die Datenbankverbindung
aufzubauen und die Dateien zu eröffnen.
Die Dispose-Subroutine ist die letzte Routine die in diesem Programm ausgeführt wird,
hier sollte man die Daten und die Verbindung zur Datenbank schliessen.
/Region Constructor

     BegSr Dispose Access(*Public) Modifier(*Overrides)
         DclSrParm disposing Type(*Boolean)
         If disposing
             //
             // TODO: close your files and disconnect your databases here
             //

                    Close F4211LA
                    Close memF4211
                    DisConnect dbProduction

        EndIf
         *Base.Dispose(disposing)
     EndSr

     BegConstructor Access(*Public)
         //
         // Required for Windows Form Designer support
         //
         InitializeComponent()

           //
           // TODO: Add any constructor code after InitializeComponent call
           //

                    Connect dbProduction

                                                                  -94-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
                    Open F4211LA
                    Open memF4211

                    stb.Text = "DB " + dbProduction.DBName + " in Verwendung"

     EndConstructor

/endregion

7.9.2.4.       Events

Hier werden die Ereignisse abgearbeitet. Im Initialisieren werden die Datenlisten in den
Comboboxen aus der Feldbeschreibung der Daten eingelesen.
    /Region Events

     // Initialisierungsarbeiten Formular
        BegSr Form1_Load Access(*Private) Event(*this.Load)
                DclSrParm sender Type(*Object)
                DclSrParm e Type(System.EventArgs)

                    // Zeilenschaltung initialisieren
                    @CRLF = System.Environment.NewLine

                    // Error-Provider auf KundenNummer setzen
                    epSDDOCO.SetIconAlignment(*this.txtSDDOCO, ErrorIconAlignment.MiddleRight)
                    epSDDOCO.SetIconPadding(*this.txtSDDOCO, 2)
                    epSDDOCO.BlinkRate = 1000
                    epSDDOCO.BlinkStyle = System.Windows.Forms.ErrorBlinkStyle.AlwaysBlink

                    // Felder aus Tabelle einlesen und in ComboBox ausgeben
                    ForEach dc     Type(DataColumn) +
                           Collection(memF4211.DataSet.Tables(0).Columns())

                               cboFelder.Items.Add(dc.ColumnName)

                               // -1 sind numerische Werte
                               if dc.Maxlength < 0
                                      cboSummenFeld.Items.Add(dc.ColumnName)
                               Endif

                    EndFor

          EndSr


          // bei Klick auf Start wird diese Routine aufgerufen
          BegSr btnStart_Click Access(*Private) Event(*this.btnStart.Click)
                 DclSrParm sender Type(*Object)
                 DclSrParm e Type(System.EventArgs)

                    SetMousePtr *HourGlass

                    ExSr   LoadDataFromFile
                    ExSr   InitExcel
                    ExSr   CreateExcelHeader
                    ExSr   PutDataIntoExcel

                    // Schalten der Buttons
                    btnCheck.Enabled = *true
                    btnUpdate.Enabled = *true
                    btnStart.Enabled = *false

                    // Meldung an den Benutzer
                    MsgBox Msg("Bearbeiten Sie nun die Daten in Excel") +
                    Title("Daten bereit") Icon(*information)

                    SetMousePtr *Dft

          EndSr


          // bei Klick auf Prüfen wird ProcessData mit *true (=prüfen) aufgerufen
          BegSr btnCheck_Click Access(*Private) Event(*this.btnCheck.Click)


                                                                  -95-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
                    DclSrParm sender Type(*Object)
                    DclSrParm e Type(System.EventArgs)

                    ProcessData(*true)

          EndSr


          // bei Klick auf Update wird ProcessData mit *false (=Update) aufgerufen
          BegSr btnUpdate_Click Access(*Private) Event(*this.btnUpdate.Click)
                 DclSrParm sender Type(*Object)
                 DclSrParm e Type(System.EventArgs)

                    // *false führt Update durch, der Rückgabewert gibt den Erfolg bekant
                    If ProcessData(*false)
                           MsgBox Msg("Speichern der geänderten Daten erfolgreich") + Title("Daten
                           geändert") Icon(*information)
                    Else
                           MsgBox Msg("Fehler bei Speichern der Daten") +
                           Title("Daten korrigieren") Icon(*exclamation)
                    Endif

                    *this.Close()

          EndSr


     /Endregion

7.9.2.5.       Logik

In dieser Region werden alle Subroutinen und Funktionen abgelegt die mit dem Handling
der Daten zu tun haben. Sie finden hier Routinen vom Einlesen der Daten bis zur
Prüfung der Auftragsnummer.


7.9.2.5.1.    Einlesen der Daten mit ReadRange
ReadRange ist ein neuer OpCode von ASNA der den Zugriff auf einen bestimmten
Bereich in der Datei eingrenzt. Es können Unter- und Obergrenze gesetzt werden.
Ausgegeben werden die Daten in einen MemoryFile der ein DotNet-DataSet darstellt und
für den iSeries-Programmierer mit einem Subfile vergleichbar ist.
    /Region Logic

     // Daten aus der iSeries in Excel laden
     BegSr LoadDataFromFile

                    // neuer Command von ASNA ersetzt SETLL
                    ReadRange F4211LA Firstkey(kfVON) Access(*nolock)
                    If %Found
                           DoWhile NOT %EOF(F4211LA)
                                   IF SDKCOO = txtSDKCOO.Text
                                          // Spalte mit Daten aus iSeries-Programm füllen
                                          SDVR01 = GetDateOutOfJulian(SDDRQJ)
                                          Write memF4211
                                   Endif
                                   Read F4211LA
                           Enddo
                    Endif

     EndSR


7.9.2.5.2.   Aufrufen eines iSeries-Programmes
Die Parameterfelder und Parameterlisten wurden bereits vorbereitet. In dieser Routine
wird nur mehr der Aufruf abgehandelt.
Die Funktion bekommt einen numerisch-gezonten Input-Parameter und liefert ein Datum
in Form einer Zeichenkette zurück.

                                                                  -96-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
     // Julianisches Datum über iSeries-Programm auslesen
     BegFunc GetDateOutOfJulian       Type(*string)
                DclSrPArm      zonInVal      like(SDDRQJ)

                    parm#SIDAT = zonInVal.ToString()

                    Call Pgm("Petroplus/X0028")               DB(dbProduction)   Err(*extended) +
                           Parmlist(plX0028)

                    LeaveSR parm#EDAT

          EndFunc



7.9.2.5.3.   Erstellen des Excel-Objektes
Das Feld xlApp wird als Excel-Objekt instanziert und hält die Verbindung zwischen
Programm und Worksheet. Hier werden ins Worksheet ein Workbook ausgegeben, das
erste Worksheet wird mit dem Namen des Titels benannt.
     // Excel-Worksheet erstellen
     BegSr InitExcel

                    // Excel instanzieren
                    xlApp = *New Excel.Application()

                    xlApp.Visible    = *True
                    xlApp.WindowState = XlWindowState.xlMaximized

                    // Neues Workbook anlegen und erstes Sheet benennen
                    xlBook         = xlApp.Workbooks.Add( *Noparm )
                    xlSheet      = xlbook.Sheets[1] *As Excel.Worksheet
                    xlSheet.Name = txtTitel.Text

  EndSR


7.9.2.5.4.     Ausgeben der Überschrift
In Zeile 1 wird der Titel des Worksheets, in Zeile 2 und 3 der Feldname und die
maximale Feldlänge ausgegeben.
In der Schleife über die Felder werden die Spalten der Keyfelder in die Arbeitsfelder
ausgegeben um sie bei der Prüfung einfach ansprechen zu können.
     // Überschriftszeile in Excel ausgeben
     BegSr CreateExcelHeader

          DclFld i                       *integer2
          DclFld strLength               *string

          //Feldanzahl ermitteln
          iFieldCount = memF4211.DataSet.Tables(0).Columns.Count()

          // Titel festlegen, Spalten verbinden und zentrieren
          xlSheet.Cells[1,1] = txtTitel.Text
          xlRange = xlSheet.Range[xlSheet.Cells[1,1], xlSheet.Cells[1,iFieldCount+1]]
          xlRange.HorizontalAlignment = xlHAlign.xlHAlignCenter
          xlRange.Merge( *NoParm )

          // Feldnamen und Max.Länge in Überschrift holen
          xlSheet.Cells[2,1] = "Feldname"
          xlSheet.Cells[3,1] = "Max.Länge"

          // Schleife über Datenfelder zur Ausgabe der Felddaten in Überschrift
          ForEach dc     Type(DataColumn)      Collection(memF4211.DataSet.Tables(0).Columns())

                    i = i + 1
                    strLength = dc.Maxlength.Tostring()

                    // -1 sind numerische Werte
                    xlSheet.Cells[2,i+1] = dc.ColumnName
                    if NOT strLength.StartsWith("-")

                                                                  -97-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
                               xlSheet.Cells[3,i+1] = dc.Maxlength.Tostring()
                    Endif

                    // herausfinden der Spalte die farblich markiert werden soll
                    if dc.ColumnName = cboFelder.Text
                           iMarkColumn = i+1
                    Endif

                    // merken der Spalte mit dem Satzkey
                    Select
                           when dc.ColumnName.ToUpper() =                  fnSDDOCO
                                   ikSDDOCO = i+1
                           when dc.ColumnName.ToUpper() =                  fnSDDCTO
                                   ikSDDCTO = i+1
                           when dc.ColumnName.ToUpper() =                  fnSDKCOO
                                   ikSDKCOO = i+1
                           when dc.ColumnName.ToUpper() =                  fnSDLNID
                                   ikSDLNID = i+1
                    Endsl

                    If dc.ColumnName.ToUpper() = cboSummenfeld.text
                           iSUMFLD = i+1
                    Endif

          EndFor

          // Letzte Überschriftszeile merken
          iEndHeader = 3

          // Bemerkungsfeld anhängen
          iMSgColumn = iFieldCount+2
          xlSheet.Cells[3,iMSgColumn] = "Bemerkung"
          xlRange = xlSheet.Range[xlSheet.Cells[1,iMSgColumn], xlSheet.Cells[3,iMSgColumn]]
          xlRange.VerticalAlignment = xlVAlign.xlVAlignCenter
          xlRange.Merge( *NoParm )

          // Überschriftsbereich auswählen
          xlRange=xlSheet.Range[xlSheet.Cells[1,1], xlSheet.Cells[3,iMSgColumn]]

         // Einstellungen für Bereich festlegen
         With xlRange
                 .Horizontalalignment = xlHAlign.xlHAlignCenter
                 .Font.Bold                    = 1         // Achtung *ON arbeitet hier nicht
                 .Font.Name                    = 'Arial'
                 .Font.Size                    = 10
                 .Interior.Colorindex = 15             // Hellgrau
                 .Interior.Pattern             = 1            // xlSolid
                 .Borders.Linestyle            = 1            // Durchgehende Linie
                 .Borders.Weight               = 3            // Mittelbreit
         EndWith


     EndSR


7.9.2.5.5.   Ausgeben der Daten
Ein von Microsoft empfohlener Weg ist es die Daten in den Zwischenspeicher ‚Clipboard’
zu laden um sie dann an Excel zu übergeben. Die Felder werden mit TAB getrennt und
am Ende der Zeile ein CRLF eingefügt.
Nachdem die Zeilen eingefügt wurden ist das Summenfeld einzufügen und die
Formatierung anzupassen.
// Daten in Excel ausgeben
BegSr PutDataIntoExcel

          DclFld    strWrk *string
          DclFld    Clip    Type( System.Windows.Forms.Clipboard )
          DclFld    strSumme       *string
          DclFld    strError       *string

          // Schleife über die Sätze des MemoryFiles
          ForEach dr     Type(DataRow) Collection(memF4211.DataSet.Tables(0).rows)

                    // Schleife über alle Felder

                                                                  -98-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
                    ForEach dc Type(DataColumn) Collection(memF4211.DataSet.Tables(0).Columns)

                               // Feld für Feld einlesen
                               strWrk = strWrk + @TAB + dr.Item(dc.ColumnName).Tostring()

                    EndFor

                    // Zeilenschaltung am Satzende
                    strWrk = strWrk + @CRLF

          EndFor

          // Beginn und Ende der Datenzeilen festlegen
          iStrData = iEndHeader + 1
          iEndData = istrData + memF4211.DataSet.Tables(0).rows.Count - 1

          // gewünschte Spalte markieren
          if iMarkColumn > 0
                 xlRange=xlSheet.Range[xlSheet.Cells[istrData,iMarkColumn],
                 xlSheet.Cells[iEndData,iMarkColumn]]
                 With xlRange
                         .Font.Bold             = 1          // Achtung *ON arbeitet hier nicht
                         .Font.Name             = 'Arial'
                         .Font.Size             = 10
                         .Interior.Colorindex = 34         // Hellblau
                         .Interior.Pattern      = 1          // xlSolid
                         .Borders.Linestyle     = 1          // Durchgehende Linie
                         .Borders.Weight               = 2    // schmal
                 EndWith
          Endif

          // Bemerkungsfeld formatieren
          xlRange=xlSheet.Range[
                 xlSheet.Cells[1,iMSgColumn], xlSheet.Cells[iEndData,iMSgColumn] ]
          xlRange.Interior.Colorindex= 40             // Rosa
          xlRange.Interior.Pattern       = 1        // xlSolid
          xlRange.Borders.Linestyle      = 1        // Durchgehende Linie
          xlRange.Borders.Weight = 3         // Mittelbreit

          // Summenfeld einbauen
          if iSUMFLD > 0
                 xlRange = xlSheet.Range[ xlSheet.Cells[iEndData+1,iSUMFLD],
                 xlSheet.Cells[iEndData+1,iSUMFLD] ]
                 strSumme = "=SUMME(Z" + istrData.ToString() + "S" + iSUMFLD.ToString() + ":Z" +
                 iEndData.ToString() + "S" + iSUMFLD.ToString() + ")"
                 Try
                         xlRange.FormulaR1C1 = strsumme
                 Catch ex Exception
                         strError = ex.Message
                 EndTry
                 With xlRange
                         .Font.Bold                     = 1
                         .Font.Name                     = 'Arial'
                         .Font.Size                     = 10
                         .Interior.Colorindex = 15           // Hellgrau
                         .Interior.Pattern      = 1             // xlSolid
                         .Borders.Linestyle     = 1             // Durchgehende Linie
                         .Borders.Weight                = 3            // Mittelbreit
                 EndWith
          Endif

          // Daten aus Clipboard in Excel übergeben
          xlSheet.Range[ xlSheet.Cells[istrData,1], xlSheet.Cells[iEndData,iFieldCount + 1] ].Select(
          )
          Clip.SetDataObject ( strWrk )
          xlSheet.Paste ( *NoParm, *NoParm )

          // Autofit für alle Spalten ausführen
          xlRange = xlSheet.Columns            // Select all columns
          xlRange.AutoFit()

EndSR




                                                                  -99-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
7.9.2.5.6.    Prüfroutinen aus dem UserDialog
Hier sind die Prüfroutinen die aus dem Formular angesprochen werden.
          // Prüfung des Eingabefeldes Nummer
          BegFunc NummerOK      Type(*boolean)

                    // sicherheitshalber den Start-Button deaktivieren
                    btnStart.Enabled = *false

                    // wenn leer - keine Prüfung
                    If txtSDDOCO.Text = *blank
                           LeaveSr *true
                    Endif

                    // ist der Wert numerisch - feststellen durch zuweisen in gepacktes Feld
                    Try
                           kfVON = txtSDDOCO.Text
                    Catch ex Exception
                           stb.Text = "Auftragsnummer ist nicht numerisch"
                           LEaveSr *false
                    EndTry

                    // prüfen ob der Kunde existiert
                    Chain F4211LA Key(kfVON)
                    If %found
                           btnStart.Enabled = *true
                           stb.Text = "Daten für Auftrag " + %trim(txtSDDOCO.text) +
                           " können verarbeitet werden"
                           LEaveSR *true
                    Else
                           stb.Text = "Auftrag wurde nicht gefunden"
                           LeaveSr *false
                    Endif

                    LEaveSR *true

          EndFunc



          // prüfen der Kundennummer
          BegSr txtSDDOCO_Validated Access(*Private)                       Event(*this.txtSDDOCO.Validated)
                 DclSrParm sender Type(*Object)
                 DclSrParm e Type(System.EventArgs)

           If NummerOK()
               // Fehler im ErrorProvider löschen
               epSDDOCO.SetError(*this.txtSDDOCO, "")
           Else
               // Fehler im Errorprovider setzen
               epSDDOCO.SetError(*this.txtSDDOCO, "Auftragsnummer st ungültig")
           EndIf

          EndSr


          /endregion



7.9.2.5.7.      Prüf- und Verarbeitungs-Hauptroutine
Diese Routine prüft oder verarbeitet die Daten, abhängig vom Aufrufparameter. Werden
bei der Prüfung Fehler gefunden wird eine MessageBox angezeigt. Die Verarbeitung pro
Zeile läuft in einer eigenen Routine der die Zeilennummer übergeben wird.
     // eine eigene CodeRegion für den Dialog Excel/iSeries
     /region Prüfen und Update

     // Datenverarbeitung prüfen oder updaten
     // wird über eine Routine abgewickelt die die Steuerung übernimmt
     BegFunc ProcessData       Type(*boolean)
                DclSrParm      bCheck *boolean // *True = prüfen, *False = Update



                                                                 -100-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
                    // Diverse Variablen
                    DclFld bOK            Type(*boolean) inz(*true)
                    DclFld i              *integer4
                    DclFld iErrCnt *integer4

                    // Schleife über die Datenfelder
                    Do iStrData    iEndData       i

                               // iErrCnt wird per Referenz übergeben, somit wird im
                               // aufgerufenen Programm die Variable aus dieser Routine
                               // bearbeitet
                               If bCheck
                                      if not CheckLine(i,*ByRef iErrCnt)
                                              bOK = *false
                                      Endif
                               Else
                                      if not UpdateLine(i,*ByRef iErrCnt)
                                              bOK = *false
                                      Endif
                               Endif

                    Enddo

                    // Autofit für alle Spalten ausführen
                    xlRange = xlSheet.Columns
                    xlRange.AutoFit()

                    // Meldung ausgeben wenn Fehler gefunden wurden
                    If not bOK
                           MSgBox Msg("Es wurden " + iErrCnt.ToString() + " Fehler gefunden")
                           Title("Fehler in Daten") Icon(*information)
                    Endif

                    LeaveSr bOK

     EndFunc




7.9.2.5.8.   Prüfen einer Zeile
Die Prüfung erfolgt durch zuweisen des Feldwerts in das Datenfeld und Prüfung der
Länge bei Alpha-Felder. Dabei werden Fehler abgefangen und im Fehlerfall eine
Meldung zurückgegeben und der Fehlerschalter erhöht.
Der Satzkey wird gegen die Datei geprüft um festzustellen ob der Satz bereits existiert.
Entsprechend wird ein Hinweis in die erste Spalte ausgegeben.
     // prüfen einer Datenzeile
     // die Zeilennummer wird per Value übergeben
     // der Fehlerzähler per Referenz, dadurch wird die Variable iErrCnt im
     // aufrufenden Programm verwendet,
     // der Name ist egal, der Typ muss übereinstimmen
     BegFunc    CheckLine      Type(*boolean)
                DclSrParm      iLine *integer4
                DclSrParm      iErrors *integer4     By(*reference)

                    // diverse Felder
                    DclFld i                                  *integer4
                    DclFld dr                                 Type(DataRow)
                    DclFld strMsgText              *string
                    DclFld strFeld                 *string
                    DclFld strWert                 *string
                    DclFld strMaxLen               *string
                    DclFld iMaxLen                 *Integer2
                    DclFld bOK                             *boolean           Inz(*true)

                    // zur Prüfung einen neuen Satz erstellen der aber nicht ausgegeben wird
                    dr = memF4211.DataSet.Tables(0).NewRow()
                    strMsgText = *blank

                    // Schleife über die Excel-Datenfelder
                    Do Fromval(2) Toval(iMsgColumn-1) Index(i)

                               // Feldnamen herausfinden

                                                                 -101-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
                               xlRange = xlSheet.Cells[2,i] *as Range
                               xlRange.Select()
                               strFeld = xlRange.Text.ToString()

                               // für Zeichenfelder Feldlänge herausfinden
                               xlRange = xlSheet.Cells[3,i] *as Range
                               xlRange.Select()
                               strMaxLen = xlRange.Text.ToString()
                               if strMAxlen <> *blank
                                      iMaxLen = strMaxLen
                               Else
                                      iMAxLen = 0
                               Endif

                               // Feldwert aus Datenzeile einlesen
                               xlRange = xlSheet.Cells[iLine,i] *as Range
                               xlRange.Select()
                               strWert = xlRange.Text.ToString()

                               // Fehler abfangen (CPF0000)
                               try

                                         // versuchen den Feld den Wert zu übergeben
                                         dr.Item(strFeld) = strWert

                               Catch ex Exception

                                         // bei FEhler reagieren
                                         strMsgText = strMsgText + strFeld + "(" + ex.Message + ");"
                                         iErrors = iErrors + 1
                                         xlRange=xlSheet.Range[ xlSheet.Cells[iLine,i], xlSheet.Cells[iLine,i]
                                         ]
                                         xlRange.Interior.Colorindex = 40                    // Rosa
                                         bOk = *false

                               EndTry

                               // Zeichenfelder auf gültige Länge prüfen
                               if strWert.Length > iMaxLen and iMaxLen > 0

                                         strMsgText = strMsgText + strFeld +
                                         "(um " + %char(strWert.Length - iMaxLen) +
                                         " Stellen zu lang);"
                                         iErrors = iErrors + 1
                                         xlRange=xlSheet.Range[ xlSheet.Cells[iLine,i], xlSheet.Cells[iLine,i]
                                         ]
                                         xlRange.Interior.Colorindex = 40                    // Rosa
                                         bOk = *false

                               Endif

                               // merken Felder für den Satzkey
                               Select
                                      when i = ikSDDOCO
                                              kfSDDOCO = strWert
                                      when i = ikSDDCTO
                                              kfSDDCTO = strWert
                                      when i = ikSDKCOO
                                              kfSDKCOO = txtSDKCOO.text // strWert
                                      when i = ikSDLNID
                                              kfSDLNID = strWert
                               Endsl

                    Enddo

                    // wenn kein Fehler auf Datensatz der iSEries prüfen
                    // und *Update/*Neuanlage in erste Spalte ausgeben
                    if strMsgText = *blank

                               kfSDKCOO = "00000"

                               Chain F4211LA Key(klUpdate) // nicht für SQL-Server Access(*nolock)
                               IF %found()
                                      xlSheet.Cells[iLine,1] = @UPD
                               Else
                                      xlSheet.Cells[iLine,1] = @NEW
                               Endif

                                                                 -102-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
                               UNLOCK F4211LA // für SQL-Server

                    Endif

                    // Text in Meldungsspalte ausgeben
                    xlSheet.Cells[iLine,iMsgColumn] = strMsgText
                    LeaveSR bOK

          EndFunc



7.9.2.5.9.   Update des Datensatzes
Angängig vom Wert der Spalte 1 werden die Daten der Zeile verarbeitet. Ist hier *Delete,
*Update oder *Neuanlage so wird die entsprechende Funktion ausgeführt.
Die Schlüsselfelder werden aus der in der Load-Routine herausgefundenen Spalte
geladen. Die Zuweisung der Daten in die Felder erfolgt über den Feldnamen.
     // Update der iSeries aus einer Datenzeile von Excel
     BegFunc    UpdateLine     Type(*boolean)
                DclSrParm      iLine *integer4
                DclSrParm      iErrors *integer4 By(*reference)

                    DclFld     i                              *integer4
                    DclFld     dr                             Type(DataRow)
                    DclFld     strMsgText          *string
                    DclFld     strFeld             *string
                    DclFld     strWert             *string
                    DclFld     strVerarb           *string
                    DclFld     bOK                            *boolean        Inz(*true)

                    strMsgText = *blank

                    // Verarbeitung nur wenn Spalte 1 nicht leer
                    xlRange = xlSheet.Cells[iLine,1] *as Range
                    xlRange.Select()
                    strVerarb = xlRange.Text.ToString()

                    if strVerarb = *blank
                           LeaveSr bOK
                    Endif

                    // Keyfelder für Update auslesen
                    xlRange = xlSheet.Cells[iLine,ikSDDOCO] *as Range
                    xlRange.Select()
                    kfSDDOCO = xlRange.Text.ToString()

                    xlRange = xlSheet.Cells[iLine,ikSDDCTO] *as Range
                    xlRange.Select()
                    kfSDDCTO = xlRange.Text.ToString()

//                  weil Excel die ‘00000’ auf ‘0’ verkürzt …
//                  xlRange = xlSheet.Cells[iLine,ikSDKCOO] *as Range
//                  xlRange.Select()
                    kfSDKCOO = txtSDKCOO.Text // xlRange.Text.ToString()

                    xlRange = xlSheet.Cells[iLine,ikSDLNID] *as Range
                    xlRange.Select()
                    kfSDLNID = xlRange.Text.ToString()


                    // wenn Satz gelöscht werden soll
                    If strVerarb.ToUpper() = @DEL
                           Delete F4211LA Key(klUpdate) Err(*extended)
                           If %Error()
                                   strMsgText = "Fehler bei Löschen Datensatz"
                                   iErrors = iErrors + 1
                                   bOK = *false
                           Else
                                   strMsgText = "Löschen erfolgreich"
                           Endif

                    Else



                                                                 -103-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
                               // Satz auf iSeries sperren
                               Chain F4211LA Key(klUpdate)                 Access(*dft)

                               // Update auf Datenfelder
                               Do Fromval(2) Toval(iMsgColumn-1) Index(i)

                                         xlRange = xlSheet.Cells[2,i] *as Range
                                         xlRange.Select()
                                         strFeld = xlRange.Text.ToString()

                                         xlRange = xlSheet.Cells[iLine,i] *as Range
                                         xlRange.Select()
                                         strWert = xlRange.Text.ToString()

                                         try

                                                   Select

                                                              when strFeld = "SDMCU"
                                                                     SDMCU = strWert

                                                              when strFeld = "SDLITM"
                                                                     SDLITM = strWert

                                                              when strFeld = "SDOCTO"
                                                                     SDOCTO = strWert

                                                   EndSL

                                         // evtl. Fehler abfangen
                                         Catch ex Exception

                                                   strMsgText = strMsgText + strFeld +
                                                   "(" + ex.Message + ");"
                                                   iErrors = iErrors + 1
                                                   xlRange=xlSheet.Range[xlSheet.Cells[iLine,i],
                                                   xlSheet.Cells[iLine,i] ]
                                                   xlRange.Interior.Colorindex = 40                // Rosa
                                                   bOk = *false

                                         EndTry

                               Enddo

                               // wenn kein Fehler Update/Write ausführen
                               if strMsgText = *blank

                                         IF %found()
                                                Update F4211LA Err(*extended)
                                         Else
                                                Write F4211LA Err(*extended)
                                         Endif

                                         If %Error()
                                                strMsgText = "Fehler bei Ausgabe in Datenbank"
                                                iErrors = iErrors + 1
                                                bOK = *false
                                         Else
                                                strMsgText = "Ausgabe erfolgreich"
                                         Endif

                               Endif

                               UNLOCK F4211LA // für SQL-Server

                    Endif

                    // Meldung in Infospalte ausgeben
                    xlSheet.Cells[iLine,iMsgColumn] = strMsgText
                    LeaveSR bOK

     EndFunc


     /Endregion



                                                                 -104-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
7.10.Office-Funktionen herausfinden


7.10.1. Makro einsetzen
Ein Großteil der Zeit die man für Office-Programmierung benötigt geht damit verloren
nach Funktionen von Office zu suchen.
Wenn man im Internet nach Lösungen sucht muss man sich erfahrungsgemäß durch
Tonnen von Datenmüll suchen bis man die gewünschte Lösung gefunden hat.
In der Excel-Hilfe kann es vorkommen dass man Lösungen angeboten bekommt die zur
englischen Originalversion passen aber für die Sprachversion nicht verwendbar sind.

Der beste Weg ist Office selbst die Lösung beschreiben zu lassen, das geht wie folgt:


1. Makro-Aufzeichnung starten




                                                       2. gewünschte Funktion ausführen




3. Aufzeichnung beenden




                                                                           4. VB-Editor aufrufen


                                                                 -105-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
Das Makro enthält die ausgeführten Funktionen.
Dieser Code lässt sich in das Programm übertragen, Beispiele dazu sehen in jeder
Routine.




7.10.2. Vorsicht bei Formeln
Die Summenformel ist so nicht in Excel übertragbar.
Achten Sie auch darauf bei Einfügen von Formeln mögliche Fehler abzufangen.
Excel wirft eine Fehlermeldung aus wenn eine ungültige Formel ausgegeben wird die
das Programm zum Absturz bringt wenn Sie nicht in einem Try-Catch-Block abgefangen
wird.
Prüfen Sie Formeln mit der Online-Hilfe. Hier ist zur Erinnerung der Code aus unserem
Beispiel.
          // Summenfeld einbauen
          if iSUMFLD > 0
                 xlRange = xlSheet.Range[ xlSheet.Cells[iEndData+1,iSUMFLD],
                 xlSheet.Cells[iEndData+1,iSUMFLD] ]
                 strSumme = "=SUMME(Z" + istrData.ToString() + "S" + iSUMFLD.ToString() + ":Z" +
                 iEndData.ToString() + "S" + iSUMFLD.ToString() + ")"
                 Try
                         xlRange.FormulaR1C1 = strsumme
                 Catch ex Exception
                         strError = ex.Message
                 EndTry
                 With xlRange
                         .Font.Bold                     = 1
                         .Font.Name                     = 'Arial'
                         .Font.Size                     = 10
                         .Interior.Colorindex = 15           // Hellgrau
                         .Interior.Pattern      = 1             // xlSolid
                         .Borders.Linestyle     = 1             // Durchgehende Linie
                         .Borders.Weight                = 3            // Mittelbreit
                 EndWith
          Endif


Die funktionierende Summenformel die hier ausgegeben wird lautet "=SUMME(Z3S3:Z6S3)"
Unser VB-Makro empfiehlt aber "=SUM(R[-3]C:R[-1]C)", diese Funktion arbeitet definitiv
nicht.
Formeln sind immer abhängig von der Office-Version und der Sprache. Der Wunsch von
Microsoft war es dem Benutzer die Arbeit so angenehm wie möglich zu machen, für den
Programmierer ist dadurch die Arbeit nicht einfacher geworden.
                                                                 -106-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
7.11.Integration von Word und iSeries mit RPG.NET


7.11.1. Konzept




                Kunden- Artikel-
                und WWS-Daten
                von der iSeries                                                              Bilder und Texte
                                                                                             vom Server




                   Ausgabe in ein
                   Word-Dokument
                                                                           Druck und
                                                                           Versand per Post




                                                                           Direkt in eMail
                                                                           ausgeben



                                                                           Dokumentarchiv
                                                                           oder elektron.
                                                                           Archiv




                                                                 -107-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
7.11.2. Umsetzung
7.11.2.1. Kopf- und Fussinformationen

Das Bearbeitungsprogramm wird mit Kopf- und Fußdaten vorbelegt. Die Erfassung der
Positionen erfolgt über komfortable Dialoge.

Zum Kopftext kann ein Bild ausgewählt werden.




                                                                 -108-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
7.11.2.2. Artikelpositionen

Die Artikelliste wird über den Auswahl-Link aufgerufen. Die Auswahl erfolgt über
markieren und OK.




Die Mengen und
Preise werden direkt
im Grid editiert.
Der Gesamtwert wird
automatisch
mitgerechnet.




                                                                           Die Detailmaske zeigt die
                                                                           Positionsdaten an und lässt
                                                                           Bilder und Texte für den Druck
                                                                           vor    und/oder      nach  der
                                                                           Positionszeile erfassen.




                                                                 -109-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES

Die Auswahl und Bearbeitung von
Bildern und Texten erfolgt über
Kontext-Menü’s.
Die Dateiauswahl basiert auf den
gewohnten Windows-Dialogen.




                                                                 -110-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
In den Textbereichen sind
Bearbeitungsmöglichkeiten
vorgesehen. Einige häufig
verwendete             wie
Fett/Unterstreichen werden
direkt angeboten.
Natrülich    können auch
Tabellen und Bilder in den
Texten mitkommen.




                                                                           Anspruchsvollere
                                                                           Formatierungswünsche
                                                                           werden         über       den
                                                                           ‚Schriftart’-Dialog erfüllt.




                                                                 -111-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
Die Positionstabelle bietet eine Übersicht über die erfassten Positionen und ihre
Zusatzinformationen.




                                                                 -112-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
7.11.3. Das Ergebnis




Dieses Beispiel zeigt dass es problemlos möglich ist Daten, Text und Bilder über
RPG.NET weiterzuverarbeiten.

Beachten Sie dass die Textattribute wie Fett, Unterstrichen, Farbe etc. vollständig
übernommen werden.

Die Formateinstellungen des Basisdokuments wurden übernommen, können aber auch
übersteuert werden.




                                                                 -113-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
7.11.4. Technische Voraussetzungen
Die Voraussetzungen am Client sind

         Betriebssystem Windows
         IP-Verbindung zur iSeries
         ASNA-DataGate zum Zugriff (auf der iSeries)
         MS-Word zur Bearbeitung der Dokumente


Die Textbausteine und Vorlagen werden in RTF-Format vorbereitet, die Ausgabe erfolgt
in Word.

RTF ist ein standardisiertes Format für Texte das auch von Word gelesen und
ausgegeben werden kann.




7.11.5. Programmcode Word
Hier ist die Druckroutine für die Ausgabe in Word, natürlich ist das Word-Objekt wie
Excel zu referenzieren.
In einer Vorlaufschleife werden für jede Datenzeile zuerst Objekte erstellt die danach in
ein einziges Dokument gebunden werden.
          /region Word

          BegSr btnDrucken_Click Access(*Private) Event(*this.btnDrucken.Click)
                 DclSrParm sender Type(*Object)
                 DclSrParm e Type(System.EventArgs)                      Löschen      bestehender
                                                                             RTF-Files und sichern
                    If File.Exists(strWorkDir + "\KOPF.rtf")                 der aktuellen RTF-Files
                           File.Delete(strWorkDir + "\KOPF.rtf")
                    Endif
                    rtbKopf.SaveFile(strWorkDir + "\KOPF.rtf", RichTextBoxStreamType.RichText)


                    If File.Exists(strWorkDir + "\FUSS.rtf")
                           File.Delete(strWorkDir + "\FUSS.rtf")
                    Endif
                    rtbFuss.SaveFile(strWorkDir + "\FUSS.rtf", RichTextBoxStreamType.RichText)


                    DclFld picTag Type(PICClass)         new()
                    picTag = pbKopf.Tag *as PICClass
                    If File.Exists(strWorkDir + "\KOPF")
                           File.Delete(strWorkDir + "\KOPF")
                    Endif
                    if pbKopf.Image <> *nothing
                           File.Copy(picTag.FileName,strWorkDir + "\KOPF")
                    Endif

                    DclFld    strInFileName *string
                    DclFld    strInPosName   *string
                    DclFld    strOutFileName *string
                    dclfld    WordApp          type(Word.Application)      Deklaration der
                    dclfld    WordDoc          type(Word.Document)         Word-Objekte
                    dclfld    Selection        type(Word.Selection)
                    dclfld    ActiveDocument   type(Word.Document)
                    dclfld    Table1           type(Word.Table)
                    dclfld    Row              type(Word.Row)


                                                                 -114-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
         RPG.NET BESTPRACTICES
                             dclfld Range                      type(Word.Range)

                             strInFileName  = asrApp.GetValue("Vorlage", Type.GetType("System.String")) *as
                                                   String
                             strInPosName   = asrApp.GetValue("PosVorlage", Type.GetType("System.String")) *as
                                                   String
                             strOutFileName = asrApp.GetValue("AusgabePfad", Type.GetType("System.String")) *as
                                                   String
                             strOutFileName = strOutFileName + "\Auftrag_" + txtAuftragsnr.Text + ".DOC"

                             If File.Exists(strOutFileName)
                                    File.Delete(strOutFileName)
                             Endif
                                                                                             Word-Objekt erzeugen
                             WordApp                        = *New Word.Application()

                             WordApp.Visible = *true

                             /// Vorlauf - erzeugen der Positionsmodule Jede Zeile im Grid bearbeiten             -
                             DclFld i *integer2                         Vorlaufschleife
                             DclFld p *string
                             i = 0
                             ForEach Name(dr) Collection(dgvPositionen.Rows)   Type(DataGridViewRow)
                                    Application.DoEvents()


                                        WordDoc             = WordApp.Documents.Open(strInPosName, *noparm, *noparm,
                                                                    *noparm, *noparm, *noparm, *noparm, *noparm, *noparm,
 Word-Dokument auf                                          *noparm, *noparm, *noparm, *noparm, *noparm, *noparm, *noparm
 Positionsebene öffnen                                      )

                                        ActiveDocument = WordApp.ActiveDocument

                                        Selection                      = WordApp.Selection

                            If File.Exists(strWorkDir + "\VOR_" + i.ToString() + ".rtf")
                                   Selection.GoTo(Word.wdGoToItem.wdGoToBookmark,
                                           *noparm, *noparm, "VORTEXT")
   Vor-Zeile-Objekte einbauen              Selection.InsertFile(strWorkDir + "\VOR_" + i.ToString() +
                                           ".rtf",*noparm,*noparm,*noparm,*noparm)
                            Endif
                            If File.Exists(strWorkDir + "\VOR_" + i.ToString())
                                   Selection.GoTo(Word.wdGoToItem.wdGoToBookmark,
                                           *noparm, *noparm, "VORBILD")
                                   Selection.InlineShapes.AddPicture (strWorkDir + "\VOR_" +
                                            i.ToString() , *false, *true, *noparm)
   Zeile in Rahmen bauen Endif
                            p = %char(i + 1)
                            Selection.GoTo(Word.wdGoToItem.wdGoToBookmark, *noparm, *noparm, "POS")
                            Selection.TypeText(p.ToString())
                            Selection.MoveRight(Word.WdUnits.wdCell,1, *noparm)
                            Selection.TypeText(dr.cells("dgcMenge").value.tostring())
                            Selection.MoveRight(Word.WdUnits.wdCell,1, *noparm)
                            Selection.TypeText(dr.cells("dgcArtikelNummer").value.tostring())
                            Selection.MoveRight(Word.WdUnits.wdCell,1, *noparm)
                            Selection.TypeText(dr.cells("dgcArtikelBezeichnung").value.tostring())
                            Selection.MoveRight(Word.WdUnits.wdCell,1, *noparm)
                            Selection.TypeText(dr.cells("dgcPreis").value.tostring())
                            Selection.MoveRight(Word.WdUnits.wdCell,1, *noparm)
                            Selection.TypeText(dr.cells("dgcWert").value.tostring())

                            If File.Exists(strWorkDir + "\NACH_" + i.ToString() + ".rtf")
                                   Selection.GoTo(Word.wdGoToItem.wdGoToBookmark,
                                           *noparm, *noparm, "NACHTEXT")
Nach-Zeile-Objekte einbauen        Selection.InsertFile(strWorkDir + "\NACH_" + i.ToString() +
                                           ".rtf",*noparm,*noparm,*noparm,*noparm)
                            Endif
                            If File.Exists(strWorkDir + "\NACH_" + i.ToString())
                                   Selection.GoTo(Word.wdGoToItem.wdGoToBookmark,
 Zeilen-Dokument speichern                 *noparm, *noparm, "NACHBILD")
                                   Selection.InlineShapes.AddPicture (strWorkDir +
                                           "\NACH_" + i.ToString() , *false, *true, *noparm)
                            Endif

                                        WordDoc.SaveAs (strWorkDir + "\POS_" + i.ToString() +
                                                       ".doc",*noparm,*noparm,*noparm,*noparm,*noparm,*noparm,


                                                                          -115-
         Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
   RPG.NET BESTPRACTICES
                                                 *noparm,*noparm,*noparm,*noparm,*noparm,*noparm,*noparm,*nopa
                                                 rm,*noparm )
                                  WordDoc.Close (*noparm,*noparm,*noparm)

                                  i = i + 1
                                                      Schleifenende Vorlauf
                       EndFor


                       WordDoc = WordApp.Documents.Open(strInFileName, *noparm, *noparm, *noparm,
                                             *noparm, *noparm, *noparm, *noparm, *noparm, *noparm,
                                     *noparm, *noparm, *noparm, *noparm, *noparm, *noparm )

                       ActiveDocument = WordApp.ActiveDocument
                                                                              Ergebnis-Dokument erstellen
Kopfdaten              Selection                      = WordApp.Selection
ausgeben
                       /// Header
                       Selection.GoTo(Word.wdGoToItem.wdGoToBookmark, *noparm, *noparm, "ANREDE")
                       Selection.Find.ClearFormatting()
                       Selection.TypeText(cboAnrede.text)

                       Selection.GoTo(Word.wdGoToItem.wdGoToBookmark, *noparm, *noparm, "NAME1")
                       Selection.Find.ClearFormatting()
                       Selection.TypeText(txtName.text)

                       Selection.GoTo(Word.wdGoToItem.wdGoToBookmark, *noparm, *noparm, "NAME2")
                       Selection.Find.ClearFormatting()
                       Selection.TypeText(txtName2.text)

                       Selection.GoTo(Word.wdGoToItem.wdGoToBookmark, *noparm, *noparm, "STRASSE")
                       Selection.Find.ClearFormatting()
                       Selection.TypeText(txtAnschrift.text)

                       Selection.GoTo(Word.wdGoToItem.wdGoToBookmark, *noparm, *noparm, "LAND")
                       Selection.Find.ClearFormatting()
                       Selection.TypeText(txtLand.text)

                       Selection.GoTo(Word.wdGoToItem.wdGoToBookmark, *noparm, *noparm, "PLZ")
                       Selection.Find.ClearFormatting()
                       Selection.TypeText(txtPlz.text)

                       Selection.GoTo(Word.wdGoToItem.wdGoToBookmark, *noparm, *noparm, "ORT")
                       Selection.Find.ClearFormatting()
                       Selection.TypeText(txtOrt.text)

                       Selection.GoTo(Word.wdGoToItem.wdGoToBookmark, *noparm, *noparm, "BELEGNR")
                       Selection.Find.ClearFormatting()
                       Selection.TypeText(txtAuftragsnr.text)

                       Selection.GoTo(Word.wdGoToItem.wdGoToBookmark, *noparm, *noparm, "BELEGDATUM")
                       Selection.Find.ClearFormatting()
                       Selection.TypeText(DateTime.Now.ToShortDateString())

                       If File.Exists(strWorkDir + "\KOPF.rtf")
                              Selection.GoTo(Word.wdGoToItem.wdGoToBookmark, *noparm, *noparm, "KOPFTEXT")
                              Selection.Find.ClearFormatting()
                              Selection.InsertFile(strWorkDir +
                                      "\KOPF.rtf",*noparm,*noparm,*noparm,*noparm)
                       Endif

                       If File.Exists(strWorkDir + "\FUSS.rtf")
                              Selection.GoTo(Word.wdGoToItem.wdGoToBookmark, *noparm, *noparm, "FUSSTEXT")
                              Selection.Find.ClearFormatting()
                              Selection.InsertFile(strWorkDir +
                                      "\FUSS.rtf",*noparm,*noparm,*noparm,*noparm)
                       Endif

                       If File.Exists(strWorkDir + "\KOPF")
                              Selection.GoTo(Word.wdGoToItem.wdGoToBookmark, *noparm, *noparm, "KOPFBILD")
                              Selection.Find.ClearFormatting()
                              Selection.InlineShapes.AddPicture (strWorkDir + "\KOPF" ,
                                      *false, *true, *noparm)
                       Endif

                       Selection.GoTo(Word.wdGoToItem.wdGoToBookmark, *noparm, *noparm, "POSTAB")
                       Selection.Find.ClearFormatting()

                                                                    -116-
   Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
        RPG.NET BESTPRACTICES
                            i = 0
                            ForEach Name(dr) Collection(dgvPositionen.Rows)                Type(DataGridViewRow)
                                   Application.DoEvents()
Schleife über
Positionen, alle                       If File.Exists(strWorkDir + "\POS_" + i.ToString() + ".doc")
                                              Selection.InsertFile(strWorkDir + "\POS_" + i.ToString() +
Positions-                                    ".doc",*noparm,*noparm,*noparm,*noparm)
dokumente                                     Selection.MoveDown(Word.WdUnits.wdLine,1, *noparm)
                                       Endif
einlesen und
                                       i = i + 1
ins Ergebnis-
dokument                    EndFor
integrieren.                Selection.GoTo(Word.wdGoToItem.wdGoToBookmark, *noparm, *noparm, "SUMTAB")
                            Selection.TypeText("Auftragswert gesamt " + lblGesamtWert.Text)


                       WordDoc.SaveAs (strOutFileName,*noparm,*noparm,*noparm,*noparm,
                                  *noparm,*noparm,*noparm,*noparm,*noparm,*noparm,*noparm,
                                  *noparm,*noparm,*noparm,*noparm )
                                                                                   Ergebnisdokument
                  EndSr                                                            ausgeben
                  /endregion



        7.11.6. Text in RTF-Box formatieren
        In diesem Projekt ist auch eine relativ interessante Steuerung des Textes in einer RTF-
        Box umgesetzt. Darum hier der Code und ein paar erklärende Worte:

        Es wird ein Objekt vom Typ RTFClass / PICClass erzeugt und einem Control
        zugeordnet.
        Diese Klasse enthält das Control selbst und einige Informationen, es hat auch einen
        eigenen Construktor zum Erzeugen des Controls das es beinhaltet.

                  BegClass RTFClass     Access(*Public)
                         DclFld rtbName        Type(*string)                        Access(*Public)
                         DclFld rtbControl     Type(RichTextBox)                    Access(*Public)
                         DclFld rtbText        Type(*string)                        Access(*Public)
                         DclFld FileName       Type(*string)                        Access(*Public)

                            BegConstructor Access(*Public)
                                   rtbControl = *new RichTextBox()
                            EndConstructor

                  EndClass

                  BegClass PICClass     Access(*Public)
                         DclFld picName        Type(*string)                        Access(*Public)
                         DclFld picControl     Type(PictureBox)                     Access(*Public)
                         DclFld picImage       Type(Image)                                 Access(*Public)
                         DclFld FileName       Type(*string)                        Access(*Public)

                            BegConstructor Access(*Public)
                                   picControl = *new PictureBox()
                            EndConstructor

                  EndClass




                                                                         -117-
        Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
In der FormLoad werden die RTF und PIC – Objekte erzeugt, ein Contextmenü wird
ihnen zugewiesen.
          /region Events

          BegSr Form1_Load Access(*Private) Event(*this.Load)
                 DclSrParm sender Type(*Object)
                 DclSrParm e Type(System.EventArgs)

                    DclFld oRTFKopf       Type(RTFClass) new()
                    oRTFKopf.rtbControl   = rtbKopf
                    oRTFKopf.rtbName      = "rtbKopf"
                    rtbKopf.Tag           = oRTFKopf
                    SetContextTag(oRTFKopf, ctmRTB)

                    DclFld oPicKopf       Type(PICClass) new()
                    oPicKopf.FileName     = ""
                    oPicKopf.picControl   = pbKopf
                    oPicKopf.picName      = "pbKopf"
                    SetContextTag(oPicKopf, ctmPic)

                    DclFld oRTFVor Type(RTFClass) new()
                    oRTFVor.rtbControl    = rtbVor
                    oRTFVor.rtbName       = "rtbVor"
                    rtbVor.Tag            = oRTFVor
                    SetContextTag(oRTFVor, ctmRTBVor)

                    DclFld oPicVor Type(PICClass) new()
                    oPicVor.FileName      = ""
                    oPicVor.picControl    = pbVor
                    oPicVor.picName       = "pbVor"
                    SetContextTag(oPicVor, ctmPicVor)

                    DclFld oRTFNach       Type(RTFClass) new()
                    oRTFNach.rtbControl   = rtbNach
                    oRTFNach.rtbName      = "rtbNach"
                    rtbNach.Tag           = oRTFNach
                    SetContextTag(oRTFNach, ctmRTBNach)

                    DclFld oPicNach       Type(PICClass) new()
                    oPicNach.FileName     = ""
                    oPicNach.picControl   = pbNach
                    oPicNach.picName      = "pbNach"
                    SetContextTag(oPicNach, ctmPicNach)

          EndSr

Um nicht für jede Funktion im Contextmenü eine eigene Eventsubroutine erzeugen zu
müssen wird ein Handler pro Control erzeugt der das Sendeobjekt an eine zentrale
Handler-Subroutine weitergibt.
          BegSr ContextRTF_Click Access(*Private) Event(*this.itmNormal.Click, *this.itmFett.Click,
                          *this.itmUnterstreichen.Click, *this.itmSchriftart.Click,
                          *this.itmTextauswahl.Click, *this.itmTextspeichern.Click,
                          *this.itmTextEinfuegen.Click)
                 DclSrParm sender Type(*Object)
                 DclSrParm e Type(System.EventArgs)

                    BearbeiteRTF(Sender)

          EndSr

          BegSr ContextRTFVOR_Click Access(*Private) Event(*this.itmVNormal.Click,
                          *this.itmVFett.Click, *this.itmvUnterstreichen.Click,
                          *this.itmVSchriftart.Click, *this.itmVTextAuswaehlen.Click,
                          *this.itmVTextSpeichern.Click, *this.itmVTextEinfuegen.Click)
                 DclSrParm sender Type(*Object)
                 DclSrParm e Type(System.EventArgs)

                    BearbeiteRTF(Sender)

          EndSr



                                                                 -118-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
          BegSr ContextRTFNACH_Click Access(*Private) Event(*this.itmNNormal.Click,
                          *this.itmNFett.Click, *this.itmNUnterstreichen.Click,
                          *this.itmNSchriftart.Click, *this.itmNTextAuswaehlen.Click,
                          *this.itmNTextSpeichern.Click, *this.itmNTextEinfuegen.Click)
                 DclSrParm sender Type(*Object)
                 DclSrParm e Type(System.EventArgs)

                    BearbeiteRTF(Sender)

          EndSr

Die zentrale Handler-Subroutine holt sich aus dem übergebenen Objekt das im Tag
versteckte RTF-Objekt raus und bearbeitet die Auswahl des Context-Menü’s.
          BegSr BearbeiteRTF Access(*Private)
                 DclSrParm sender Type(*Object)

                    DclFld     ctm                 Type(ToolStripMenuItem)
                    DclFld     rtbTag              Type(RTFClass)        new()
                    DclFld     rtb                 Type(RichTextBox)     new()
                    DclFld     fnt                 Type(Font)
                    DclFld     nam                 Type(*string)

                    ctm                  = sender *as ToolStripMenuItem

                    If ctm.Tag = *nothing
                           ctm.Tag = *new RTFClass()
                    Endif                                                  RTF-Klasse aus TAG holen
                    rtbTag = ctm.Tag *as RTFClass
                    rtb           = rtbTag.rtbControl *as RichTextBox
                    nam           = rtbTag.FileName
                    fnt           = rtb.SelectionFont

                    Select
                               When ctm.Text = itmNormal.Text
                                      rtb.SelectionFont = *new Font(fnt.FontFamily, fnt.Size,
                                               FontStyle.Regular)
                               When ctm.Text = itmFett.Text
                                      rtb.SelectionFont = *new Font(fnt.FontFamily, fnt.Size,
 Auswahl des                                   FontStyle.Bold)
 Context-                      When ctm.Text = itmUnterstreichen.Text
                                      rtb.SelectionFont = *new Font(fnt.FontFamily, fnt.Size,
 Menü’s                                        FontStyle.Underline)
 abarbeiten                    When ctm.Text = itmSchriftart.Text
                                      dlgFont.ShowColor = *true
                                      dlgFont.Font   = rtb.SelectionFont
                                      dlgFont.Color = rtb.SelectionColor
                                      If dlgFont.ShowDialog() <> DialogResult.Cancel
                                              rtb.SelectionFont     = dlgFont.Font
                                              rtb.SelectionColor    = dlgFont.Color
                                      EndIf
                               When ctm.Text = itmTextauswahl.Text *OR ctm.Text = itmTexteinfuegen.Text
                                      ofd.FileName                  = nam
                                      ofd.DefaultExt                = ".rtf"
                                      ofd.InitialDirectory = asrApp.GetValue("TextPfad",
                                                     Type.GetType("System.String")) *as String
                                      ofd.Filter                    = "rtf files (*.rtf)|"
                                      ofd.FilterIndex               = 1
                                      If ofd.ShowDialog() <> DialogResult.Cancel
                                              if ctm.Text = itmTextauswahl.Text
                                                     rtb.LoadFile(ofd.FileName,
                                                              RichTextBoxStreamType.RichText)
                                              Else
                                                     DclFld rtbInsert Type(RichTextBox)    new()
                                                     rtbInsert.LoadFile(ofd.FileName,
                                                              RichTextBoxStreamType.RichText)
                                                     rtb.SelectedText = rtbInsert.Text
                                              Endif
                                              rtbTag.FileName       = ofd.FileName
                                      EndIf
                               When ctm.Text = itmTextspeichern.Text
                                      sfd.FileName                  = nam
                                      sfd.InitialDirectory = asrApp.GetValue("TextPfad",
                                                     Type.GetType("System.String")) *as String
                                      sfd.Filter                    = "rtf files (*.rtf)|"

                                                                 -119-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
                                         sfd.FilterIndex               = 1
                                         sfd.OverwritePrompt           = *true
                                         If sfd.ShowDialog() <> DialogResult.Cancel
                                                rtb.SaveFile(sfd.FileName, RichTextBoxStreamType.RichText)
                                                rtbTag.FileName        = sfd.FileName
                                         EndIf
                    EndSl

                    Select
                               When rtbTag.rtbName = "rtbKopf"
                                      rtbKopf        = rtb
                                      rtbKopf.Tag    = rtbTag
                               When rtbTag.rtbName = "rtbVor"
                                      rtbVor         = rtb
                                      rtbVor.Tag     = rtbTag
                               When rtbTag.rtbName = "rtbNach"
                                      rtbNach        = rtb
                                      rtbNach.Tag    = rtbTag
                    EndSl


          EndSr

Die zentrale Handler-Subroutine für die Bild-Bearbeitung ist durch die geringere Anzahl
an Funktionen ein bisschen übersichtlicher als die RTF-Routine, sie folgt aber
demselben Prinzip.
          BegSr itmBildsuchen_Click Access(*Private) Event(*this.itmBildsuchen.Click,
                                 *this.itmBildsuchenNach.Click, *this.itmBildsuchenVor.Click,
                                 *this.itmBildloeschen.Click, *this.itmBildloeschenVOR.Click,
                                 *this.itmBildloeschenNACH.Click)
                 DclSrParm sender Type(*Object)
                 DclSrParm e Type(System.EventArgs)

                    DclFld     ctm           Type(ToolStripMenuItem)
                    DclFld     picTag Type(PICClass)        new()
                    DclFld     pic           Type(PictureBox)      new()
                    DclFld     nam           Type(*string)

                    ctm           = sender *as ToolStripMenuItem
                    picTag = ctm.Tag *as PICClass
                    pic           = picTag.picControl *as PictureBox
                    nam           = picTag.FileName

                    Select
                               When ctm.Text = itmBildsuchen.Text
                                      ofd.FileName           = ""
                                      ofd.DefaultExt         = ".jpg"
                                      ofd.InitialDirectory = asrApp.GetValue("BildPfad",
                                                      Type.GetType("System.String")) *as String
                                      ofd.Filter             = "jpg files (*.jpg)|*.jpg|All files
                                                                      (*.*)|*.*"
                                      ofd.FilterIndex        = 1
                                      If ofd.ShowDialog() <> DialogResult.Cancel
                                              LOADPICTURE           File(ofd.FileName)     Target(pic.Image)
                                              picTag.FileName       = ofd.FileName
                                      EndIf
                               When ctm.Text = itmBildloeschen.Text *or
                                              ctm.Text = itmBildloeschenVOR.Text *or
                                              ctm.Text = itmBildloeschenNACH.Text
                                      pic.Image = *nothing
                                      picTag.FileName = ""
                    EndSl


                    Select
                               When picTag.picName = "pbKopf"
                                      pbKopf                 = pic
                                      pbKopf.Tag             = picTag
                                      if ctm.Text = itmBildloeschen.Text
                                              pbKopf.Image = *nothing
                                      Endif
                               When picTag.picName = "pbVor"
                                      pbVor                  = pic
                                      pbVor.Tag              = picTag


                                                                 -120-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
                                      if ctm.Text = itmBildloeschenVOR.Text
                                              pbVOR.Image = *nothing
                                      Endif
                               When picTag.picName = "pbNach"
                                      pbNach                 = pic
                                      pbNach.Tag             = picTag
                                      if ctm.Text = itmBildloeschenNACH.Text
                                              pbNACH.Image = *nothing
                                      Endif

                    EndSl

          EndSr




                                                                 -121-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
8. Webseiten
Die hier angeführten Informationen helfen Ihnen beim Einstieg in die Webentwicklung mit
VS 2005, können aber keinesfalls Schulungen ersetzen. Das Thema Web-Entwicklung
ist sehr umfangreich, es verlangt mehr Designer-Qualität als Windows-Projekte und ist
deshalb hier nur im Ansatz beschrieben.
Weitere Informationen finden Sie im Handbuch ‚AVRWeb’ das sie von der ActionPack-
CD oder im Internet downloaden können.


8.1. Anlegen eines Projekts
Ein neues Web-Projekt wird nicht als Projekt sondern als Website gesehen.




Hier wird der Projekttyp Website oder WebService ausgewählt. Ebenso der Ort an dem
das Projekt angelegt wird.


                                                                 -122-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
8.2. Anlegen einer Masterpage
                                                                           Wie in Powerpoint oder
                                                                           Word kann man eine
                                                                           Seitenvorlage erstellen in
                                                                           die Webseiten eingebettet
                                                                           werden.




Die Mastersite kann einen Titel und die Navigation enthalten. Sie enthält in jedem Fall
einen Platzhalter für die Webseiten die angezeigt werden sollen.




                                                                 -123-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
Die Navigation basiert in den meisten Fällen auf einem Menü oder einer TreeView. Um
den ‚Breadrcrumb’ angezeigt zu bekommen (die Position in der man sich innerhalb der
Seite befindet) braucht man die XML-Datei Web.Sitemap.




8.3. Anlegen von Webseiten
Mit ‚Inhaltsseiten hinzufügen’ werden                                zur
Masterpage Inhaltsseiten angefügt.

Diese Seiten füllen dann den Inhalt des Content
der MasterPage und werden auch im Editor
entsprechend angezeigt.




                                                                 -124-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
8.4. Startseite festlegen
Die Startseite wird in
den Eigenschaftsseiten
des Projekts oder im
Contextmenü der Seite
festgelegt.
Das Projekt braucht
eine Startseite, ist keine
Startseite definiert wird
das          Verzeichnis
angezeigt und per Klick
auf eine Seite die Seite
ausgewählt.




8.5. Seitengröße festlegen
Jede Seite sollte eine
durchgängige Seitengröße
haben die einer üblichen
Bildschirmauflösung
entspricht aber nicht größer
als 1024x798 sein sollte da
sie sonst nicht auf jedem
Bildschirm         angezeigt
werden kann.




                                                                 -125-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
8.6. Seiten zeichnen
In der Toolbox finden
sich jede Menge an
Controls, sie sind im
Designer gleich wie
Windows-Controls am
Formular           zu
behandeln.




8.7. Projektorganisation
                                                 Eine wesentliche Neuerung von VS2005 ist die
                                                 Organisation der Website in Verzeichnissen.
                                                 Die Solution ist in einem anderen Verzeichnis abgelegt
                                                 als die Seiten selbst.




                                                                 -126-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
8.8. Programmierung
Generell sollte die Businesslogik in Namespaces/DLL’s ausgelagert werden.
Nur dadurch ist eine effiziente Programmierung möglich.

Für einfache Seiten genügen dann ein paar Zeilen wie z.B. hier:




Using   System
Using   System.Data
Using   System.Configuration
Using   System.Collections
Using   System.Web
Using   System.Web.Security
Using   System.Web.UI
Using   System.Web.UI.WebControls                                          Deklaration des
Using   System.Web.UI.WebControls.WebParts
Using   System.Web.UI.HtmlControls                                         Namespaces
Using NiceWare.Workshop.BaseClass
                                                                                             Deklaration der
BegClass _Default Partial(*Yes) Access(*Public) Extends(System.Web.UI.Page)
                                                                                             Klassen
          Dcldb     dbRuntime                      DBName("*Public/WORKSHOP")
          DclFld    cAdressen                      Type(Adressen)
          DclFld    cAdressDetail                  Type(Adressen.clsAdressDetail)
          DclFld    ErrorMessage                   *string
          DclFld    strTyp                         *string
          DclFld    dNummer                        System.Decimal

     BegSr Page_Load Access(*Private) Event(*This.Load)
         DclSrParm sender Type(*Object)
         DclSrParm e Type(System.EventArgs)

           If ( Request( "Typ" ) = *Nothing )
               strTyp = "K"
           Else
               strTyp = Request( "Typ" )
           EndIf
           If ( Request( "Key" ) = *Nothing )
               lblDatenArt.Text = "keine Adressnummer angegeben"
           Else
               dNummer = Request( "Key" ) *as System.Decimal


                                                                 -127-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
           EndIf

           Try
                  Connect dbRuntime
                  cAdressen = *new Adressen( dbRuntime, strTyp )
           Catch ex Exception
                  ErrorMEssage = ex.Message
           EndTry                                                           Aufbauen der
                  //
                                                                            Datenverbindung und
                  // Called the first time that the page is loaded.         Instanzierung der klassen
                  //
           If (NOT Page.IsPostBack)
                         /// Daten neu einlesen
                  cAdressDetail = cAdressen.LeseAdresse(dNummer,*false)

                    If strTyp = "K"
                           lblDatenArt.Text = "Kunde"
                    Else
                           lblDatenart.Text = "Lieferant"                  Einlesen der Daten in die
                    Endif                                                  Klasse bei erstem Laden
                    txtNUM.Text    = cAdressDetail.Nummer
                    txtANR.Text    = cAdressDetail.Anrede.trim()           des Formulars
                    txtVNA.Text    = cAdressDetail.Vorname.trim()
                    txtNNA.Text    = cAdressDetail.Nachname.trim()
                    txtSTR.Text    = cAdressDetail.Strasse.trim()
                    txtLND.Text    = cAdressDetail.Land.trim()
                    txtPLZ.Text    = cAdressDetail.PLZ.trim()
                    txtORT.Text    = cAdressDetail.Ort.trim()

                    Session["AdressDetail"] = cAdressDetail

           Else
                    //
                    // Called subsequent times that the page is displayed.
                    //
                    cAdressDetail = Session["AdressDetail"] *as Adressen.clsAdressDetail
           EndIf

     EndSr

          BegSr btnSpeichern_Click Access(*Private) Event(*This.btnSpeichern.Click)
                 DclSrParm sender Type(*Object)
                 DclSrParm e Type(System.EventArgs)

                    /// Daten der Klasse updaten
                    cAdressDetail.Nummer = txtNUM.Text                     Bei Speichern die Daten
                    cAdressDetail.Anrede = txtANR.Text                     zurückgeben und zurück
                    cAdressDetail.Vorname = txtVNA.Text
                    cAdressDetail.Nachname = txtNNA.Text                   zur Listansicht.
                    cAdressDetail.Strasse = txtSTR.Text
                    cAdressDetail.Land     = txtLND.Text
                    cAdressDetail.PLZ      = txtPLZ.Text
                    cAdressDetail.Ort      = txtORT.Text

                    /// Daten aktualisieren
                    cAdressen.AktualisiereAdresse(cAdressDetail)

                    /// Liste aufrufen
                    Response.Redirect("~/Kunden.aspx?Typ="+strTyp)

          EndSr

EndClass




                                                                 -128-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
8.9. Gestaltung
Die ASPX-Seite kann mit den Werkzeugen von VS gestaltet werden. Hinter der
Oberfläche verbirgt sich HTML-Code der auch direkt bearbeitet werden kann und in
manchen Fällen auch direkt bearbeitet werden muss.




Die Oberflächengestaltung wird über ein sog. CSS (Cascading Style Sheet) gelöst.
Indem das Aussehen der CSS-Klassen festgelegt wird.

Dem Label
‘Benutzer’ wird hier
die CSS-Klasse
‘Medium’
zugewiesen.




                                                                 -129-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
8.10.Global.ASAX
Diese Datei enthält Event-Subroutinen die sich auf den Webserver bzw. die Anwendung
beziehen und z.B. ausgeführt werden wenn der Webserver oder die Anwendung zum
ersten Mal gestartet werden.
In diesem Beispiel ist der einzige benutzte Event Application-Start. Hier wird der DB-
Name eingelesen.
BegSr Application_Start Access(*Protected)
   DclSrParm Sender Type(*Object)
   DclSrParm e Type(System.EventArgs)

    // Code that runs on application startup

   // DB-Name als Servervariable anlegen
  Application[ "DBRuntime" ] = System.Configuration.ConfigurationSettings.AppSettings("DBRuntime")

EndSr




9. WebServices erstellen
Sind eine ideale Schnittstelle für die Unterstützung von B2B-Projekten.
Sie sind mit RPG.NET einfach zu erstellen und ebenso einfach zu nutzen.

In unserem Beispiel werden die Klassen und Methoden eines Namespaces als Dienst
zur Verfügung gestellt.

Hier ist die Testansicht des Service – er wird auf dem Rechner auf dem er installiert ist
bereitgestellt.




                                                                 -130-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES




Da ein Webservice keine Oberfläche braucht ist er sehr schnell zu entwickeln.

Ich empfehle auch hier soviel Logik wie möglich in Form von DLL zu kapseln.




9.1. Programmierung


Using   System                                                             Referenz auf Namespace
Using   System.Web
Using   System.Web.Services
Using   System.Web.Services.Protocols

Using Niceware.Workshop.BaseClass

BegClass WWSDienste Access(*Public) Extends(System.Web.Services.WebService) +
               Attributes(WebService(Namespace:="http://localhost/WWSservice/", +


                                                                 -131-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
                    Description:="Ein Beispiel für Webservices erstellt mit ASNA VisualRPG.NET"), +
                    WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1))

                                                                                Attribute des Service
/region Adressen                                                                festlegen.
          /// Methode zur Rückgabe der Adressliste als Dataset
          BegFunc AdressListe   Type(System.Data.DataSet)     Access(*Public) Attributes(WebMethod())
                  DclSrParm     Art            Type(*OneChar) /// AdressArt (K/L/F)
                  DclSrParm     Key            Type(*string) /// Name oder Nummer zum aufsetzen

                    /// Methoden                                                         Klassen deklarieren und
                    DclFld cAdressen               Type(Adressen)
                    DclFld cAdressliste            Type(Adressen.clsAdressListe)         instanzieren.
                    DclFld ds                      Type(System.Data.DataSet)     new()

                    cAdressen = *new Adressen( dbRuntime, Art.ToString() )
                    cAdressliste = cAdressen.Adressliste(Key)
                    ds.Tables.Add( cAdressliste.dtAdressen.Copy() )                 Methode aufrufen und
                    LeaveSr ds                                                      Rückgabewerte als
                                                                                    Ergebnis liefern
          EndFunc




9.2. Bilder in XML-übertragen
Bilddaten müssen um mit einem Webservice übertragen werden zu können in einen
String konvertiert werden. Dazu wird üblicherweise die Konvertierungsfunktion
CONVERT.TOBASE64STRING verwendet. Hier ist ein Anwendungsbeispiel:
       BegFunc            BildToBase64                        Type(*string)
                    DclSrParm   Bild                          Type( System.Drawing.Image )

                    DclFld               cMemory                           Type( MemoryStream )
                    DclFld               cBase64                           Type( *string )

                    cMemory = *new MemoryStream()

                    Bild.Save(cMemory, ImageFormat.Bmp)

                    cBase64 = Convert.ToBase64String(cMemory.ToArray())

                    LeaveSr cBase64

       EndFunc



Hier das Rückkonvertieren des Bildes aus dem Stream
ms = *new MemoryStream( Convert.FromBase64String( cArtikelInfo.BildBase64 ) )
pbBild.Image      = *new Bitmap( ms )




                                                                 -132-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
10. WebServices nutzen

                                                            Webservices sind in DotNet ideal einzubinden,
                                                            das Beispiel zeigt die Verwendung eines
                                                            Webservices aus einem Windows-Projekt heraus.




Der Webservice wird über die Webreferenz eingebunden und steht mit seinen Klassen
und Methoden zur Verfügung.




                                                                 -133-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
Hier ist die Sicht auf den Code, das Projekt ist in VB programmiert.

Public Class Form1
    Dim WS As New localhost.WWSDienste()
    Dim dt As DataSet

     Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
                Handles btnKunden.Click

           dt = WS.AdressListe("K", "0")
           dgv.DataSource = dt.Tables(0)

     End Sub

     Private Sub btnLieferanten_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
                 Handles btnLieferanten.Click

           dt = WS.AdressListe("L", "0")
           dgv.DataSource = dt.Tables(0)

     End Sub

     Private Sub btnArtikel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
                 Handles btnArtikel.Click

           dt = WS.ArtikelListe(0)
           dgv.DataSource = dt.Tables(0)

     End Sub

     Private Sub btnBelege_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
                 Handles btnBelege.Click

           dt = WS.BelegListe(False, "")
           dgv.DataSource = dt.Tables(0)

     End Sub

End Class


Beim Nutzen eines Webservice fallen alle Deklarationen mit Ausnahme der auf den
Webservice und die Klassen die man verwendet weg, deswegen ist dieser
Programmcode alles andere als aufregend.




                                                                 -134-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
11. ASNA-Datagate Bibliotheksfunktionen
Im Namespace ASNA.DataGate.Client ist eine Vielzahl an Funktionen enthalten die
sehr effizient einsetzbar sind.
Ich habe aus meinen Projekten ein paar Routinen herausgegriffen die öfters gebraucht
werden.


11.1. DG-Datenbanken auslesen
          Using     System
          using     ASNA.DataGate.Client
          using     ASNA.DataGate.Common
          using     ASNA.DataGate.DataLink
          using     ASNA.DataGate.Providers

          …

          /// Namen der DB2-Datenverbindungen aus Registry einlesen
          /// und als ArrayList für die ComboBox zurückgeben
          BegFunc     GetDB2List Type(System.Collections.ArrayList)
          Access(*Public)

                    /// deklarieren und instanzieren
                    DclFld DBList     Type(System.Collections.ArrayList) new(*dft)

                    /// DBNamen als StringArray einlesen
                    DclArray    DbNames Type(*String) Rank(1)

                    /// Public databases
                    DbNames = dbLocal.GetNames(*True)
                    ForEach DatabaseName DbNames *String
                          If IsDB2(DatabaseName)
                                DBList.Add("*Public/" + DatabaseName)
                          Endif
                    EndFor

                    /// Local database
                    DbNames = dbLocal.GetNames(*False)
                    ForEach DatabaseName DbNames *String
                          If IsDB2(DatabaseName)
                                DBList.Add(DatabaseName)
                          Endif
                    EndFor

                    /// zurückgeben
                    LeaveSr     DBList

          EndFunc

          /// Label auf DB2 prüfen
          BegFunc     IsDB2 Type(*Boolean)
                DclSrParm   strDBName   *string

                    /// DB-einlesen, prüfen und retour
                    dbLocal.DBName = strDBName
                    IF dbLocal.Label = "DB2"
                          LeaveSr *true
                    Else
                          LeaveSr *false
                    Endif

          EndFunc



                                                                 -135-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
11.2.iSeries-Bibliotheksliste auslesen
          Using     System
          using     ASNA.DataGate.Client
          using     ASNA.DataGate.Common
          using     ASNA.DataGate.DataLink
          using     ASNA.DataGate.Providers
          …

          /// Bibliotheken aus Datenverbindungen einlesen
          BegFunc     GetLibList Type(System.Collections.ArrayList)
                                                              Access(*Public)
                DclSrParm   dbLocal    Type(AdgConnection)    By(*reference)

                    /// deklarieren und instanzieren
                    DclFld      LibList     Type(System.Collections.ArrayList)
                                                                    new(*dft)
                    DclFld dir Type(IDirectory)
                    Try
                          dir = AdgFactory.NewDirectory(dbLocal, "/")
                    Catch Type(dgException)
                          LibList.Add("<*Nothing>")
                          LeaveSr LibList
                    EndTry
                    ForEach lib Type(IAdgObject) Collection(dir.ItemList)
                          LibList.Add(lib.ToString().Remove(0, 1))
                    EndFor

                    /// zurückgeben
                    LeaveSr     LibList

          EndFunc




                                                                 -136-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
11.3.Objektliste der iSeries-Bibliotheken auslesen
          Using     System
          using     ASNA.DataGate.Client
          using     ASNA.DataGate.Common
          using     ASNA.DataGate.DataLink
          using     ASNA.DataGate.Providers
          …

          /// Objekte aus Bibliothek einlesen
          BegFunc     GetObjects Type(*boolean)     Access(*Public)
                DclSrParm   dbLocal           Type(ADGConnection)
                                                          By(*reference)
                DclSrParm   strLib            Type(*string)

                    /// deklarieren und instanzieren
                    lPFList.Clear()

                    DclFld dir Type(IDirectory)
                    Try
                          dir = AdgFactory.NewDirectory(dbLocal, strLib)
                    Catch Type(dgException)
                          LeaveSr *false
                    EndTry
                    ForEach lib Type(IAdgObject) Collection(dir.ItemList)
                          lPFList.Add(lib.ToString().Remove(0, 1 + strLib.Length))
                    EndFor

                    If strErrorMessage = *blank
                          LeaveSr           *true
                    Else
                          LeaveSr           *false
                    Endif

          EndFunc




                                                                 -137-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
11.4.Dateifeldbeschreibung auslesen

          Using     System
          using     ASNA.DataGate.Client
          using     ASNA.DataGate.Common
          using     ASNA.DataGate.DataLink
          using     ASNA.DataGate.Providers
          …

          /// Daten für Klassengenerierung erstellen
          BegFunc     PrepareGenerationData   Type(clsDB.clsGenerationData)
                                                          Access(*Public)
                DclSrParm   dbLocal     Type(ADGConnection)     By(*reference)
                DclSrParm   strLib            Type(*string)
                DclSrParm   strFile           Type(*string)
                    /// verwenden der ADG - Objekte
                    DclFld      dbFA Type(FileAdapter)
                    DclFld      dsFA Type(ADGDataSet)
                    DclFld      ktFA Type(ADGKeyTable)
                    dbFA = *new FileAdapter(dbLocal)
                    dbFA.FileName = strLib + "/" + strFile
                    dbFA.MemberName = "*FIRST"
                    dbFA.OpenNewAdgDataSet(*ByRef dsFA)
                    /// DatenFelder einlesen
                    DclFld      dtFA        Type(System.Data.DataTable)
                    DclFld      iMaxLength Type(*integer2)
                    dtFA = dsFA.Tables(0)
                    i = 0
                    ForEach dc Type(System.Data.DataColumn) Collection(dtFA.Columns)

                    /// hier stehen die Felder in Form der DataColumn zur Verfügung




                    EndFor




                                                                 -138-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
      /// die Felder der KeyTable einlesen
      ktFA = dsFA.NewKeyTable(0)
ForEach dc Type(System.Data.DataColumn) Collection(ktFA.DataTable.Columns)

EndFor




                                                                 -139-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
12. Windows-Dienste programmieren


12.1.Was ist ein Windows-Dienst ?
Ein Windows-Dienst ist ein Programm das im Hintergrundprogramm abläuft und das
ohne Programmoberfläche auskommt. Also nichts weiter als ein Batch-Programm.

Ein Dienst wird gerne für Arbeiten eingesetzt die keine besondere Kontrolle brauchen
und sicher und stabil ablaufen sollen.

Ich habe im Rahmen einer Kundenanforderung ein Dienstprojekt umgesetzt das ähnlich
wie ein Trigger eine Datei überwachen soll indem Anforderungen für ein MD5-Encryption
abgesetzt werden.

Das Programm setzt über einen LF der verarbeitete Sätze ausschließt auf einer Datei
auf und gibt für jeden Input-Text einen En-Crypteten String aus.




Bei einem Service sind ein paar Unterschiede zu Windows-Dialogprogrammen zu
beachten.

Das Projekt braucht eine Referenz auf System.ServiceProcess
Sowie auf System.Configuration.Install
Diese Namespaces sind üblicherweise nicht referenziert.

Es benötigt auch einen ProcessInstaller der für das Handling
der Installation und Deinstallation gebraucht wird.

Die Verarbeitungslogik muss verschiedene Events bedienen.


Damit man verfolgen kann was der Service macht schreibt er ein eigenes Eventlog mit.



                                                                 -140-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
12.2.PROGRAM.VR - Basis des Dienstes
In der Klasse PROGRAM finden wir die Eigenschaften und die MAIN-Methode die den
Einsprungpunkt darstellt.
Using System
Using System.Text
Using System.ServiceProcess

DclNamespace EnCryption
BegClass Program Access(*Public)
     BegProp WindowsServiceName Type( *String ) Access( *Public ) Shared( *Yes )
         BegGet
             LeaveSr "EnCryption"
             // Name your Windows service with the literal value above.
             // Both the AVRWindowsService and the ProcessInstaller classes
             // use this name. This name needs to be unique across _all_
             // Windows services. Choose it carefully (perhaps prefixing
             // it with your company name).
         EndGet
     EndProp

     BegProp WindowsServiceDescription Type( *String ) Access( *Public ) Shared( *Yes )
         BegGet
             LeaveSr "MD5 - Encryption"
             // Provide your Windows service description here. This is what shows
             // as the service description in the Service Control Manager.
         EndGet
     EndProp
     BegProp EventSource Type( *String ) Access( *Public ) Shared( *Yes )
         BegGet
             LeaveSr "EnCryptionLog"
             // Name your event log source here.
         EndGet
     EndProp

     BegProp EventLog Type( *String ) Access( *Public ) Shared( *Yes )
         BegGet
             LeaveSr "EnCryptionLog"
             // Name your event log here. While you could write to existing logs,
             // it's probably a little nicer to write to your own log--and this program
             // provides the simple code required to do that.
             // Do note that if you create multiple Windows services, you might want to keep
             // the EventLog property the same for all of them.
         EndGet
     EndProp
     BegSr Main Shared( *Yes ) Access( *Public )
         // Specify the entry point for the service.
         ServiceBase.Run( *New EnCryption() )
     EndSr
                                                                           Die Klasse Encryption wird
EndClass                                                                   instanziert und ausgeführt




                                                                 -141-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
12.3.ProcessInstaller.VR – Constructor
Im ProcessInstaller wird der Constructor des Service hinterlegt.
Using   System
Using   System.Collections
Using   System.ComponentModel
Using   System.Data
Using   System.Diagnostics
Using   System.ServiceProcess
Using   System.Text
Using   System.Configuration.Install
BegClass ProcessInstaller Extends(Installer) Access( *Public ) Attributes( RunInstaller( *True ) )
     DclFld ServiceProcessInstaller1 Type( System.ServiceProcess.ServiceProcessInstaller )
     DclFld ServiceInstaller1        Type( System.ServiceProcess.ServiceInstaller )
          BegConstructor Access(*Public)
           *This.ServiceProcessInstaller1 = *New System.ServiceProcess.ServiceProcessInstaller()
           *This.ServiceInstaller1        = *New System.ServiceProcess.ServiceInstaller()
           //
           // ServiceProcessInstaller1.
           // Read about the ServiceProcessInstaller class here:
           // http://msdn2.microsoft.com/
                  en-gb/library/system.serviceprocess.serviceprocessinstaller.aspx
           *This.ServiceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem
           *This.ServiceProcessInstaller1.Password = *Nothing
           *This.ServiceProcessInstaller1.Username = *Nothing
           //
           // ServiceInstaller1.
           // Read about the ServiceInstaller class here:
           // http://msdn.microsoft.com/library/default.asp?url=/library/
                  en-us/cpref/html/frlrfSystemServiceProcessServiceInstallerClassTopic.asp
           // Name the service.
           *This.ServiceInstaller1.ServiceName = Program.WindowsServiceName
           *This.ServiceInstaller1.Description = Program.WindowsServiceDescription
           // The value of this property must be identical to the name recorded
           // for the service in the extended ServiceBase class.
           // You could also specify Disabled and Manual start mode here.
           *This.ServiceInstaller1.StartType = System.ServiceProcess.ServiceStartMode.Automatic
           *This.Installers.Add( *This.ServiceProcessInstaller1 )
           *This.Installers.Add( *This.ServiceInstaller1 )
          EndConstructor
EndClass




                                                                 -142-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
12.4.EnCryption.VR – Hier wird gearbeitet
Hier spielt sich die Verarbeitung ab, ein Timer steuert den Ablauf des Programms.
Using   System
Using   System.Collections
Using   System.ComponentModel
Using   System.Data
Using   System.Diagnostics
Using   System.ServiceProcess
Using   System.Tex
BegClass EnCryption Extends(ServiceBase) Access( *Public )

     DclFld         EventLog1                      Type( System.Diagnostics.EventLog ) New()
     DclFld         cMD5Encrypt                    Type( MD5Encrypt )
     DclFld         tmr                                   Type( System.Timers.Timer )
     BegConstructor Access(*Public)

           *This.ServiceName = Program.WindowsServiceName
           If ( NOT System.Diagnostics.EventLog.SourceExists( Program.EventSource ) )
               System.Diagnostics.EventLog.CreateEventSource( Program.EventSource, Program.EventLog )
           EndIf

           EventLog1.Source              = Program.EventLog
          tmr = *new System.Timers.Timer()                                  Timer instanzieren und
          tmr.Interval   = 2000
          tmr.Start()                                                       Handler-SR anhängen
          AddHandler    SourceObject( tmr ) SourceEvent( Elapsed )
                 HandlerObject( *this ) HandlerSr( TimerElapsed )
     EndConstructor

     BegSr OnStart Access(*Protected) Modifier(*Overrides)
         DclSrParm args Type(*String) Rank(1)
                                                                                Beim Start des Dienstes
           Try
                  cMD5Encrypt = *new MD5Encrypt()                               die MD5-Klasse erstellen
                  EventLog1.WriteEntry( "MD5EnCrypt started" )
           Catch Ex Exception
                  EventLog1.WriteEntry( "Error on instancing MD5EnCrypt –
                                               Check DB-Connection and Files             " )
                  LEaveSr
           EndTry
     EndSr

     BegSr OnStop Access(*Protected) Modifier(*Overrides)
         EventLog1.WriteEntry( "EnCryption service stopped." )
         tmr.Stop()
     EndSr
                                                  Beim Stop Timer beenden
     BegFunc OnPowerEvent Access(*Protected) Modifier(*Overrides) Type( *Boolean )
         DclSrParm PowerStatus Type( PowerBroadcastStatus )

           DclFld Result Type( *Boolean )
           Result = *True
           LeaveSr Result
     EndFunc

     BegSr OnContinue         Access(*Protected) Modifier(*Overrides)
           tmr.Start()                                 Beim Restart Timer neu
     EndSr                                             Starten

     BegSr     TimerElapsed
        DclSrPArm     sender                       Type(System.Object)
        DclSrParm     e                                   Type(System.Timers.ElapsedEventArgs)
          EventLog1.WriteEntry( "EnCryption service on Loop" )
          cMD5Encrypt.LeseSchleife( *byRef EventLog1 )
   endSr
                                                                           Handler-Subroutine für
EndClass                                                                   Encryption
                                                                 -143-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
12.5.MD5EnCryption.VR – Crypt nach MD5
Using   System
Using   System.IO
Using   System.Text
Using   System.Data
Using   System.Security.Cryptography                                       DB-Verbindung und Datei
                                                                           implizit eröffnen lassen
BegClass MD5Encrypt Access(*Public)
          DclDB         dbCompile                             DBName("*Public/DG NET LOCAL")
          DclDiskFile   Name(MD5File)                         File("OPITEC/MD5IN") Type(*Update)   Org(*Indexed)
                  DB(dbCompile)
          /// Leseschleife
          BegSr LeseSchleife             Access(*Public)
                 DclSrParm               log            Type( System.Diagnostics.EventLog )        By(*reference)
                    DclFld LoopCounter    *integer2
                    log.WriteEntry( "EnCryption service Leseschleife" )
                    try
                               SETLL MD5File                  Key("")                  Auf erstem Satz aufsetzen
                               READ   MD5File
                               DOWHILE NOT %eof                                        und Datei durcharbeiten
                                         LoopCounter += 1
                                         MD5OUT = Encrypt( %trim(MD5IN) )
                                         MD5Time = System.DateTime.Now.ToString() + LoopCounter.tostring()
                                         log.WriteEntry( "EnCryption IN:" + MD5IN.Tostring() + " OUT:" +
                                                  MD5OUT.Tostring() )
                                         UPDATE MD5File                           Encrpytion aufrufen und
                                         READ MD5File                             Daten ausgeben
                               ENDDO
                    Catch Ex Exception
                           log.WriteEntry( "Error on EnCryption service" )
                    EndTry
                    LeaveSr
          EndSr
          /// Methode Encryption
          BegFunc Encrypt Type(*string)
                  DclSrParm      Input             Type(*string)

                    /// Encrypted String
                    Dclfld strEncrypted            Type(*string)

                    /// MD5-Objekt erzeugen
                    DclFld md5Hasher                          Type(MD5)
                    md5Hasher = MD5.Create()
                    /// ZeichenArray
                    DclArray       data   Type(*byte)    Rank(1)
                    data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(Input))
                    /// Stringbuilder
                    DclFld sBuilder       Type(StringBuilder)
                    sBuilder = *new StringBuilder()

                    /// Schleife um Ergebnis zusammenzubauen
                    Do     Fromval(0)     Toval(data.length - 1) Index(i)                   Type(*integer2)
                           sBuilder.Append(data(i).ToString("x2"))
                    ENDDO

           /// Encrypted String zurückgeben
           LeaveSr sBuilder.tostring()

          EndFunc
EndClass




                                                                 -144-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
12.6.Den Windows-Dienst installieren / deinstallieren
Ein     Windows-Dienst
wird mit dem Programm

INSTALLUTIL
<Programmname>

installiert.




Nach der Installation kann
der   Dienst      gestartet
werden:




                                                                 -145-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
Unser Dienst erstellt sofort ein Eventlog bzw. protokolliert mit:




Die De-Installation erfolgt mit INSTALLUTIL /u <Programmname>




                                                                 -146-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
13. Hintergrundverarbeitung
Es gibt Situationen in denen eine Verarbeitung im Hintergrund sinnvoll ist. .NET stellt
dafür einen Background-Worker zur Verfügung.

In meinem Beispiel wurde eine Teilstringsuche über einen Hintergrundjob gelöst der die
gefunden Datensätze in einen Arbeitsfile ausgibt welcher direkt vom Programm
eingelesen wird. Der Vorteil ist dass sich die Ergebnisse sofort zeigen und nicht erst
nach dem vollständigen Ende der Suche.

Diese Anforderung wurde bewusst nicht mit SQL sondern mit Zugriff auf Satzebene
gelöst. Üblicherweise ist das eine Situation in der %like% verwendet wird, diese
Möglichkeit wurde in dem Beispiel ausgeklammert.

Im Client-Programm wird sofort nach Start der Suche die Einleseroutine gestartet die
dafür sorgt daß die gefundenen Daten sofort im Grid angezeigt werden.
So ist es möglich daß bereits wenige Augenblicke nach dem Auffinden der ersten
Datensätze gearbeitet werden kann.




             Datenlesen

             Gibt gefunden Daten
             in Grid aus




                                    DataGate – Direktzugriff auf Satzebene


                                                               Backgroundworker Datensuche
                                                               bgwCALL
                                                                                Gibt gefunden Daten
                                                                                in Member aus




                                                                 -147-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
13.1.Start des BackGroundWorker-Jobs
Das Objekt bgwCall vom Typ System.ComponentModel.BackgroundWorker wird aus
der Toolbox – Bereich Komponenten - auf das Formular gezogen.
          /// Suchen wurde gedrückt
          BegSr btnSuchen_Click Access(*Private) Event(*this.btnSuchen.Click)
                 DclSrParm sender *Object
                 DclSrParm e System.EventArgs
                    MsgLine.Text         = "Daten werden gesucht - bitte um Geduld"

                    /// DoEbents für Meldungszeile
                    DoEvents

                    /// Abbruch anzeigen und Schalter mit False initialisieren
                    /// Suchbutton verstecken
                    btnAbbruch.visible    = *true
                    btnSuchen.Visible     = *false
                    Abbruch               = *false

                    /// Zeit für Membernamen erstellen und Member aufbauen lassen
                    MemberName = DateTime.Now.ToString("mmss")
                    if not cPartner.CreateMember( *byRef MemberName )
                           cLog.NeuerLogEintrag( cPartner.Fehler )
                    Endif                                                   Der Event       RunWorkerAsync
                    /// Backgroundjob zum erstellen der Daten starten             wird am BackGroundworker-
                    bgwCALL.RunWorkerAsync()                                      Objekt gestartet
                    /// Daten des Backgrondjobs einlesen
                    if not cPartner.PartnerListe( *byref dtPAR, *byref Abbruch, MemberName )
                           cLog.NeuerLogEintrag( cPartner.Fehler )
                    Endif
                    /// Ergenbismeldung ausgeben
                    If Abbruch
                           msgLine.Text   = dtPAR.Rows.Count + " Datensätze gefunden –
                                                                Suche wurde abgebrochen"
                    Else
                           msgLine.Text   = dtPAR.Rows.Count + " Datensätze gefunden - Suche beendet"
                    Endif
                    /// Ergebnismeldung in Log ausgeben
                    cLog.NeuerLogEintrag( msgLine.Text )
                    /// Member auf AS/400 über Backgroundjob löschen
                    bgwDELMbr.RunWorkerAsync()
          EndSr                                                            Auch das Löschen des
                                                                           Members wird über eine
                                                                           Backgroundworker gemacht




                                                                 -148-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
13.2.Der Programmaufruf für den Worker-Job
Die Eventsubroutine DoWork führt das Programm aus.
Der Worker wird an den Job übergeben.
/// Background - Worker für CALL
BegSr bgwCALL_DoWork Access(*Private) Event(*this.bgwCALL.DoWork)
       DclSrParm sender *Object
       DclSrParm e System.ComponentModel.DoWorkEventArgs
          /// worker-Objekt aus dem Parameter holen
          DclFld worker Type(BackgroundWorker)
           worker = sender *as BackgroundWorker
          /// Logeintrag für Start machen
          cLog.NeuerLogEintrag( "Start PartnerCALL - Events : " + chkEvents.Checked.ToString()   )
          /// hier wird die Methode aufgerufen, der Worker wird übergeben
          if not cPartner.PartnerCALL ( worker, MemberName,   txtART.Text,
                                                              txtSUCHBEGRIFF.Text, +
                                                              txtNAME1.Text, +
                                                              txtNAME2.Text, +
                                                              txtLAND.Text, +
                                                              txtANSPRECHPERSON.Text )
                 /// Logeintrag bei Fehler machen
                 cLog.NeuerLogEintrag( cPartner.Fehler )

          Endif
EndSr




13.3.Der Programmende des Worker-Job’s
Der Event RunWorkerCompleted wird automatisch bei Ende des Workerjobs aufgerufen.
/// Fertigstellungsmeldung des Workers ermöglicht neue Suche
BegSr bgwCALL_RunWorkerCompleted Access(*Private) Event(*this.bgwCALL.RunWorkerCompleted)
       DclSrParm sender *Object
       DclSrParm e System.ComponentModel.RunWorkerCompletedEventArgs
          /// stellt die normale Ansicht der Buttons wieder her
          btnSuchen.Visible     = *true
          btnAbbruch.visible    = *false
EndSr




                                                                 -149-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
14. DataGate aus VB oder C# verwenden
ASNA hat für die Integration des System i in .NET ein umfangreiches Set an Klassen erstellt. Diese
Technologie kann nicht nur aus RPG.NET sondern auch ‚vom Rest der Welt‘ verwendet werden.
Diese Klassen sind in den ASNA-Namespaces ASNA.DATAGATE.CLIENT enthalten.

Im Vorläuferprodukt ‚Visual RPG‘ der 90’er – Jahre waren ähnliche Routinen enthalten die auch
als DatagateComponentSuite am Markt angeboten und gut angenommen wurden.
Diese, auf dem Microsoft-Component-Object-Model basierende Zugriffstechnologie wurde aus
VB, Delphi, etc. verwendet um auf die iSeries zuzugreifen.

In diesem Abschnitt sehen wir uns die wichtigsten Objekte der ASNA-Namespaces aus dem
Blickwinkel eines VB.NET oder C#.NET Entwicklers an und vergleichen dazu die Routinen die wir
mit RPG.NET erstellt haben.


Diese einfache Beispielanwendung greift auf Satzebene auf das
System i zu und liest den nächsten Satz ein oder greift über eine
Artikelnummer direkt auf einen Satz zu.



14.1.1. Deklarationen in VB – Vergleich RPG                                Bild 14-1 Hallo System i in VB
                                                                           mit DataGate

Deklarationsbereich in VB:
     ' deklarieren der DB-Verbindung
     Dim dbLocal As New ASNA.DataGate.Client.AdgConnection("*Public/AVRTraining_Local")

     ' jede Datei braucht einen FileAdapter über den die Zugriffe gemacht werden
     Dim faART00 As New ASNA.DataGate.Client.FileAdapter()
     ' für Zugriffe mit Schlüssel wird die KeyTable deklariert
     Dim ktART00 As ASNA.DataGate.Client.AdgKeyTable
     ' das DataSet gibt den eingelesenen Datensatz zurück
     Dim dsART00 As ASNA.DataGate.Client.AdgDataSet

Instanzierung in VB:
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs)
                Handles MyBase.Load
           ' Eröffnen der DB-Verbindung
           ' für den Produktionseinsatz sollte ein Try/Catch-Block eingebaut werden
           ' um Fehler abzufangen
           dbLocal.Open()

           ' die Eigenschaften des File-Adapters werden gesetzt
           ' Zugriffsmodus auf LESEN
           faART00.AccessMode = ASNA.DataGate.Common.AccessMode.Read
           ' DB-Verbindung an den FileAdapter übergeben
           faART00.Connection = dbLocal
           ' Dateiname zuweisen
           faART00.FileName = "*LIBL/ART00"
           ' eröffnen des File-Adapters - hier wird die Struktur der Tabelle eingelesen
           ' auch hier wäre im Produktionseinsatz ein Try/Catch-Block sinnvoll da hier
           ' ein Eröffnungsfehler auftreten kann
           faART00.OpenNewAdgDataSet(dsART00)
           ' erstellen der KeyTable aus dem Satzformat der Tabelle
           ktART00 = dsART00.NewKeyTable("RART00")
     End Sub


                                                                 -150-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
Codeblock 14-1 Zugriff über DataGate auf eine System i Tabelle



Für jede Datei werden ein FileAdapter und ein DataSet erzeugt – für den Zugriff über Schlüssel
wird eine KeyTable benötigt. In RPG.NET werden diese Klassen implizit mit jedem DCLDISKFILE
erstellt.

Deklarationsbereich in RPG.NET – Instanzierung wird von RPG.NET implizit gemacht

          DclDb                dbLocal             DBName("*Public/AVRTraining_Local")
          DclDiskFile          ART00               Type(*Input) Org(*Indexed) DB(dbLocal)       File("*libl/ART00")
          Codeblock 14-2 Deklaration der Files mit RPG.NET




14.1.2. Sequentieller Zugriff in VB – Vergleich RPG
Sequentieller Zugriff mit VB.NET

     Private Sub btnNaechsterArtikel_Click(ByVal sender As System.Object,
                       ByVal e As System.EventArgs) Handles btnNaechsterArtikel.Click
           Try
                 ' einlesen des nächsten Datensatzes - übergeben werden
                 ' ADG-DataSet für den Datensatz
                 ' Einstellung der Positionierung - nächster Satz
                 ' Einstellung Sperre - keine Sperre                        READ nächster              Satz
                 faART00.ReadSequential( dsART00,
                                          ASNA.DataGate.Common.ReadSequentialMode.Next,
                                          ASNA.DataGate.Common.LockRequest.NoLock
                                          )

                 ' die eingelesenen Felder in einem String ausgeben
                 lblBezeichnung.Text = dsART00.ActiveRow("ARTANU").ToString() + " " +
                                           dsART00.ActiveRow("ARTBEZ").ToString()
           Catch ex As Exception

                 lblBezeichnung.Text = "EOF !"
                 ' bei EOF werden die Schlüsselfelder ge-cleart             SETLL              an Dateibeginn
                 ktART00.Row.Delete()
                 ' und auf den ersten Satz zugegriffen - abhängig vom Schlüssel
                 faART00.SeekKey(ASNA.DataGate.Common.SeekMode.First, ktART00)

               MessageBox.Show("Dateiende erreicht")
           End Try
     End Sub
Codeblock 14-3 Sequentieller Zugriff mit VB.NET



Sequentieller Zugriff mit RPG.NET
          BegSr btnRead_Click Access(*Private) Event(*this.btnRead.Click)
                 DclSrParm sender *Object
                 DclSrParm e System.EventArgs

                    READ    ART00
                    If %EOF

                               lblBezeichnung.Text = "EOF !"
                               SETLL ART00 Key(*loval)

                               MsgBox Msg("Dateiende erreicht")
                    ELSE
                               lblBezeichnung.Text            = %char(ARTANU) + " " + ARTBEZ
                    ENDIF


                                                                 -151-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
          EndSr
Codeblock 14-4 Sequentieller Zugriff mit RPG.NET




                                                                 -152-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES



14.1.3. Direktzugriff in VB – Vergleich RPG
Direktzugriff mit VB.NET
     Private Sub btnSuchen_Click(ByVal sender As System.Object,
                                      ByVal e As System.EventArgs) Handles btnSuchen.Click
           Try
                 ' Schlüsseltabelle mit Felder für Direktzugriff füllen
                 ktART00.Row.Item("ARTANU") = txtArtikelNr.Text

           Catch ex As Exception
               ' dieser Fehler tritt bei der Zuweisung auf - Typenfehler - etc.
               MessageBox.Show("Artikelnummer ungültig")
               Return
           End Try
           Try
                 ' Direktzugriff auf den Datensatz der Tabelle - übergeben werden
                 ' ADG-Dataset für den Datensatz
                 ' Positionierung - gleich dem Key
                 ' Sperre - keine Sperre                         CHAIN
                 ' Key-Table
                 faART00.ReadRandomKey(   dsART00,
                                          ASNA.DataGate.Common.ReadRandomMode.Equal,
                                          ASNA.DataGate.Common.LockRequest.NoLock,
                                          ktART00)
               ' die eingelesenen Felder in einem String ausgeben
               lblBezeichnung.Text = dsART00.ActiveRow("ARTANU").ToString() + " " +
                                         dsART00.ActiveRow("ARTBEZ").ToString()
           Catch ex As Exception
               lblBezeichnung.Text = "NOT FOUND !"
               Return
           End Try
     End Sub
Codeblock 14-5 Direktzugriff mit VB.NET



Direktzugriff mit RPG.NET
          BegSr btnChain_Click Access(*Private) Event(*this.btnChain.Click)
                 DclSrParm sender *Object
                 DclSrParm e System.EventArgs
                    TRY
                               ARTANU = txtArtikelnummer.Text
                    CATCH      ex Exception
                               lblBezeichnung.Text   = "Nummer ungültig"
                               LeaveSr
                    EndTry
                    CHAIN ART00          key(ARTANU)
                    If %Found
                               lblBezeichnung.Text            = %char(ARTANU) + " " + ARTBEZ
                    ELSE

                               lblBezeichnung.Text            = "NOT FOUND !"
                    ENDIF
          EndSr
Codeblock 14-6 Direktzugriff mit RPG.NET




                                                                 -153-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES



14.2.WinDialog – Aufruf von iSeries-Programmen
Das Beispiel wurde bereits in RPG.NET programmiert – hier sollen die Unterschiede zwischen
RPG.NET und VB herausgearbeitet werden.

Die Logik befindet sich in einem Projekt – Namespace DATEN das in
VB nachprogrammiert wurde.




                                                                                      Bild 14-2 Wartungsdialog in
                                                                                      VB.NET
14.2.1. Deklaration von iSeries-Programmen in VB
Public Class ART00
     ' Datenbank-Deklaration
     Dim dbLocal As New ASNA.DataGate.Client.AdgConnection("*Public/INFOCOM2")

     ' Deklaration der Objekte für den Zugriff auf die Tabelle
     Dim faART00 As New ASNA.DataGate.Client.FileAdapter()
     Dim ktART00 As ASNA.DataGate.Client.AdgKeyTable
     Dim dsART00 As ASNA.DataGate.Client.AdgDataSet
     ' Objekte für den Zugriff auf das iSeries-Programm                    Deklaration des Programmobjektes
     Dim prog As ASNA.DataGate.Client.As400Program
     Dim Parms As ASNA.DataGate.DataLink.ProgParm()                        und der Parameterliste
     ' Zugriff über SQL
     Dim dbConn As New OleDb.OleDbConnection
     Dim da As OleDb.OleDbDataAdapter
     Dim ds As New DataSet


     Public Sub New()
           ' bei Instanzierung die DB eröffnen
           dbLocal.Open()
           ' den FileAdapter, das DataSet und die Keytabelle instanzieren
           faART00.AccessMode = ASNA.DataGate.Common.AccessMode.Read
           faART00.Connection = dbLocal
           faART00.FileName = "*LIBL/ART00"
           faART00.OpenNewAdgDataSet(dsART00)               Instanzierung des Programmobjektes
           ktART00 = dsART00.NewKeyTable("RART00")          und der Parameterliste
           ' das Programmobjekt instanzieren
           prog = New ASNA.DataGate.Client.As400Program(dbLocal, "*Libl/ART056")
           ' die Parametertabelle erstellen
           Parms = New ASNA.DataGate.DataLink.ProgParm() { _
              New ASNA.DataGate.DataLink.ProgParm(
                  New ASNA.DataGate.DataLink.ProgParmType("Nummer", 0,
                         ASNA.DataGate.Common.FieldType.NewPacked(6, 0)), _
                         ASNA.DataGate.Common.DataDirection.InputOutput), _
              New ASNA.DataGate.DataLink.ProgParm(
                  New ASNA.DataGate.DataLink.ProgParmType("Lager", 0,
                         ASNA.DataGate.Common.FieldType.NewPacked(6, 2)), _
                         ASNA.DataGate.Common.DataDirection.InputOutput), _
              New ASNA.DataGate.DataLink.ProgParm(
                  New ASNA.DataGate.DataLink.ProgParmType("Menge", 0,
                         ASNA.DataGate.Common.FieldType.NewPacked(6, 2)), _
                         ASNA.DataGate.Common.DataDirection.InputOutput) _
                  }
          ' die Parametertabelle an das Programmobjekt binden                 Parameterliste an
           prog.AppendParms(Parms)                                            Programmobjekt binden
           ' den SQL-ConnectionString erstellen
           dbConn.ConnectionString = "Provider=IBMDA400.DataSource.1;Data Source=INFOCOM2;
                  User ID=neich; Password=xxx;Catalog Library List=NXTGEN"

                                                                 -154-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
           dbConn.Open()
     End Sub
Codeblock 14-7 Aufruf eines Programmes mit VB.NET




14.2.2. Aufruf von iSeries-Programmen in RPG
Die Deklaration der Parameterliste kann im Deklarationsbereich der Klasse erfolgen oder auch
lokal bei Aufruf.

          DclDb                dbRuntime                      DBName("*Public/INFOCOM2")   Access( *private )
          DclConst             cServerProgramm                Value("*libl/ART132")        Access( *private )

          DclPlist      plServerProgramm
                 DclParm ANU   Type(*packed)                               Len(   6,0 )
                 DclParm BEZ   Type(*char)                                 Len(   50 )
                 DclParm PRE   Type(*packed)                               Len(   9,2 )
                 DclParm LAG   Type(*packed)                               Len(   6,2 )
                 DclParm MWC   Type(*char)                                 Len(   1 )
                 DclParm CDE   Type(*char)                                 Len(   3 )
                 DclParm EOF   Type(*char)                                 Len(   1 )
                 DclParm ERR   Type(*char)                                 Len(   1 )
Codeblock 14-8 Deklaration einer Parameterliste mit RPG.NET

In der Routine wird das Programm aufgerufen, die Parameter werden beim Aufruf deklariert wie
es in RPG üblich ist.

          BegFunc          LeseVerfuegbarenBestand      Type( *packed )                    Len( 6,2 )
                    DclSrParm     Nummer         Type( *packed )                           Len( 6,0 )
                    /// die Menge über Random-Funktion festlegen
                    Menge = DateTime.Now.Second
                    CALL       Pgm( "*Libl/ART056" ) DB( dbRuntime )                       Err( *extended )
                               DclParm Nummer
                               DclParm Lagerstand    Type( *packed )                       Len( 6,2 )
                               DclParm Menge         Type( *packed )                       Len( 6,2 )
                    If NOT %error
                           LeaveSr Lagerstand
                    Else
                           LeaveSr *zero
                    Endif
          EndFunc
Codeblock 14-9 Aufruf eines System i Programmes mit RPG.NET




14.2.3. Aufruf von iSeries-Programmen in VB

     Public Function LeseVerfuegbarenBestand(ByVal Nummer As Decimal) As Decimal

           Dim Lager As Decimal
           Dim Menge As Decimal
           Menge = DateTime.Now.Second
           ' füllen der Paramterfelder aus lokalen und übergebenen Variablen
           prog.ObjectToParm(Nummer, "Nummer")
           prog.ObjectToParm(Lager, "Lager")
           prog.ObjectToParm(Menge, "Menge")
           ' ausführen des Programms
           prog.Execute()

           ' Feldwert aus Parameter einlesen


                                                                 -155-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
           Lager = Convert.ToDecimal(
                                prog.ParmToObject( System.Type.GetType("System.Decimal"), "Lager" )
                                )
           Return Lager
     End Function
Codeblock 14-10 Aufruf eines Programmes mit VB.NET




15. Die Beispielanwendung WinDialog in C#.NET
Die Sprache C# ist standardisiert und vielleicht deshalb die am meisten verwendete Sprache in
.NET. Bis auf die Syntax ändert sich gegenüber VB.NET gar nichts. Hier ist das Programm ART00
in C#.

using System;
using System.Collections.Generic;
using System.Text;
namespace Daten
{
    public class ART00
    {

           // deklarieren der Objekte für DB-Verbindung und des File-Adapters
           private ASNA.DataGate.Client.AdgConnection dbLocal;
           private ASNA.DataGate.Client.FileAdapter faART00;
           private ASNA.DataGate.Client.AdgDataSet dsART00;
           private ASNA.DataGate.Client.AdgKeyTable ktART00;

           // deklarieren der Objekte für den AS/400-Programmaufruf
           private ASNA.DataGate.Client.As400Program prog;
           private ASNA.DataGate.DataLink.ProgParm[] Parms;
           // deklarieren der Objekte für die SQL-Verbindung
           private System.Data.OleDb.OleDbConnection dbConn;
           private System.Data.OleDb.OleDbDataAdapter da;
           private System.Data.DataSet ds;

           // deklarieren der Datenklasse und der ArbeitsFelder
           private ArtikelDaten cArtikeldaten;
           private decimal Lager;
           private decimal Menge;

           // Programmcode für die Klasse ART00
           public ART00()
           {
                // instanzieren der DB-Verbindung
                dbLocal = new ASNA.DataGate.Client.AdgConnection("*Public/INFOCOM2");
                dbLocal.Open();
                // instanzieren des File-Adapters, des DataSets und der KeyTable
                faART00 = new ASNA.DataGate.Client.FileAdapter();
                faART00.Connection = dbLocal;
                faART00.FileName = "*LIBL/ART00";
                faART00.AccessMode = ASNA.DataGate.Common.AccessMode.Read;
                faART00.OpenNewAdgDataSet(out dsART00);
                ktART00 = dsART00.NewKeyTable("RART00");
                // instanzieren des Programmobjektes
                prog = new ASNA.DataGate.Client.As400Program(dbLocal, "*Libl/ART056");
                ASNA.DataGate.DataLink.ProgParm[] Parms = new ASNA.DataGate.DataLink.ProgParm[]
                    {
                      /// erstellen der Programmparameter
                      new ASNA.DataGate.DataLink.ProgParm(
                          new ASNA.DataGate.DataLink.ProgParmType("Nummer", 0,
                                 ASNA.DataGate.Common.FieldType.NewPacked(6, 0)),
                                 ASNA.DataGate.Common.DataDirection.InputOutput) ,
                      new ASNA.DataGate.DataLink.ProgParm(
                          new ASNA.DataGate.DataLink.ProgParmType("Lager", 0,
                                 ASNA.DataGate.Common.FieldType.NewPacked(6, 2)),
                                 ASNA.DataGate.Common.DataDirection.InputOutput) ,
                      new ASNA.DataGate.DataLink.ProgParm(
                          new ASNA.DataGate.DataLink.ProgParmType("Menge", 0,
                                 ASNA.DataGate.Common.FieldType.NewPacked(6, 2)),
                                 ASNA.DataGate.Common.DataDirection.InputOutput) ,

                                                                 -156-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
                      };
                prog.AppendParms(Parms);
                // instanzieren und eröffnen der SQL-Verbindung
                dbConn = new System.Data.OleDb.OleDbConnection();
                dbConn.ConnectionString = "Provider=IBMDA400.DataSource.1;Data Source=INFOCOM2;
                          User ID=neich; Password=xxx;Catalog Library List=NXTGEN";
                dbConn.Open();
           }

          // Methode LISTE
           public System.Data.DataTable Liste()
           {
               ds = new System.Data.DataSet();
               da = new System.Data.OleDb.OleDbDataAdapter("Select * from NXTGEN.ART00", dbConn);
               da.Fill(ds);
               return ds.Tables[0].Copy();
           }

           // Methode ARTIKELDATEN deklarieren
           public class ArtikelDaten
           {
               public decimal Nummer;
               public string Bezeichnung;
               public decimal Preis;
               public decimal Lagerstand;
               public string MwstCode;
               public decimal Verfuegbar;
               public System.Drawing.Image Bild;
           }

           // Methode LESEN gibt die Datenklasse ARTIKELDATEN zurück
           public ArtikelDaten Lesen( decimal Nummer )
           {
               // erstellen der DatenKlasse
               cArtikeldaten = new ArtikelDaten();
                // zuweisen der Nummer in die KeyTable
                ktART00.Row["ARTANU"] = Nummer;
                try {
                     // Datensatz über Schlüssel einlesen - EOF durch TRY/CATCH abgesichert
                     faART00.ReadRandomKey(dsART00,
                                ASNA.DataGate.Common.ReadRandomMode.Equal,
                                ASNA.DataGate.Common.LockRequest.NoLock,
                                ktART00);
                     }
                catch (Exception ex) { return cArtikeldaten; }
                // Datenklasse mit Feldwerten füllen
                cArtikeldaten.Nummer = Convert.ToDecimal(dsART00.ActiveRow["ARTANU"]);
                cArtikeldaten.Bezeichnung = dsART00.ActiveRow["ARTBEZ"].ToString();
                cArtikeldaten.Preis = Convert.ToDecimal(dsART00.ActiveRow["ARTPRE"]);
                cArtikeldaten.Lagerstand = Convert.ToDecimal(dsART00.ActiveRow["ARTLAG"]);
                cArtikeldaten.MwstCode = dsART00.ActiveRow["ARTMWC"].ToString();
                cArtikeldaten.Verfuegbar = LeseVerfuegbarenBestand(cArtikeldaten.Nummer);
                cArtikeldaten.Bild = LeseBild(cArtikeldaten.Nummer);
                // Datenklasse zurückgeben
                return cArtikeldaten;
           }

           // private Funktion liest für eine Artikelnummer den verfügbaren Bestand ein
           private decimal LeseVerfuegbarenBestand(decimal Nummer)
               {

                      Menge = System.DateTime.Now.Second;
                      // Felder in Paramter übernehmen
                      prog.ObjectToParm(Nummer, "Nummer");
                      prog.ObjectToParm(Lager, "Lager");
                      prog.ObjectToParm(Menge, "Menge");
                      // Programm aufrufen
                      prog.Execute();
                      // Rückgabewert aus Parameterobjekt einlesen und zurückgeben
                      return Convert.ToDecimal(
                                      prog.ParmToObject(System.Type.GetType("System.Decimal"), "Lager")
                                      );

                                                                 -157-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
                }
           // Bild einlesen
           private System.Drawing.Image LeseBild(decimal Nummer)
               {
                   return System.Drawing.Image.FromFile("C:\\AVRTraining\\Bilder\\" +
                                 Nummer.ToString() + ".jpg");
               }
           }
     }




                                                                 -158-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
16. Programminstallation


16.1.Versionskonflikte zwischen DLL-Versionen lösen
Bei Verwendung von unterschiedlichen Compilerversionen innerhalb eines
Unternehmens kommt es vor dass referenzierte DLL’s auf ältere Compiler-DLL’s
aufbauen und bei der Umwandlung der Programme folgender Hinweis kommt:
------ Erstellen gestartet: Projekt: AssemblyZuordnen, Konfiguration: Debug Any CPU ------
Der Konflikt zwischen ASNA.System.Condition, Version=8.1.419.0, Culture=neutral,
PublicKeyToken=cc5be03abdd0c649 und ASNA.System.Condition, Version=8.1.390.0, Culture=neutral,
PublicKeyToken=cc5be03abdd0c649 kann nicht aufgelöst werden. Auswahl von ASNA.System.Condition,
Version=8.1.419.0, Culture=neutral, PublicKeyToken=cc5be03abdd0c649 nach dem Zufallsprinzip.
…


Nachdem wir nur im Ausnahmefall wollen dass DLL‘s nach dem Zufallsprinzip
zugeordnet werden legen wir die zu verwendende Version über die APP.CONFIG fest:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>

   <runtime>
     <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
       <dependentAssembly>
         <assemblyIdentity name="ASNA.System.Condition" culture="neutral"
                                         publicKeyToken="cc5be03abdd0c649"/>
         <bindingRedirect oldVersion="8.1.390.0" newVersion="8.1.419.0"/>
       </dependentAssembly>
       <dependentAssembly>
         <assemblyIdentity name="ASNA.DataGate.Client.Security" culture="neutral"
                                         publicKeyToken="c16fa022756bd74b"/>
         <bindingRedirect oldVersion="8.1.390.0" newVersion="8.1.419.0"/>
       </dependentAssembly>
       <dependentAssembly>
         <assemblyIdentity name="ASNA.DataGate.Ole" culture="neutral"
                                         publicKeyToken="caa6424d9c6983b8"/>
         <bindingRedirect oldVersion="8.1.390.0" newVersion="8.1.419.0"/>
       </dependentAssembly>
       <dependentAssembly>
         <assemblyIdentity name="ASNA.VisualRPG.Runtime" culture="neutral"
                                         publicKeyToken="d7106be54d30c861"/>
         <bindingRedirect oldVersion="8.1.390.0" newVersion="8.1.419.0"/>
       </dependentAssembly>
       <dependentAssembly>
         <assemblyIdentity name="ASNA.DataGate.Client" culture="neutral"
                                         publicKeyToken="78aac8f1f3f86b73"/>
         <bindingRedirect oldVersion="8.1.390.0" newVersion="8.1.419.0"/>
       </dependentAssembly>
     </assemblyBinding>
   </runtime>


</configuration>

Empfehlung:
Es ist sinnvoll innerhalb eines Unternehmens mit derselben Compilerversion zu arbeiten!




                                                                 -159-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
17. Interfaces
Interfaces sind, wie der Name schon sagt Schnittstellen. In einem Softwareprojekt
werden Interfaces verwendet um ein klares Schichtenmodell zu planen und somit
Verantwortlichkeiten festzulegen.

Im Interface werden Eigenschaften und Methoden von Klassen festgelegt aber nicht
implementiert. Somit kann zur Laufzeit eines Programmes entschieden werden welche,
von mehreren zur Verfügung stehenden Klassen die Arbeit übernimmt.

Eine typische Anwendung von Interfaces sind die Schnittstellen zu Office-Programmen
oder zur Datenzugriffsschicht.



                                                       Anwendungsprogramm



                                                              Objekt-Server



                                        Excel9                     Excel11    Excel12



Hier ist das Anwendungsprogramm, das steuert über eine Objekt-Serverklasse welche
der Klassen Excel9, Excel11 oder Excel12 die Arbeit übernimmt.

Die Beispiele mit Office sind nicht zufällig gewählt da es durch die verschiedenen Office-
Versionen immer wieder zu Problemen durch geänderte Funktionalität und
Parameteranzahl kommen kann.




                                                                 -160-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
17.1.Beispielanwendung UseInterfaces
In dieser Beispielanwendung wurde ein Beispiel auf dem Objekt Auto aufgebaut.
Das Objekt Auto wird in einem Interface beschrieben:
                               Using System
                               Using System.Text
                               DclNamespace MeineInterfaces
                               BegInterface IAuto Access(*Public)

                                         BegProp Farbe Type( System.Drawing.Color )
                                                 BegGet
                                                 EndGet
                                         endProp
                                         BegProp Bild Type( System.Drawing.Image)
                                                 BegGet
                                                 EndGet
                                         EndProp
                                         BegProp AnzahlPlaetze Type( *integer2 )
                                                 BegGet
                                                 EndGet
                                         EndProp

                               EndInterface


Jede dieser Eigenschaften muss von der Klasse die das Interface Auto implementiert
unterstützt werden. Wenn die Unterstützung nicht vollständig ist wird die Klasse nicht
kompiliert.
Using System
Using System.Text
DclNamespace MeinAudi

BegClass Auto Access(*Public)                      Implements(MeineInterfaces.IAuto)
          DclFld               img                 Type( System.Drawing.Image )
          BegProp              Farbe     Type( System.Drawing.Color )         Access( *Public )
                                                        Implements(MeineInterfaces.IAuto.Farbe)
                    BegGet
                               LeaveSr System.Drawing.Color.Silver
                    EndGet
          endProp
          BegProp              Bild                Type( System.Drawing.Image) Access( *Public )
                                                          Implements(MeineInterfaces.IAuto.Bild)
                    BegGet
                               LoadPicture         File( "Audi.bmp" ) Target( img )
                               LeaveSr img
                    EndGet
          EndProp
          BegProp              AnzahlPlaetze       Type( *integer2 )            Access( *Public )
                                                          Implements(MeineInterfaces.IAuto.AnzahlPlaetze)
                    BegGet
                               LeaveSr 2
                    EndGet
          EndProp
EndClass

Der Name der Klasse und der Eigenschaften ist nebensächlich, wichtig ist die
IMPLEMENTS-Anweisung.

Natürlich können Ereignisse und Methoden ebenso implementiert werden wie hier die
Eigenschaften, durch die Konzentration auf Eigenschaften ist das Beispiel übersichtlich
und daher leicht verständlich.


                                                                 -161-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
Der Objektserver übernimmt die Aufgabe die Objekte zur Verfügung zu stellen.
          Using System
          Using System.Text
          DclNamespace MeinObjectServer
          BegClass ObjectServer Access(*Public)

                    BegFunc           GetAuto                 Type( MeineInterfaces.iAuto )           Access( *Public )
                               DclSrParm      Code            Type( *string )
                               Select
                                         When Code = "A"
                                                LeaveSr *new MeinAudi.Auto()
                                         When Code = "M"
                                                LeaveSr *new MeinMercedes.Auto()
                               EndSl
                               LeaveSr *new MeinPorsche.Auto()
                    EndFunc

          EndClass


Hier sind die Deklarationen und der Aufruf aus dem Hauptprogramm
          DclFld cObjectServer           Type( MeinObjectServer.ObjectServer )                new()
          DclFld cAuto                   Type( MeineInterfaces.IAuto )


…
          BegSr rb_CheckedChanged Access(*Private)                   Event(   *this.radioButton1.CheckedChanged,
                                                                              *this.radioButton2.CheckedChanged,
                                                                              *this.radioButton3.CheckedChanged)
                    DclSrParm sender *Object
                    DclSrParm e System.EventArgs
                    DclFld MeinRadioButton       Type( Radiobutton )
                    MeinRadiobutton = sender *as Radiobutton
                    cAuto = cObjectServer.GetAuto( MeinRadiobutton.Tag.ToString() )
                    txtFarbe.BackColor             = cAuto.Farbe
                    txtPlaetze.Text                = cAuto.AnzahlPlaetze
                    pbBild.Image                   = cAuto.Bild
          EndSr


Hier wird dem Objektserver eingesetzt um zur Laufzeit ein Objekt auf Basis des
Interfaces zu instanzieren. Siehe Beispiel:




                                                                 -162-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc
RPG.NET BESTPRACTICES
17.2.Referenzstrukturen
Natürlich müssen die Referenzen innerhalb der Solution entsprechend strukturiert sein.
Hier ist die Hauptanwendung UseInterfaces – sie referenziert auf die Interfaces und den
ObjektServer.




MeineInterfaces braucht auf nichts zu
referenzieren da es sich hier nur um die
Schnittstellenbeschreibung handelt die
von allen Klassen implementiert wird.



Der ObjektServer hingegen muss das Interface
und alle Implementierungen referenzieren




                                                            Hier als Beispiel die Implementierung des IAuto-
                                                            Interfaces durch MeinAudi. Es genügt die
                                                            Interfaces zu referenzieren.




                                                                 -163-
Dokument C:\Docstoc\Working\pdf\5748f036-1ce8-4fb0-a49e-0fe076faf181.doc

						
Related docs
Other docs by UUzgpZav
evolution revolution anarchie
Views: 1  |  Downloads: 0
nbs
Views: 6  |  Downloads: 0
Runtime Package Contents
Views: 11  |  Downloads: 0
How to Migrate SIMS
Views: 37  |  Downloads: 2
inzynierska
Views: 237  |  Downloads: 0
??????? ?? ?????? ? MapInfo
Views: 40  |  Downloads: 0
Chromatography LAB
Views: 13  |  Downloads: 0
Ad hoc ????? IPv6 ?? ??
Views: 11  |  Downloads: 0
Slide 1
Views: 9  |  Downloads: 0
Part 1: Bag-of-words models
Views: 19  |  Downloads: 0