von Gastautor Michael Höhne, 4D Entwickler (München, Deutschland)
Es gibt ein Feature in 4D v18 R5, das vielleicht übersehen wurde, oder zumindest bisher nicht viel Beachtung gefunden hat: Formular-Makros. Um ehrlich zu sein, hatte auch ich bis vor kurzem nicht viel Zeit mit ihnen verbracht. In diesem Blogbeitrag zeige ich Ihnen ein Makro, das Ihnen viel Zeit bei der Anwendung von Namenskonventionen für Listenfeldspalten, Spaltenüberschriften und Fußzeilen spart. Sie können es leicht an Ihre Bedürfnisse anpassen. Ein entsprechendes Repo ist auch auf Github verfügbar.
Kontext zuerst …
Wir verwenden Makros in unserer täglichen Arbeit, um die Entwicklung zu erleichtern, und das ist eine wirklich coole Funktion. Wie viele andere Unternehmen haben auch wir Kodierungsrichtlinien. Sie sind wichtig, wenn man in größeren Teams arbeitet, um den Code der anderen besser zu verstehen. Die 4D Sprache hat sich lange Zeit nicht verändert und unsere Richtlinien auch nicht. Dann kamen ORDA, Klassen und neue Syntaxen für die Deklaration von Variablen und Methoden! Es war an der Zeit, diese neuen Sprachelemente zu integrieren und eine allgemeine Überprüfung unserer internen Spezifikationen vorzunehmen. Ein Teil dieser Überprüfung waren die Namen von Formularobjekten (die allerdings nichts mit den neuen Elementen zu tun hatten). Wir sprachen über Kontrollkästchen, Optionsfelder, Felder und schließlich Listenfelder. Ich werde nicht über unsere Benennungsstile schreiben. Das Wichtigste ist, dass ein Listenfeld nicht nur einen einzigen Objektnamen hat. Es hat Namen für die Liste selbst, jede Spalte und jede Spaltenüberschrift und -fußzeile. Ein einfaches Listenfeld mit 5 Spalten hat 16 Objektnamen. Wenn Sie Namenskonventionen auf eine Listbox anwenden wollen, kann das ein langwieriger Prozess sein. Wäre es nicht schön, wenn man das automatisieren könnte?
Objektnamen für eine Listbox mit Unternehmen könnten sein:
- listCompanies für die Liste selbst,
- listCompaniesCol1, listCompaniesCol2, …, für die Spaltennamen,
- listCompaniesCol1Header, listCompaniesCol2Header, …, für die Spaltenüberschriften,
- listCompaniesCol1Footer, listCompaniesCol2Footer, …, für die Spaltenfüße.
Dies ist nur eine Möglichkeit, wie man es machen kann. Wenn Sie Entwickler nach ihren Präferenzen bei der Namensgebung fragen, werden Sie höchstwahrscheinlich viele verschiedene Meinungen erhalten. Es kann jedoch ein ähnliches Muster wie bei den obigen Namen geben: Alle Spalten-, Kopf- und Fußzeilennamen basieren auf dem Namen der Listbox. Diese Namen können durch ein Formularmakro zugewiesen werden!
Schnellstart
Formularmakros sind über das Kontextmenü des Formulardesigners verfügbar:
Wenn Sie noch nie mit Formularmakros gearbeitet haben, ist es wahrscheinlich, dass Ihr Kontextmenü überhaupt kein Makro-Menü enthält:
In diesem Fall müssen Sie als Erstes eine formMacros.json-Datei erstellen und diese im Ordner Sources Ihres Projekts ablegen:
Die formMacros.json-Datei definiert die Namen der Menüpunkte und die Klassen, die den Code implementieren. Die Datei, die ich in diesem Beispiel verwende, ist sehr einfach:
{ "macros": { "Rename Listbox columns": { "class": "RenameListboxColumns" } } }
Sie weist 4D an, einen Menüpunkt namens „Rename Listbox columns“ zum Menü Macros hinzuzufügen (im Kontextmenü des Formular-Designers). Wenn Sie auf den Menüpunkt klicken, wird die Methode onInvoke einer Klasse namens „RenameListboxColumns“ aufgerufen (dazu gleich mehr). Die Datei formMacros.json wird einmal beim Laden des Projekts gelesen. Änderungen am Inhalt werden erst beim erneuten Laden des Projekts auf die Benutzeroberfläche angewendet. Wenn Sie die Datei formMacros.json gerade erstellt haben, schließen Sie Ihr Projekt und öffnen Sie es erneut, um das Menü Makros im Formulardesigner anzuzeigen. Ausführliche Informationen über Formularmakros finden Sie hier.
Formularmakros sind Klassen
Die Implementierung eines Formularmakros befindet sich innerhalb einer Klasse. Die Eigenschaft „class“ in der Makrodefinition („class“: „RenameListboxColumns“) definiert den Namen des Makros. Damit unser Makro etwas tun kann, müssen wir diese Klasse erstellen:
Um zu sehen, ob es funktioniert, fügen Sie den folgenden Code hinzu:
// Macro invoked in the form designer.
Function onInvoke($editor: Object)->$return: Object
ALERT ("Hallo Welt!") // Return the modified page object
$return :=New object("aktuelleSeite"; $editor.editor)
Wenn Sie im Menü Makros die Option „Listbox-Spalten umbenennen“ auswählen, sollten Sie die Warnmeldung erhalten:
Dies ist eine grundlegende Prüfung, um sicherzustellen, dass alles korrekt eingerichtet ist. Um einen möglichen Fallstrick aufzuzeigen, ändern Sie den Code in:
// Macro invoked in the form designer.
Function onInvoke($editor: Object)->$return: Object
This .showHelloWorldMessage() // Return the modified page object
$return: =New object("currentPage"; $editor.editor)
Function showHelloWorldMessage()
ALERT ("Hallo Welt!")
Wählen Sie erneut das Menü „Listbox-Spalten umbenennen“:
4D lädt die Klassendefinition nur einmal. Sie können die Implementierung der Klassenmethoden ändern, aber neue Methoden oder Änderungen an Methodenparametern sind nicht verfügbar, bis Sie das Projekt neu laden. Das ist etwas unpraktisch, aber man gewöhnt sich daran. Nach dem Neuladen des Projekts funktioniert der Code einwandfrei.
Hinzufügen von Funktionen
Jetzt, da Sie wissen, wie man ein Formularmakro einrichtet, können wir ein paar Funktionen hinzufügen! Die Anzeige einer Hello World-Meldung ist zwar nett, aber es wäre sinnvoll, wenn ein Makro mit dem Namen „Rename Listbox columns“ mehr tun würde. Zum Beispiel Spalten umbenennen.
Die Methode onInvoke übergibt ein Objekt namens $editor. Dieses Objekt enthält Informationen über das Formular, an dem Sie gerade arbeiten. currentSelection ($editor.editor.currentSelection) enthält die Objektnamen aller ausgewählten Objekte. Wenn Sie kein Objekt ausgewählt haben, ist es leer.
currentPage ($editor.editor.currentPage ) enthält alle Objekte , die sich auf der aktuellen Formularseite befinden. form ($editor.editor.form) enthält das gesamte Formular. Diese Objekte sind einfach Teile der JSON-Deklaration des Formulars. Wenn Sie sich einen Überblick über die Objektstrukturen verschaffen wollen, öffnen Sie einfach die form.4DForm-Datei, an der Sie gerade arbeiten, in einem Texteditor.
Da wir die Objektnamen der Spalten der Listbox, der Spaltenköpfe und der Fußzeilen umbenennen wollen, wird die Hauptanwendungslogik wie folgt aussehen:
- Schleife durch die ausgewählten Objekte. Führen Sie für jedes gefundene Listenfeld die folgenden Schritte aus:
- Durchlaufen Sie die Spalten der Listbox und berechnen Sie die Objektnamen für jede Spalte, jede Spaltenüberschrift und jede Fußzeile anhand eines bestimmten Musters.
- Wenn einer der berechneten Objektnamen bereits im Formular verwendet wird, wird der Umbenennungsprozess abgebrochen und dem Benutzer eine Fehlermeldung angezeigt.
- Andernfalls benennen Sie das Objekt um.
Beginnen wir mit dem ersten Thema:
- Führen Sie eine Schleife durch die ausgewählten Objekte. Führen Sie für jedes gefundene Listenfeld die zuvor beschriebenen Schritte aus:
// Macro invoked in the form designer.
Function onInvoke($editor: Object)->$return: Object
var $objectName : Text
var $formObject : Object
This .editor:=$editor.editor
If (This.editor.currentSelection.length>0)
For each ($objectName; This.editor.editor)
$formObject :=This.editor.currentPage.objects[$objectName]
If (This.isListbox($formObject))
This .renameListboxColumns($objectName; $formObject)
End if
End for each
End if
// Return the modified page object
$return :=New object("currentPage"; This.editor.currentPage)
Dieser Code stimmt mit dem Satz überein (Schleife durch die ausgewählten Objekte, Prüfung auf Listenfelder und Umbenennung ihrer Spalten). Aber wie erkennen wir ein Listenfeld? Setzen wir einen Haltepunkt in den Code und untersuchen ihn:
Jedes Objekt hat eine Eigenschaft type. Für ein Listenfeld wird diese auf „listbox“ gesetzt. Dies ist dasselbe, was Sie sehen, wenn Sie die Datei form.4dform in einem Texteditor öffnen:
// Check if the form object is a listbox
Function isListbox($formObject: Object)->$isListbox: Boolean
$isListbox :=($formObject.type="listbox")
Ziemlich einfach! Natürlich könnte man diesen Einzeiler auch direkt in onInvoke einfügen, anstatt eine neue Methode zu deklarieren, aber nach rund 30 Jahren objektorientierter Entwicklung bin ich es gewohnt, kleine Methoden zu erstellen. Es hilft, leicht verständlichen und leicht zu wartenden Code zu schreiben.
Da wir nun wissen, wie man ein Listenfeld identifiziert, können wir den nächsten Schritt tun:
- Führen Sie eine Schleife durch die Spalten der Listbox und berechnen Sie die Objektnamen für jede Spalte, jede Spaltenüberschrift und jede Fußzeile anhand eines bestimmten Musters.
// Renames all columns, column headers and footers based on
// the listbox object name.
Function renameListboxColumns( : ; : ) : : : :=1 ( ; . ) $lbxName Text $listbox Object
var $col Object
var $index Integer
var $newObjectName Text
$index
For each$col $listboxcolumns
This . ( ; +"Col "+ ( )) setObjectName$col $lbxNameString$index
This . ( . ; . +"Footer") setObjectName$colfooter $colname
This . ( . ; . +"Kopfzeile") := +1setObjectName$colheader $colname
$index$index
End for each
Die Umbenennung eines Objekts sollte nicht zu doppelten Objektnamen führen.
- Wenn einer der berechneten Objektnamen bereits im Formular verwendet wird, brechen Sie den Umbenennungsprozess ab und zeigen dem Benutzer eine Fehlermeldung an.
- Andernfalls benennen Sie das Objekt um.
// Changes the object name of $formObject to the $newObjectName.
// If the new object name is different than the current name
// and form object with that name already exists on any page
// of the form, the processing is aborted and a message is shown in the designer.
Function setObjectName( : ; : ) ( # . ) ( . ( . . ; )) ("Objektname " +" bereits verwendet. Umbenennung abgebrochen.") $formObject Object $newObjectName Text
If$newObjectName$formObjectname
IfThisisObjectNameUsedInFormThiseditorform $newObjectName
ALERT$newObjectName
ABORT // abort further processing
Else
$formObject. :=name$newObjectName
End if
End if
Die Prüfung auf einen vorhandenen Objektnamen erfolgt mit einigen weiteren Methoden: isObjectNameUsedInForm, isObjectNameUsedInPage, und isObjectNameUsedInListbox. Sie sind in dem endgültigen Beispiel enthalten, das auf Github verfügbar ist. Es enthält auch ein Formular, das Sie zum Testen des Makros verwenden können:
Formularmakros in der täglichen Arbeit verwenden
Wenn die Implementierung eines Formularmakros spezifisch für ein einzelnes Projekt ist, dann macht es durchaus Sinn, es direkt zu diesem Projekt hinzuzufügen. Aber Makros wie dieses können in jedem Projekt nützlich sein, das Formulare enthält. Sie können die Klasse natürlich in jedes Projekt kopieren und die Makrodefinition zu jeder formMacros.json-Datei hinzufügen, aber es ist einfacher, stattdessen eine Komponente zu erstellen.
Eine Formularmakrokomponente ist nichts anderes als ein 4D Projekt mit einer formMacros.json Datei und den darin definierten Klassen. Der einzige Unterschied besteht darin, dass Sie eine Komponente erstellen und sie in den entsprechenden Komponentenordner Ihres Projekts oder in den Komponentenordner der 4D Anwendung legen, damit sie standardmäßig in allen Projekten verfügbar ist.
Wenn Sie nur das Makro verwenden möchten und sich nicht um die Implementierung kümmern: Das Beispiel auf GitHub enthält die kompilierte Komponente im Ordner Build.