Custom Renderer für die Konfiguration von Zellen in Listen oder auf Seiten
Produktlinie
Standard
|Expert
Betriebsart
CLOUD ABO
|ON-PREMISES
Module
Leistung & CRM
Budget & Teilprojekt
Fremdkosten
Ressourcenplanung
Business Intelligence
Mit Custom Renderern ist es möglich, die Anzeige und Berechnung der zugrunde liegenden Daten von Listenzellen und Feldern auf Seiten selbst konfigurieren zu können.
Im Gegensatz zu den eingebauten Renderern , welche - in der Spaltenkonfiguration von Listeneinstellungen eingesetzt - einen vordefinierten und nicht änderbaren Vorgang auf den angezeigten Werten anwenden, können mit Custom Renderern eigene Vorgänge definiert werden, welche bei der Anzeige zur Anwendung kommen.
Die Custom Renderer werden in Python erstellt. Eine Python Klasse als Custom Renderer kann die folgenden Attribute bzw. Methoden definieren:
ValueExpression
.Attribut | Methode | Beschreibung |
---|---|---|
value |
get_value(self, rowobj, expression, subscriber) |
Das ist die eigentliche Custom Renderer Methode. Darin wird angegeben, was in der Zelle angezeigt wird. Ein Custom Renderer ohne def get_value(self, rowobj, expression, subscriber): return self.evalocl(expression, rowobj) |
set_value(self, rowobj, expression, value) |
Ist die Zelle beschreibbar, wird der vom User eingegebene Wert mit dieser Methode geschrieben. def set_value(self, rowobj, expression, value): setattr(rowobj, expression, value) Es muss sichergestellt sein, dass der übergebene Wert den gewünschten Typ hat. Standardmässig ist Wichtiger Hinweis: Keine Dialoge oder anderer Code mit Haltepunkten nach dem Ändern von Werten, die in einer get-Methode eine Subscription auslösen: In einer set-Methode werden Daten verändert. Wird dadurch eine Subscription getriggert, wird die Liste neu berechnet. Dabei werden auch alle bestehenden Renderer neu geladen. Zeigt man nun in der set-Methode einen Dialog oder stoppt den Code anderswie, gilt der Änderungsvorgang technisch als abgeschlossen, und durch die Subscription wird die Liste neu berechnet. Damit wird auch der Renderer, der den Dialog anzeigt, entfernt, was zu einem Fehler führt: |
|
value_type |
get_value_type(self) |
Erlaubt es, den erwarteten Datentyp für den Custom Renderer anzugeben. Die Folgende Werte sind erlaubt:
Anwendungsbeispiel: Ein Custom Renderer arbeitet mit Stundensätzen (currency). Damit die class Ansatz: value_type = "currency" def get_value(self, rowobj, expression, subscriber): return self.evalocl(expression, rowobj) def set_value(self, rowobj, expression, value): setattr(rowobj, expression, value) Wichtiger Hinweis:
|
display_type |
get_display_type(self) Die |
Zugelassene Werte sind:
class Aktiv: display_type = 'checkbox' value_type = 'boolean' def get_value(self, rowobj, expression, subscriber): return self.evalocl('aktiv', rowobj) def set_value(self, rowobj, expression, value): rowobj.aktiv = value
class CopyProjectRenderer: display_type = "button" def get_value(self, rowobj, expression, subscriber): return "Projekt kopieren" def get_buttoniconid(self, rowobj, expression, subscriber): return self.evalocl('iconIndex', rowobj) def button_clicked(self, rowobj, expression): scripttext = self.evalocl("scripteintrag->select(bezeichnung='Projekt kopieren')->first.scripttext") return vtcapp.scriptexecute(scripttext, rowobj) |
buttoniconid |
get_buttoniconid(self, rowobj, expression, subscriber) |
display_type = "button" def get_buttoniconid(self, rowobj, expression, subscriber): return self.evalocl('iconIndex', rowobj) Wird kein Icon
angegeben, oder gibt es das Icon nicht, dann wird das Icon Ist explizit ein Leerstring "" angegeben, dann wird der Button für diese Zelle ausgeblendet. |
button_clicked(self, rowobj, expression) |
display_type = "button" def button_clicked(self, rowobj, expression): return vtcapp.msgbox("Hello World") Wichtiger Hinweis: Keine Dialoge oder anderer Code mit Haltepunkten nach dem Ändern von Werten, die in einer get-Methode eine Subscription auslösen: Werden in einer button_clicked Methode Daten verändert, können dadurch Subscription getriggert und die Liste neu berechnet werden. Dabei werden auch alle bestehenden Renderer neu geladen. Zeigt man nun in nach der Datenänderung noch einen Dialog an oder stoppt den Code anderswie, gilt der Änderungsvorgang technisch als abgeschlossen, und durch die Subscription wird die Liste neu berechnet. Damit wird auch der Renderer, der den Dialog anzeigt, entfernt, was zu einem Fehler führt: |
|
buttontooltip |
get_buttontooltip(self, rowobj, expression, subscriber) |
Mit der class simpleBtn: display_type='button' buttoniconid='flash' buttontooltip='Hier ein Tipp' class complexBtn: display_type='button' buttoniconid='shark_fin' def get_buttontooltip(self,rowObject, expression, subscriber): return self.evalocl("bemerkung", rowObject) |
converter |
get_converter(self) Die |
Folgende Converter können verwendet werden:
class Minutes: value_type = "integer" converter = "minutes" def get_value(self, rowobj, expression, subscriber): return rowobj.evalocl(expression) def set_value(self, rowobj, expression, value): setattr(rowobj, expression, value) |
is_readonly |
get_is_readonly(self, rowobj, expression, subscriber) |
Damit kann gesteuert werden, ob eine Zelle schreibgeschützt ist oder nicht. Mögliche Werte: |
is_cascadedset |
get_is_cascadedset(self, rowobj, expression, subscriber) |
Geübtere Vertec-Nutzer kennen das Verhalten, dass die Schrift grün wird, wenn man gewisse Werte überschreibt, und Schwarz ist, wenn es sich um einen berechneten Wert handelt, zum Beispiel bei der Projektbudgetierung . So sieht man auf der Oberfläche auf den ersten Blick, ob ein Wert auf dem Objekt selbst gesetzt (also irgend ein Standard überschrieben) wurde, oder ob es sich um den Standardwert handelt. Mit
Mögliche Werte: |
text_color |
get_text_color(self, rowobj, expression, subscriber) |
Damit kann die Schriftfarbe gesteuert werden. Gültig sind alle Werte der Vertec Farbpalette . Standard: Es greift der Vertec Standard. |
background_color |
get_background_color(self, rowobj, expression, subscriber) |
Damit kann die Hintergrundfarbe der Zelle gesteuert werden. Gültig sind alle Werte der Vertec Farbpalette . Standard: Es greift der Vertec Standard. |
Die Objektinstanz (self
) bietet folgende Attribute und Methoden an:
self.evalocl(expression, rootobj=None) |
Erlaubt das Evaluieren von OCL-Expressions auf dem Objekt.
Im OCL Evaluator steht eine Variable self.evalocl("varRowObject") |
self.container |
Bietet Zugriff auf das Container Businessobjekt der Listenansicht bzw. der Seite. |
self.context |
Ab Vertec 6.6. Verweist auf das Context Objekt. Das Context Objekt ist bei normalen Listen der Container (Ordner oder Link Container). Bei Ressourcenplanungsansichten kann das Context Objekt auch ein einzelner UserEintrag sein. |
self.controller |
Ab Vertec 6.6. In Verwendung mit List Controllern
die Referenz auf das List Controller Objekt: Wenn in einer Liste mit einem List Controller eine Spalte einen Custom Renderer verwendet, dann erhält der Custom Renderer unter self.controller eine Referenz auf den List Controller. Dies ist für alle Custom Renderer der Liste dasselbe Objekt. |
Anmerkung: Mit Restrict Scripting können Custom Renderer verwendet werden, der Python Code selbst unterliegt aber den damit üblichen Einschränkungen.
"Subscriben" heisst, dass man einem bestimmten Member mitteilt, dass man mitbekommen möchte, wenn es sich ändert. Man schreibt sich sozusagen ein in die Benachrichtigungsliste dieses Members.
Deshalb ist bei den get
Methoden jeweils ein Subscriber als Parameter mitgeliefert, damit sich die angezeigten Werte aktualisieren können, wenn sich die Daten ändern.
Die Subscriptions werden automatisch gesetzt, wenn die Werte im Renderer via self.evalocl()
berechnet werden:
self.evalocl(expression, object)
rowobj
.Damit sind die Werte automatisch richtig subscribed und die get
Methode wird erneut aufgerufen, wenn sich die darunter liegenden Daten ändern.
Wichtig: es muss dafür wirklich self.evalocl()
verwendet werden, ein evalocl()
auf einem Objekt reicht dafür nicht. Statt rowobj.evalocl("eintraege")
muss also self.evalocl("eintraege", rowobj)
verwendet werden.
def get_value(self, rowobj, expression, subscriber): return self.evalocl(expression, rowobj)
Ist das aus irgendeinem Grund nicht möglich und wird der Wert via Python Code ermittelt, gibt es dafür den Parameter subscriber
. Dieser dient dazu, diese Subscription von Hand zu setzen. Dafür gibt es die Python Methode
subscribetomember(membername, subscriber)
auf allen Vertec-Objekten.
Beispiel mit OCL und ohne Subscriber | Beispiel mit Python und mit Subscriber |
---|---|
def get_value(self, rowobj, expression, subscriber): return self.evalocl("code", rowobj) |
def get_value(self, rowobj, expression, subscriber): rowobj.subscribetomember("code", subscriber) return rowobj.code |
Wichtig ist, dass man auf alle Members, von denen man Änderungen mitbekommen möchte, subscribed (siehe Beschreibung oben).
Eine manuell gesetzte Subscription wird immer nur auf dem entsprechenden Member gesetzt, macht also keine "chain". Beispiel, von einer Leistung ausgehend:
projektleiter = self.evaolocl("projekt.projektleiter", leistung)
müsste mit Subscriptions via Python Methode wie folgt aufgeteilt werden:
projekt = leistung.projekt leistung.subscribetomember("projekt", subscriber) if projekt: projektleiter = projekt.projektleiter projekt.subscribetomember("projektleiter", subscriber) else: projektleiter = None
So sind schnell Fehler eingebaut, und es gibt auch weitere Nachteile:
Wir empfehlen deshalb, immer self.evalocl(expression, rowobj)
zu verwenden.
Die Custom Renderer werden als Python Script erstellt. Für jeden Custom Renderer wird eine entsprechende Klasse definiert, in welcher dann die einzelnen Attribute oder Methoden gesetzt werden.
Der Aufruf des einzelnen Renderers erfolgt dann über Scriptnamen.Rendererklasse
. Deshalb empfiehlt es sich, hier sprechende Namen zu vergeben.
Hier das Beispiel eines vollständigen Renderers:
class BemerkungProjectCustomRenderer: def get_value(self, rowobj, expression, subscriber): return self.evalocl("bemerkung", rowobj) def set_value(self, rowobj, expression, value): rowobj.bemerkung = value def get_is_readonly(self, rowobj, expression, subscriber): return False def get_is_cascadedset(self, rowobj, expression, subscriber): return False def get_value_type(self): return 'string' def get_text_color(self, rowobj, expression, subscriber): return 'clBlack' def get_background_color(self, rowobj, expression, subscriber): return 'clWhite'
Oder, via Definition von Variablen:
class Compact: value = '120' converter = 'minutes' is_readonly = True value_type = 'integer' background_color = 'clLightYellow' text_color = 'clDarkBlue'
Wichtig: Der Wert value
bzw. die Methode get_value()
muss immer definiert sein, da die Zelle bzw. das Feld sonst nichts anzeigt.
In der entsprechenden Spalte der Liste wird der Renderer wie folgt angegeben:
<Bezeichnung des Scripteintrags>.<Name einer Python Klasse im Script>
Wichtig: Nach einer Anpassung des Renderers muss die Liste neu geladen werden, damit die Änderungen greifen. Das geschieht am einfachsten, indem weg- und wieder hingeklickt wird.
Beim Customizing von Seiten kann im XML neu das Attribut Renderer
gesetzt werden. Die Konvention ist gleich wie bei den Listeneinstellungen
<Bezeichnung des Scripteintrags>.<Name einer Python Klasse im Script>
Sobald ein Custom Renderer gesetzt ist, wird alles über diesen Renderer geschlauft. Ein Custom Renderer muss also immer eine get_value()
Methode haben, sonst wird in der Liste nichts angezeigt. Die Expression (bzw. ValueExpression) wird dem Renderer zwar übergeben, aber für die Anzeige nicht mehr beachtet.
Mittels Custom Renderer können Key-Values auf User-Einträgen direkt in der Liste eingegeben werden. Bei der ersten Eingabe wird der Key erzeugt, für die Anzeige wird der Wert direkt aus dem Key gelesen. Der Aufbau ist wie folgt:
In den Listeneinstellungen wird als Expression der Name des Keys geschrieben. Hier muss darauf geachtet werden, dass der Begriff klein geschrieben wird. Beim Verlassen des Feldes kommt die Meldung, dass die OCL Expression ungültig ist.
Diese Meldung wird mit Nein
bestätigt, das führt zu keinen Problemen, wenn als Renderer der entsprechende Key gesetzt ist.
Wichtig: Die Meldung MUSS erscheinen, da man sonst versehentlich ein bestehendes Member erwischt bzw. den Key gleich benannt hat wie ein bestehendes Member, was zu einem ungültigen Zustand führt!
Mit folgendem Code wird ein Key gesetzt mit dem als Expression eingegebenen Namen (im Beispiel betrag
):
class KeyCurr: value_type = 'currency' def get_value(self, rowobj, expression, subscriber): return self.evalocl("keycurr('{}')".format(expression), rowobj) def set_value(self, rowobj, expression, value): if str(value): rowobj.setkeyvalue(expression, value)
Hinweis: Dieser Code kann für alle Keys vom Typ Currency verwendet werden.
Verwendung in einer Seite:
In den Seiteneinstellungen
wird der Name des Keys in die ValueExpression
geschrieben.
<Page Override="InvoiceFurtherInfo"> <TextBox Name="KeyBetrag" Renderer="CustomRendererClasses.KeyCurr" Label="Betrag" ContentAlignment="Right" ValueExpression="betrag" WidthFraction="0.5" PlaceAfter="ReminderLevel" /> </Page>
Mittels Custom Renderer können Tags auf User-Einträgen direkt in der Liste gesetzt werden. Der Tag wird dabei mittels Checkbox angezeigt und kann gesetzt oder entfernt werden.
Der entsprechende Renderer lautet wie folgt:
class SetTag: display_type = 'checkbox' value_type = 'boolean' def get_value(self, rowobj, expression, subscriber): return self.evalocl("hasTag('{}')".format(expression), rowobj) def set_value(self, rowobj, expression, value): if value: rowobj.addtag(expression) else: rowobj.removetag(expression)
Als Expression
der Spalte in den Listeneinstellungen
bzw. als ValueExpression
in den Seiteneinstellungen
wird der Name des entsprechenden Tags angegeben (ohne Anführungszeichen).