Blogs

Introducing PVL - the Portable Visual Library

If you follow our changelogs, you may have wondered about an acronym that recently keeps appearing there: PVL. This post explains what PVL is, why we built it, and how you can use it in your own DK developed applications.

The problem PVL solves

The TatukGIS Developer Kernel is available for Delphi (VCL, FMX), .NET (WinForms), Java (Swing), and Python, among other platforms. For years, every dialog window had to be written separately for each platform. That was costly, it slowed development, and because every change had to be rolled out across all platforms simultaneously, it increased the risk of inconsistencies along the way.

PVL, the Portable Visual Library, began as a small project to unify dialog windows. It is a GUI abstraction layer: a form is written only once against PVL classes and is rendered through the native library behind the scenes for: VCL, FMX, WinForms, or Swing. The same code, the same functionality, on every platform.

From internal tool to public API

What began as an internal effort to unify a few of the more important DK dialogs grew until PVL became the main UI platform for DK.Python: the entire Python interface and all Python samples are written in PVL.

The single biggest undertaking was the Layer Properties form. Within a few weeks, a new PVL-based form replaced the old one on every platform, which immediately allowed us to extend it with new functionality such as layer restructuring and field rules. Thanks to PVL, the recently released Topology Layer shipped on day one with built-in wizards on all supported platforms.

Though PVL was initially meant to remain internal, a growing number of questions from DK users convinced us to make the API public to better support our customers in their cross-platform development efforts.

What is available today

Nearly all controls related to layer properties have already been ported to PVL: Font, Color, Symbology, Symbology Library, Bitmap, FieldFactor, LegendForm, Statistics, as well as the Grid Wizard, the Vector Wizard, and all the new Topology Layer tools. Porting of the remaining DK and Editor dialogs is ongoing.

A few components (ViewerWnd, ViewerBmp, NorthArrow, Scale, 3D, Attributes, Legend, and PrintPreview) are not yet implemented natively in PVL. For these we provide simple proxy classes so that PVL can already serve as a complete, reliable GUI library for your DK application.

PVL renders through VCL, FMX, WinForms, and Swing, and is the native UI layer of the Python edition. WPF and MAUI are not supported, and PVL does not apply to the ASP.NET edition (its interface runs in the browser) or to the ActiveX edition.

The PVL project is not yet finished. Future plans (not yet scheduled) include a simple form designer.

How to use it

All PVL components ship with the Developer Kernel and are free for use by every developer with a DK license.

You can build forms directly, or inherit from TGIS_PvlForm, TGIS_PvlModalForm, or TGIS_PvlModalWizard to create your own reusable components.

Below is the same simple modal form (a label, an edit box, and an OK button) implemented on each platform.

Delphi

Delphi: declaration
unit PVL.MyPvlForm;

interface

  uses
    PVL.GisPvl,
    PVL.GisPvlForms;

  type
    MyPvlForm = class ( TGIS_PvlForm )
      public
        Text                     : String         ;
      private
        oLabel                   : TGIS_PvlLabel  ;
        oEdit                    : TGIS_PvlEdit   ;
        oButton                  : TGIS_PvlButton ;
      protected
        procedure DoInitControls ; override;
      private
        procedure onClick        ( _sender : TObject
                                 );
    end;

implementation

  // onClick event which is fired when oButton is pressed
  procedure MyPvlForm.onClick(
    _sender : TObject
  ) ;
  begin
    // Assign text to a variable which can be accessed outside of the form
    Text := oEdit.Text ;
    // Ensure that form closes with Ok result
    Self.ModalResult := TGIS_PvlModalResult.Ok ;
  end;

  procedure MyPvlForm.DoInitControls ;
  begin
    // Initialize Form and give it proper size and caption
    Self.ClientWidth := 350 ;
    Self.ClientHeight := 100 ;
    Self.Caption := 'PVL showcase form' ;

    // Initialize oLabel, give it proper caption as well as place it on the parent element
    oLabel := TGIS_PvlLabel.Create( Context ) ;
    oLabel.Caption := 'PVL showcase label' ;
    oLabel.Place( ClientWidth - 2 * Context.HMargin, 0,
                  nil, Context.HMargin,
                  nil, Context.VMargin ) ;

    // Initialize oEdit, give it proper caption as well as place it on the parent element
    oEdit := TGIS_PvlEdit.Create( Context ) ;
    oEdit.Text := 'PVL showcase edit' ;
    oEdit.Place( ClientWidth - 2 * Context.HMargin, 0,
                 nil, Context.HMargin,
                 oLabel, Context.VSpace ) ;

    // Initialize oButton, give it proper caption as well as place it on the parent element
    oButton := TGIS_PvlButton.Create( Context ) ;
    oButton.Caption := 'Ok' ;
    oButton.Place( 100, 0,
                   nil, - Context.VMargin,
                   nil, ClientHeight - oButton.Height - Context.HMargin ) ;

    // Assign onClick event to onClick property
    oButton.OnClick := onClick ;
  end;

