[ Previous document | Content Table | Next document ]

13    Forms

13.1    Introduction

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 OpenOffice.org 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 OpenOffice.org Writer, these elements are represented by components from com.sun.star.form.

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

When discussing forms, the difference between form documents and logical forms have to be distnguished. The form document refers to a document as a whole, and logical forms is a logical concept, basically a set of controls with additional properties. See below for details.
Within the scope of this chapter, when a "form" is referred to, we mean the logical form. The logical form is more interesting from the API programmer's perspective.

13.2    Models and Views

13.2.1    The Model-View Paradigm

A basic concept to understand about forms and controls in OpenOffice.org 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.


In UNO, the simplest conceivable model is a component implementing com.sun.star.beans.XPropertySet 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 OpenOffice.org. 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:


Illustration 1.1


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.

13.2.2    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 is sees.


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 com.sun.star.awt, the com.sun.star.awt.UnoControl and com.sun.star.awt.UnoControlModel services. As discussed later, the model hierarchy in com.sun.star.form.component extends the hierarchy of com.sun.star.awt, whereas the control hierarchy in com.sun.star.form.control is small.

Everything 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 OpenOffice.org Basic dialogs (11.5.2 Basic and Dialogs - Programming Dialogs and Dialog Controls - Dialog Controls).

13.2.3    Model-View Interaction

When a model and a view interoperate, a data transfer in both directions is required, from the model to the view and conversely.

Consider a simple text field. The model for a control implements a com.sun.star.form.component.TextField 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 f 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 com.sun.star.beans.XPropertyChangeListener 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, of 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.

13.2.4    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 OpenOffice.org by inserting new controls, resizing them, and modifying their properties,together with control models and shapes. although OpenOffice.org hides this. In live mode, the controls interact with the user for data input.

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

The following example switches a given document view between the two modes: (Forms/DocumentViewHelper.java)

