6.3 Page

A first cut version of the page class will be generated. This includes the constructors, empty stubs for the most important life-cycle(Page Life Cycle)-methods and empty stubs for all widget event methods.

Every time a XMA page becomes visible protected void enter() is called. For DialogPages opened with their invoke() method, this method is called exactly once per instance. For NotebookPages this method is called every time the user navigates to the page. For EmbeddedPages it depends on their parent page. Their enter() is called every time enter() is called on their parent. If they are embedded dynamically into a page which is already visible, enter() is called at the time of embedding. If DialogPages are embedded, they are treated just like EmbeddedPages.

When the method is called, all widgets and widget models are already initialized.

In this method you will typically read some input parameters(Component Properties)and do a remote procedure call(Remote Procedure Calls) to fill the rest of the widgets with data.

    String strParamSID = getTypedComponent().getSparbuchid();
    idSparbuch.set(strParamSID);
    newRemoteCall("search").execute();
[Tip]

Principally you can do any kind of initialisation here.

[Caution]

Static initialization of widgets which are supported by the guidesigner (e.g. labels, layout, etc) must be done there.

[Caution]

Initial enabling/disabling of widgets is better done in stateChanged().

Every time a user event which changes any widget of the page happens, the widget models are updated from their widgets and determineState() and stateChanged() of the affected page are called in sequence exactly once after the event method. They are called the first time immediately after enter(). During the creation of a page and during the execution of event methods no events are triggered.

In public void determineState() you should calculate the current state of the page from the current values of the widget models.

In public void stateChanged() you should enables or disable widgets according to the current state of the page. Do not forget to disable all action-buttons if there are any errors from validation of the user inputs.

    if(getErrorCount()>0){
        okW.setEnabled(false);
    } else{
        okW.setEnabled(true);
        getShell().setDefaultButton( okW );
    }

Almost all events are handled by the runtime. User input is validated and all valid changes in the widgets are propagated automatically to the widget models. There are just a few events which are handled directly by the programmer. The triggering SWT-Event is not passed as argument to your event method, but it can be accessed by calling getCurrentEvent() inside your event method.

[Caution]

It is not recommended to attach event handler directly to SWT-widgets. For all events handled by the runtime, this is explicitly forbidden, since it would interfere with the XMA-Runtime.

In the rare case you have to use your own event handler on an SWT-widget, you have to disable all events for the runtime during your event processing.

This is best be done by using the XMA event adapters for SWT (provided with XMA since version 1.8.5). There exist event save event handling adapters for selection events on widgets not supported by the Guidesigner like toolbar items, mouse events, keyboard events, focus events and drag and drop. See the javadoc for: XMAFocusAdapter, XMAKeyAdapter, XMAMouseAdapter, XMASelectionAdapter, XMADragSourceAdapter, XMADropTargetAdapter.

You use these adaptors pretty much like the event adaptors delivered with SWT. The main difference is, that you do not overload e.g. widgetSelected() directly but the method widgetSelectedImpl() instead. This is because the original method contains the event disabling code and calls the corresponding -impl method in all XMA-adaptors. Additionally it needs a your page as parameter for its constructor to do its work. The adaptor also catches all exceptions not handled by the -impl method, logs it and shows it to the user.

    itemW.addSelectionListener(new XMASelectionAdapter(this) {
        public void widgetSelectedImpl(SelectionEvent event) {
            // place your event handling code here ...
        }
    });

Note: An added listener is only notified if its Widget or Control has the focus!

Old code in XMA-applications often still has event disabling code like the next example directly in the event handling methods.

    IDialogPage dialog = getDialogPage();
    try {
        dialog.setEventsEnabled(false);
        ... // your event handling code goes here
    } finally {
        dialog.setEventsEnabled(true);
    }

This still works ok, but may be missing exception handling code. Unhandled exceptions in event listeners directly attached to SWT-widgets lead to unexpected behaviour. It is recommended to upgrade to the XMA-adaptors mentioned above.

Keys with a certain function which are available from the whole application (like the F1 help) can be implemented by using the XMAKeyAdapter. In this case the listener cannot be added to a Widget or Control as it would be only notified when its item has the focus. Instead it is added to the Display.

