Michael L Brereton - 03
October 2008, http://www.ewesoft.com/
<< Previous: Event Handling
>> Next:
Handles And Tasks
Using
The Editor For Form-Centric Entry
Using
LiveObject For Data-Centric Entry
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:
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).
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.
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.
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();
}
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.
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 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.