LibreOffice Developer's Guide: Chapter 13 - Forms

From The Document Foundation Wiki
Jump to navigation Jump to search

Forms offer a method of control-based data input. A form or form document consists of a set of controls, where each one enters a single piece of data. In a simple case, this could be a plain text field allowing you to insert some text without any word breaks. When we speak of forms, we mean forms and controls, because these cannot be divided.

If an internet site asks you for information, for example, for a product registration you are presented with fields to enter your name, your address and other information. These are HTML forms.

Basically, this is what LibreOffice forms do. They enhance nearly every document with controls for data input. This additional functionality put into a document is called the form layer within the scope of this chapter.

The most basic functionality provides the controls for HTML form documents mentioned above: If you open an HTML document with form elements in LibreOffice Writer, these elements are represented by components from <idlmodule>com.sun.star.form</idlmodule>.

The more enhanced functionality provides support for data-aware forms. These are forms and controls that are bound to a data source registered in LibreOffice to enter data into tables of a database. For more information about data sources and data access in general, refer to the Database Access.

Since OpenOffice.org 2.0.0, form controls also feature a generalization of this concept. They can be bound to external components, which supply an own value. Both values - the one of the external component, and the current value of the control - are synchronized, so that a change in one of them is immediately propagated to the other. This allows new features, where the most notable is that you can bind form controls to spreadsheet cells.

When discussing forms, form documents and logical forms have to be distinguished. The phrase form document refers to a document as a whole, while a logical form is basically a set of controls with additional properties. Within the scope of this chapter, when a "form" is referred to, the logical form is meant.

Models and Views

The model describes appearance and behaviour of the element. The view is the visual representation of the element. The relationship between models and views is important to understand and is described in detail in this section.

The Model-View Paradigm

A basic concept to understand about forms and controls in LibreOffice is the model-view paradigm. For a given element in your document, for example, a text field in your HTML form, it says that you have exactly one model and an arbitrary number of views.

The model is what is part of your document in that it describes how this element looks, and how it behaves. The model even exists when you do not have an open instance of your document. If it is stored in a file, the file contains a description of the model of your element.

Note pin.svg

Note:
In UNO, the simplest conceivable model is a component implementing <idl>com.sun.star.beans.XPropertySet</idl> only. Every aspect of the view could then be described by a single property. In fact, as you will see later, models for form controls are basically property sets.

The view is a visual representation of your model. It is the component which looks and behaves according to the requirements of the model. You can have multiple views for one model, and they would all look alike as the model describes it. The view is visible to the user. It is for visualizing the model and handles interactions with the user. The model, however, is merely a "dumb" container of data.

A good example to illustrate this is available in LibreOffice. Open an arbitrary document and choose the menu item Window > New Window. A second window is opened showing the same document displayed in the first window. This does not mean that the document was opened twice, it means you opened a second view of the same document, which is a difference. In particular, if you type some text in one of the windows, this change is visible in both windows. That is what the model-view paradigm is about: Keep your document data once in the model, and when you need to visualize the data to the user, or need interaction from the user that modifies the document, create views to the model as needed.

Between model and view a 1:n relationship exists:

The Model View Paradigm
Note pin.svg

Note:
Note that the relation is directed. Usually, a view knows its model, but the model itself does not know about the views which visualize it.

Models and Views for Form Controls

Form controls follow the model-view paradigm. This means if you have a form document that contains a control, there is a model describing the control's behavior and appearance, and a view that is the component the user sees.

Note pin.svg

Note:
Note that the term "control" is ambiguous here. Usually, from the user's perspective, it is what is seen in the document. As the model-view paradigm may not be obvious to the user, the user tends to consider the visible representation and the underlying model of the control as one thing, that is, a user who refers to the control usually means the combination of the view and the model. As opposed to the user's perspective, when the UNO API for the form layer refers to a control, this means the view of a form element, if not stated otherwise.

The base for the controls and models used in the form layer are found in the module <idlmodule>com.sun.star.awt</idlmodule>, the <idl>com.sun.star.awt.UnoControl</idl> and <idl>com.sun.star.awt.UnoControlModel</idl> services. As discussed later, the model hierarchy in <idlmodule>com.sun.star.form.component</idlmodule> extends the hierarchy of <idlmodule>com.sun.star.awt</idlmodule>, whereas the control hierarchy in <idlmodule>com.sun.star.form.control</idlmodule> is small.

Everything from the model-view interaction for form controls is true for other UNO controls and UNO control models, as well. Another example for components that use the model-view paradigm are the controls and control models in LibreOffice Basic dialogs (Dialog Controls).

Model-View Interaction

When model and view collaborate, data transfer is usually required in both directions, from model to view and backwards.

Consider a simple text field. The model for a control implements a <idl>com.sun.star.form.component.TextField</idl> service. This means it has a property Text, containing the current content of the field, and a property BackgroundColor specifying the color that should be used as background when drawing the text of the control.

First, if the value of the BackgroundColor property is changed, the control is notified of the change. This is done by UNO listener mechanisms, such as the <idl>com.sun.star.beans.XPropertyChangeListener</idl> allowing the control to listen for changes to model properties and react accordingly. Here the control would have to redraw itself using the new background color.

In fact this is a common mechanism for the communication between model and view: The view adds itself as listener for any aspect of the model which could affect it, and when it is notified of changes, it adjusts itself to the new model state. This means that the model is always the passive part. The model does not know its views, or at least not as views, but only their role as listeners, while the views know their model.

On the other hand, if the view is used for interaction with the user, the data needs to be propagated from the view to the model. The user enters data in a text field, and the change is reflected in the model. Remember that the user sees the control only, and everything affects the control in the first step. If the user interacts with the view with the intention of modifying the model, the view propagates changes to the model.

In our example, the user enters text into the control, the control automatically updates the respective property at the model (Text), thus modifying the document containing the model.

Form Layer Views

View Modes

An important aspect to know when dealing with forms is that the view for a form layer is in different modes. More precise, there is a design mode available, opposite to a live mode. In design mode, you design your form interactively with LibreOffice by inserting new controls, resizing them, and modifying their properties, together with control models and shapes, although LibreOffice hides this. In live mode, the controls interact with the user for data input.

The live mode is the natural mode for form views, because usually a form is designed once and used again.

The following example switches a given document view between the two modes:

/** toggles the design mode of the form layer of active view of our sample document
*/
protected void toggleFormDesignMode() throws java.lang.Exception {
    // get a dispatcher for the toggle URL
    URL[] aToggleURL = new URL[] {new URL()};
    aToggleURL[0].Complete = new String(".uno:SwitchControlDesignMode");
    XDispatch xDispatcher = getDispatcher(aToggleURL);

    // dispatch the URL - this will result in toggling the mode
    PropertyValue[] aDummyArgs = new PropertyValue[] {};
    xDispatcher.dispatch(aToggleURL[0], aDummyArgs);
}

The basic idea is to dispatch the URL ".uno:SwitchControlDesignMode" into the current view. This triggers the same functionality as if the button Design Mode On/Off was pressed in LibreOffice. In fact, SwitchControlDesignMode is the UNO name for the slot triggered by this button.

Locating Controls

A common task when working with form documents using the LibreOffice API is to obtain controls. Given that there is a control model, and a view to the document it belongs to, you may want to know the control that is used to represent the model in that view. This is what the interface <idl>com.sun.star.view.XControlAccess</idl> at the controller of a document view is made for.

/** retrieves a control within the current view of a document
    @param xModel
        specifies the control model which's control should be located
    @return
        the control tied to the model
*/
public XControl getControl(XControlModel xModel) throws com.sun.star.uno.Exception {
    XControlAccess xCtrlAcc = (XControlAccess)UnoRuntime.queryInterface(
        XControlAccess.class , m_xController);
    // delegate the task of looking for the control
    return xCtrlAcc.getControl(xModel);
}

Focusing Controls

To focus a specific control in your document, or more precisely, in one of the views of your document:

/** sets the focus to a specific control
    @param xModel
        a control model. The focus is set to that control which is part of our view
        and associated with the given model.
*/
public void grabControlFocus(Object xModel) throws com.sun.star.uno.Exception {
    // look for the control from the current view which belongs to the model
    XControl xControl = getControl(xModel);

    // the focus can be set to an XWindow only
    XWindow xControlWindow = (XWindow)UnoRuntime.queryInterface(Xwindow.class, xControl);

    // grab the focus
    xControlWindow.setFocus();
}

As you can see, focusing controls is reduced to locating controls. Once you have located the control, the <idl>com.sun.star.awt.XWindow</idl> interface provides everything needed for focusing.

Form Elements in the Document Model

The model of a document is the data that is made persistent, so that all form elements are a part of it. Refer to chapter Frame-Controller-Model Paradigm in LibreOffice for additional information. This is true for logical forms, as well as for control models. Controls, that is, the view part of form elements, are not made persistent, thus are not accessible in the document model.

A Hierarchy of Models

The components in the form layer are organized hierarchically in an object tree. Their relationship is organized using the standard interfaces, such as <idl>com.sun.star.container.XChild</idl> and <idl>com.sun.star.container.XIndexAccess</idl>.

As in every tree, there is a root with inner nodes and leaves. There are different components described below that take on one or several of these roles.

