Eve Application Development

Michael L Brereton - 03 October 2008, http://www.ewesoft.com/

 

Beginners Guide - Contents

<< Previous: Event Handling

>> Next: Handles And Tasks

GUI Data Editing

GUI Data Editing   1

Using The Editor For Form-Centric Entry. 2

Using LiveObject For Data-Centric Entry. 8

 

 

The Eve environment provides an easy way to transfer data between a GUI control (e.g. an Input text control, or a CheckBox control) and a Field (member variable) of an object. This is done via an eve.ui.data.Editor control, which inherits from Form.

 

There are two easy ways of doing this:

  1. Create a customized Editor form with the data that you want to edit as normal Java fields. Then add controls that will be used to edit the fields on-screen and write specially named methods to handle events generated during field editing.

  2. Create a customized eve.ui.data.LiveObject with the data that you want to edit as normal Java fields. Then override the addToPanel() method to add controls that will be used to edit the fields on-screen to an Editor. Then write specially named methods to handle events generated during field editing.


The first method is Form-centric – it is useful for forms that act as control panel and where the data does not need to be persistent. The second method is Data-centric – it keeps the data and the GUI separate and is useful for editing data that must be persistent. The LiveObject class has a number of methods that you can use for persisting the data it contains.

 

An InputStack is a very useful container that can be used to build simple user interfaces. It does all the work of aligning prompts with their associated controls – all you have to do is to add a control and specify the prompt that should be displayed with it. It can handle multiple columns and it can be set to place the prompt and the control on separate lines (for small display screens).

Using The Editor For Form-Centric Entry

We will use an example of a Login Screen for this. We will react to changes in data and to the action of buttons being pressed.

Setting Up The Fields

This first section of the code shows the data that will be input and the layout of the Editor.

 

package evesamples.ui.editor;

 

import eve.ui.Button;

import eve.ui.CellPanel;

import eve.ui.CheckBox;

import eve.ui.Control;

import eve.ui.Gui;

import eve.ui.Input;

import eve.ui.InputStack;

import eve.ui.Label;

import eve.ui.MessageBox;

import eve.ui.ScrollableHolder;

import eve.ui.VerticalScrollPanel;

import eve.ui.data.Editor;

 

public class LoginScreen extends Editor{

      // Desktop options.

      public static final int DESKTOP_DEFAULT = 0;

      public static final int DESKTOP_KDE = 1;

      public static final int DESKTOP_GNOME = 2;

      // The main inputs.

      public String userName = "name";

      public String password = "zippy";

      public int desktopType = DESKTOP_KDE;

      public boolean rememberPassword = true;

      public String command = "sudo startup";

      public boolean changePassword = false;

      public String newPassword = "";

      public String confirmNewPassword = "";

      public String newPasswordMessage = "";

      //

      private Control changePasswordSection;

      private static String[] desktopChoices =  { "Default","KDE","Gnome" };

      // Convenience method to get an input stack.

      private InputStack getInputStack(CellPanel destination,String text,CellPanel subPanel)

      {

            if (destination == null) destination = this;

            if (subPanel != null) destination = subPanel;

            InputStack is = new InputStack();

            destination.addLast(is).setCell(HSTRETCH);

            if (text != null) {

                  if (subPanel != null) subPanel.setText(text);

                  else is.setText(text);

            }

            return is;

      }

      // Convenience method to add a password field to an input stack.

      private Input addPasswordField(InputStack stack, String prompt)

      {

            Input in = stack.addInput(prompt,"");

            in.isPassword = true;

            return in;

      }

      // Setup the LoginScreen.

      public LoginScreen()

