Custom renderer

Custom renderer for configuring rows in lists or on pages

Product line

Standard

|

Expert

Operating mode

CLOUD ABO

|

ON-PREMISES

Modules

Services & CRM

Budget & Phases

Purchases

Resource Planning

Business Intelligence

Created: 07.06.2022
Updated: 07.05.2024 | Added important note for set_value() and button_clicked().

You can use custom renderers to configure the display and calculation of the underlying data of list rows and fields on pages for yourself.

In contrast to the built-in renderers , which, if used in the column configuration of list settings, apply a predefined and unchangeable operation to the displayed values, custom renderers can be used to define custom operations, which are applied to the display.

The custom renderers are created in Python . A Python class as a custom renderer can define the following attributes and methods:

Attributes and methods

Parameter legend
  • rowobj: Current row object in the list or current object of the page.
  • expression: The given expression, in the list settings this is the value in the field of the same name; in the page settings , this is the ValueExpression .
  • subscriber: Optional notification of member changes, see subscriber section below.
Attributes Method Description
value
get_value(self, rowobj, expression, subscriber)

This is the actual custom renderer method. It specifies what is shown in the row.

A custom renderer without value or get_value() does not display anything. Thefore, this value or method must always be set.

def get_value(self, rowobj, expression, subscriber):
    return self.evalocl(expression, rowobj)
  set_value(self, rowobj, expression, value)

If the row is writable, the value entered by the user is written using this method.

def set_value(self, rowobj, expression, value):
    setattr(rowobj, expression, value)

You must make sure that the given value is of the desired type. By default, value is a string value. For all data types that cannot be written as a string, a matching control (display_type) or the value_type is set accordingly (see below).

Important note:

No dialogs or other code with breakpoints after changing values which trigger a subscription  in a get method:

A set method changes data. If this triggers a subscription, the list is recalculated. All existing renderers are also reloaded. If the set method shows a dialog or stops the code in any other way, the modification process is technically completed, and the subscription recalculates the list. This also removes the renderer showing the dialog, which results in an error:You cannot use a COM object that has been separated from the underlying RCW.

value_type
get_value_type(self)

Allows you to specify the expected data type for the custom renderer.

The set_value method (see above) then takes as an argument a value of the specified type and with the get_value method, it is expected to return this type.

The following values are allowed:

  • integer: integers
  • currency: currencies
  • float: fixed point numbers
  • date: date values
  • datetime: date and time values
  • boolean: true/false values
  • string: character strings
  • blob: long text (and pictures)

Example of application: A custom renderer works with hourly rates (currency). In order for the set_value method to receive a currency value, the get_value_type method must be defined or the value_type must be set to currency:

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)

Important note:

  • For boolean values, the display_type checkbox must also be activated (see below).
  • Blob fields must be long text. Images cannot be displayed in list rows, only icons (see display_type icon below).
  • The renderers do not work with object references.
display_type

get_display_type(self)

The get_display_type method is evaluated per column, so rowobj, expression and subscriber objects are not included.

Permitted values are:

  • checkbox: The column represents a checkbox. As value_type 'boolean’ must be specified. The corresponding get_value and set_value methods then process boolean values.
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: The column is displayed as an icon. The get_value function must have an integer (icon index ) or string (icon name ) is returned.
    class Icon:
        display_type = 'icon'
        def get_value(self, rowobj, expression, subscriber):
            return self.evalocl('iconindex', rowobj)
  • button: The cell displays an action button. The get_value method determines what is shown as the button text. In addition, an icon can be added by setting the buttoniconid  or callling up the get_buttoniconid method. This must return an integer (icon index ) or string (icon name ).
    Note: In page settings, icons are not shown on the buttons, only in the list.
    • With the button_clicked method, you can define what should happen when the user clicks on the button.
    • With the buttontooltip method (from version 6.6), a tooltip can be shown on the button.
 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)