FormComponent Service

The basis for all form related models is the <idl>com.sun.star.form.FormComponent</idl> service. Its basic characteristics are:

  • it exports the <idl>com.sun.star.container.XChild</idl> interface
  • it has a property Name
  • it exports the <idl>com.sun.star.lang.XComponent</idl> interface

Form components have a parent and a name, and support lifetime control that the common denominator for form elements and logical forms, as well as for control models.

FormComponents Service

In the level above, a single form component is a container for components. Stepping away from the document model, you are looking for a specific form component, such as the model of a control, you pass where all the control models are attached. This is the <idl>com.sun.star.form.FormComponents</idl> component. The service offers basic container functionality, namely an access to its elements by index or by name), and a possibility to enumerate its elements.

Provided that you have a container at hand, the access to its elements is straightforward. For example, assume you want to enumerate all the elements in the container, and apply a specific action for every element. The enumFormComponents() method below does this by recursively enumerating the elements in a <idl>com.sun.star.form.FormComponents</idl> container.

/** enumerates and prints all the elements in the given container
*/
public static void enumFormComponents(XNameAccess xContainer, String sPrefix)
        throws java.lang.Exception {
    // loop through all the element names
    String aNames[] = xContainer.getElementNames();
    for (int i=0; i<aNames.length; ++i) {
        // print the child name
        System.out.println(sPrefix + aNames[i]);

        // check if it is a FormComponents component itself
        XServiceInfo xSI = (XServiceInfo)UnoRuntime.queryInterface(XServiceInfo.class,
            xContainer.getByName(aNames[i]));

        if (xSI.supportsService("com.sun.star.form.FormComponents")) {
            // yep, it is
            // -> step down
            XNameAccess xChildContainer = (XNameAccess)UnoRuntime.queryInterface(
                XNameAccess.class, xSI);
            enumFormComponents(xChildContainer, new String(" ") + sPrefix);
        }
    }
}

/** enumerates and prints all the elements in the given container, together with the container itself
*/
public static void enumFormComponents(XNameAccess xContainer) throws java.lang.Exception {
    XNamed xNameAcc = (XNamed)UnoRuntime.queryInterface(XNamed.class, xContainer);
    String sObjectName = xNameAcc.getName();
    System.out.println( new String("enumerating the container named \"") + sObjectName +
        new String("\"\n"));

    System.out.println(sObjectName);
    enumFormComponents(xContainer, " ");
}

Logical Forms

Forms as technical objects are also part of the document model. In contrast to control models, forms do not have a view representation. For every control model, there is a control the user interacts with, and presents the data back to the user. For the form, there is no view component.

The basic service for logical forms is <idl>com.sun.star.form.component.Form</idl>. See below for details regarding this service. For now, we are interested in that it exposes the <idl>com.sun.star.form.FormComponent</idl> service, as well as the <idl>com.sun.star.form.FormComponents</idl> service. This means it is part of a form component container, and it is a container. Thus, in our hierarchy of models, it can be any node, such as an inner node having children, that is, other form components, as well as a leaf node having no children, but a parent container. Of course both of these roles are not exclusive. This is how data aware forms implement master-detail relationships. Refer to the Data Awareness.

Forms Container

In our model hierarchy, we have inner nodes called the logical forms, and the basic element called the form component. As in every tree, our hierarchy has a root, that is, an instance of the <idl>com.sun.star.form.Forms</idl> service. This is nothing more than an instance of <idl>com.sun.star.form.FormComponents</idl>. In fact, the differentiation exists for a non-ambiguous runtime instantiation of a root.

Note pin.svg

Note:
Note that the <idl>com.sun.star.form.Forms</idl> service does not state that components implementing it are a <idl>com.sun.star.form.FormComponent</idl>. This means this service acts as a tree root only, as opposed to a <idl>com.sun.star.form.Forms</idl> that is a container, as well as an element, thus it can be placed anywhere in the tree.

Actually, it is not necessary for external components to instantiate a service directly. Every document has at least one instance of it. A root forms container is tied to a draw page, which is an element of the document model, as well. Refer to <idl>com.sun.star.drawing.DrawPage</idl>. A page optionally supports the interface <idl>com.sun.star.form.XFormsSupplier</idl> giving access to the collection. In the current LibreOffice implementation, Writer and Calc documents fully support draw pages supplying forms.

The following example shows how to obtain a root forms collection, if the document model is known which is denoted with s_aDocument.

/** gets the <type scope="com.sun.star.drawing">DrawPage</type> of our sample document
*/
public static XDrawPage getDocumentDrawPage() throws java.lang.Exception {
    XDrawPage xReturn;

    // in case of a Writer document, this is rather easy: simply ask the XDrawPageSupplier
    XDrawPageSupplier xSuppPage = (XDrawPageSupplier)UnoRuntime.queryInterface(
        XDrawPageSupplier.class, s_aDocument);
    xReturn = xSuppPage.getDrawPage();
    if (null == xReturn) {
        // the model itself is no draw page supplier - then it may be an Impress or Calc
        // (or any other multi-page) document
        XDrawPagesSupplier xSuppPages = (XDrawPagesSupplier)UnoRuntime.queryInterface(
            XDrawPagesSupplier.class, s_aDocument);
        XDrawPages xPages = xSuppPages.getDrawPages();

        xReturn = (XdrawPage)UnoRuntime.queryInterface(XDrawPage.class, xPages.getByIndex(0));

        // Note that this is not really error-proof code: If the document model does not support the
        // XDrawPagesSupplier interface, or if the pages collection returned is empty, this will break.
    }

    return xReturn;
}

/** retrieves the root of the hierarchy of form components
*/
public static XNameContainer getFormComponentTreeRoot() throws java.lang.Exception {
    XFormsSupplier xSuppForms = (XFormsSupplier)UnoRuntime.queryInterface(
        XFormsSupplier.class, getDocumentDrawPage());

    XNameContainer xFormsCollection = null;
    if (null != xSuppForms) {
        xFormsCollection = xSuppForms.getForms();
    }
    return xFormsCollection;
}

Form Control Models

The control models are discussed in these sections. The basic service for a form layer control model is <idl>com.sun.star.form.FormControlModel</idl> that is discussed in more detail below. A form control model promises to support the <idl>com.sun.star.form.FormComponent</idl> service, meaning that it can act as a child in our model hierarchy.

In addition, it does not claim that the <idl>com.sun.star.form.FormComponents</idl> service (plural s) is supported meaning that form control models are leaves in our object tree. The only exception from this is the grid control model. It is allowed to have children representing the models of the columns.

An overview of the whole model tree has been provided. With the code fragments introduced above, the following code dumps a model tree to the console:

// dump the form component tree
enumFormComponents(getFormComponentTreeRoot());

Control Models and Shapes

There is more to know about form components in a document.

From Shapes, you already know about shapes. They are also part of a document model. The control shapes, <idl>com.sun.star.drawing.ControlShape</idl> are made to be tied to control models. They are specialized to fully integrate form control models into a document.

In theory, there can be a control shape without a model tied to it, or a control model which is part of the form component hierarchy, but not associated with any shape. In the first case, an empty shape is displayed in the document view. In the second case, you see nothing. It is possible to have a shape which is properly tied to a control model, but the control model is not part of the form component hierarchy. The model can not interact with the rest of the form layer. For example, it is unable to take advantage of its data awareness capabilities.

Warning.svg

Warning:
The user interface of LibreOffice does not allow the creation of orphaned objects, but you can create them using the API. When dealing with controls through the API, ensure that there is always a valid relationship between forms, control models, and shapes.


A complete object structure in a document model with respect to the components relevant for our form layer looks the following:

A complete object structure

Programmatic Creation of Controls

As a consequence from the previous paragraph, we now know that to insert a form control, we need to insert a control shape and control model into the document's model.

The following code fragment accomplishes that:

/** creates a control in the document

    <nowiki><p>Note that <em>control<em> here is an incorrect terminology. What the method really does is
    it creates a control shape, together with a control model, and inserts them into the document model.
    This will result in every view to this document creating a control described by the model-shape
    pair.</p></nowiki>

    @param sFormComponentService
        the service name of the form component to create, e.g. "TextField"
    @param nXPos
        the abscissa of the position of the newly inserted shape
    @param nXPos
        the ordinate of the position of the newly inserted shape
    @param nWidth
        the width of the newly inserted shape
    @param nHeight
        the height of the newly inserted shape
    @return
        the property access to the control's model
*/
public static XPropertySet createControlAndShape(String sFormComponentService, int nXPos,
        int nYPos, int nWidth, int nHeight) throws java.lang.Exception {
    // let the document create a shape
    XMultiServiceFactory xDocAsFactory = (XMultiServiceFactory)UnoRuntime.queryInterface(
        XMultiServiceFactory.class, s_aDocument);
    XControlShape xShape = (XControlShape)UnoRuntime.queryInterface(XControlShape.class,
        xDocAsFactory.createInstance("com.sun.star.drawing.ControlShape"));

    // position and size of the shape
    xShape.setSize(new Size(nWidth * 100, nHeight * 100));
    xShape.setPosition(new Point(nXPos * 100, nYPos * 100));

    // and in a OOo Writer doc, the anchor can be adjusted
    XPropertySet xShapeProps = (XPropertySet)UnoRuntime.queryInterface(XPropertySet.class, xShape);
    TextContentAnchorType eAnchorType = TextContentAnchorType.AT_PAGE;
    if (classifyDocument(s_aDocument) == DocumentType.WRITER) {
        eAnchorType = TextContentAnchorType.AT_PARAGRAPH;
    }
    xShapeProps.setPropertyValue("AnchorType", eAnchorType);

    // create the form component (the model of a form control)
    String sQualifiedComponentName = "com.sun.star.form.component." + sFormComponentService;
    XControlModel xModel = (XControlModel)UnoRuntime.queryInterface(XControlModel.class,
        s_aMSF.createInstance(sQualifiedComponentName));

    // knitt them
    xShape.setControl(xModel);

    // add the shape to the shapes collection of the document
    XShapes xDocShapes = (XShapes)UnoRuntime.queryInterface(XShapes.class, getDocumentDrawPage());
    xDocShapes.add(xShape);

    // and outta here with the XPropertySet interface of the model
    XPropertySet xModelProps = (XpropertySet)UnoRuntime.queryInterface(
        XpropertySet.class, xModel);
    return xModelProps;
}