      {

            title = "Login Screen";

            //

            // This might be run on a PocketPC/Smartphone,

            // So use a VerticalScrollPanel to hold the data.

            // This will also allow the SIP to move active controls

            // to be visible.

            //

            CellPanel data = new CellPanel();

            VerticalScrollPanel vsp = new VerticalScrollPanel(

                        new ScrollableHolder(data));

            addLast(vsp);

            // Start with the standard user name and password.

            {

                  InputStack is = getInputStack(data,"Login",null);

                  is.inputLength = 20;

                  Input in = is.addInput("User Name:", "");

                  // Add it to the field list, match the name to the Java Field.

                  addField(in,"userName");

                  // Now do the password, in a single line.

                  addField(addPasswordField(is,"Password:"),"password");

            }

            // Add some options.

            {

                  InputStack is = getInputStack(data,"Options",null);

                  addField(is.addChoice("Desktop:", desktopChoices, 0),"desktopType");

                  addField(is.addInput("Command:", ""),"command");

                  addField(is.addCheckBox("Remember:"),"rememberPassword");

            }

            // Add the option to change the password.

            {

                  InputStack is = null;

                  data.addLast(addField(new CheckBox("Change Password"),"changePassword"))

                        .setCell(HSTRETCH);

                  CellPanel cp = new CellPanel();

                  data.addLast(cp).setCell(HSTRETCH);

                  changePasswordSection = is = getInputStack(data,"Change Password",cp);

                  addField(addPasswordField(is,"New Password:"),"newPassword");

                  addField(addPasswordField(is,"Confirm Password:"),"confirmNewPassword");

                  addField(cp.addLast(new Label(" ")),"newPasswordMessage").setCell(HSTRETCH);

                  changePasswordSection.modify(Disabled, 0);

            }

            // Add the action buttons.

            // addButton() will add it to the bottom of the Form

            // or the the SoftKeyBar as appropriate.

            {

                  Button b = new Button("Login");

                  addButton(b);

                  addField(b,"login");

                  // Add the standard Cancel button.

                  // That will cause the Editor to exit with a value

                  // of IDCANCEL.

                  doButtons(CANCELB);

            }

            // If there is a SIP tell all controls to leave it on

            // to reduce screen resizing.

            modifyAll(KeepSIP,0);

      }

 

The Editor is created in three sections: The User Name/Password section (“Login”), the Login Options section (“Options”) and the Change Password section (“Change Password”).

 

In the “Login” section we first create and add an InputStack with the name “Login”. Then we add an Input using addInput() in the InputStack and then add that returned Input as a field to the Editor using addField(in,"userName");. Note that userName matches the field name defined at the start of the class.

 

The following line adds the password field and then adds it as a field to the editor in a single line.

            {

                  InputStack is = getInputStack(data,"Login",null);

                  is.inputLength = 20;

                  Input in = is.addInput("User Name:", "");

                  // Add it to the field list, match the name to the Java Field.

                  addField(in,"userName");

                  // Now do the password, in a single line.

                  addField(addPasswordField(is,"Password:"),"password");

            }

 

Then we add the options, including a Choice as the first one.

 

            // Add some options.

            {

                  InputStack is = getInputStack(data,"Options",null);

                  addField(is.addChoice("Desktop:", desktopChoices, 0),"desktopType");

                  addField(is.addInput("Command:", ""),"command");

                  addField(is.addCheckBox("Remember:"),"rememberPassword");

            }

 

Then we add the Change Password section. Note that this section is initially disabled – we will enable it when the user selects the “Change Password” option.

 

            // Add the option to change the password.

            {

                  InputStack is = null;

                  data.addLast(addField(new CheckBox("Change Password"),"changePassword"))

                        .setCell(HSTRETCH);

                  CellPanel cp = new CellPanel();

                  data.addLast(cp).setCell(HSTRETCH);

                  changePasswordSection = is = getInputStack(data,"Change Password",cp);

                  addField(addPasswordField(is,"New Password:"),"newPassword");

                  addField(addPasswordField(is,"Confirm Password:"),"confirmNewPassword");

                  addField(cp.addLast(new Label(" ")),"newPasswordMessage").setCell(HSTRETCH);

                  changePasswordSection.modify(Disabled, 0);

            }

 

Lastly, we will add in the command buttons – one custom button called “Login” and then the standard Form Cancel button.

 