If no icon  is specified or if the icon is not present, then the flash icon is shown.

If an empty string ““ is explicitly specified, the button for this row is hidden.

  button_clicked(self, rowobj, expression)
display_type = "button"
def button_clicked(self, rowobj, expression):
    return vtcapp.msgbox("Hello World")

Important note:

No dialogs or other code with breakpoints after changing values that trigger a subscription in a get method:

If data is changed in a button_clicked method, subscription can be triggered and the list recalculated. This will also reload all existing renderers. If you now display a dialog after changing the data or stop the code in any other way, the modification process is technically completed and the subscription recalculates the list. This also removes the renderer showing the dialog, which results in an error: You cannot use a COM object that has been separated from the underlying RCW.

buttontooltip
get_buttontooltip(self, rowobj, expression, subscriber)

With the buttontooltip method (from version 6.6), a tooltip can be shown on the button.

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)

The get_converter method is evaluated per column, so rowobj, expression, and subscriber objects are not included.

The following converters can be used:

 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)

This can be used to control whether or not a row is read-only.

Possible values: True or False. Default value: False .

is_cascadedset
get_is_cascadedset(self, rowobj, expression, subscriber)

More experienced Vertec users are familiar with the behavior that the font turns green when you overwrite certain values and is black when it is a calculated value, for example in project budgeting .

In this way, you can see at first glance on the interface whether a value has been set on the object itself (i.e. a default has been overwritten) or whether it is the default value.

This can be replicated with is_cascadedset or get_is_cascadedset:

  • The get_value looks at whether a certain field is described and otherwise takes the default value.
  • The set_value describes the particular field.
  • The is_cascadedset is True if the particular field is described.

Possible values: True (green font) or False (black font). Default value: False .

text_color
get_text_color(self, rowobj, expression, subscriber)

This allows you to control the font color. Valid are all values of the Vertec color palette .

Default: The Vertec default applies.

background_color
get_background_color(self, rowobj, expression, subscriber)

This allows you to control the background color of the row. All values in the Vertec color palette are valid.

Default: The Vertec default applies.

The object instance ( self ) offers the following attributes and methods:

self.evalocl(expression, rootobj=None)

Allows OCL expressions to be evaluated on the object.

  • expression: The column or field expression
  • rootobj: Optional. Any Vertec object can be specified here. If specified, evaluation occurs on this object, otherwise globally.

The OCL evaluator provides a variable varRowObject. This allows direct access to the current row object in the list.

 self.evalocl("varRowObject")
self.container
Provides access to the container business object of the list view or page.
self.context
Available from Vertec 6.6. Refers to the context object. The context object is the container (folder or link container) for normal lists. For Resource planning views , the context object can also be a single user entry.
self.controller

Available from Vertec 6.6. Used with list controllers which refer to the list controller object: If a column in a list with a list controller uses a custom renderer, then the custom renderer gets a reference to the list controller under self.controller. This is the same
object for all custom renderers in the list. Consequently, data can be precalculated in the initialize() method of the list controller, which is then available for all the list's custom renderers.

Note: Custom renderers can be used with restrict scripting , but the Python code itself is subject to the usual limitations.

Subscriber

Subscribe means that you tell a certain member that you want to be notified when things change. You subscribe to that member’s notification list.

That is why a subscriber is supplied as a parameter with each get method so that the displayed values can be updated when the data changes.

The subscriptions are set automatically when the values in the renderer are calculated via self.evalocl():

self.evalocl(expression, object)
  • self: the custom renderer instance, available as a variable in the methods.
  • expression: The OCL expression to be evaluated.
  • object: Optional argument. If an object is passed here, the expression is applied to the passed object. When used with the custom renderer, this is usually the rowobj.

This means that the values are automatically correctly subscribed and the get method is called again when the underlying data changes.

Important: self.evalocl() must be used, evalocl() on an object is not enough. Therefore, self.evalocl("eintraege", rowobj) must be used instead of
self.evalocl("eintraege", rowobj).