Looking at the example above, the basic procedure is:

  • create and initialize a shape
  • create a control model
  • announce the control model to the shape
  • insert the shape into the shapes collection of a draw page

The above does not mention inserting the control model into the form component hierarchy, which is a contradiction of our previous discussion. We have previously said that every control model must be part of this hierarchy to prevent corrupted documents, but it is not harmful.

In every document, when a new control shape is inserted into the document, through the API or an interaction with a document's view, the control model is checked if it is a member of the model hierarchy. If it is not, it is automatically inserted. Moreover, if the hierarchy does not exist or is incomplete, for example, if the draw page does not have a forms collection, or this collection does not contain a form, this is also corrected automatically.

With the code fragment above applied to a new document, a logical form is created automatically, inserted into the forms hierarchy, and the control model is inserted into this form.

Note pin.svg

Note:
Note that this is an implementation detail. Internally, there is an instance listening at the page's shapes, that reacts upon insertions. In theory, there could be other implementations of LibreOffice API that do not contain this mechanism. In practice, the only known implementation is LibreOffice.

Warning.svg

Warning:
Note that the order of operations is important. If you insert the shape into the page's shape collection, and tie it to its control model after, the document would be corrupted: Nobody would know about this new model then, and it would not be inserted properly into the form component hierarchy, unless you do this.


You may have noticed that there is nothing about the view. We only created a control model. As you can see in the complete example for this chapter, when you have an open document, and insert a model and a shape, a control (the visual representation) is also created or else you would not see anything that looks like a control.

The control and model have a model-view relationship. If the document window is open, this window is the document view. If the document or the model is modified by inserting a control model, the view for every open view for this document reacts appropriately and creates a control as described by the model. The <idlm>com.sun.star.awt.UnoControlModel:DefaultControl</idlm> property describes the service to be instantiated when automatically creating a control for a model.

Form Components

According to the different form document types, there are different components in the <idlmodule>com.sun.star.form</idlmodule> module serving different purposes. Basically, we distinguish between HTML form functionality and data awareness functionality that are covered by the form layer API.

Control Models

As you know from Form Control Models, the base for all our control models is the <idl>com.sun.star.form.FormControlModel</idl> service. Let us look at the most relevant elements of the declaration of this service and what a component must do to support it:

<idl>com.sun.star.awt.UnoControlModel</idl>

This service specifies that a form control model complies to everything required for a control model by the UNO windowing toolkit as described in module <idlmodule>com.sun.star.awt</idlmodule>. This means support for the <idl>com.sun.star.awt.XControlModel</idl> interface, for property access and persistence.

<idl>com.sun.star.form.FormComponent</idl>

This service requires a form control model is part of a form component hierarchy. Refer to chapter A Hierarchy of Models.

<idl>com.sun.star.beans.XPropertyState</idl>

This optional interface allows the control model properties to have a default value. All known implementations of the FormControlModel service support this interface.

<idlml>com.sun.star.form.FormControlModel:ClassId</idlml>

This property determines the class of a control model you have , and it assumes a value from the <idl>com.sun.star.form.FormComponentType</idl> enumeration. The same is done using the <idl>com.sun.star.lang.XServiceInfo</idl> interface that is supported by every component, and as shown below it can be indispensable. Using the <idlml>com.sun.star.form.FormControlModel:ClassId</idlml> property is faster.
Note pin.svg

Note:
Note that the <idl>com.sun.star.form.FormControlModel</idl> service does not state anything about data awareness. It describes the requirements for a control model which can be part of a form layer.

See chapter Data Awareness for additional information about the controls which are data aware.

The following example shows how to determine the type of a control model using the ClassId property introduced above:

/** retrieves the type of a form component.
    <p>Speaking strictly, the function recognizes more than form components. Especially,
    it survives a null argument. which means it can be safely applied to the a top-level
    forms container; and it is able to classify grid columns (which are no form components)
    as well.</p>
*/
static public String classifyFormComponentType(XPropertySet xComponent)
        throws com.sun.star.uno.Exception {
    String sType = "<unknown component>";

    XServiceInfo xSI = (XserviceInfo)UnoRuntime.queryInterface(XServiceInfo.class, xComponent);

    XPropertySetInfo xPSI = null;
    if (null != xComponent)
        xPSI = xComponent.getPropertySetInfo();

    if ( ( null != xPSI ) && xPSI.hasPropertyByName("ClassId")) {
        // get the ClassId property
        XPropertySet xCompProps = (XPropertySet)UnoRuntime.queryInterface(
            XPropertySet.class, xComponent);

        Short nClassId = (Short)xCompProps.getPropertyValue("ClassId");
        switch (nClassId.intValue())
        {
            case FormComponentType.COMMANDBUTTON: sType = "Command button"; break;
            case FormComponentType.RADIOBUTTON : sType = "Radio button"; break;
            case FormComponentType.IMAGEBUTTON : sType = "Image button"; break;
            case FormComponentType.CHECKBOX : sType = "Check Box"; break;
            case FormComponentType.LISTBOX : sType = "List Box"; break;
            case FormComponentType.COMBOBOX : sType = "Combo Box"; break;
            case FormComponentType.GROUPBOX : sType = "Group Box"; break;
            case FormComponentType.FIXEDTEXT : sType = "Fixed Text"; break;
            case FormComponentType.GRIDCONTROL : sType = "Grid Control"; break;
            case FormComponentType.FILECONTROL : sType = "File Control"; break;
            case FormComponentType.HIDDENCONTROL: sType = "Hidden Control"; break;
            case FormComponentType.IMAGECONTROL : sType = "Image Control"; break;
            case FormComponentType.DATEFIELD : sType = "Date Field"; break;
            case FormComponentType.TIMEFIELD : sType = "Time Field"; break;
            case FormComponentType.NUMERICFIELD : sType = "Numeric Field"; break;
            case FormComponentType.CURRENCYFIELD: sType = "Currency Field"; break;
            case FormComponentType.PATTERNFIELD : sType = "Pattern Field"; break;

            case FormComponentType.TEXTFIELD :
                // there are two known services with this class id: the usual text field,
                // and the formatted field
                sType = "Text Field";
                if (( null != xSI) && xSI.supportsService(
                    "com.sun.star.form.component.FormattedField")) {
                    sType = "Formatted Field";
                }
                break;

            default:
                break;
        }
    }
    else {
        if ((null != xSI) && xSI.supportsService("com.sun.star.form.component.DataForm")) {
            sType = "Form";
        }
    }

    return sType;
}

Note the special handling for the value <idlml>com.sun.star.form.FormComponentType:TEXTFIELD</idlml>. There are two different services where a component implementing them is required to act as text field, the <idl>com.sun.star.form.component.TextField</idl> and <idl>com.sun.star.form.component.FormattedField</idl>. Both services describe a text component, thus both have a class id of <idlml>com.sun.star.form.FormComponentType:TEXTFIELD</idlml>. To distinguish between them, ask the components for more details using the <idl>com.sun.star.lang.XServiceInfo</idl> interface.

Forms

The LibreOffice API features different kinds of forms, namely the <idl>com.sun.star.form.component.Form</idl>, <idl>com.sun.star.form.component.HTMLForm</idl>, and <idl>com.sun.star.form.component.DataForm</idl>. The two different aspects described with these services are HTML forms used in HTML documents, and data aware forms used to access databases. Data awareness is discussed thoroughly in Data Awareness.

Note pin.svg

Note:
Though different services exist for HTML and data aware forms, there is only one form implementation in LibreOffice that implements both services simultaneously.

The common denominator of HTML forms and data aware forms is described in the <idl>com.sun.star.form.component.Form</idl> service. It includes the FormComponent and FormComponents service, in addition to the following elements:

<idl>com.sun.star.form.XForm</idl>

This interface identifies the component as a form that can be done with other methods, such as the <idl>com.sun.star.lang.XServiceInfo</idl> interface. The <idl>com.sun.star.form.XForm</idl> interface distinguishes a form component as a form. The XForm interface inherits from <idl>com.sun.star.form.XFormComponent</idl> to indicate the difference, and does not add any further operations.