            // Add the action buttons.

            // addButton() will add it to the bottom of the Form

            // or the the SoftKeyBar as appropriate.

            {

                  Button b = new Button("Login");

                  addButton(b);

                  addField(b,"login");

                  // Add the standard Cancel button.

                  // That will cause the Editor to exit with a value

                  // of IDCANCEL.

                  doButtons(CANCELB);

            }

            // If there is a SIP tell all controls to leave it on

            // to reduce screen resizing.

            modify(KeepSIP,0);

 

This is how the Editor looks when run:

 

                               

 

On the desktop.                                            With the /p5 (PocketPC WM5) option.            With the /s5 (Smartphone WM5) option.

Handling Field Events

We will handle two types of field events – FieldChanged and Action events. To intercept and handle these events all we need to do is define a method that looks like this:

 

            public void <field_name>_changed(Editor ed) or public void <field_name>_changed()

and

            public void <field_name>_action(Editor ed) or public void <field_name>_action()

 

Note that the methods must be public. The Editor parameter is optional and is really only useful with Data-centric editing, since with the Editor the parameter will always be itself.

 

To start, we handle a change to the userName field by converting it to lower case letters and then sending it back to the Input. The toControls(fields) method is used to transfer data from a set of fields to their on-screen editing controls. The parameter it takes is a comma separated list of field names. There is also a version that takes no arguments that causes all the Editor controls to update themselves. There are also corresponding fromControls() methods that cause data to be moved from the controls to the fields.

 

      /*

       * Handle the "userName" changed event.

       */

      public void userName_changed(Editor ed)

      {

            userName = userName.toLowerCase();

            toControls("userName");

      }

      /*

       * Handle the "changePassword" changed event.

       */

      public void changePassword_changed()

      {

            changePasswordSection.set(Disabled,!changePassword);

            if (changePassword){

                  Gui.ensureVisible(findControlFor("newPasswordMessage"));

                  findControlFor("newPassword").takeFocus(ByRequest);

            }

            changePasswordSection.repaintNow();

      }

 

The changePassword_changed() method is more complicated. It enables/disables the section containing the new/confirm password inputs depending on the state of changePassword. If changePassword is true, it additionally calls Gui.ensureVisible() to ensure that the newPasswordMessage field is visible and then sets the focus to the newPassword field.

 

Next we handle the new passwords.

 

      private String validateNewPasswords(boolean forLogin)

      {

            if (!changePassword) return null;

            if (newPassword.length() < 6) return "New password not long enough!";

            if ((confirmNewPassword.length() == 0) && !forLogin)

                  return "Confirm the new password.";

            if (!newPassword.equals(confirmNewPassword)) return "New passwords don't match!";

            return null;

      }

      private String doValidateNewPasswords(boolean forLogin)

      {

            String got = validateNewPasswords(forLogin);

            newPasswordMessage = got == null ? "" : got;

            toControls("newPasswordMessage");

            return got;

      }

      public void newPassword_changed()

      {

            doValidateNewPasswords(false);

      }

      public void confirmNewPassword_changed()

      {

            doValidateNewPasswords(false);

      }

 

As the new/confirm password fields are changed, we do some validation and display a single lined message as the field newPasswordMessage. The validateNewPasswords() determines the correct message and doValidateNewPasswords() will update the display with the result of validateNewPasswords(). Handling the newPassword and confirmNewPassword changed events is then simply a call to doValidateNewPasswords().

 

Lastly we react to the user pressing the Login button. If there is an error validating the new passwords a message is flashed and login is denied. Otherwise we simply show a successful login message, but in a real application the login procedure would be done now.

 

      public void login_action()

