Vertec AI Chatbot

Der Chatbot hilft Ihnen bei Fragen rund um das Produkt und die Anpassbarkeit der Software. Wie bei allen AI-generierten Daten sollten die Antworten bei kritischen Informationen verifiziert werden. Nehmen Sie dafür gerne Kontakt mit uns auf. Weitere Informationen zur Verarbeitung der Chat-Daten bieten wir auf der Datenschutzseite.

Die besten Antworten liefert der Chatbot, wenn Ihr Input möglichst viele Informationen enthält. Zum Beispiel:

«Welche Apps stehen im Vertec Cloud Abo zur Verfügung?»

Custom Renderer

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

Erstellt: 07.06.2022
Aktualisiert: 07.05.2024 | Wichtiger Hinweis bei set_value() und bei button_clicked() eingefügt.

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:

Attribute und Methoden

Legende der Parameter
  • rowobj: Aktuelles Zeilen-Objekt in der Liste bzw. aktuelles Objekt der Seite.
  • expression: Die mitgegebene Expression, in den Listeneinstellungen der Wert aus dem gleichnamigen Feld, in den Seiteneinstellungen die ValueExpression.
  • subscriber: Optionale Benachrichtigung bei Member-Änderungen, siehe Abschnitt subscriber weiter unten.
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 value bzw. get_value() zeigt nichts an. Dieser Wert bzw. diese Methode muss also immer gesetzt werden.

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 value ein String-Wert. Bei allen Datentypen, die nicht als String geschrieben werden können, muss ein passendes Steuerelement (display_type) verwendet oder der value_type entsprechend gesetzt wird (siehe unten).

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: Ein COM-Objekt, das vom zugrunde liegenden RCW getrennt wurde, kann nicht verwendet werden..

value_type
get_value_type(self)

Erlaubt es, den erwarteten Datentyp für den Custom Renderer anzugeben.

Die set_value Methode (siehe oben) erhält dann als Argument einen Wert des angegebenen Typs und bei der get_value Methode wird erwartet, dass dieser Typ zurückgegeben wird.

Folgende Werte sind erlaubt:

  • integer: Ganzzahlen
  • currency: Währungen
  • float: Festkommazahlen
  • date: Datumswerte
  • datetime: Datums- und Zeitwerte
  • boolean: Wahr/Falsch Werte
  • string: Zeichenketten
  • blob: Langer Text (und Bilder)

Anwendungsbeispiel: Ein Custom Renderer arbeitet mit Stundensätzen (currency). Damit die set_value Methode einen Currency-Wert erhält, muss die get_value_type Methode definiert bzw. der value_type auf currency gesetzt werden:

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:

  • Bei boolean Werten muss zusätzlich der display_type checkbox gewählt werden (siehe unten).
  • Bei blob-Feldern muss es sich um langen Text handeln. Bilder können nicht in Listenzellen dargestellt werden, nur Icons (siehe display_type icon unten).
  • Die Renderer funktionieren nicht mit Objektreferenzen.
display_type

get_display_type(self)

Die get_display_type Methode wird pro Spalte ausgewertet, daher werden rowobj, expression und subscriber Objekte nicht mitgeliefert.

Zugelassene Werte sind:

  • checkbox: Die Spalte stellt eine Checkbox dar. Als value_type muss 'boolean' angegeben werden. Die entsprechenden get_value und set_value Methoden verarbeiten dann boolean-Werte.
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
  • icon: Die Spalte wird als Icon dargestellt. Die get_value Funktion muss dazu einen Integer (Icon Index) oder String (Icon Name) zurückgeben.
    class Icon:
        display_type = 'icon'
        def get_value(self, rowobj, expression, subscriber):
            return self.evalocl('iconindex', rowobj)
  • button: Die Zelle zeigt einen Action Button an. Mit der get_value Methode wird eruiert, was als Buttontext angezeigt wird.
    Zusätzlich kann auch ein Icon hinzugefügt werden, indem die buttoniconid gesetzt bzw. die get_buttoniconid Methode aufgerufen wird. Diese muss einen Integer (Icon Index) oder String (Icon Name) zurückgeben.
    Hinweis: In Seiteneinstellungen werden Icons auf den Buttons nicht dargestellt, nur in der Liste.
    • Mit der button_clicked Methode kann definiert werden, was ablaufen soll, wenn der User auf den Button klickt.
    • Mit der buttontooltip Methode (ab Version 6.6) kann auf dem Button ein Tooltip angezeigt werden.
 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 flash angezeigt.

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: Ein COM-Objekt, das vom zugrunde liegenden RCW getrennt wurde, kann nicht verwendet werden.

buttontooltip
get_buttontooltip(self, rowobj, expression, subscriber)

Mit der buttontooltip Methode (ab Version 6.6) kann auf dem Button ein Tooltip angezeigt werden.

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 get_converter Methode wird pro Spalte ausgewertet, daher werden rowobj, expression und subscriber Objekte nicht mitgeliefert.

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: True oder False. Standardwert: False.

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 is_cascadedset bzw. get_is_cascadedset kann das nachgebildet werden:

  • Der get_value schaut, ob ein gewisses Feld beschrieben ist, und nimmt ansonsten den Standardwert.
  • Der set_value beschreibt das gewisse Feld.
  • Das is_cascadedset ist True, wenn das gewisse Feld beschrieben ist.

Mögliche Werte: True (grüne Schrift) oder False (schwarze Schrift). Standardwert: False.

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.

  • expression: Die Spalten- bzw. Feld-Expression
  • rootobj: Optional. Hier kann ein beliebiges Vertec Objekt angegeben werden. Falls angegeben, erfolgt die Evaluierung auf diesem Objekt, ansonsten global.

Im OCL Evaluator steht eine Variable varRowObject zur Verfügung. Dies erlaubt den direkten Zugriff auf das aktuelle Zeilen-Objekt in der Liste.

 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.
Dadurch können beispielsweise in der initialize() Methode des List Controllers Daten vorberechnet werden, welche anschliessend für alle Custom Renderer der Liste verfügbar sind.

Anmerkung: Mit Restrict Scripting können Custom Renderer verwendet werden, der Python Code selbst unterliegt aber den damit üblichen Einschränkungen.

Subscriber

"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)
  • self: Instanz des Custom Renderers, als Variable in den Methoden verfügbar.
  • expression: Die OCL Expression, die ausgewertet werden soll.
  • object: Optionales Argument. Wird hier ein Objekt übergeben, wird die Expression auf das übergebene Objekt angewendet. In Verwendung mit dem Custom Renderer ist das normalerweise das 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)
Subscriptions von Hand setzen

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:

  • Viel komplexerer Code - 7 Zeilen anstatt eine Einzige.
  • Manuelles Handling von None Werten, im Gegensatz zu OCL, welches das elegant automatisch macht.

Wir empfehlen deshalb, immer self.evalocl(expression, rowobj) zu verwenden.

Einen Custom Renderer erstellen

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.

Verwendung in Listeneinstellungen

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.

Verwendung im Customizing von Seiten

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.

Anwendungsfälle

Einen Key setzen

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!

Als Beispiel wird ein Key namens betrag gesetzt:

 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)

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>

Einen Tag setzen

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).

Bitte wählen Sie Ihren Standort