end.
Delphi: usage
uses
  PVL.MyPvlForm in 'PVL.MyPvlForm.pas';

begin
  // Create form
  // Parent of the form can be null but its strongly recommended for it to be a native
  // platform control.
  var form : MyPvlForm := MyPvlForm.Create( nil ) ;
  var text : String ;
  var proc : TGIS_Proc ;

  // You have to pick one overload of the TGIS_PvlForm.ShowModal

  // Show form with a proc and free it after usage ( asynchronic version )
  // Proc is beeing used to perform action upon closing form.
  proc := procedure( _modal_result : TGIS_PvlModalResult )
  begin
    if _modal_result <> TGIS_PvlModalResult.Ok then
      exit ;
    text := form.Text ;
  end;
  form.ShowModal( proc, Assigned( proc ) ) ;

  // Show form without a proc ( synchronic version )
  try
    if form.ShowModal <> TGIS_PvlModalResult.Ok then
      exit;
    text := form.Text ;
  finally
    // If we use TGIS_PvlForm.Close we don't need to call free otherwise we have to
    // form.free ;
  end;
end.
PVL_DelphiForm.png

.NET (WinForms, C#)

C#: form class
using TatukGIS.NDK.PVL;

namespace TestRendering
{
    class MyPvlForm : TGIS_PvlForm
    {
        public String Text;
        private TGIS_PvlLabel oLabel;
        private TGIS_PvlEdit oEdit;
        private TGIS_PvlButton oButton;

        public MyPvlForm(object _parent) : base(_parent)
        {
        }

        // onClick event which is fired when oButton is pressed
        private void onClick(object _sender)
        {
            // Assign text to a variable which can be accessed outside of the form
            Text = oEdit.Text;
            // Ensure that form closes with Ok result
            this.ModalResult = TGIS_PvlModalResult.OK;
        }

        protected override void DoInitControls()
        {
            this.ClientWidth = 350;
            this.ClientHeight = 100;
            this.Caption = "PVL showcase form";

            // Initialize oLabel, give it proper caption as well as place it on the parent element
            oLabel = new TGIS_PvlLabel(Context);
            oLabel.Caption = "PVL showcase label";
            oLabel.Place(ClientWidth - 2 * Context.HMargin, 0,
                null, Context.HMargin,
                null, Context.VMargin);

            // Initialize oEdit, give it proper caption as well as place it on the parent element
            oEdit = new TGIS_PvlEdit(Context);
            oEdit.Text = "PVL showcase edit";
            oEdit.Place(ClientWidth - 2 * Context.HMargin, 0,
                null, Context.HMargin,
                oLabel, Context.VSpace);

            // Initialize oButton, give it proper caption as well as place it on the parent element
            oButton = new TGIS_PvlButton(Context);
            oButton.Caption = "Ok";
            oButton.Place(100, 0,
                null, -Context.VMargin,
                null, ClientHeight - oButton.Height - Context.HMargin);

            // Assign onClick event to onClick property
            oButton.OnClick = onClick;
        }
    }
}
C#: usage
static void Main()
{
    String text;
    MyPvlForm form = new MyPvlForm(null);

    // You have to pick one overload of the TGIS_PvlForm.ShowModal

    // Show form with a proc and free it after usage ( asynchronic version )
    // Proc is beeing used to perform action upon closing form.
    Action<TGIS_PvlModalResult> proc = (res) =>
    {
        if (res != TGIS_PvlModalResult.OK)
            return;

        text = form.Text;
    };
    form.ShowModal(proc, proc != null);

    // Show form without a proc ( synchronic version )
    if (form.ShowModal() != TGIS_PvlModalResult.OK)
        return;
    else
        text = form.Text;

    Application.Run();
}

PVL_DOTNETForm.png

Python

Since DK.Python runs purely on PVL, you inherit and implement your own component directly:

Python
import tatukgis_pdk as pdk

class MyPvlForm(pdk.TGIS_PvlForm):

    def __init__(self, owner):
        super().__init__(owner)

        # Initialize Form and give it proper size and caption
        self.ClientWidth = 350
        self.ClientHeight = 100
        self.Caption = "PVL showcase form"

        # Initialize oLabel, give it proper caption as well as place it on the parent
        # element
        self.oLabel = pdk.TGIS_PvlLabel(self.Context)
        self.oLabel.Caption = "PVL showcase label"
        self.oLabel.Place(
            self.ClientWidth - 2 * self.Context.HMargin,
            0,
            None,
            self.Context.HMargin,
            None,
            self.Context.VMargin
        )

        # Initialize oEdit, give it proper caption as well as place it on the parent
        # element
        self.oEdit = pdk.TGIS_PvlEdit(self.Context)
        self.oEdit.Text = "PVL showcase edit"
        self.oEdit.Place(
            self.ClientWidth - 2 * self.Context.HMargin,
            0,
            None,
            self.Context.HMargin,
            self.oLabel,
            self.Context.VSpace
        )

        # Initialize oButton, give it proper caption as well as place it on the parent
        # element
        self.oButton = pdk.TGIS_PvlButton(self.Context)
        self.oButton.Caption = "OK"
        self.oButton.Place(
            100,
            0,
            None,
            -self.Context.VMargin,
            None,
            self.ClientHeight - self.oButton.Height - self.Context.HMargin
        )

        # Assign onClick event to onClick property
        self.oButton.OnClick = self.on_ok

    def on_ok(self, sender):
        # Assign text to a variable which can be accessed outside of the form
        self.oText = self.oEdit.Text

        # Ensure that form closes with Ok result
        # As python has no enums we have to emulate it
        # so to use it we need to create an object first.
        self.ModalResult = pdk.TGIS_PvlModalResult().OK

# Create form
# Parent of the form has to be None when using Python
form = MyPvlForm(None)

# You have to pick one overload of the TGIS_PvlForm.ShowModal
def modal_proc(modal_result):
    if modal_result != pdk.TGIS_PvlModalResult().OK:
        return

    Text: str = form.oText

# Show form with a proc and free it after usage ( asynchronic version )
# Proc is beeing used to perform action upon closing form.
form.ShowModal(
    modal_proc,
    modal_proc != None
)

try:
    # Show form without a proc ( synchronic version )
    if form.ShowModal() != pdk.TGIS_PvlModalResult().OK:
        raise SystemExit

    Text: str = form.oText
finally:
    form.Free()
PVL_PythonForm.png

Java

Java uses getter and setter methods, since the language has no properties:

Java: form class
import tatukgis.jdk.pvl.*;

public class Form extends TGIS_PvlForm {
    public String Text;
    private TGIS_PvlLabel oLabel;
    private TGIS_PvlEdit oEdit;
    private TGIS_PvlButton oButton;
    private TGIS_PvlEvent onClick;

    public Form(Object _owner) {
        super(_owner);
    }

    @Override
    public void DoInitControls() {
        // Initialize Form and give it proper size and caption
        this.setClientWidth(350);
        this.setClientHeight(100);
        this.setCaption("PVL showcase form");

        // Initialize oLabel, give it proper caption as well as place it on the parent element
        oLabel = new TGIS_PvlLabel(getContext());
        oLabel.setCaption("PVL showcase label");
        oLabel.Place(getClientWidth() - 2 * getContext().getHMargin(), 0,
                null, getContext().getHMargin(),
                null, getContext().getVMargin());

        // Initialize oEdit, give it proper caption as well as place it on the parent element
        oEdit = new TGIS_PvlEdit(getContext());
        oEdit.setText("PVL showcase edit");
        oEdit.Place(getClientWidth() - 2 * getContext().getHMargin(), 0,
                null, getContext().getHMargin(),
                oLabel, getContext().getVSpace());

        // Initialize oButton, give it proper caption as well as place it on the parent element
        oButton = new TGIS_PvlButton(getContext());
        oButton.setCaption("Ok");
        oButton.Place(100, 0,
                null, -getContext().getVMargin(),
                null, getClientHeight() - oButton.getHeight() - getContext().getHMargin());

        // OnClick event. We cant assign it when we declare it because this function is
        // called from within the constructor
        onClick = (Object _sender) -> {
            // Assign text to a variable which can be accessed outside of the form
            Text = oEdit.getText();
            // Ensure that form closes with Ok result
            this.setModalResult(TGIS_PvlModalResult.OK);
        };

        // Assign onClick event to onClick property
        oButton.setOnClick(onClick);
    }
}
Java: usage
public static void main(String[] args) {
    // Create a form.
    // Parent of the form can be null but its strongly recommended for it to be a native
    // platform control.
    Form form = new Form(null);
    // To be able to get values from lambda expression
    AtomicReference<String> text = new AtomicReference<>();

    // You have to pick one overload of the TGIS_PvlForm.ShowModal

    // Show form with a proc and free it after usage ( asynchronic version )
    // Proc is beeing used to perform action upon closing form.
    Consumer<Integer> proc = (res) -> {
        if (res != TGIS_PvlModalResult.OK)
            return;
        text.set(form.Text);
    };
    form.ShowModal(proc, proc != null);

    // Show form without a proc ( synchronic version )
    if (form.ShowModal() != TGIS_PvlModalResult.OK)
        return;
    else
        text.set(form.Text);
}
PVL_JavaForm.png

Try it

PVL is already in your DK installation: no extra downloads, and no extra cost. Build a form once, compile it on another platform, and the same UI appears. As always, we welcome your questions and feedback.

See also our earlier post: Editor PVL Example Script in Python.

Posted: July 17, 2026