      {

            String msg = doValidateNewPasswords(true);

            if (msg != null){

                  Gui.flashMessage(msg,this);

                  findControlFor("newPassword").takeFocus(ByRequest);

                  return;

            }

            String text = "You have successfully logged in!";

            if (changePassword) text += "\n\nYour password has been changed\nfor your next login.";

            new MessageBox("Logged In",text,MessageBox.MBOK).execute();

      }

 

Using LiveObject For Data-Centric Entry

The eve.ui.data.LiveObject class has a number of uses, however here we will only discuss how they can be used to edit data in an Editor. The procedure is very similar to the Form-Centric entry except that the data is kept separate from the Editor and the Editor can be set to edit different instances of the Object.

Setting Up The Fields

This is very similar to the Form-Centric entry and it starts in almost the same way. However we must setup the Editor a little differently. A LiveObject implements LiveData and that has the method:

 

public Editor getEditor(int editorOptions);

 

However we do not need to override this method – it is already implemented to deal with all the complicated issues of creating an Editor and associating it with the data object. Instead we override the method:

 

public void addToPanel(CellPanel addTo, Editor ed, int editorOptions);

 

When this method is called, the Editor is already created but you do not add controls to it directly. Instead, you add all your controls to the CellPanel addTo. The editorOptions parameter is the same value that was used when getEditor() was called. The Editor is also set up such that addTo is placed inside a ScrollbarPanel which you can enable by calling enableScrolling(boolean horizontally, boolean vertically) on the Editor.

 

package evesamples.ui.editor;

 

import eve.ui.Button;

import eve.ui.CellPanel;

import eve.ui.CheckBox;

import eve.ui.Control;

import eve.ui.Gui;

import eve.ui.Input;

import eve.ui.InputStack;

import eve.ui.Label;

import eve.ui.MessageBox;

import eve.ui.data.Editor;

import eve.ui.data.LiveObject;

 

public class LoginObject extends LiveObject{

 

      // Desktop options.

      public static final int DESKTOP_DEFAULT = 0;

      public static final int DESKTOP_KDE = 1;

      public static final int DESKTOP_GNOME = 2;

      // The main inputs.

      public String userName = "name";

      public String password = "zippy";

      public int desktopType = DESKTOP_KDE;

      public boolean rememberPassword = true;

      public String command = "sudo startup";

      public boolean changePassword = false;

      public String newPassword = "";

      public String confirmNewPassword = "";

      public String newPasswordMessage = "";

      //

      private static String[] desktopChoices =  { "Default","KDE","Gnome" };

      // Convenience method to get an input stack.

      private InputStack getInputStack(CellPanel destination,String text,CellPanel subPanel)

      {

            if (subPanel != null) destination = subPanel;

            InputStack is = new InputStack();

            destination.addLast(is).setCell(Control.HSTRETCH);

            if (text != null) {

                  if (subPanel != null) subPanel.setText(text);

                  else is.setText(text);

            }

            return is;

      }

      // Convenience method to add a password field to an input stack.

      private Input addPasswordField(InputStack stack, String prompt)

      {

            Input in = stack.addInput(prompt,"");

            in.isPassword = true;

            return in;

      }

 

      public void addToPanel(CellPanel addTo, Editor ed, int editorOptions)