<idl>com.sun.star.awt.XTabControllerModel</idl>

This is used for controlling tab ordering and control grouping. As a logical form is a container for control models, it is a natural place to administer information about the relationship of its control children. The tab order, that is, the order in which the focus travels through the controls associated with the control models when the user presses the Tab key, is a relationship, and thus is maintained on the form.
Note that changing the tab order through this interface also affects the models. The <idl>com.sun.star.form.FormControlModel</idl> service has an optional property TabIndex that contains the relative position of the control in the tabbing order. For example, a straightforward implementation of <idlm>com.sun.star.awt.XTabControllerModel:setControlModels</idlm>() would be simply to adjust all the TabIndex properties of the models passed to this method.

HTML Forms

The <idl>com.sun.star.form.component.HTMLForm</idl> service reflects the requirements for HTML form documents. Looking at HTML specifications, you can submit forms using different encodings and submit methods, and reset forms. The HTMLForm service description reflects this by supporting the interfaces <idl>com.sun.star.form.XReset</idl> and <idl>com.sun.star.form.XSubmit</idl>, as well as some additional properties related to the submit functionality.

The semantics of these interfaces and properties are straightforward.For additional details, refer to the service description, as well as the HTML specification.

Data Awareness

A major feature of forms in LibreOffice is that they can be data aware. You create form documents where the user manipulates data from a database that is accessible in LibreOffice. For more details about data sources, refer to chapter Database Access. This includes data from any table of a database, or data from a query based on one or more tables.

The basic idea is that a logical form is associated with a database result set. A form control model, which is a child of that form, is bound to a field of this result set, exchanging the data entered by the user with the result set field.

Forms

Forms support Row Sets, are loadable, can be filtered and sorted, and have parameters that you can set.

Forms as Row Sets

Besides forms, there is already a component that supports a result set, the <idl>com.sun.star.sdb.RowSet</idl>.If you look at the <idl>com.sun.star.form.component.DataForm</idl>, a DataForm also implements the <idl>com.sun.star.sdb.RowSet</idl> service, and extends it with additional functionality. Row sets are described in The RowSet Service.

Loadable Forms

A major difference of data forms compared to the underlying row set is the that forms are loaded, and provide an interface to manipulate this state.

XLoadable xLoad = (XLoadable)FLTools.getParent(aControlModel, XLoadable.class);
xLoad.reload();

Loading is the same as executing the underlying row set, that is, invoking the <idlml>com.sun.star.sdbc.XRowSet:execute</idlml>() method. The <idl>com.sun.star.form.XLoadable</idl> is designed to fit the needs of a form document, for example, it unloads an already loaded form.

The example above shows how to reload a form. Reloading is executing the row set again. Using reload instead of execute has the advantage of advanced listener mechanisms:

Look at the <idl>com.sun.star.form.XLoadable</idl> interface. You can add a <idl>com.sun.star.form.XLoadListener</idl>. This listener not only tells you when load-related events have occurred that is achieved by the <idl>com.sun.star.sdbc.XRowSetListener</idl>, but also when they are about to happen. In a complex scenario where different listeners are added to different aspects of a form, you use the <idlm>com.sun.star.form.XLoadable:reload</idlm>() call to disable all other listeners temporarily. Re-executing a row set is a complex process, thus it triggers a lot of events that are only an after effect of the re-execution.

Note pin.svg

Note:
Though all the functionality provided by <idl>com.sun.star.form.XLoadable</idl> can be simulated using the <idl>com.sun.star.sdbc.XRowSet</idl> interface, you should always use the former. Due to the above-mentioned, more sophisticated listener mechanisms, implementations have a chance to do loading, reloading and unloading much smoother then.

An additional difference between loading and executing is the positioning of the row set: When using <idlm>com.sun.star.sdbc.XRowSet:execute</idlm>(), the set is positioned before the first record. When you use <idlm>com.sun.star.form.XLoadable:load</idlm>(), the set is positioned on the first record, as you would expect from a form.

Sub Forms

A powerful feature of LibreOffice are sub forms. This does not mean that complete form documents are embedded into other form documents, instead sub form relationships are realized by nesting logical forms in the form component hierarchy.

When a form notices that its parent is not the forms container when it is loaded and in live mode, but is dependent on another form, it no longer acts as a top-level form. Whenever the parent or master form moves to another record, the content of the sub or detail form is re-fetched. This way, the content of the sub form is made dependent on the actual value of one or more fields of the parent form.

Typical use for a relationship are tables that are linked through key columns, usually in a 1:n relationship. You use a master form to travel through all records of the table on the 1 side of the relationship, and a detail form that shows the records of the table on the n side of the relationship where the foreign key matches the primary key of the master table.

To create nested forms at runtime, use the following example:

// retrieve or create the master form
m_xMasterForm = ....

// bind it to the salesman table
m_xMasterForm.setPropertyValue("DataSourceName", m_aParameters.sDataSourceName);
m_xMasterForm.setPropertyValue("CommandType", new Integer(CommandType.TABLE));
m_xMasterForm.setPropertyValue("Command", "SALESMAN");

// create the details form
XIndexContainer xSalesForm = m_aDocument.createSubForm(m_xMasterForm, "Sales");
XPropertySet xSalesFormProps = (XPropertySet)UnoRuntime.queryInterface(
    XPropertySet.class, xSalesForm);

// bind it to the all those sales belonging to a variable salesmen
xSalesFormProps.setPropertyValue("DataSourceName", m_aParameters.sDataSourceName);
xSalesFormProps.setPropertyValue("CommandType", new Integer( CommandType.COMMAND));
xSalesFormProps.setPropertyValue("Command",
    "SELECT * FROM SALES AS SALES WHERE SALES.SNR = :salesman");

// the master-details connection
String[] aMasterFields = new String[] {"SNR"}; // the field in the master form
String[] aDetailFields = new String[] {"salesman"}; // the name in the detail form
xSalesFormProps.setPropertyValue("MasterFields", aMasterFields);
xSalesFormProps.setPropertyValue("DetailFields", aDetailFields);

The code snippet works on the following table structure:

Sales tables

The code is straightforward, except for setting up the connection between the two forms. The master form is bound to SALESMEN, and the detail form is bound to a statement that selects all fields from SALES, filtered for records where the foreign key, SALES.SNR, equals a parameter named salesman.

As soon as the MasterFields and DetailFields properties are set, the two forms are connected. Every time the cursor in the master form moves, the detail form reloads after filling the salesman parameter with the actual value of the master forms SNR column.

Filtering and Sorting

Forms support quick and easy filtering and sorting like the underlying row sets. For this, the properties <idlm>com.sun.star.sdb.RowSet:Filter</idlm>, <idlm>com.sun.star.sdb.RowSet:ApplyFilter</idlm> and <idlm>com.sun.star.sdb.RowSet:Order</idlm> area used.

// set this as filter on the form
String sCompleteFilter = "";
if ((null != sOdbcDate) && (0 != sOdbcDate.length())) {
    sCompleteFilter = "SALEDATE >= ";
    sCompleteFilter += sOdbcDate;
}
m_xSalesForm.setPropertyValue("Filter", sCompleteFilter);
m_xSalesForm.setPropertyValue("ApplyFilter", new Boolean(true));

// and reload the form
XLoadable xLoad = (XLoadable)UnoRuntime.queryInterface(XLoadable.class, m_xSalesForm);
xLoad.reload();

In this fragment, a filter string is built first. The "SALEDATE >= {D '2002-12-02'}" is an example for a filter string. In general, everything that appears after the WHERE clause of an SQL statement is set as a Filter property value. The same holds true for the Order property value and an ORDER BY clause.

Tip.svg

Tip:
The notation for the date in braces: This is the standard ODBC notation for date values, and it is the safest method to supply LibreOffice with date values. It also works if you are using non-ODBC data sources, as long as you do not switch on the Native SQL option. Refer to <idlm>com.sun.star.sdbc.Statement:EscapeProcessing</idlm>. LibreOffice understands and sometimes returns other notations, for instance, in the user interface where that makes sense, but these are locale-dependent, which means you have to know the current locale if you use them.


Then the ApplyFilter property is set to true. This is for safety, because the value of this property is unknown when creating a new form. Every time you have a form or row set, and you want to change the filter, remember to set the ApplyFilter property at least once. Afterwards, reload() is called.

In general, ApplyFilter allows the user of a row set to enable or disable the current filter quickly without remembering it. To see what the effects of the current filter are, set ApplyFilter to false and reload the form.

Parameters

Data Aware Forms are based on statements. As with other topics in this chapter, this is not form specific, instead it is a functionality inherited from the underlying <idl>com.sun.star.sdb.RowSet</idl>. Statements contain parameters where some values are not specified, and are not dependent on actual values in the underlying tables. Instead they have to be filled each time the row set is executed, that is, the form is loaded or reloaded.

A typical example for a statement containing a parameter is

SELECT * FROM SALES WHERE SALES.SNR = :salesman

There is a named parameter salesman, which is filled before a row set based on a statement is executed. The orthodox method to use is the <idl>com.sun.star.sdbc.XParameters</idl> interface, exported by the row set.