def get_value(self, rowobj, expression, subscriber):
    return self.evalocl(expression, rowobj)
Set subscriptions manually

If, for some reason, this is not possible and the value is determined via Python code, you can use the subscriber parameter to set this subscription manually. Python methods exist for this

subscribetomember(membername, subscriber)

on all Vertec objects.

Example with OCL and without subscriber Example with Python and with 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

It is important that you subscribe to all the members about which you want to be notified when they change (see description above).

A manually set subscription is always set only on the corresponding member so it does not chain. Example, starting from a service:

projektleiter = self.evaolocl("projekt.projektleiter", leistung)

would have to be split with subscriptions via the Python method as follows:    

projekt = leistung.projekt
leistung.subscribetomember("projekt", subscriber)
if projekt:
    projektleiter = projekt.projektleiter
    projekt.subscribetomember("projektleiter", subscriber)
else:
    projektleiter = None

This means that errors are quickly built in, and there are also other disadvantages:

  • Much more complex code – 7 rows instead of a single one.
  • Manual handling of none values, in contrast to OCL, which does this automatically in an elegant way.

Therefore, we recommend that you always use self.evalocl(expression, rowobj).

Creating a custom renderer

The custom renderer is created as a Python script . A relevant class is defined for each custom renderer, in which the individual attributes or methods are then defined.

You can call up the individual renderer via ​<Designation of script>.<Name of Python class in script> . Therefore, we recommend you assign meaningful names here.

Below is an example of a complete renderer:

 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'

Or, via definition of variables:

 class Compact:
    value = '120'
    converter = 'minutes'
    is_readonly = True
    value_type = 'integer'
    background_color = 'clLightYellow'
    text_color = 'clDarkBlue'

Important: The value value or the method get_value() must always be defined, otherwise the row or field does not show anything.

How to use it in list settings

In the relevant list column, the renderer is specified as follows:

<Designation of script>.<Name of Python class in script>

Important: After adjusting the renderer, the list must be reloaded for the changes to take effect. The easiest way to do this is to click away and back.

How to use it when customizing pages

When customizing pages, the attribute Renderer can be set in XML. The convention is the same as in the list settings

<Designation of script>.<Name of Python class in script>

As soon as a custom renderer is set, everything is looped over that renderer. A custom renderer must always have a get_value() method, otherwise nothing is shown in the list. The expression (or ValueExpression) is transferred to the renderer, but is ignored for display.

Use cases

Setting a key

You can use custom renderers to enter key values on user entries directly in the list. The key is created with the first entry. For the display, the value is read directly from the key. The structure is as follows:

In the list settings, the name of the key is written as an expression. You must make sure that the term is written in lowercase. When leaving the field, the message appears that the OCL expression is invalid.

You can reply to this notification with No. This does not cause problems if the relevant key is set as the renderer.

Important: The error message MUST appear. Otherwise, you might accidentally use an existing member or name the key the same as an existing member, which leads to an invalid state!

With the following code, the key with the specified name (as written in expression, in our example amount) is set:

 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)

Hint: You can use this code for any key of type currency.

Using a custom renderer on a page:

In the page settings , the name of the key is written in the ValueExpression.

<Page Override="InvoiceFurtherInfo">
    <TextBox Name="KeyBetrag" Renderer="CustomRendererClasses.KeyCurr" Label="Betrag" 
        ContentAlignment="Right" ValueExpression="amount" WidthFraction="0.5" PlaceAfter="ReminderLevel" />
</Page>

Setting a tag

You can use custom renderers to set tags on user entries directly in the list . The tag is shown by a checkbox and can be set or removed.

The relevant renderer is as follows:

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)

The name of the relevant tag (without quotation marks) is specified as Expression in the list settings or as ValueExpression in the page settings .

Bitte wählen Sie Ihren Standort