      {

            ed.title = "Login Screen";

            //

            // This might appear on a PocketPC/Smartphone,

            // So use a VerticalScrollPanel to hold the data.

            // This will also allow the SIP to move active controls

            // to be visible.

            //

            ed.enableScrolling(false, true);

            CellPanel data = addTo;

            // Start with the standard user name and password.

            {

                  InputStack is = getInputStack(data,"Login",null);

                  is.inputLength = 20;

                  Input in = is.addInput("User Name:", "");

                  // Add it to the field list, match the name to the Java Field.

                  ed.addField(in,"userName");

                  // Now do the password, in a single line.

                  ed.addField(addPasswordField(is,"Password:"),"password");

            }

            // Add some options.

            {

                  InputStack is = getInputStack(data,"Options",null);

                  ed.addField(is.addChoice("Desktop:", desktopChoices, 0),"desktopType");

                  ed.addField(is.addInput("Command:", ""),"command");

                  ed.addField(is.addCheckBox("Remember:"),"rememberPassword");

            }

            // Add the option to change the password.

            {

                  InputStack is = null;

                  data.addLast(ed.addField(new CheckBox("Change Password"),"changePassword"))

                        .setCell(ed.HSTRETCH);

                  CellPanel cp = new CellPanel();

                  data.addLast(cp).setCell(ed.HSTRETCH);

                  Control changePasswordSection = is = getInputStack(data,"Change Password",cp);

                  ed.addField(addPasswordField(is,"New Password:"),"newPassword");

                  ed.addField(addPasswordField(is,"Confirm Password:"),"confirmNewPassword");

                  ed.addField(cp.addLast(new Label(" ")),"newPasswordMessage").setCell(ed.HSTRETCH);

                  changePasswordSection.modify(ed.Disabled, 0);

                  ed.getProperties().set("changePasswordSection",changePasswordSection);

            }

            // Add the action buttons.

            // addButton() will add it to the bottom of the Form

            // or the the SoftKeyBar as appropriate.

            {

                  Button b = new Button("Login");

                  ed.addButton(b);

                  ed.addField(b,"login");

                  // Add the standard Cancel button.

                  // That will cause the Editor to exit with a value

                  // of IDCANCEL.

                  ed.doButtons(ed.CANCELB);

            }

            // If there is a SIP tell all controls to leave it on

            // to reduce screen resizing.

            ed.modify(ed.KeepSIP,0);

      }

 

Note the line:

 

                  ed.getProperties().set("changePasswordSection",changePasswordSection);

 

In the previous version we kept a private variable called changePasswordSection. However, since the LiveObject that created the Editor is not necessarily the same LiveObject that is edited, the edited LiveObject may not have a valid reference to changePasswordSection. So instead we associate changePasswordSection with the Editor by setting it as a property of the Editor.

Handling Field Events

Handling the fields is again done in almost exactly the same way. However this time we need the reference to the Editor to handle any control modifications we might do.

 

      /*

       * Handle the "userName" changed event.

       */

      public void userName_changed(Editor ed)

      {

            userName = userName.toLowerCase();

            ed.toControls("userName");

      }

      /*

       * Handle the "changePassword" changed event.

       */

      public void changePassword_changed(Editor ed)

      {

            Control changePasswordSection = (Control)ed.getProperties().getValue("changePasswordSection",null);

            changePasswordSection.set(ed.Disabled,!changePassword);

            if (changePassword){

                  Gui.ensureVisible(ed.findControlFor("newPasswordMessage"));

                  ed.findControlFor("newPassword").takeFocus(ed.ByRequest);

            }

            changePasswordSection.repaintNow();

      }

     

      private String validateNewPasswords(boolean forLogin)

      {

            if (!changePassword) return null;

            if (newPassword.length() < 6) return "New password not long enough!";

            if ((confirmNewPassword.length() == 0) && !forLogin)

                  return "Confirm the new password.";

            if (!newPassword.equals(confirmNewPassword)) return "New passwords don't match!";

            return null;

      }

      private String doValidateNewPasswords(boolean forLogin,Editor ed)

      {

            String got = validateNewPasswords(forLogin);

            newPasswordMessage = got == null ? "" : got;

            ed.toControls("newPasswordMessage");

            return got;

      }

      public void newPassword_changed(Editor ed)

      {

            doValidateNewPasswords(false,ed);

      }

      public void confirmNewPassword_changed(Editor ed)

      {

            doValidateNewPasswords(false,ed);

      }

      public void login_action(Editor ed)

      {

            String msg = doValidateNewPasswords(true,ed);

            if (msg != null){

                  Gui.flashMessage(msg,ed);

                  ed.findControlFor("newPassword").takeFocus(ed.ByRequest);

                  return;

            }

            String text = "You have successfully logged in!";

            if (changePassword) text += "\n\nYour password has been changed\nfor your next login.";

            new MessageBox("Logged In",text,MessageBox.MBOK).execute();

      }

 

And again this works in precisely the same way as the Form-Centric version.