However, forms allow another way. They export the <idl>com.sun.star.form.XDatabaseParameterBroadcaster</idl> interface that allows your component to add itself as a listener for an event which is triggered whenever the form needs parameter values.

In a form, filling parameters is a three-step procedure. Consider a form that needs three parameters for execution.

  1. The master-detail relationship is evaluated. If the form's parent is a <idl>com.sun.star.form.component.DataForm</idl>, then the MasterFields and DetailFields properties are evaluated to fill in parameter values. For an example of how this relationship is evaluated, refer to chapter Sub Forms.
  2. If there are parameter values left, that is, not filled in, the calls to the <idl>com.sun.star.sdbc.XParameters</idl> interface are examined. All values previously set through this interface are filled in.
  3. If there are still parameter values left, the <idl>com.sun.star.form.XDatabaseParameterListener</idl>s are invoked. Any component can add itself as a listener using the <idl>com.sun.star.form.XDatabaseParameterBroadcaster</idl> interface implemented by the form. The listeners then have the chance to fill in anything still missing.

Unfortunately, LibreOffice Basic scripts currently cannot follow the last step of this procedure - there is a known implementation issue which prevents this.

Data Aware Controls

The second part of the Data Awareness capabilities of LibreOffice are data aware controls. While a form is always associated with a complete result set, it represents this result set, a single control is bound to one data column that is part of the form which is the control's parent.

As always, the relevant information is stored in the control model. The basic service for control models which are data-aware is <idl>com.sun.star.form.DataAwareControlModel</idl>.

There are two connections between a control model and the column it is bound to:

DataField

This is the property that determines the name of the field to bind to. Upon loading the form, a control model searches the data columns of the form for this name, and connects to it. An explanation for "connects" is provided below.
Note that this property is a suggestion only. It tells the control model to connect to the data column, but this connection may fail for various reasons, for example, no such column may exist in the row set.
Even if this property is set to a non-empty string, this does not mean anything about the control being connected.

BoundField

Once a control model has connected itself to a data column, the respective column object is also remembered. This saves clients of a control model the effort to examine and handle the DataField, they simply rely on BoundField.
Opposite to the DataField property, BoundField is reliable in that it is a valid column object if and only if the control is properly connected.

The overall relationship for data awareness is as follows:

Data awareness relationship

Control Models as Bound Components

You expect that the control displays the current data of the column it is tied to. Current data means the data in the row that the <idl>com.sun.star.form.component.DataForm</idl> is currently located on. Now, the control does not know about data-awareness, only the control model does, but we already have a connection between the model and control: As described in the chapter about model-view interaction, Model-View Interaction, the control listens for changes to the model properties, as well as updates them when a user interacts with the control directly.

For instance, you know the Text property of a simple text input field, <idl>com.sun.star.form.component.TextField</idl>that is updated by the control when the user enters text. When the property is updated through any other means, the control reacts appropriately and adjusts the text it displays.

This mechanism is found in all controls. The only difference is the property used to determine the contents to be displayed. For instance, numeric controls <idl>com.sun.star.form.component.NumericField</idl> have a property Value representing the current numerical value to be displayed.Although the name differs, all control models have a dedicated content property.

This is where the data-awareness comes in. A data-aware control model bound to a data column uses its content property to exchange data with this column. As soon as the column value changes, the model forwards the new value to its content property, and notifies its listeners. One of these listeners is the control that updates its display:

Control fetching data

Committing Controls

The second direction of the data transfer is back from what the user enters into the control. The text entered by a user is immediately forwarded to the value property of the control model. This way, both the control and the control model are always consistent.

Next, the content property is transferred into the data column the control is bound to. As opposed to the first step, this is not done automatically. Instead, this control is committed actively.

Committing is the process of transferring the current value of the control to the database column. The interface used for this is <idl>com.sun.star.form.XBoundComponent</idl> that provides the method commit. Note that the XBoundComponent is derived from <idl>com.sun.star.form.XUpdateBroadcaster</idl>. This means that listeners are added to a component to monitor and veto the committing of data.

The following diagram shows what happens when the user decides to save the current record after changing a control:

Control committing data

Note that in the diagram, there is a controller instance involved. In general, this is any instance capable of controlling the user-form interaction. In LibreOffice, for every document view and form, there is an instance of the <idl>com.sun.star.form.FormController</idl> service, together with some not-yet UNO-based code that takes on the role of a controller.

External value suppliers

Chapter Data Aware Controls discussed form controls that exchange their value, as entered by the user, with database columns. At certain times, this type of form control initializes itself from the column, or writes its current value into the column.

In addition, list and combo box controls are able to retrieve, in various ways, their list content from a database.

Since OpenOffice.org 2.0.0, it is possible for form controls to exchange data with external components. This is a generalization of the data awareness concept: form controls are now able to bind their value to any external value supplier, without knowing anything about the value supplier except an abstract UNO interface.

Similarly, list and combo boxes can obtain their list content from an external component, as long as they support a certain interface.

The <idlmodule>com.sun.star.form.binding</idlmodule> module collects all interfaces and services related to this new functionality.

Value Bindings

Unlike the functionality for binding form controls to database columns, value bindings are external to the form control/model. A control that can be bound (note that not all existing controls actually can) supports a certain interface, and a binding supports another one. That is all both parties need to know.

The illustration below shows the most important interfaces and services collaborating here.

Basic class diagram for value components and value bindings

Note that there is yet no notion about form controls at all. Those interfaces are only concerned with components representing a value, and components implementing a binding for this value. In fact, the generic mechanism for binding values is described with a complete disregard of form controls. The components supporting the <idls>com.sun.star.form.binding.XBindableValue</idls> interface are called value components.

The central interface is <idls>com.sun.star.form.binding.XValueBinding</idls>, which is to be supported by components that want to impose their value on a value component. The following table describes its methods:

Methods of <idl>com.sun.star.form.binding.XValueBinding</idl>
<idlm>com.sun.star.form.binding.XValueBinding:getSupportedValueTypes</idlm>() Allows negotiation of a type in which values are exchanged. Usually, both a binding and a value component only support a certain set of types, in which the values can be exchanged. If the sets of a given binding and a given value component do not intersect, both can not be knit together.
<idlm>com.sun.star.form.binding.XValueBinding:supportsType</idlm>() Allows a value component to explicitly ask whether a given binding supports a given type.

This method can be used as shortcut: components do not need to examine the complete type sequence of a binding.

Additionally, this method is usually used to implement a precedence of types. A value component can ask a potential binding for certain supported types, in a certain order. The first type that is accepted by the binding (if any) can then be used for exchanging the value.

<idlm>com.sun.star.form.binding.XValueBinding:getValue</idlm>() Retrieves the current value as represented by the binding. Callers specify a type of the value, and the binding must provide the value in exactly this type, or throw an <idl>com.sun.star.form.binding.IncompatibleTypesException</idl> if this is not possible.
<idlm>com.sun.star.form.binding.XValueBinding:setValue</idlm>() Propagates a new value to the binding.

The <idls>com.sun.star.form.binding.ValueBinding</idls> service extends the <idls>com.sun.star.form.binding.XValueBinding</idls> interface with two aspects:

<idl>com.sun.star.util.XModifyBroadcaster</idl>

This allows a value binding to actively notify changes in its value. A value component can register itself as <idls>com.sun.star.util.XModifyListener</idls> at the binding. In fact, that is the only way that the relationship between the binding and the value component can become bidirectional. Without the support of the <idls>com.sun.star.util.XModifyBroadcaster</idls> interface, value components can only actively propagate their value to the binding, but not the reverse.

support for read-only bindings

The <idlm>com.sun.star.form.binding.ValueBinding:ReadOnly</idlm> property can be used to specify that the value represented by a binding currently cannot be modified. If the read-only state of a binding's value can change during its lifetime, it should allow registering <idls>com.sun.star.beans.XPropertyChangeListener</idls>s for the <idlm>com.sun.star.form.binding.ValueBinding:ReadOnly</idlm> property, so value components using the binding can act on the current state.

Form Controls accepting Value Bindings

How do form controls and value bindings relate to each other? When looking at all the form control functionality that has so far been discussed, the following questions come need to be answered:

  • Which control types do support value bindings?
  • For a given control type, which aspect actually is its value, which is exchanged with an external binding?
  • How do external value bindings interact with data awareness, for example, controls that exchange their value with a database column?
  • What can you do with all this?

The first two questions are easy: Every control that allows user input also supports value bindings. The data that the user entered (this may be, for instance, plain text, or an image, or a check state) is considered the value of the control, and thus exchanged with the external binding.

The basic service is the <idl>com.sun.star.form.binding.BindableControlModel</idl>, which specifies a control model supporting external value bindings. For a concrete control type, for instance, a check box, a service such as BindableCheckBox would be expected, which specifies how a check box control model exchanges its value with an external binding.

However, all controls that potentially could support a binding also are data aware (see Data Aware Controls). Thus, the first step is to answer the third question from above. The service <idl>com.sun.star.form.binding.BindableDataAwareControlModel</idl> is about data aware control models with value binding capabilities. You are referred to the documentation of the <idls>com.sun.star.form.binding.BindableDataAwareControlModel</idls> service for all the details, but the two most interesting details are as follows:

Priority External value bindings overrule any active SQL-column binding. If an external component is bound to a control model that currently has an active SQL binding, this SQL binding is suspended until the external binding is revoked.

Immediacy When a <idls>com.sun.star.form.binding.BindableDataAwareControlModel</idls> is bound to an external value, then every change in the control model's value is immediately reflected in the external binding. This is a difference to SQL bindings of most <idls>com.sun.star.form.DataAwareControlModel</idls>s, where changes in the model's value are only propagated to the bound column upon explicit request via <idlm>com.sun.star.form.XBoundComponent:commit</idlm>.

The illustration below shows the service hierarchy for control models that are also value components. It also shows how concrete control types fit in, for example by using check boxes.

Form control models supporting value bindings

The following covers the last question from the above list: What is this good for?

LibreOffice already contains two practical applications:

Spreadsheet cell bindings

In a LibreOffice spreadsheet document, you always could insert form controls. Since version OpenOffice.org 2.0.0, you can, in their properties, bind form controls to arbitrary cells within the document. That is, every change made in this cell is propagated to the control, and vice versa. This is implemented using the value binding mechanism described in this chapter. See <idl>com.sun.star.table.CellValueBinding</idl> for more details.
The following piece of code creates a cell value binding in a spreadsheet document, for cell A1 on the first sheet, and knits it to a numeric control model:

// insert our sample control
XPropertySet numericControl = m_formLayer.insertControlLine( "DatabaseFormattedField",
    "enter a value", "", 10 );

// a value binding for cell A1 on the first
CellAddress address = new CellAddress( (short)0, (short)0, (short)0 );
Object[] initParam = new Object[] { new NamedValue( "BoundCell", address ) };
XValueBinding cellBinding = (XValueBinding)UnoRuntime.queryInterface(
    XValueBinding.class,
    m_document.createInstanceWithArguments(
        "com.sun.star.table.CellValueBinding", initParam ) );

// bind it to the control model
XBindableValue bindable = (XBindableValue)UnoRuntime.queryInterface(
    XBindableValue.class, numericControl
);
bindable.setValueBinding( cellBinding );

XML form bindings

LibreOffice features XML forms. These are form documents whose data model is a DOM tree. They are realized with the usual form controls and logical forms, and this time the controls are bound to DOM nodes using the value binding mechanism.

External List Sources

The previous chapter introduced an abstraction of the data aware mechanism for form controls: They can not exchange their value with database columns or with arbitrary value bindings, without recognizing anything except UNO interfaces.

When you look at what controls can do with database content, you may find list and combo boxes useful. They are able to retrieve the content of their lists from a database.

Similar to the value binding mechanism, there is also an abstraction available for components supplying list entries to form controls: <idl>com.sun.star.form.binding.ListEntrySource</idl> and <idl>com.sun.star.form.binding.XListEntrySink</idl>.

The relationship between <idls>com.sun.star.form.binding.XListEntrySource</idls>s and <idls>com.sun.star.form.binding.XListEntrySink</idls>s is shown in the illustration below.

Interfaces and services involved with external list sources

As with value bindings, LibreOffice already makes use of this concept in spreadsheet documents. The following piece of code, for instance, creates a <idls>com.sun.star.table.CellRangeListSource</idls>, and binds it to a list box. After that, the list boxes content will always be synchronized with the content in the chosen cell range.

CellRangeAddress rangeAddress = new CellRangeAddress( sheet, column,
    topRow, column, bottomRow );
Object[] initParam = new Object[] { new NamedValue( "CellRange", rangeAddress ) };
XListEntrySource entrySource = (XListEntrySource)UnoRuntime.queryInterface(
    XListEntrySource.class, m_document.createInstanceWithArguments(
        "com.sun.star.table.CellRangeListSource", initParam ) );

XListEntrySink consumer = (XListEntrySink)UnoRuntime.queryInterface(
    XListEntrySink.class, listBox );
consumer.setListEntrySource( entrySource );

Note that a <idls>com.sun.star.table.CellRangeListSource</idls> can not be obtained at a global service manager. Instead, you have to retrieve it from the document to whose cells you want to bind the list box.

Validation

Form controls in LibreOffice always featured a simple type of validation for their value. For instance, for a numeric field you can specify minimum an maximum values (<idlm>com.sun.star.awt.UnoControlNumericFieldModel:ValueMin</idlm> and <idlm>com.sun.star.awt.UnoControlNumericFieldModel:ValueMax</idlm>). However, those validity constraints have some disadvantages:

  • They are enforced as soon as the control loses the focus. That is, if you enter a number into a numeric field that is greater than the allowed maximum, then it is automatically corrected to be the maximum.
  • They are enforced silently. There is no warning to the user, and no visual feedback at the moment the value is invalid, and not yet corrected. In particular, there is no explanation about why a certain input was (or will be) automatically corrected.

LibreOffice features a mechanism for validating the content of form controls, at the time of user input.

The basic interface for this mechanism is <idls>com.sun.star.form.validation.XValidator</idls>:

Methods of <idl>com.sun.star.form.validation.XValidator</idl>
<idlm>com.sun.star.form.validation.XValidator:isValid</idlm> This is the central method of a validator. It is able to decide, for a given value, whether it is valid or not. Note that there is no notion about the semantics of valid. This is in the responsibility of the concrete service implementing this interface.
<idlm>com.sun.star.form.validation.XValidator:explainInvalid</idlm> Explains, for a given value, why it is considered invalid. The explanation should be human-readable because other components are expected to present it to the user.
<idlm>com.sun.star.form.validation.XValidator:addValidityConstraintListener</idlm> Registers a new validity listener. As a basic idea, a validator can not be stateless: Depending on its current internal state, the validator can consider the very same value as valid or invalid. Such validator components should notify a change in their state, and thus a potential change in the validity of associated <idls>com.sun.star.form.validation.XValidatable</idls> instances, to all listeners registered with this method. The listeners are encouraged to re-validate whatever data is guarded by the validator.
<idlm>com.sun.star.form.validation.XValidator:removeValidityConstraintListener</idlm> Revokes a previously registered validity listener.

A validator is to be used with a component that can be validated: <idls>com.sun.star.form.validation.XValidatable</idls>. This interface allows to set and to get a validator instance.

Until now, nothing has been said about form components. You may also note that nothing has been said about the data that is being validated. Though a value is passed to the <idlm>com.sun.star.form.validation.XValidator:isValid</idlm> method, there is no explanation about where it originates from. In particular, the <idls>com.sun.star.form.validation.XValidatable</idls> does not specify a means to obtain its value.

This is where <idl>com.sun.star.form.validation.XValidatableFormComponent</idl> comes in. Note that it derives from <idls>com.sun.star.form.validation.XValidatable</idls>.

Methods of <idl>com.sun.star.form.validation.XValidatableFormComponent</idl>
<idlm>com.sun.star.form.validation.XValidatableFormComponent:isValid</idlm> Determines whether the current value, as represented by the component, is valid. This is a shortcut to calling the validator's <idlm>com.sun.star.form.validation.XValidator:isValid</idlm> method, with the current value of the component.
<idlm>com.sun.star.form.validation.XValidatableFormComponent:getCurrentValue</idlm> Specifies the current value, as represented by the component.

As an example, a <idls>com.sun.star.form.component.TextField</idls> would return its text, while a <idls>com.sun.star.form.component.DateField</idls> would return its date value.

<idlm>com.sun.star.form.validation.XValidatableFormComponent:addFormComponentValidityListener</idlm> Adds a listener to observe the validity of the component. This validity is determined by two aspects: The current value of the component, and the validator's opinion about this value (and thus implicitly by the current validator of the component, which may also change). To be notified of changes in this composed validity, you need to register a <idls>com.sun.star.form.validation.XFormComponentValidityListener</idls> at the form component.
<idlm>com.sun.star.form.validation.XValidatableFormComponent:removeFormComponentValidityListener</idlm> Revokes a previously registered validity listener.

Now, the overall picture for services and interfaces concerned with form control validation can be seen in the following figure:

Validation of form controls

Notice the <idls>com.sun.star.form.validation.ValidatableControlModel</idls> service: It specifies the basic functionality of form control models, which allow their current value to be validated against an external validator.

In LibreOffice, there is one feature that uses the functionality introduced in this chapter: XML form documents. They are implemented using the interfaces and services from <idlmodule>com.sun.star.xforms</idlmodule>. In particular, an XForms binding (<idls>com.sun.star.xforms.Binding</idls>) is a validator. This way, LibreOffice form controls can be used to enter values for XForms DOM trees, respecting the restrictions imposed on those value as part of the XForms model.

Validation in LibreOffice

The LibreOffice Software Development Kit shows the power of form control validation with an example program. This program creates a form document with various types of controls, most of which are bound to external validators. Those validator objects impose (exemplary and rather arbitrary) restrictions on the control values.

As shown in the example program, the form runtime environment of LibreOffice makes use of several features of the validation API, illustrating the advantage over the old, property-based, built-in validation mentioned in chapter Validation.