The following listing shows an example of adding an ESC hot key. Create a suitable adapter:

    XMAKeyAdapter adapter = new XMAKeyAdapter(this){
        public void keyPressedImpl(KeyEvent event)  {
            if (event.keyCode == SWT.ESC){
                getDialog().closeCancel();
            }
        }         

Then set this adapter at the Display as filter:

    getShell().getDisplay().addFilter(SWT.KeyDown, new TypedListener(adapter));

KeyEvent.keyCode identifies the actual pressed key, whereas KeyEvent.character represents the value resulting from a key after all modifiers were applied (like CTRL or SHIFT). The class SWT offers constants identifying keyboard keys, but comparisons with a character is possible as well.

The following methods can be overridden if you need some more control over the closing of the page.

protected boolean close() will be called every time the dialog is tried to be closed by user interaction (like by the click on the close icon). If you return true, the dialog will actually be closed. If you return false, the dialog will remain open. This method is only available on DialogPages. It will not be called after closeOK() or closeCancel().

protected void leave() will be called every time the page becomes invisible. For DialogPages this method is called exactly once per instance. For NotebookPages and WizardPages this method is called every time the user navigates off the page. In this method some cleanup code can be placed. It will be called after close(), closeOk() or closeCancel().

The runtime catches all exceptions thrown by your code and shows them in a modal message box. If you want to change the way the runtime shows exceptions, you can overwrite the method showException() and implement your own user notification.

Errors detected by validators (Formatters/Validators) are shown in the status bar. You can add additional error messages with the method setError() on the dialog page. It assigns an error message to a widget. Only one message will be shown in the status bar at any given time. If there exists an error for the widget currently owning the focus, this error will be shown, else the error of the next widget in the tab order will be shown. If there exists a validation error and an error set with setError() for the same widget, the validation error will be shown.

Do not forget to remove the error message with clearError() when the error condition no longer exists.

Warning and info messages can be assigned to widget, too using the methods setWarning() and setInfo(). Warning and info messages are showed in the status bar, too. They are only showed, if no error is set for any widget. The precedence rules are quite similar to the rules for the errors. If there exists a warning for the widget currently owning the focus, this warning will be shown, else the warning of the next widget in the tab order will be shown. If there exists no warning, the info for the widget owning the focus if exists, else the info for the next widget in tab order will be shown. If there exists no errors, warnings or infos, the status bar will be empty.

Warnings and infos have to be removed using clearWarning() and clearInfo().

If you create errors, warnings or infos assigend to widgets on an embedded page, these errors, warnings and infos will be automatically removed if the embedded page is removed.

You can determine the total number of widgets with errors on the dialog with the method getErrorCount(). It counts all validation errors and erros set with setError(). Warnings and infos are not counted. This method will be useful for enabling/disabling of buttons.

The DialogPage offers two protected attributes for status bar manipulation: DialogPage.statusBar and the DialogPage.statusBarComposite, the parent of the status bar. Per default the statusBar fills the statusBarComposite to 100%. If another appearance is wished (as an additional output field) the statusBar can be reattached and additional widgets can be attached to the statusBarComposite. The reattachment can be done for one specific DialogPage by overriding (and call to the super method) DialogPage.createWidgets() and DialogPage.removeWidgets(). If the status bar should be changed for all DialogPages then this is done in the project specific DialogPage parent by overriding DialogPage.initGUI() and DialogPage.removeWidgetsBase().

Note: Always call statusBarComposite.layout(false) after reattachment!

To set the focus to a desired control you call setFocus() on the DialogPage and pass the desired widget as parameter.

    getDialogPage().setFocus(nameW);
This method works even in situations, where calling setFocus() directly on SWT-Controls does not work, for example if called in the enter() method of a dynamically embedded page.

I first cut version of the page class will be generated. This includes only the constructor.

You will have to implement the server side event methods(Server side) here.

You can only call dialog pages within the same component as the caller directly. To call a dialog of a different component see Calling a Component. You call the component, which in turn will call one of its dialog pages.

To call a modal dialog page within the same component, you create an instance of the page, eventually set some input data and call invoke() on it.

There are three constructors to create a dialog page:

public <classname>(PageClient parent)   

creates the page with the given page as parent. It is created inside the same component as parent and uses the window of parent as parent window. Use this method if want to call the page from an other page.

public <classname>(ComponentClient component, Shell parent) 

creates the page inside the given component with the given shell as parent window. Use this method if you call the page from the component.

public <classname>(ComponentClient component)  

creates the page inside the given component without a parent window. Use this method only if you do not have a parent page or parent window.

    // create the dialog page
    SpardetailDialog dialog = new SpardetailDialog(this);
    // enter some data
    dialog.idSparbuch.set( idSparbuch );
    // open the dialog
    boolean ok = dialog.invoke();
    // read back some result data
    if(ok) { saldo = dialog.betSaldo.toDouble(); }
    // free the page model
    dialog.removeModel(); 
If the property ynModelLazyGenerated of the dialog page is false, which is the default, its widget models are created from the constructor. You can access and use the widget models immediately after calling the constructor. The SWT-widgets do not exist at this time.

To actually show the dialog page call invoke() on it. For modal dialogs invoke() blocks until the Dialog is closed. It returns true if the dialog is closed with closeOk(). It returns false if the dialog is canceled. For non modal dialogs invoke just opens the dialog and returns immediately.

After the dialog is closed you can still access its widget models. Only the SWT-widgets are already destroyed.

After the dialog is closed you should free its model, by calling removeModel(). This saves memory by removing the server side page during the next remote procedure call. This is especially important if you implement several pages within a component.

When a component is closed, all its pages are freed automatically.

With XMA it is possible to create pages which can be visually embedded in other pages. Such embedded pages show their content in place inside the parent page.

First you create(Create a new Page)an EmbeddedPage in the guidesigner. Then you create the widgets of the page the same way as for any other page.

The size of the embedded page you see in the guidesigner is maybe not the size the page will have in its embedded place. The actual size of an embedded page is determined by its user who defines its FormData.

Programming am embedded component is pretty much the same as programming an other page(Programming a Client Side Page).

You cannot directly embed a page outside its component. If you want to use the embedded page in different components, you have to create an embedded component(Embedded Component).

To embed the page inside the same component, you create a XMA Container with the guidesigner. XMA Container is a special widget which serves as placeholder for the embedded page. You create(Create a new Widget) it like any other widget. You need to choose the embedded page for the property "EmbeddedPage". The place and size of the embedded page is defined by the place and size of the XMA Container.

The guidesigner does not generate a widget for the XMA Container. The code for embedding the embedded page is generated instead. You can access the embedded page with the instance name you entered for the XMA Container.

You can communicate with the embedded page via its member variables.

To give you more flexibility, DialogPages have been made embeddable, too. DialogPages can not be directly statically embedded in the guidesigner. Within its component DialogPages can be dynamically embedded using Tasks(Calling Components)or the technique described in Dynamically Embedding a Component(Dynamically Embedding a Component). Just pass the DialogPage as argument instead of a component.

To embed the DialogPage in a different component, you have to embed its component(Embedding the Component).

The guidesigner generates a base class for each page. This class has the name of the page class with "Gen" appended. You must not modify this class since it will be overridden every time you generate the component.

The generated base class contains member variables for

You can use these member variables, but do not assign new values to them. All member variables are initialized and synchronized with the server side by the runtime.

There are generated methods for

  • creating all widget models, initializing them and set the validators

  • creating all widgets, initializing them, setting all properties defined in the guidesigner and defining the layout

  • attaching all widget models to their respective widgets

  • dispatching events to the correct widget event method(Widget Events)

  • removing all widgets

  • removing all widget models

All these methods are called by the runtime.

The generated base class contains member variables for

You can use these member variables, but do not assign new values to them. All member variables are initialized and synchronized with the server side by the runtime.

There are generated methods for

You should call the transfer methods whenever you need to synchronize the widget models with the business data objects. The other methods are called by the runtime.

The information provided here is intended to be some background information to get a better understanding of what is going on when pages are opened or closed. Most of the methods presented here are implemented by the generated base class.

The following methods are called by the runtime to initialize a page.

  1. In the Constructor(Calling a Dialog Page) of the page the following information is stored in the page.

    • If the page is statefull or stateless on the server. This information is supplied by the generated base class. You define it by defining the property ynStateless of the page in the guidesigner.

    • The component containing this page. You pass this information to the constructor, or it is derived from the parent page.

    • The parent page of this page. This information is only supplied if you pass it to the constructor.

    • For dialogs pages: The style of the shell and the parent shell. The style of the shell is supplied by the generated base class. You define it by defining the properties codModality, ynClose, ynMin, ynMax, ynResize of XMADialogPage in the guidesigner. The parent shell is only supplied if you pass it to the constructor.

    • For notebook pages: The parent notebook. It is supplied by the generated base class of the containing page.

  2. Next createModels() is called (in the constructor). If you set the property ynModelLazyGenerated to true in the guidesigner, this method is called when the page is becoming visible the first time. It is called directly before createWidgets(). If the property ynModelLazyGenerated is set to false, is called directly from the constructor of the generated base class. Default is false and calling it from the constructor.

    The method is implemented in the generated base class.

    • It creates all widget models(What are Widget Models good for?) of the page and sets their respective validators(Formatters/Validators). You define the validators in the guidesigner.

    • It registers the page at the component. From this time on, all widget models of the page will be synchronized with the server.

    • It creates all subpages with ynModelLazyGenerated set to false.

    It is first called on the page itself and then on all created subpages.

  3. After invoking the page (by calling invoke()) createWidgets() is called. This method is called when the page is becoming visible the first time. If the widget models(What are Widget Models good for?) are not already created (ynModelLazyGenerated is true), createModels() is called first. The method is implemented in the generated base class. Here the main part of the SWT related initialization is done. Everything that is done here is to be defined in the guidesigner. It is first called on the page itself and then on all subpages.

    • It creates the composite of the page, if not already created

    • It instantiates the widgets with its styles

    • It sets constant texts (title, labels, tool tips) and images

    • It registers the event adapter

    • It defines the layout (attachments, distances, sizes)

    • It sets the initial focus

    • It defines the tab order

    It is first called on the page itself and then on all subpages.

  4. Next enter() is called. This method is called every time when the page is becoming visible. For DialogPages this method is called exactly once. For NotebookPages and WizardPages this method is called every time the user navigates to the page. For EmbeddedPages it depends on the page where they are embedded.

    In this method you will place your initialization code for the page; e.g. populating the widget models with data.

    It is first called on the page itself and then on all subpages.

  5. Next determineState() and stateChanged() are called. This methods are called every time one of the following events is fired by SWT: verifyText, modifyText, focusLost, widgetSelected, widgetDefaultSelected. This means on every keystroke on an editable widget and every time a selection or the focus changes. For widgetSelected and widgetDefaultSelected they will be called after the event method you defined in the guidesigner for the widget.

    This methods will also be called immediately after enter() . So it will be guarantied, they are called once before the page becomes visible.

    In this methods you will place your code for enabling and disabling widgets.

    Both methods are first called on the page itself and then on all subpages.

Upon termination of a page, as by closing a dialog, the runtime calls the following methods.

  1. close() is called every time the dialog receives a close event. It is not called if you call closeOK() or closeCancel() .

    In this method you can do some cleanup and decide if you really want the dialog to be closed. If you return false, the dialog will not be closed and the rest of these methods will not be called.

  2. leave() is called every time the page ends to be visible. For DialogPages this method is called exactly once. For NotebookPages and WizardPages this method is called every time the user navigates to another page. For EmbeddedPages it depends on the page where they are embedded.

    In this method you can place some cleanup code.

    It is first called on all subpages and then on the page itself.

  3. Next removeWidgets() is called. The method is implemented in the generated base class.

    It sets all member variables for the widgets to null.

    It is first called on all subpages and then on the page itself.

  4. removeModel() is not called automatically by the runtime, since you may still need to be able to read the data of the page after the dialog is closed. You have to call it yourself, if you have more dialog pages in your component. When the component terminates all pages of the component are freed automatically.

    The method is implemented in the generated base class.

    • It sets all member variables for the widget models to null.

    • It de-registers the page at the component. With the next RPC the page will be freed at the server.

    It is first called on all subpages and then on the page itself.

Normally page initializations are done by the enter() method (and often an RPC is called from there). This method will be called after the page window and its widgets are created (but still invisible). Unfortunately the invoke mechanism has not finished completely at this point so that a simple closeCancel() produces an unexpected behavior (a NullPointerException). Also for more tricky solutions like using Display.asyncExec() a safe, consistent system status can not be guaranteed.

A better way is to ensure the correct page initialization before invoking the page window with its widgets. Models (on client and server side) are already created in the page's constructor (if in the gui designer ynModelLazyGenerated is false - which is the default). At the client side the GUI related objects like widgets, the dialog or the shell is not existing at this point. These are created in the invoke() method (see Creation of the page). So you can implement an initialization method, for example init(), that can also call a RPC method for server side initialization. Call this method before invoke(). If an error occurs the page needs to be invoked. The following demo code, based on the auto generated sample from the XMA project creation wizard, should show this in detail.

Let‘s assume, that a main page TestDlg in a component Example has a RPC called init(), that is called from the client's enter() method. In the example below we can a NullPointerException because the inconsistent state caused by the exception thrown in enter().

Bad example:

 // client.Example:
    public void invoke(Composite parentComposite) {
        if ( parentComposite instanceof Shell ) {
            new TestDlg(this,(Shell)parentComposite).invoke();
        } else {
            new TestDlg(this).invoke();
        }
    }
// client.TestDlg:
    protected void enter() {
        newRemoteCall("init").execute(); // pray for no exception here
    }
// server.TestDlg:
    public void init (RemoteCall call, RemoteReply reply) {
        simpleCombo1.add(new String[] { "colt", "hat", "horse" });
        simpleCombo1.select("hat");
        if ( goodBadUgly() != GOOD )
            throw new AppException("Not the good guy.");
    }

Instead we can simply call the RPC in a method called before invoke(), for example init() (the server's init() stays the same).

Good example:

// client.Example:
    public void invoke(Composite parentComposite) {
        TestDlg dlg = null;
        if ( parentComposite instanceof Shell ) {
            dlg = new TestDlg(this,(Shell)parentComposite);
        } else {
            dlg = new TestDlg(this);
        }
        dlg.init(); // exception will break here
        dlg.invoke();
    }
// client.TestDlg:
    protected void init() {
        newRemoteCall("init").execute(); // no fear
    } 

What happens now if an Exception is thrown by init()? The invoke() will never be reached, so the page window will not be displayed. The Exception will be caught outside Example.invoke() by the XMA Runtime and displayed in a generic message box.

On the client side there exist some different base classes for pages. The generated base class of your page class is always derived from one of these. On the server side all pages are derived from PageServer.

A DialogPage is a page representing a dialog. It has its own window.

For using this class see Programming a Client Side Page.

A NotebookPage represents one page of a notebook. It becomes visible, when the corresponding tab is selected. It becomes invisible, every time an other tab is selected. It can only exist inside an other page.

To create a NotebookPage, first you create a tabfolder in the guidesigner. Then you create the NotebookPage as child of the tabfolder in the guidesigner.

From the enclosing page, you can access the NotebookPage via the variable with the instance name you has chosen in the guidesigner. You can use this instance on client and server side.

If you want to show some tabs in the tabfolder only if a certain condition is true, you can do the following:

  1. You prevent the automatic addition of the tab in the generated code by setting the property YNDynamic(Notebook Page) of the corresponding NotebookPage to true in the guidesigner.

  2. You create the NotebookPage and add it to the Notebook by calling addPage() in enter() of the Page containing the Notebook, if your want to show it.

        pageOne = new PageOne(tabfolder);
        tabfolder.addPage(pageOne);

You can remove it again later by calling removePage() .

An EmbeddedPage represents a rectangular area inside a page. It can be used to show the same group of widgets on different pages. It can also be used as main page of an embeddable component(Embedded Component). It is visually embedded into an other page and can only exist inside an other page or inside an embeddable component.

For using this class see Embedded Page.

A special DialogPage is a WizardPage, which is a frame for several EmbeddedPages (see EmbeddedPage) and navigates thru them. Normally the last page finishes the use case. Read more in Wizards

An AppShell represents a main window of an application. There are two classes derived from AppShell:

  • MenuAppShell

    Implements SWT-menus the shell's menu bar. It looks like a standard windows application menu.

  • TreeMenuAppShell

    Implements a tree used as menu (usually located at the left side of the window). Its menu looks like the navigation tree of web application

For using these classes see .

A wizard as user interface term means a multi step dialog, that is well focused to one use case and will be normally committed on the last step. Wizards in openXMA are realized by

Wizards often have a start (welcome) page with explaning texts, also can each page have an explanation area on top with a headline, some explaining text and maybe an icon.

Some wizards can be finished earlier, not only on the last page, in that case the values of the last, skipped pages will be taken as default.