Support vertec.com

Custom Renderer

Custom Renderer für die Konfiguration von Zellen in Listen oder auf Seiten

Standard

|

Expert

CLOUD ABO

|

ON-PREMISES

Leistung & CRM

Budget & Teilprojekt

Fremdkosten

Ressourcen & Projektplanung

Business Intelligence

Erstellt: 07.06.2022
Aktualisiert: 07.09.2022 | Neues Attribut buttoniconid und Ergänzung zu display_type ab Version 6.5.0.22 beschrieben.

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

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:

  • Für boolean Werte funktioniert das nicht so, sondern es muss stattdessen 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. Die entsprechenden get_value und set_value Methoden verarbeiten dann boolean-Werte.
    class Aktiv:
        display_type = 'checkbox'
        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 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.

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, subscriber):
        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, expres-sion, subscriber):
    return self.evalocl('iconIndex', rowobj)
  button_clicked(self, rowobj, expression, subscriber)
display_type = "button"
def button_clicked(self, rowobj, expression, subscriber):
    return vtcapp.msgbox("Hello World")
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.

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.

Evaluiert man den Rückgabewert von get_value() via OCL (self.evalocl()), dann geschieht das automatisch. In diesem Fall wird get_value() wird erneut aufgerufen, wenn sich die darunter liegenden Daten ändern. Der OCL-Evaluator subscribed automatisch auf die OCL-Evaluierung und zusätzlich auf das Ergebnis Member. In diesem Fall muss man keinen Subscriber angeben.

Wichtig hierbei ist zu wissen, dass dafür self.evalocl() verwendet werden muss. Ein aktivitaet.evalocl("eintraege") beispielsweise macht keine Subscription, es muss stattdessen self.evalocl("eintraege", aktivitaet) verwendet werden.

Ausserdem wird eine manuell gesetzte Subscription 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() zu verwenden.

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

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>
Bitte wählen Sie Ihren Standort