For instance, invalid values in form controls, where invalid is defined by the external validator object, are not enforcing a valid value automatically. Instead, the invalidity is shown with a red border around the control. If the control does not allow for a red border, its text is underlined with red waves. Additionally, the explanation why a certain value is invalid appears as tooltip at the respective control.

This way, the user who fills in a form receives immediate feedback about which values and controls need attention, without destroying whatever information has already been entered.

Validations and Bindings

Chapter Value Bindings introduced form components that can exchange the value with external components, as long as those support the <idls>com.sun.star.form.binding.XValueBinding</idls> interface. Also, chapter Validation introduced form components whose value can be validated by external components.

These concepts can be combined. This way, it is possible to build highly customized form documents.

In fact, this is what the <idl>com.sun.star.form.validation.ValidatableBindableControlModel</idl> service does: it combines the services <idls>com.sun.star.form.binding.BindableControlModel</idls> with the <idls>com.sun.star.form.validation.ValidatableControlModel</idls>.

As soon as you establish a validator at the model (<idlm>com.sun.star.form.validation.XValidatable:setValidator</idlm>), which is also an <idls>com.sun.star.form.binding.XBindableValue</idls>, then it is used both as value binding and as validator. Every attempt to establish another binding will be denied, as long as the combined validator/binding is in place.

Note pin.svg

Note:
In LibreOffice, every form control model that can be validated and is also bindable, also supports the <idls>com.sun.star.form.validation.ValidatableBindableControlModel</idls> service. That is, the validator and the binding are coupled, if possible.

Scripting and Events

To create form documents that are able to do more than just reading and writing plain data, it is often necessary to enhance the form with scripting functionality. That is, for a given form component, you want to declare that a certain script should be called, when a certain event occurs.

For example, you may need a check box control, which calls a certain macro when its check state changes. Within this macro, you can, for instance, enable or disable other controls that depend on the check box being checked.

One possible solution is to use a completely programmatic approach: just bind a script to the OnLoad event of the whole document, create an <idls>com.sun.star.awt.XItemListener</idls>, and register it at the check box control in question, using the <idls>com.sun.star.awt.XCheckBox</idls> interface.

However, this method is inefficient, because the whole scripting engine starts when the document is loaded. The scripting engine should start at the latest possible point, which is the moment the user clicks the check box for the first time.

Form components feature a mechanism that is more efficient. For every form component part of a hierarchy of form components, you can specify a script to be called upon a certain event. You only specify this once, at the time the form component is created and placed in the document.

Look at the <idl>com.sun.star.form.FormComponents</idl> service, which was encountered previously. The service includes the interface <idl>com.sun.star.script.XEventAttacherManager</idl>. This interface allows you to manage the events that are associated with the elements in the <idls>com.sun.star.form.FormComponents</idls> container.

Note that an event together with an associated script is described by the <idls>com.sun.star.script.ScriptEventDescriptor</idls>, with the following elements:

Properties of <idl>com.sun.star.script.ScriptEventDescriptor</idl>
<idlm>com.sun.star.script.ScriptEventDescriptor:ListenerType</idlm> Specifies the completely qualified name of a listener interface. This implies that you can only register scripts for events that are notified using UNO.
<idlm>com.sun.star.script.ScriptEventDescriptor:EventMethod</idlm> Specifies a method of the ListenerType interface.

Together with the ListenerType member, this completely describes the event for which a script is to be registered.

<idlm>com.sun.star.script.ScriptEventDescriptor:AddListenerParam</idlm> Specifies a parameter that is to be used when adding listeners, as described by the ListenerType element) For most listener types, this is not necessary.
<idlm>com.sun.star.script.ScriptEventDescriptor:ScriptType</idlm> Specifies which type of script is to be associated with the event. <idl>com.sun.star.form.FormComponents</idl> currently only support StarBasic here.
<idlm>com.sun.star.script.ScriptEventDescriptor:ScriptCode</idlm> Specifies which script code to call. In case of ScriptType being StarBasic, this must specify a Basic procedure or function, either within the application-wide code repository, or within the document which the form component belongs to. In the first case, the script code starts with "application:", else with "document:".

The following example registers a certain Basic procedure for the event which is triggered when the state of a radio button control changes, for example, when a radio button has been selected or deselected:

Dim oEvent as new com.sun.star.script.ScriptEventDescriptor
oEvent.ListenerType = "com.sun.star.awt.XItemListener"
oEvent.EventMethod = "itemStateChanged"
oEvent.ScriptType = "StarBasic"
oEvent.ScriptCode = "application:Standard.macro_assignment.onColorChange"
oSampleForm.registerScriptEvent( i, oEvent )

For the ith sub component of oSampleForm, this associates the macro onColorChange, located in the module macro_assignment of the application-wide library Standard, with the <idlm>com.sun.star.awt.XItemListener:itemStateChanged</idlm> event. You can use this with every form component which supports notification of <idls>com.sun.star.awt.XItemListener</idls>s, in particular with radio buttons (<idls>com.sun.star.awt.XRadioButton</idls>) and check boxes (<idls>com.sun.star.awt.XCheckBox</idls>).

Note that simply registering script events at a <idls>com.sun.star.form.FormComponents</idls> instance does not do anything. In particular, registering script events does not yet mean that the script will be called automatically. In fact, the <idls>com.sun.star.script.XEventAttacherManager</idls> interface merely acts as a container to remember the associated events. In a living form document, there are controller instances involved, which take care of the scripts that are really being called, by adding themselves as <idls>com.sun.star.script.XScriptListener</idls> to the event attach manager.

Common Tasks

This chapter is dedicated to problems that may arise when you are working with (or script) form documents, and cannot be solved by LibreOffice's built-in methods, but have a solution in the LibreOffice UNO API.

Initializing Bound Controls

All form controls specify a default value that is used when initially displaying the control, and when it is reset. For instance, resetting (<idl>com.sun.star.form.XReset</idl>) happens when a form is moved to the insert row, that allows data to be inserted as a new row into the underlying row set.

Now, you do not want a fixed default value for new records, but a dynamically generated one that is dependent on the actual context at the moment the new record is entered.

Or, you want to have real null values for date fields. This is currently not possible, because the <idl>com.sun.star.form.component.DateField</idl> service interprets a null default as an instruction to use the current system date. Effectively, you cannot have date fields in forms which default to null on new records, but you can get this by programming the API.

public void handleReset(EventObject aEvent) throws com.sun.star.uno.RuntimeException {
    if (((Boolean)xFormProps.getPropertyValue("IsNew")).booleanValue()) {
        // the form is positioned on the insert row

        Object aModifiedFlag = xFormProps.getPropertyValue("IsModified");

        // get the columns of the form
        XColumnsSupplier xSuppCols = (XColumnsSupplier)UnoRuntime.queryInterface(
            XColumnsSupplier.class, xFormProps);
        XNameAccess xCols = xSuppCols.getColumns();

        // and update the date column with a NULL value
        XColumnUpdate xDateColumn = (XColumnUpdate)UnoRuntime.queryInterface(
            XColumnUpdate.class, xCols.getByName("SALEDATE"));
        xDateColumn.updateNull();

        // then restore the flag
        xFormProps.setPropertyValue("IsModified", aModifiedFlag);
    }
}

The first decision is where to step in. We chose to add a reset-listener to the form, so that the form is reset as soon as it has been positioned on the new record. The <idlm>com.sun.star.form.XResetListener:resetted</idlm>() method is called after the positioning is done.

However, resets also occur for various reasons therefore check if the form is really positioned on the insert row, indicated by the IsNew property being true.

Now besides retrieving and updating the data column with the desired value, null, there is another obstacle. When the form is moved to the insert row, and some values are initialized, the row should not be modified. This is because a modified row is saved in the database, and we only initialized the new row with the defaults, the user did not enter data. We do not want to store the row, therefore we save and restore the IsModified flag on the form while doing the update.

Automatic Key Generation

Another problem frequently encountered is the automatic generation of unique keys. There are reasons for doing this on the client side, and missing support, for example, auto-increment fields in your database backend, or you need this value before inserting the row. LibreOffice is currently limited in re-fetching the server-side generated value after a record has been inserted.

Assume that you have a method called generateUniqueKey() to generate a unique key that could be queried from a key generator on a database server, or in a single-user-environment by selecting the maximum of the existing keys and incrementing it by 1. This fragment inserts the generated value into the given column of a given form:

public void insertUniqueKey(XPropertySet xForm, String sFieldName) throws com.sun.star.uno.Exception {
    // get the column object
    XColumnsSupplier xSuppCols = (XColumnsSupplier)UnoRuntime.queryInterface(
        XColumnsSupplier.class, xForm);
    XNameAccess xCols = xSuppCols.getColumns();
    XColumnUpdate xCol = (XColumnUpdate)UnoRuntime.queryInterface(
        XColumnUpdate.class, xCols.getByName(sFieldName));

    xCol.updateInt(generateUniqueKey(xForm, sFieldName));
}

A solution to determine when the insertion is to happen has been introduced in a previous chapter, that is, we could fill in the value as soon as the form is positioned on the insert row, wait for the user's input in the other fields, and save the record.

