WWS Synergie-Analyse

Werbung
Best Practices
Entwicklung mit VisualRPG.NET
Version 2.9 vom 16.6.2008
Autor:
Christian Neißl
-1Dokument D:\75878992.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
-2Dokument D:\75878992.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
-3Dokument D:\75878992.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
-4Dokument D:\75878992.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
-5Dokument D:\75878992.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
-6Dokument D:\75878992.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
-7Dokument D:\75878992.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
-8Dokument D:\75878992.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.
-9Dokument D:\75878992.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.
-10Dokument D:\75878992.doc
RPG.NET BESTPRACTICES
3. Einführung in DataGate
DataGate ist die Datenbankschicht
die zwischen RPG.NET und den
Datenbanken der iSeries, SQLServer 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, SQLServer oder Acceler8DB gefahren
werden.
3.1. Anlegen einer Datenbank
Mit den DB-Wizard legen wir uns eine Projektdatenbank an.
Mithilfe des Browserfensters kann
Verzeichnis für die DB gewählt werden.
ein
-11Dokument D:\75878992.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.
-12Dokument D:\75878992.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.
-13Dokument D:\75878992.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.
-14Dokument D:\75878992.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
eingerichtet werden.
kann
die
Bibliotheksliste
Unter der Karte INITIAL
die Bibliotheksliste Zeile
für Zeile eingeben und
OK drücken.
-15Dokument D:\75878992.doc
RPG.NET BESTPRACTICES
4. Windows-Projekte
4.1. Anlegen eines Projekts
Nach
Start
von
VisualStudio2005
das Menü DATEINEU-PROJEKT
auswählen.
Das Verzeichnis ‚Workshop* unter
Laufwerk C anlegen.
Projekttyp ‚WindowsApplication’
Name ‚WWS’ angeben und
drücken.
und
OK
Das Ergebnis ist ein leeres Projekt.
-16Dokument D:\75878992.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 MausklickRechts 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.
-17Dokument D:\75878992.doc
RPG.NET BESTPRACTICES
Das
Startformular
für
das
Projekt
wird
in
PROJEKTEIGENSCHAFTEN festgelegt. Wir stellen
Formular WWS.frmMAIN ein.
Der Text wird auf ‚WWSHauptprogramm
festgelegt.
Unter ICON wählen wir
die DISC aus dem ICONVerzeichnis.
(Liegt auf CD bei)
-18Dokument D:\75878992.doc
den
das
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
-19Dokument D:\75878992.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
und benennen die horizontal angelegten Einträge
dem Prefix ‚mnu’ und Ihrem Namen da sie
Hauptmenü, die vertikalen Einträge als ‚itm’ da
einen Eintrag darstellen.
fort
mit
ein
sie
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.
-20Dokument D:\75878992.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 iSeriesRPGProgramm zu machen.
-21Dokument D:\75878992.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 ÖffnenDialogs zuerst ‚ALLE DATEIEN’
einstellen.
Weisen Sie den Menüelementen Icons nach Ihrem
Geschmack zu.
-22Dokument D:\75878992.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
-23Dokument D:\75878992.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.
-24Dokument D:\75878992.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.
-25Dokument D:\75878992.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>
<!-<add
<add
<add
<add
<add
<add
<add
<add
Parameter für DB-Verbindung zur Laufzeit -->
key="DBName" value="*Public/MAC"/>
key="Server" value="192.168.1.6"/>
key="Label" value="DB2"/>
key="Port" value="5042"/>
key="User" value="MAC"/>
key="PW" value="MAC"/>
key="Description" value="for demonstration purposes"/>
key="Libl" value="ZMODERN;"/>
<!-<add
<add
<add
<add
<add
ProgrammParameter -->
key="Fehlermail" value="mailto:[email protected]?Subject=Fehlermeldung-MT800Win"/>
key="WartungsTimeOutSek" value="30"/>
key="PicturePath" value="C:\NiceWare\ASNA\Interessenten\MACSchweiz\Bilder\"/>
key="PictureExtension" value=".jpg"/>
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 DBVerbindung 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
-26Dokument D:\75878992.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
-27Dokument D:\75878992.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.
-28Dokument D:\75878992.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
UserDaten
Kennwort
Type(*boolean) Access(*Public)
DclSrParm
cUserDaten
By(*REFERENCE)
Type(UserDaten)
DclSrParm
cKennwort
Len(20)
Type(*char)
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.
-29Dokument D:\75878992.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)
...
LeaveSr bOK
EndFunc
-30Dokument D:\75878992.doc
Inz(*false)
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.
-31Dokument D:\75878992.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.
-32Dokument D:\75878992.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.
-33Dokument D:\75878992.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.
-34Dokument D:\75878992.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)
DclDB
Name(dbCompile)
DBName("")
DBName("*Public/Workshop")
/// iSeries-Tabellen
DclDiskFile
Name(PFINH)
Org(*Indexed) DB(dbCompile) ImpOpen(*NO)
File("*LIBL/PFINH")
Type(*Input)
/// Datenfelder für Properties
DclFld
Name(strErrorMessage) Type(*string)
/// Keylists und Keyfelder
DclKlist
k2PFINH
DclKFld kfFIRM
DclKfld kfPARM
Fehlerinfo als Datenfeld
für die Property
/// Keylists und Keyfelder
DclKlist
k3PFINH
DclKfld kfFIRM
DclKfld kfPARM
DclKfld kfKEY
KeyListen
/// 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
-35Dokument D:\75878992.doc
File-Deklarationen
RPG.NET BESTPRACTICES
Datenklasse für Transport der Daten
zwischen Dialog und Business-Logik
/// Parameterklasse
BegClass
clsParmData
Access(*Public)
/// Firmennummer
DclFld
Firma
DclFld
Parameter
DclFld
Key
Type(*string)
Type(*string)
Type(*string)
Access(*Public)
Access(*Public)
Schlüsselfelder
für Zugriff
Access(*Public)
auf Parameterdatei
/// Wertliste in DataTable
DclFld
ParameterWerte
Type(DataTable)
Access(*Public)
/// Anzahl der Datensätze
DclFld
Anzahl
Type(*integer4)
Access(*Public)
EndClass
Satzanzahl in Tabelle
Rückgabe der Parameter
in Form einer Tabelle
Eigenschaft ErrorMessage
deklarieren
/// Properties wie ErrorMessage
BegProp
ErrorMessage
Type(*string)
BegGet
LeaveSr strErrorMessage
EndGet
EndProp
Access(*Public)
/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
Try
Access(*Public)
Close PFINH
Catch ex Exception
strErrorMessage = ex.Message
EndTry
EndSr
/endregion
-36Dokument D:\75878992.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
DclSrParm cParmData
Type(*boolean)
Access(*Public)
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)
Else
Chain
PFINH
Key(k3PFINH)
Endif
LastKey(k2PFINH)
/// 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
-37Dokument D:\75878992.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()
/// Parameter aus DB einlesen
DclFld cParameter
Type(clsParameter)
DclFld cParamBelege
Type(clsParameter.clsParmData)
/// Konstante Felder
DclConst
conBelegtyp
Objekt deklarieren
(Objekt im Speicher
anlegen)
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
/// erstellen der DB-Verbindung
BegFunc GetDBConnection
Type(*boolean)
Auslesen DB-Name aus APP.CONFIG
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
-38Dokument D:\75878992.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 für Parameterhandling erzeugen
cParameter
= *new clsParameter( dbRuntime )
Objekt instanzieren
(erstellen des Objekts)
/// 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
If cParameter.LeseParameter(*ByRef cParamBelege)
Eigenschaften des
Objekts zuweisen
/// Schleife über die Tabelle
ForEach dr Type(DataRow) Collection(cParamBelege.ParameterWerte.Rows)
/// BelegItem erstellen
Schleife
DclFld itm
Type(ToolStripMenuItem)
itm
= *new ToolStripMenuItem()
Tabelle
itm.Text
= dr.item("Wert").tostring().trim()
itm.Tag
= dr.item("Key").Tostring().trim()
itm.Name
= "mnu" + dr.item("Key").Tostring().trim()
Try
LOADPICTURE
File(asrApp.GetValue("BelegIcon",
Type.GetType("System.String")) *as String)
Target(itm.Image)
Catch ex Exception
MenüEintrag
EndTry
Methode des Objekts
ausführen und Parameter
per REFERENCE
übergeben
(erstellen des Objekts)
/// Menühandler deklarieren
Addhandler
SourceObject(itm)
SourceEvent(Click)
MenüEintrag an
Belegmenü anhängen
/// BelegItem in Belegmenü hängen
mnuBelege.DropDownItems.Add(itm)
EndFor
Endif
LeaveSr *true
EndFunc
/endregion
Hier sind die programmatisch erzeugten
Menüeinträge.
-39Dokument D:\75878992.doc
über
programmatisch erzeugen
HandlerSr(BelegClick)
EreignisHandler
programmatisch erzeugen
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
-40Dokument D:\75878992.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.
-41Dokument D:\75878992.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
-42Dokument D:\75878992.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.
-43Dokument D:\75878992.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.
-44Dokument D:\75878992.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)
Org(*Indexed) Addrec(*Yes)
DB(dbCompile)
File("*LIBL/LISTAP")
ImpOpen(*NO)
///
Memoryfile
DclMemoryFile Name(LISTAPm) FileDesc("*LIBL/LISTAP")
ImpOpen(*NO)
RnmFmt(memLief)
Type(*Update)
DBDesc(dbCompile)
Die Datenklasse der Lieferantenliste enthält eine Tabelle für den MemoryFile.
/// Datenklasse für Lieferantenliste
BegClass
clsLieferantenliste
Access(*Public)
/// Listendaten
DclFld
Firma
DclFld
AbNummer
/// Anzahl der Datensätze in Liste
DclFld
Anzahl
/// Kopfdaten als Tabelle
DclFld
Liste
Like(FIRNR)
Like(LIFNR)
Access(*Public)
Access(*Public)
Type(*integer4)
Access(*Public)
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
DclSrParm cLieferantenliste
Type(*boolean)
Type(clsLieferantenliste)
/// 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
-45Dokument D:\75878992.doc
Access(*Public)
By(*reference)
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.
-46Dokument D:\75878992.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
DclEvent
DclEvent
DclEvent
deklarieren
FillGrids
FillLieferanten
FillTeile
Access(*Public)
Access(*Public)
Access(*Public)
In der Startroutine werden die HANDLER erstellt die eine Ereignisbehandlung festlegen.
…
/// Lieferanten
cLieferanten
cLieferantenListe
= *new clsLieferanten( dbRuntime )
Erstellt
= *new clsLieferanten.clsLieferantenliste()
/// Teile
cTeile
cTeileListe
AddHandler
AddHandler
AddHandler
= *new clsTeile( dbRuntime )
= *new clsTeile.clsTeileliste()
SourceObject(*this)
SourceObject(*this)
SourceObject(*this)
ein Ereignis und weist eine
Subroutine zu die aufgerufen wird
wenn das Ereignis eintritt
SourceEvent(FillGrids)
HandlerSr(FillAllGrids)
SourceEvent(FillLieferanten) HandlerSr(FillLieferantenGrid)
SourceEvent(FillTeile)
HandlerSr(FillTeileGrid)
*this.FillGrids()
Die Ereignisse werden unter *THIS angeboten.
Hier die Subroutinen die die Ereignisse behandeln.
/// das Ereignis abfangen
BegSr FillAllGrids
*this.FillLieferanten()
*this.FillTeile()
Das Ereignis das alle Grids füllt löst
seinerseits die Einzelnen
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
EndTry
If cLieferanten.LeseLieferantenliste(*byRef cLieferantenliste)
dgvLieferanten.DataSource
= cLieferantenliste.Liste
Endif
-47Dokument D:\75878992.doc
Zuweisen der
DataTable ans Grid
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
DclArray
DclFld dt
DclFld dr
DclFld i
DclFld strKey
Stahlsorte
foundRows
Type(DataRow)
Type(DataTable)
Type(DataRow)
*integer4
*string
Rank(1)
Array für Ergebnis des
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()
dt.Clear()
Neue Tabelle mit
selbem Format
erzeugen
SELECT ausführen
/// Alle gefilterten Sätze lesen und in Tabelle übergeben
do fromval(i) toval( foundRows.GetUpperBound(0) )
/// Neuen Satz anlegen, mit Werten Füllen und in Tabelle übergeben
dr = dt.NewRow()
dr.ItemArray = foundRows(i).ItemArray
dt.Rows.Add( dr )
Enddo
/// gefilterte Daten in Grid anzeigen
dgv.DataSource = dt
endif
-48Dokument D:\75878992.doc
Schleife über
Selektionsergebnis mit
füllen der Ausgabetabelle
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)
/// BelegItem in Contextmenü hängen
ctmBelege.Items.Add(itm)
SourceEvent(Click)
HandlerSr(BelegClick)
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
DclFld itm
Type(ToolStripMenuItem)
itm = sender *as ToolStripMenuItem
Aus dem Menüobjekt wird der Inhalt
der TAG-Eigenschaft, aus dem Grid
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:
-49Dokument D:\75878992.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)
-50Dokument D:\75878992.doc
Access(*Public)
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)
/// 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
-51Dokument D:\75878992.doc
Feldinhalt in Liste
übergeben
RPG.NET BESTPRACTICES
In dieses Feld werden beim
gewünschten Satz *ONMerker 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:
-52Dokument D:\75878992.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
Ereignisparameter
übernehmen
DclFld txtBox Type(TextBox)
txtBox = sender *as TextBox
txtBox.Text = txtBox.Text.Trim()
txtBox.SelectAll()
EndSr
Mögliche Leerstellen
abschneiden
Alle Zeichen
markieren.
-53-
Dokument D:\75878992.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)
-54Dokument D:\75878992.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 SPLITFunktion vom Datentyp STRING
lblKW.text = „2006/35“
If lblKW.text <> *blank
DclArray
aKW
DclFld
oc
oc = "/"
aKW = lblKW.Text.Split(oc)
If aKW.length = 2
dr.Item("BPLJJ")
dr.Item("BPLWW")
Endif
Endif
Type(*string)
Type(*onechar)
= %int(aKW[0])
= %int(aKW[1])
-55Dokument D:\75878992.doc
Rank(1)
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
DclFld
DclFld
DclFld
DclFld
i *integer4
zSumme *decimal
zMenge Type(*zoned)
zPreis Type(*zoned)
zTotal Type(*zoned)
Len(10,3)
Len(13,3)
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
-56Dokument D:\75878992.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
dclfld
dgvChk
dgvCbo
Type(System.Windows.Forms.DataGridViewCheckBoxColumn) new()
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
-57Dokument D:\75878992.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
-58Dokument D:\75878992.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
-59Dokument D:\75878992.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
DclFld
DclFld
DclFld
tn
ts
dtGA
dtGB
Type(TreeNode) /// Hauptknoten
Type(TreeNode) /// Subknoten
Type(*date)
/// Hilfsfeld für Datum glt-ab
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)
-60Dokument D:\75878992.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
-61Dokument D:\75878992.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
-62Dokument D:\75878992.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
kfTYP
kfBENR
kfPOSN
kfATNR
=
=
=
=
=
Firma
BelegTyp
BelegNummer
PositionNr
*blank
LeaveSr HoleTextAusDaten()
EndFunc
/// Einlesen der ArtikelTexte
BegFunc LeseText
Type(*String)
DclSrParm
Firma
Type(*char)
DclSrParm
ArtikelNr
Type(*char)
kfFIRM
kfTYP
kfBENR
kfPOSN
kfATNR
=
=
=
=
=
Access(*Public)
Len(2)
Len(18)
Firma
*BLANK
*ZERO
*zero
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.
-63Dokument D:\75878992.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'
DclParm CCPARM
*IN30 = *OFF
If( CCPRZ = 1 )
*IN30 = *ON
EndIf
) DB( dbRuntime )
6.37.AS/400-API’s aufrufen
6.37.1. Benutzer des DG-Servicejobs auslesen
DclFld
DclFld
DclFld
DclFld
DclFld
Receiver
Length
Format
JobName
JobID
Type(*char)
Type(*binary)
Type(*char)
Type(*char)
Type(*char)
Call
Pgm("QUSRJOBI")
DclParm Receiver
DclParm Length
DclParm Format
DclParm JobName
DclParm JobID
Len(100)
Len(4,0)
Len(8)
Len(26)
Len(16)
inz(100)
Inz("JOBI0100")
inz("*")
DB(ProdDB)
DBDirection(*output)
DBDirection(*input)
DBDirection(*input)
DBDirection(*input)
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')
DclParm DtaQName
DclParm DtaQLib
DclParm DtaQLen
DclParm DSData
DclParm DtaQWait
DB(dbBGLesen) Err(*extended)
DBDirection(*input)
DBDirection(*input)
DBDirection(*output)
DBDirection(*output)
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
-64Dokument D:\75878992.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
Mode
Data
Message format
Error Msg details
Return code
Type
Char
Char
Char
Char
Char
Länge
4
3000
1
2000
1
Ab dem ersten Hex’00’ in Stelle 147
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**
-65Dokument D:\75878992.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*****************************************************************
-66Dokument D:\75878992.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.
-67Dokument D:\75878992.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.
-68Dokument D:\75878992.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
DclFld Jahr
DclFld Art
Type( *decimal )
Type( *decimal )
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
/// Key-Datenstruktur
DclDs kyDS
DclDsFld
DclDsFld
DclDsFld
DBDesc(dbLocal) FileDesc("Examples/CSMaster")
erstellen
kyKundenNr
kyJahr
kyArt
like(CSCustNo)
like(CSYear)
like(CSType)
-69Dokument D:\75878992.doc
RPG.NET BESTPRACTICES
/// Listenaufrufe mit verschiedenen Paramtern bearbeiten
BegFunc
Liste Type(DataTable)
Access(*public)
DclSrParm
KundenNr
like(CSCustNo)
kyKundenNr
kyJahr
kyArt
= KundenNr
= *zero
= *zero
LEaveSr LeseListe( 1 )
EndFunc
BegFunc
Liste
DclSrParm
DclSrPArm
Type(DataTable)
Access(*public)
KundenNr
like(CSCustNo)
Jahr
like(CSYear)
kyKundenNr
kyJahr
kyArt
= KundenNr
= Jahr
= *zero
LEaveSr LeseListe( 2 )
EndFunc
BegFunc
Liste
DclSrParm
DclSrPArm
DclSrPArm
Type(DataTable)
Access(*public)
KundenNr
like(CSCustNo)
Jahr
like(CSYear)
Art
like(CSType)
kyKundenNr
kyJahr
kyArt
= KundenNr
= Jahr
= 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
READ
CSMasterL1
When KeyFelder = 2
SETLL CSMasterL1
READ
CSMasterL1
When KeyFelder = 3
SETLL CSMasterL1
READ
CSMasterL1
EndSl
Key( %kds( kyDS, 1) )
Key( %kds( kyDS, 2) )
Key( %kds( kyDS, 3) )
/// Leseschleife
DoWhile NOT %EOF
/// Ausgabe des MemoryFiles
WRITE CSMaster
/// READE mit Keystruktur
Select
When KeyFelder = 1
READE CSMasterL1
When KeyFelder = 2
READE CSMasterL1
When KeyFelder = 3
READE CSMasterL1
EndSl
Enddo
/// Ausgabe der Tabelle
LEaveSr CSMaster.DataSet.Tables(0)
EndFunc
EndClass
-70Dokument D:\75878992.doc
key( %kds( kyDS, 1 ) )
key( %kds( kyDS, 2 ) )
key( %kds( kyDS, 3 ) )
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 ASCIIbasierenden 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 )
DclSrParm
NR
DclSrParm
DUMP
DclSrParm
VZU
Access( *Public )
Like( CMCUSTNO )
Like( CMACTIVE )
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
-71Dokument D:\75878992.doc
RPG.NET BESTPRACTICES
Hier ist das ILE-RPG das die Parameter übernimmt und füllt:
H DEBUG(*YES)
FCMASTNEWL1IF
FCSMASTERL1IF
E
E
K DISK
K DISK
D CUSTDS
D SALEDS
E DS
E DS
EXTNAME(CMASTNEW)
EXTNAME(CSMASTER)
C
C
C
C
C
C
*ENTRY
PLIST
PARM
PARM
PARM
PARM
PARM
C
C
C
*LIKE
*LIKE
*LIKE
DEFINE
DEFINE
DEFINE
CMCUSTNO
*INLR
*INLR
C
C
C
(DUMP = *ON)
'BEFORE'
IF
DUMP
ENDIF
C
NR
CHAIN
CMASTNEWL1
C
NR
CHAIN
CSMASTERL1
IF
EVAL
EVAL
EVAL
EVAL
EVAL
EVAL
EVAL
EVAL
EVAL
EVAL
EVAL
EVAL
ENDIF
(VZUMK = *ON)
CSSALES01 = CSSALES01
CSSALES02 = CSSALES02
CSSALES03 = CSSALES03
CSSALES04 = CSSALES04
CSSALES05 = CSSALES05
CSSALES06 = CSSALES06
CSSALES07 = CSSALES07
CSSALES08 = CSSALES08
CSSALES09 = CSSALES09
CSSALES10 = CSSALES10
CSSALES11 = CSSALES11
CSSALES12 = CSSALES12
IF
DUMP
ENDIF
(DUMP = *ON)
NR
DUMP
VZUMK
CUSTDS
SALEDS
NR
DUMP
VZUMK
90
91
C
C
C
C
C
C
C
C
C
C
C
C
C
C
C
C
C
C
'AFTER'
MOVE
*ON
*INLR
-72Dokument D:\75878992.doc
*
*
*
*
*
*
*
*
*
*
*
*
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
-1
RPG.NET BESTPRACTICES
Hier ist das Ergebnis nachdem alle Felder in die Listboxen übernommen wurden.
-73Dokument D:\75878992.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")
-74Dokument D:\75878992.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.
-75Dokument D:\75878992.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.
-76Dokument D:\75878992.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.
-77Dokument D:\75878992.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
-78Dokument D:\75878992.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
-79Dokument D:\75878992.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%
-80Dokument D:\75878992.doc
RPG.NET BESTPRACTICES
.GetSeriesDrawing(0).SetDataPointExplode( 0, 40 )
.Grid.AxisPie.LabelFormatMask = "C"
.GetSeriesDrawing(0).PieUnits = PieUnits.Value
.ChartType = ChartType.Pie3D
// currency
EndWith
EndSr
/endregion
6.50.Embedded SQL und offener Datenzugriff
Da VisualRPG.NET auf DotNet
Zugriffsmodelle verwenden.
aufsetzt
können
wir
alle
Datenbanken
und
In diesem binden wir die iSeries über ODBC, SQL-Server über ManagedProvider und
Access über OLEDB ein.
-81Dokument D:\75878992.doc
RPG.NET BESTPRACTICES
6.50.1. Namespaces und Deklarationen
Using
Using
Using
Using
System.Data
System.Data.ODBC
System.Data.OleDb
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
-82Dokument D:\75878992.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
DclSrParm
strSQL
DclFld
DclFld
DclFld
DclFld
conn
cmdData
daData
dsData
*string
*string
Type(Odbc.OdbcConnection) new()
Type(Odbc.OdbcCommand)
new()
Type(Odbc.OdbcDataAdapter) new()
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
-83Dokument D:\75878992.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
DclFld
DclFld
DclFld
conn
cmdData
daData
dsData
Type(SqlConnection)
Type(SqlCommand)
Type(SQLDataAdapter)
Type(DataSet)
new()
new()
new()
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
-84Dokument D:\75878992.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
DclSrParm
strSQL
DclFld conn
DclFld daData
DclFld dsData
*string
*string
Type(OleDBConnection)
Type(OleDBDataAdapter)
Type(DataSet)
new()
new()
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
-85Dokument D:\75878992.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)
-86Dokument D:\75878992.doc
RPG.NET BESTPRACTICES
DclFld
DclFld
xlSheet
Clip
Type(Excel.Worksheet)
Type( System.Windows.Forms.Clipboard )
SetMousePtr *HourGlass
/// Mauspointer auf Sanduhr stellen
xlApp = *New Excel.Application()
/// Instazieren von Excel
xlBook
xlApp.Visible
= xlApp.Workbooks.Add( *Noparm )
= *True
/// Neues Workbook anlegen
/// 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
xlRange.AutoFit()
/// Alle Spalten in den Arb.Bereich holen
/// 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.
-87Dokument D:\75878992.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)
DclFld columnIndex
Type(*integer4)
inz(1)
/// 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
-88Dokument D:\75878992.doc
RPG.NET BESTPRACTICES
o
o
o
o
o
o
o
Arbeitsblätter benennen
Überschriften, Daten und Summenzeilen einfügen
bestimmte Spalten oder ganze Bereiche farblich hervorheben
Schriften und –Attribute festlegen
iSeries-Programme aufrufen und Ergebnisse in Excel-Zellen ablegen
Daten aus Excel an die iSeries zurückgeben, prüfen, aktualisieren, …
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 OfficeProdukten 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.
-89Dokument D:\75878992.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 BetriebssystemVersionen 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.
-90Dokument D:\75878992.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 DotNetFunktionen, es geht darum den Aufruf eines iSeries-Programmes zu zeigen.)
o Erstellen einer Überschrift für das Arbeitsblatt
-91Dokument D:\75878992.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 )
//
DclDB
Name( dbProduction )
//
DclDB
Name( dbProduction )
DBname( "ASNA SQL DB" )
DBname( "*public/DG NET LOCAL" )
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
DclMemoryFile
Name
( F4211LA
)
Type( *Update )
DB
( dbProduction )
+
File
( "*Libl/F4211LA" )
Org
( *Indexed
)
Addrec ( *yes
)
ImpOpen ( *No
)
( memF4211 )
FileDesc
RnmFmt
ImpOpen
-92Dokument D:\75878992.doc
+
+
+
+
DBDesc
( dbProduction )
( "*Libl/F4211" )
+
( I4211,R4211 )
+
( *No
)
+
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
DclFld
xlBook
DclFld
xlRange
DclFld
xlSheet
DclFld
IX
DclFld
IR
DclFld
NumRecs
DclFld
@Cancel
DclFld
@CRLF
DclFld
iFieldCount
DclFld
iEndHeader
DclFld
iStrData
DclFld
iEndData
DclFld
iMarkColumn
DclFld
iMsgColumn
DclFld
iKeyColumn
Type(Excel.Application)
Type(Excel.Workbook)
Type(Excel.Range)
Type(Excel.Worksheet)
Type( *Integer4 )
Type( *Integer4 )
Type( *Integer4 )
Type( *Boolean )
Type( *String )
Type( *Integer4 )
Type( *Integer4 )
Type( *Integer4 )
Type( *Integer4 )
Type( *Integer4 )
Type( *Integer4 )
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
-93Dokument D:\75878992.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)
DclFld parm#EDAT
Type(*char)
DclFld parm#FFMT
Type(*char)
DclFld parm#TFMT
Type(*char)
DclFld parm#SEP
Type(*char)
DclFld parm$ERTST
Type(*char)
DclFld parm$CTRY
Type(*char)
DclFld parm#FJPN
Type(*char)
DclFld parm#TJPN
Type(*char)
DclFld parm#EDAT2
Type(*char)
DclFld parm#SIDT2
Type(*char)
Len(6)
Len(8)
Len(7)
Len(7)
Len(7)
Len(1)
Len(2)
Len(1)
Len(1)
Len(10)
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
-94Dokument D:\75878992.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
ExSr
ExSr
ExSr
LoadDataFromFile
InitExcel
CreateExcelHeader
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)
-95Dokument D:\75878992.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.
-96Dokument D:\75878992.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")
Parmlist(plX0028)
DB(dbProduction)
Err(*extended) +
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
DclFld strLength
*integer2
*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("-")
-97Dokument D:\75878992.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() =
ikSDDOCO = i+1
when dc.ColumnName.ToUpper() =
ikSDDCTO = i+1
when dc.ColumnName.ToUpper() =
ikSDKCOO = i+1
when dc.ColumnName.ToUpper() =
ikSDLNID = i+1
Endsl
fnSDDOCO
fnSDDCTO
fnSDKCOO
fnSDLNID
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
DclFld
DclFld
DclFld
strWrk *string
Clip
Type( System.Windows.Forms.Clipboard )
strSumme
*string
strError
*string
// Schleife über die Sätze des MemoryFiles
ForEach dr
Type(DataRow) Collection(memF4211.DataSet.Tables(0).rows)
// Schleife über alle Felder
-98Dokument D:\75878992.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
-99Dokument D:\75878992.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)
DclSrParm sender Type(*Object)
DclSrParm e Type(System.EventArgs)
Event(*this.txtSDDOCO.Validated)
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
-100Dokument D:\75878992.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
//
//
//
If
iErrCnt wird per Referenz übergeben, somit wird im
aufgerufenen Programm die Variable aus dieser Routine
bearbeitet
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
DclFld dr
DclFld strMsgText
DclFld strFeld
DclFld strWert
DclFld strMaxLen
DclFld iMaxLen
DclFld bOK
*integer4
Type(DataRow)
*string
*string
*string
*string
*Integer2
*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
-101Dokument D:\75878992.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
-102Dokument D:\75878992.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
DclFld
DclFld
DclFld
DclFld
DclFld
DclFld
i
dr
strMsgText
strFeld
strWert
strVerarb
bOK
*integer4
Type(DataRow)
*string
*string
*string
*string
*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
-103Dokument D:\75878992.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
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
-104Dokument D:\75878992.doc
// Rosa
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
-105Dokument D:\75878992.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.
-106Dokument D:\75878992.doc
RPG.NET BESTPRACTICES
7.11.Integration von Word und iSeries mit RPG.NET
7.11.1. Konzept
Kunden- Artikelund 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
-107Dokument D:\75878992.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.
-108Dokument D:\75878992.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.
-109Dokument D:\75878992.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.
-110Dokument D:\75878992.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.
-111Dokument D:\75878992.doc
RPG.NET BESTPRACTICES
Die Positionstabelle bietet eine Übersicht über die erfassten Positionen und ihre
Zusatzinformationen.
-112Dokument D:\75878992.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.
-113Dokument D:\75878992.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
der aktuellen RTF-Files
If File.Exists(strWorkDir + "\KOPF.rtf")
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
DclFld
DclFld
dclfld
dclfld
dclfld
dclfld
dclfld
dclfld
strInFileName *string
strInPosName
*string
strOutFileName *string
WordApp
type(Word.Application)
WordDoc
type(Word.Document)
Selection
type(Word.Selection)
ActiveDocument
type(Word.Document)
Table1
type(Word.Table)
Row
type(Word.Row)
-114Dokument D:\75878992.doc
Deklaration der
Word-Objekte
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
WordApp
Word-Objekt erzeugen
= *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
Word-Dokument auf
Positionsebene öffnen
-
= WordApp.Documents.Open(strInPosName, *noparm, *noparm,
*noparm, *noparm, *noparm, *noparm, *noparm, *noparm,
*noparm, *noparm, *noparm, *noparm, *noparm, *noparm, *noparm
)
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,
*noparm, *noparm, "NACHBILD")
Zeilen-Dokument speichern
Selection.InlineShapes.AddPicture (strWorkDir +
"\NACH_" + i.ToString() , *false, *true, *noparm)
Endif
WordDoc.SaveAs (strWorkDir + "\POS_" + i.ToString() +
".doc",*noparm,*noparm,*noparm,*noparm,*noparm,*noparm,
-115Dokument D:\75878992.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
Kopfdaten
ausgeben
Selection
Ergebnis-Dokument erstellen
= WordApp.Selection
/// 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()
-116Dokument D:\75878992.doc
RPG.NET BESTPRACTICES
i = 0
ForEach Name(dr) Collection(dgvPositionen.Rows)
Application.DoEvents()
Schleife über
Positionen, alle
Positionsdokumente
einlesen und
ins Ergebnisdokument
integrieren.
Type(DataGridViewRow)
If File.Exists(strWorkDir + "\POS_" + i.ToString() + ".doc")
Selection.InsertFile(strWorkDir + "\POS_" + i.ToString() +
".doc",*noparm,*noparm,*noparm,*noparm)
Selection.MoveDown(Word.WdUnits.wdLine,1, *noparm)
Endif
i = i + 1
EndFor
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
ausgeben
EndSr
/endregion
7.11.6. Text in RTF-Box formatieren
In diesem Projekt ist auch eine relativ interessante Steuerung des Textes in einer RTFBox 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)
DclFld rtbControl
Type(RichTextBox)
DclFld rtbText
Type(*string)
DclFld FileName
Type(*string)
Access(*Public)
Access(*Public)
Access(*Public)
Access(*Public)
BegConstructor Access(*Public)
rtbControl = *new RichTextBox()
EndConstructor
EndClass
BegClass PICClass
Access(*Public)
DclFld picName
Type(*string)
DclFld picControl
Type(PictureBox)
DclFld picImage
Type(Image)
DclFld FileName
Type(*string)
BegConstructor Access(*Public)
picControl = *new PictureBox()
EndConstructor
EndClass
-117Dokument D:\75878992.doc
Access(*Public)
Access(*Public)
Access(*Public)
Access(*Public)
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
-118Dokument D:\75878992.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
DclFld
DclFld
DclFld
DclFld
ctm
ctm
rtbTag
rtb
fnt
nam
Type(ToolStripMenuItem)
Type(RTFClass)
new()
Type(RichTextBox)
new()
Type(Font)
Type(*string)
= 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
Auswahl des
ContextMenü’s
abarbeiten
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,
FontStyle.Bold)
When ctm.Text = itmUnterstreichen.Text
rtb.SelectionFont = *new Font(fnt.FontFamily, fnt.Size,
FontStyle.Underline)
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)|"
-119Dokument D:\75878992.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
DclFld
DclFld
DclFld
ctm
Type(ToolStripMenuItem)
picTag Type(PICClass)
new()
pic
Type(PictureBox)
new()
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
-120Dokument D:\75878992.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
-121Dokument D:\75878992.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 ActionPackCD 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.
-122Dokument D:\75878992.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.
-123Dokument D:\75878992.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
Masterpage Inhaltsseiten angefügt.
zur
Diese Seiten füllen dann den Inhalt des Content
der MasterPage und werden auch im Editor
entsprechend angezeigt.
-124Dokument D:\75878992.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.
-125Dokument D:\75878992.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.
-126Dokument D:\75878992.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
Using
Using
Using
Using
Using
Using
Using
Using
Using
System
System.Data
System.Configuration
System.Collections
System.Web
System.Web.Security
System.Web.UI
System.Web.UI.WebControls
System.Web.UI.WebControls.WebParts
System.Web.UI.HtmlControls
Deklaration des
Namespaces
Using NiceWare.Workshop.BaseClass
BegClass _Default Partial(*Yes) Access(*Public) Extends(System.Web.UI.Page)
Dcldb
DclFld
DclFld
DclFld
DclFld
DclFld
dbRuntime
cAdressen
cAdressDetail
ErrorMessage
strTyp
dNummer
DBName("*Public/WORKSHOP")
Type(Adressen)
Type(Adressen.clsAdressDetail)
*string
*string
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
-127Dokument D:\75878992.doc
Deklaration der
Klassen
RPG.NET BESTPRACTICES
EndIf
Try
Connect dbRuntime
cAdressen = *new Adressen( dbRuntime, strTyp )
Catch ex Exception
ErrorMEssage = ex.Message
EndTry
//
// Called the first time that the page is loaded.
//
If (NOT Page.IsPostBack)
/// Daten neu einlesen
cAdressDetail = cAdressen.LeseAdresse(dNummer,*false)
If strTyp = "K"
lblDatenArt.Text = "Kunde"
Else
lblDatenart.Text = "Lieferant"
Endif
txtNUM.Text
= cAdressDetail.Nummer
txtANR.Text
= cAdressDetail.Anrede.trim()
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()
Aufbauen der
Datenverbindung und
Instanzierung der klassen
Einlesen der Daten in die
Klasse bei erstem Laden
des Formulars
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
cAdressDetail.Anrede = txtANR.Text
cAdressDetail.Vorname = txtVNA.Text
cAdressDetail.Nachname = txtNNA.Text
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
-128Dokument D:\75878992.doc
Bei Speichern die Daten
zurückgeben und zurück
zur Listansicht.
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.
-129Dokument D:\75878992.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 DBName 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.
-130Dokument D:\75878992.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
Using
Using
Using
System
System.Web
System.Web.Services
System.Web.Services.Protocols
Referenz auf Namespace
Using Niceware.Workshop.BaseClass
BegClass WWSDienste Access(*Public) Extends(System.Web.Services.WebService) +
Attributes(WebService(Namespace:="http://localhost/WWSservice/", +
-131Dokument D:\75878992.doc
RPG.NET BESTPRACTICES
Description:="Ein Beispiel für Webservices erstellt mit ASNA VisualRPG.NET"), +
WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1))
Attribute des Service
festlegen.
/region Adressen
/// 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
DclFld cAdressen
DclFld cAdressliste
DclFld ds
Type(Adressen)
Type(Adressen.clsAdressListe)
Type(System.Data.DataSet)
new()
cAdressen = *new Adressen( dbRuntime, Art.ToString() )
cAdressliste = cAdressen.Adressliste(Key)
ds.Tables.Add( cAdressliste.dtAdressen.Copy() )
LeaveSr ds
EndFunc
Klassen deklarieren und
instanzieren.
Methode aufrufen und
Rückgabewerte als
Ergebnis liefern
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
DclSrParm
Bild
DclFld
DclFld
Type(*string)
Type( System.Drawing.Image )
cMemory
cBase64
Type( MemoryStream )
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 )
-132Dokument D:\75878992.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.
-133Dokument D:\75878992.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.
-134Dokument D:\75878992.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
using
using
using
using
System
ASNA.DataGate.Client
ASNA.DataGate.Common
ASNA.DataGate.DataLink
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
-135Dokument D:\75878992.doc
RPG.NET BESTPRACTICES
11.2.iSeries-Bibliotheksliste auslesen
Using
using
using
using
using
System
ASNA.DataGate.Client
ASNA.DataGate.Common
ASNA.DataGate.DataLink
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
-136Dokument D:\75878992.doc
RPG.NET BESTPRACTICES
11.3.Objektliste der iSeries-Bibliotheken auslesen
Using
using
using
using
using
System
ASNA.DataGate.Client
ASNA.DataGate.Common
ASNA.DataGate.DataLink
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
-137Dokument D:\75878992.doc
RPG.NET BESTPRACTICES
11.4.Dateifeldbeschreibung auslesen
Using
using
using
using
using
System
ASNA.DataGate.Client
ASNA.DataGate.Common
ASNA.DataGate.DataLink
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
-138Dokument D:\75878992.doc
RPG.NET BESTPRACTICES
/// die Felder der KeyTable einlesen
ktFA = dsFA.NewKeyTable(0)
ForEach dc Type(System.Data.DataColumn) Collection(ktFA.DataTable.Columns)
EndFor
-139Dokument D:\75878992.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.
-140Dokument D:\75878992.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
EndClass
-141Dokument D:\75878992.doc
Die Klasse Encryption wird
instanziert und ausgeführt
RPG.NET BESTPRACTICES
12.3.ProcessInstaller.VR – Constructor
Im ProcessInstaller wird der Constructor des Service hinterlegt.
Using
Using
Using
Using
Using
Using
Using
Using
System
System.Collections
System.ComponentModel
System.Data
System.Diagnostics
System.ServiceProcess
System.Text
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
-142Dokument D:\75878992.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
Using
Using
Using
Using
Using
Using
System
System.Collections
System.ComponentModel
System.Data
System.Diagnostics
System.ServiceProcess
System.Tex
BegClass EnCryption Extends(ServiceBase) Access( *Public )
DclFld
DclFld
DclFld
EventLog1
cMD5Encrypt
tmr
Type( System.Diagnostics.EventLog ) New()
Type( MD5Encrypt )
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
Timer instanzieren und
Handler-SR anhängen
tmr = *new System.Timers.Timer()
tmr.Interval
= 2000
tmr.Start()
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
die MD5-Klasse erstellen
Try
cMD5Encrypt = *new MD5Encrypt()
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()
EndSr
BegSr
TimerElapsed
DclSrPArm
sender
DclSrParm
e
Beim Restart Timer neu
Starten
Type(System.Object)
Type(System.Timers.ElapsedEventArgs)
EventLog1.WriteEntry( "EnCryption service on Loop" )
cMD5Encrypt.LeseSchleife( *byRef EventLog1 )
Handler-Subroutine für
Encryption
endSr
EndClass
-143Dokument D:\75878992.doc
RPG.NET BESTPRACTICES
12.5.MD5EnCryption.VR – Crypt nach MD5
Using
Using
Using
Using
Using
System
System.IO
System.Text
System.Data
System.Security.Cryptography
DB-Verbindung und Datei
implizit eröffnen lassen
BegClass MD5Encrypt Access(*Public)
DclDB
dbCompile
DclDiskFile
Name(MD5File)
DB(dbCompile)
/// Leseschleife
BegSr LeseSchleife
DclSrParm
DBName("*Public/DG NET LOCAL")
File("OPITEC/MD5IN") Type(*Update)
Access(*Public)
log
Type( System.Diagnostics.EventLog )
Org(*Indexed)
By(*reference)
DclFld LoopCounter
*integer2
log.WriteEntry( "EnCryption service Leseschleife" )
try
SETLL MD5File
READ
MD5File
DOWHILE NOT %eof
Key("")
Auf erstem Satz aufsetzen
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() )
Encrpytion aufrufen und
Daten ausgeben
UPDATE MD5File
READ MD5File
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
md5Hasher = MD5.Create()
Type(MD5)
/// 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)
sBuilder.Append(data(i).ToString("x2"))
ENDDO
/// Encrypted String zurückgeben
LeaveSr sBuilder.tostring()
EndFunc
EndClass
-144Dokument D:\75878992.doc
Type(*integer2)
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:
-145Dokument D:\75878992.doc
RPG.NET BESTPRACTICES
Unser Dienst erstellt sofort ein Eventlog bzw. protokolliert mit:
Die De-Installation erfolgt mit INSTALLUTIL /u <Programmname>
-146Dokument D:\75878992.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
-147Dokument D:\75878992.doc
Gibt gefunden Daten
in Member aus
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
/// Backgroundjob zum erstellen der Daten starten
bgwCALL.RunWorkerAsync()
RunWorkerAsync
wird am BackGroundworkerObjekt 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()
Auch das Löschen des
Members wird über eine
Backgroundworker gemacht
EndSr
-148Dokument D:\75878992.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
-149Dokument D:\75878992.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
-150Dokument D:\75878992.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
DclDiskFile
dbLocal
ART00
DBName("*Public/AVRTraining_Local")
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
READ nächster
' Einstellung Sperre - keine Sperre
faART00.ReadSequential( dsART00,
ASNA.DataGate.Common.ReadSequentialMode.Next,
ASNA.DataGate.Common.LockRequest.NoLock
)
Satz
' 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
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
-151Dokument D:\75878992.doc
an Dateibeginn
RPG.NET BESTPRACTICES
EndSr
Codeblock 14-4 Sequentieller Zugriff mit RPG.NET
-152Dokument D:\75878992.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
CHAIN
' Sperre - keine Sperre
' 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
CATCH
ARTANU = txtArtikelnummer.Text
ex Exception
lblBezeichnung.Text
= "Nummer ungültig"
LeaveSr
EndTry
CHAIN ART00
If %Found
key(ARTANU)
lblBezeichnung.Text
= %char(ARTANU) + " " + ARTBEZ
lblBezeichnung.Text
= "NOT FOUND !"
ELSE
ENDIF
EndSr
Codeblock 14-6 Direktzugriff mit RPG.NET
-153Dokument D:\75878992.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.
14.2.1. Deklaration von iSeries-Programmen in VB
Bild 14-2 Wartungsdialog in
VB.NET
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
Dim prog As ASNA.DataGate.Client.As400Program
Dim Parms As ASNA.DataGate.DataLink.ProgParm()
Deklaration des Programmobjektes
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"
Instanzierung des Programmobjektes
faART00.OpenNewAdgDataSet(dsART00)
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
prog.AppendParms(Parms)
Parameterliste an
Programmobjekt binden
' den SQL-ConnectionString erstellen
dbConn.ConnectionString = "Provider=IBMDA400.DataSource.1;Data Source=INFOCOM2;
User ID=neich; Password=xxx;Catalog Library List=NXTGEN"
-154Dokument D:\75878992.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)
DclParm BEZ
Type(*char)
DclParm PRE
Type(*packed)
DclParm LAG
Type(*packed)
DclParm MWC
Type(*char)
DclParm CDE
Type(*char)
DclParm EOF
Type(*char)
DclParm ERR
Type(*char)
Len(
Len(
Len(
Len(
Len(
Len(
Len(
Len(
6,0 )
50 )
9,2 )
6,2 )
1 )
3 )
1 )
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 )
DclSrParm
Nummer
Type( *packed )
Len( 6,2 )
Len( 6,0 )
/// die Menge über Random-Funktion festlegen
Menge = DateTime.Now.Second
CALL
Pgm( "*Libl/ART056" ) DB( dbRuntime )
DclParm Nummer
DclParm Lagerstand
Type( *packed )
DclParm Menge
Type( *packed )
Err( *extended )
Len( 6,2 )
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
-155Dokument D:\75878992.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) ,
-156Dokument D:\75878992.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")
);
-157Dokument D:\75878992.doc
RPG.NET BESTPRACTICES
}
// Bild einlesen
private System.Drawing.Image LeseBild(decimal Nummer)
{
return System.Drawing.Image.FromFile("C:\\AVRTraining\\Bilder\\" +
Nummer.ToString() + ".jpg");
}
}
}
-158Dokument D:\75878992.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!
-159Dokument D:\75878992.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 OfficeVersionen immer wieder zu Problemen durch geänderte Funktionalität und
Parameteranzahl kommen kann.
-160Dokument D:\75878992.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)
DclFld
img
BegProp
Farbe
BegGet
Implements(MeineInterfaces.IAuto)
Type( System.Drawing.Image )
Type( System.Drawing.Color )
Access( *Public )
Implements(MeineInterfaces.IAuto.Farbe)
LeaveSr System.Drawing.Color.Silver
EndGet
endProp
BegProp
Bild
Type( System.Drawing.Image) Access( *Public )
Implements(MeineInterfaces.IAuto.Bild)
LoadPicture
LeaveSr img
File( "Audi.bmp" ) Target( img )
AnzahlPlaetze
Type( *integer2 )
Access( *Public )
Implements(MeineInterfaces.IAuto.AnzahlPlaetze)
BegGet
EndGet
EndProp
BegProp
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.
-161Dokument D:\75878992.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
DclSrParm
Code
Type( MeineInterfaces.iAuto )
Type( *string )
Access( *Public )
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
DclFld cAuto
Type( MeinObjectServer.ObjectServer )
Type( MeineInterfaces.IAuto )
new()
…
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
txtPlaetze.Text
pbBild.Image
= cAuto.Farbe
= cAuto.AnzahlPlaetze
= cAuto.Bild
EndSr
Hier wird dem Objektserver eingesetzt um zur Laufzeit ein Objekt auf Basis des
Interfaces zu instanzieren. Siehe Beispiel:
-162Dokument D:\75878992.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 IAutoInterfaces durch MeinAudi. Es genügt die
Interfaces zu referenzieren.
-163Dokument D:\75878992.doc
Herunterladen