/** 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 OpenOffice.org.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 OpenOffice.org 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 com.sun.star.view.XControlAccess at the controller of a document view is made for. (Forms/DocumentViewHelper.java)

/** 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);

}

Focussing Controls

To focus a specific control in your document, or more precisely, in one of the views of your document: (Forms/DocumentViewHelper.java)

/** 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, focussing controls is reduced to locating controls. Once you have located the control, the com.sun.star.awt.XWindow interface provides everything needed for focussing.

13.3    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 6.1.1 Office Development - OpenOffice.org Application Environment - Overview - Framework API - Frame-Controller-Model Paradigm 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.

13.3.1    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 com.sun.star.container.XChild and com.sun.star.container.XIndexAccess.

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 com.sun.star.form.FormComponent service. Its basic characteristics are:

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 com.sun.star.form.FormComponents 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 com.sun.star.form.FormComponents container. (Forms/FormLayer.java)

/** 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's 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 com.sun.star.form.component.Form. See below for details regarding this service. For now, we are interested in that it exposes the com.sun.star.form.FormComponent service, as well as the com.sun.star.form.FormComponents 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 13.5 Forms - 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 com.sun.star.form.Forms service. This is nothing more than an instance of com.sun.star.form.FormComponents. In fact, the differentiation exists for a non-ambiguous runtime instantiation of a root.


Note that the com.sun.star.form.Forms service does not state that components implementing it are a com.sun.star.form.FormComponent. This means this service acts as a tree root only, opposite to a com.sun.star.form.Forms 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 com.sun.star.drawing.DrawPage. A page optionally supports the interface com.sun.star.form.XFormsSupplier giving access to the collection. In the current OpenOffice.org 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. (Forms/DocumentHelper.java)

/** 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 com.sun.star.form.FormControlModel that is discussedin more detail below. A form control model promises to support the com.sun.star.form.FormComponent service, meaning that it can act as a child in our model hierarchy.

In addition, it does not claim that the com.sun.star.form.FormComponents 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());

13.3.2    Control Models and Shapes

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

From 9.3.2 Drawing - Working with Drawing Documents - Shapes, you already know about shapes. They are also part of a document model. The control shapes, com.sun.star.drawing.ControlShape 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.


The user interface of OpenOffice.org 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:


Illustration 1.2

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: (Forms/FormLayer.java)

/** creates a control in the document

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

    @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:

The above does not mention about 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 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 OpenOffice.org API that do not contain this mechanism. In practice, the only known implementation is OpenOffice.org.


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 com.sun.star.awt.UnoControlModel:DefaultControl property describes the service to be instantiated when automatically creating a control for a model.

13.4    Form Components

13.4.1    Basics

According to the different form document types, there are different components in the com.sun.star.form 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 13.3.1 Forms - Form Elements in the Document Model - Hierarchy - Form Control Models, the base for all our control models is the com.sun.star.form.FormControlModel service. Let us look at the most relevant elements of the declaration of this service and what a component must do to support it:

com.sun.star.awt.UnoControlModel

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 com.sun.star.awt. This means support for the com.sun.star.awt.XControlModel interface, for property access and persistence.

com.sun.star.form.FormComponent

This service requires a form control model is part of a form component hierarchy. Refer to chapter 13.3.1 Forms - Form Elements in the Document Model - Hierarchy.

com.sun.star.beans.XPropertyState

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

com.sun.star.form.FormControlModel:ClassId

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


Note that the com.sun.star.form.FormControlModel 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 13.5 Forms - 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: (Forms/FLTools.java)

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

Forms

The OpenOffice.org API features different kinds of forms, namely the com.sun.star.form.component.Form, com.sun.star.form.component.HTMLForm, and com.sun.star.form.component.DataForm. 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 13.5 Forms - Data Awareness.


Though different services exist for HTML and data aware forms, there is only one form implementation in OpenOffice.org htat implements both services simultaneously.

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

com.sun.star.form.XForm

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

com.sun.star.awt.XTabControllerModel

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 com.sun.star.form.FormControlModel service has an optional property TabIndexthat contains the relative position of the control in the tabbing order. For example, a straightforward implementation of com.sun.star.awt.XTabControllerModel:setControlModels() would be simply to adjust all the TabIndex properties of the models passed to this method.

13.4.2    HTML Forms

The com.sun.star.form.component.HTMLForm 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 com.sun.star.form.XReset and com.sun.star.form.XSubmit, 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.

13.5    Data Awareness

A major feature of forms in OpenOffice.org is that they can be data aware. You create form documents where the user manipulates data from a database that is accessible in OpenOffice.org. For more details about data sources, refer to chapter 12 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 cis 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.

13.5.1    Forms

Forms as Row Sets

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

Loadable Forms

A major difference of data forms compared to the underlying row set is the that forms are loaded, and t 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 com.sun.star.sdbc.XRowSet:execute() method. The com.sun.star.form.XLoadable is designed to fit the needs of a form document, for example, it a 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 com.sun.star.form.XLoadable interface. You can add a com.sun.star.form.XLoadListener. This listener not only tells you when load-related events have occurred that is achieved by the com.sun.star.sdbc.XRowSetListener, 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 com.sun.star.form.XLoadable:reloading() 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.


Though all the functionality provided by com.sun.star.form.XLoadable can be simulated using the com.sun.star.sdbc.XRowSet 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 com.sun.star.sdbc.XRowSet:execute(), the set is positioned before the first record. When you use com.sun.star.form.XLoadable:load(), the set is positioned on the first record, as you would expect from a form.

Sub Forms

A powerful feature of OpenOffice.org 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 trough 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: (Forms/FormLayer.java)

    // 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:


Illustration 1.3

The code is straigh forward, except 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 com.sun.star.sdb.RowSet:Filter, com.sun.star.sdb.RowSet:ApplyFilter and com.sun.star.sdb.RowSet:Order area used. (Forms/SalesFilter.java)

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


Note the notation for the date in braces: This is the standard ODBC notation for date values, and it is the safest method to supply OpenOffice.org 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 tocom.sun.star.sdbc.Statement:EscapeProcessing. OpenOffice.org 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. Everytime 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 com.sun.star.sdb.RowSet. 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 com.sun.star.sdbc.XParameters interface, exported by the row set.

However, forms allow another way. They export the com.sun.star.form.XDatabaseParameterBroadcaster 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 com.sun.star.form.component.DataForm, 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 13.5.1 Forms - Data Awareness - Forms - Sub Forms.

  2. If there are parameter values left, that is, not filled in, the calls to the com.sun.star.sdbc.XParameters interface are examined. All values previously set through this interface are filled in.

  3. If there are still parameter values left, the com.sun.star.form.XDatabaseParameterListeners are invoked. Any component can add itself as a listener using the com.sun.star.form.XDatabaseParameterBroadcaster interface implemented by the form.
    The listeners then have the chance to fill in anything still missing.

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

13.5.2    Data Aware Controls

The second part of the Data Awareness capabilities of OpenOffice.org 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 com.sun.star.form.DataAwareControlModel.

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 isa 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:


Illustration 1.4

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 com.sun.star.form.component.DataForm 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, 13.2.3 Forms - Models and Views - 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, com.sun.star.form.component.TextFieldthat 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 com.sun.star.form.component.NumericField 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:


Illustration 1.5

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 com.sun.star.form.XBoundComponent that provides the method commit. Note that the XBoundComponent is derived from com.sun.star.form.XUpdateBroadcaster. This means that listeners are added to a component to monitor and veto the committing of data.


Illustration 1.6

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

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 OpenOffice.org, for every document view and form, there is an instance of the com.sun.star.form.FormController service, together with some not-yet UNO-based code that takes on the role of a controller.

13.6    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 OpenOffice.org's built-in methods, but have a solution in the OpenOffice.org UNO API.

13.6.1    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 (com.sun.star.form.XReset) 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 com.sun.star.form.component.DateField 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. (Forms/FormLayer.java)

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 com.sun.star.form.XReset:resetted() 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.

13.6.2    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. OpenOffice.org 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: (Forms/KeyGenerator.java)

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 com.sun.star.sdb.XRowSetApproveBroadcaster is used. It notifies listeners when rows are inserted, the listeners can veto this, and final changes can be made to the new record: (Forms/KeyGenerator.java)

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;

}

13.6.3    Data Validation

OpenOffice.org's only offering for client-side data validation is that it automatically rejects null values for fields where input is required.

Often you want to validate data as soon as it is written. You have two possibilities here:

Note the important differences between both solutions. Using an com.sun.star.form.XUpdateListener 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 com.sun.star.sdb.XRowSetApproveListener 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: (Forms/GridFieldValidator.java)

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;

}

[ Previous document | Content Table | Next document ]