Another approach is to step in immediately before the record is inserted. For this, the <idl>com.sun.star.sdb.XRowSetApproveBroadcaster</idl> is used. It notifies listeners when rows are inserted, the listeners can veto this, and final changes can be made to the new record:

public boolean approveRowChange(RowChangeEvent aEvent) throws com.sun.star.uno.RuntimeException {
    if (RowChangeAction.INSERT == aEvent.Action) {
        // the affected form
        XPropertySet xFormProps = (XpropertySet)UnoRuntime.queryInterface(
            XpropertySet.class, aEvent.Source);
        // insert a new unique value
        insertUniqueKey(xFormProps, m_sFieldName);
    }
    return true;
}

Data Validation

If you happen to have a scripting language that is not capable of creating own components, such as StarBasic, then the validation mechanisms described in chapter Validation can not be used: They rely on a component being created that implements the <idls>com.sun.star.form.validation.XValidator</idls> interface.

If, despite this, you want to validate data in controls bound to a database, then you have two alternative possibilities:

  • From the chapter Committing Controls, you can approve updates, and veto the changes a control wants to write into the data column it is bound to.
  • Additionally, you can step in later. You know how to use a <idl>com.sun.star.sdb.XRowSetApproveListener</idl> for doing last-minute changes to a record that is about to be inserted.
Besides this, you can use the listener to approve changes to the row set data. When the <idl>com.sun.star.sdb.RowChangeAction</idl> is sent to the listeners, it distinguishes between different kinds of data modification. You can implement listeners that act differently for insertions and simple updates.

Note the important differences between both solutions. Using an <idl>com.sun.star.form.XUpdateListener</idl> implies that the data operations are vetoed for a given control. Your listener is invoked as soon as the respective control is committed, for instance, when it loses the focus. This implies that changes done to the data column by other means than through this control are not monitored.

The second alternative is using an <idl>com.sun.star.sdb.XRowSetApproveListener</idl> meaning you veto changes immediately before they are sent to the database. Thus, it is irrelevant where they have been made previously. In addition, error messages that are raised when the user actively tries to save the record are considered less disturbing than error messages raised when the user simply leaves a control.

The example below shows the handling for denying empty values for a given control:

public boolean approveUpdate(EventObject aEvent) throws com.sun.star.uno.RuntimeException {
    boolean bApproved = true;

    // the control model which fired the event
    XPropertySet xSourceProps = UNO.queryPropertySet(aEvent.Source);

    String sNewText = (String)xSourceProps.getPropertyValue("Text");
    if (0 == sNewText.length()) {
        // say that the value is invalid
        showInvalidValueMessage();
        bApproved = false;

        // reset the control value
        // for this, we take the current value from the row set field the control
        // is bound to, and forward it to the control model
        XColumn xBoundColumn = UNO.queryColumn(xSourceProps.getPropertyValue("BoundField"));
        if (null != xBoundColumn) {
            xSourceProps.setPropertyValue("Text", xBoundColumn.getString());
        }
    }

    return bApproved;
}

Programmatic Assignment of Scripts to Events

Sometimes, you want to create a document programmatically, including form controls, and assigning certain scripts to certain events for those controls. In the userinterface, this is done by using the property browser. Programmatically, this is somewhat more difficult.

As an example, if you want to programmatically create a document containing radio buttons. When those radio buttons change their state (i.e. are selected), a certain Basic script should be called.

One possibility is to make use of the OnLoad event of the document as a whole. Using this event, you can create a listener of the desired type (in our sample case: <idl>com.sun.star.awt.XItemListener</idl>), and register it at the control in question.

Tip.svg

Tip:
In LibreOffice Basic, you can create an UNO listener using the procedure CreateUnoListener.


This approach has three disadvantages: First, it is expensive. The scripting environment is loaded every time the document is loaded, which is too early. It should be loaded as late as possible, which is when the radio buttons really change their state.

Second, it is error prone. There are certain circumstances where LibreOffice Basic listeners are automatically revoked, without the document being closed. In those cases, you need to manually reload the document, or re-run your OnLoad script for re-creating the listener.

Third, it is complex. You would, in your OnLoad initialization script, need to manually obtain the control in questions, which can involve a large amount of code.

A better solution is to use the event attacher manager mechanism described in Scripting and Events.

The following example creates a text document with three radio buttons, all three calling the same script when being selected.

Note pin.svg

Note:
When you try the following code, you may need to adjust the location of the script being called. At the moment, it assumes a module name macro_assignment in the application-wide library named Standard.

REM ***** BASIC *****

Option Explicit

Sub Main
    ' create a new writer document
    Dim oDocument as Object
    Dim oEmptyArgs() as new com.sun.star.beans.PropertyValue
    oDocument = StarDesktop.LoadComponentFromURL( "private:factory/swriter", "_blank", 0,
        oEmptyArgs )
    Erase oEmptyArgs

    ' create a new logical form
    Dim oFormsCollection as Object
    oFormsCollection = oDocument.DrawPage.Forms
    Dim oSampleForm as Object
    oSampleForm = createUnoService( "com.sun.star.form.component.DataForm" )
    oFormsCollection.insertByName( "sample form", oSampleForm )

    ' create three radio buttons associated with three colors
    Dim oControlShape as Object
    Dim oControlModel as Object

    ' we want to add the equivalent of an com.sun.star.awt.XItemListener
    Dim sListenerInterfaceName as String
    sListenerInterfaceName = "com.sun.star.awt.XItemListener"
    Dim sListenerMethodName as String
    sListenerMethodName = "itemStateChanged"

    ' we want the onColorChange function in this module to be called
    Dim sMacroLocation as String
    sMacroLocation = "application:Standard.macro_assignment.onColorChange"
        ' note that this assumes that the module is called macro_assignment, and
        ' resides in the "Standard" library of the application-wide Basic macros

    Dim sColors(2) as String
    sColors(0) = "red"
    sColors(1) = "green"
    sColors(2) = "blue"
    Dim i as Integer
    For i = 0 To 2
        ' a shape
        oControlShape = oDocument.createInstance( "com.sun.star.drawing.ControlShape" )
        positionShape( oControlShape, 1000, 1000 + i * 800, 5000, 600 )

        ' a control model
        oControlModel = createUnoService( "com.sun.star.form.component.RadioButton" )
        oControlModel.Name = "colors"
        oControlModel.Label = "make it " & UCase( sColors( i ) )
        oControlModel.Tag = sColors( i )
        oSampleForm.insertByIndex( i, oControlModel )

        ' knit both
        oControlShape.Control = oControlModel
            ' yes, unfortunately the terminology is inconsistent here ...

        ' add the shape to the DrawPage
        oDocument.DrawPage.add( oControlShape )

        ' bind a macro to the "stateChanged" event
        Dim oEvent as new com.sun.star.script.ScriptEventDescriptor
        oEvent.ListenerType = sListenerInterfaceName
        oEvent.EventMethod = sListenerMethodName
        oEvent.ScriptType = "StarBasic"
        oEvent.ScriptCode = sMacroLocation
        oSampleForm.registerScriptEvent( i, oEvent )
    Next i

    ' switch the document (view) to alive mode
    Dim oURL as new com.sun.star.util.URL
    oURL.Complete = ".uno:SwitchControlDesignMode"
    createUnoService( "com.sun.star.util.URLTransformer" ).parseStrict( oURL )
    Dim oDispatcher as Object
    oDispatcher = oDocument.CurrentController.Frame.queryDispatch( oURL, "", 63 )
    oDispatcher.dispatch( oURL, oEmptyArgs() )
    Erase oURL

    ' set the focus to the first control
    oDocument.CurrentController.getControl( oSampleForm.getByIndex( 0 ) ).setFocus
End Sub

' this sets the size and position of a given shape
' Additionally, it anchors this shape at a paragraph
Sub positionShape( oShape as Object, X as Integer, Y as Integer, Width as Integer, Height as Integer )
    oShape.AnchorType = com.sun.star.text.TextContentAnchorType.AT_PARAGRAPH
        ' Not that this implies that you can use it for text documents only.
        ' The rest of the function also works for shapes in other documents

    Dim oPos as new com.sun.star.awt.Point
    oPos.X = X
    oPos.Y = Y
    oShape.setPosition( oPos )
    Erase oPos

    Dim oSize as new com.sun.star.awt.Size
    oSize.Width = Width
    oSize.Height = Height
    oShape.setSize( oSize )
    Erase oSize
End Sub

' This will be bound to the radio button's state changes
' At the moment, it simply changes their text color, but of course
' you could do more than this here ...
Sub onColorChange( oClickEvent as Object )
    If ( oClickEvent.Selected > 0 ) Then
        Dim nColor as Long
        Select Case oClickEvent.Source.Model.Tag
            Case "red"
                nColor = CLng( "&HFF0000" )
            case "green"
                nColor = CLng( "&H00FF00" )
            case "blue"
                nColor = CLng( "&H0000FF" )
        End Select

        Dim oControlParent as Object
        oControlParent = oClickEvent.Source.Model.Parent
        Dim i as Integer
        For i = 0 to oControlParent.getCount() - 1
            oControlParent.getByIndex( i ).TextColor = nColor
        Next i
    End If
End Sub

Heckert GNU white.svg

Content on this page is licensed under the Public Documentation License (PDL).