Download Eve Application Development - CourseLog

Document related concepts
no text concepts found
Transcript
The Eve Virtual Machine and SDK
SDK Documentation Downloads
Forum
Is Eve an Evolution of Ewe?
The major underlying difference between the old Ewe VM and the new Eve VM is that Eve
supports the Java Thread API. This includes the Thread class, and the
synchronized/wait()/notify() keyword and methods.
This difference has two major implications:
1. The native VM had to be rewritten completely.
2. The Java libraries had to be substantially rewritten.
However the main benefit of supporting Java threading makes the major work involved completely
justifiable: now standard java.xxx classes could be used in the library instead of ewe.xxx classes
which mimicked their java.xxx counterparts using the workable, but incompatible Ewe threading
model. Thus, instead of having eve.io.InputStream classes, the java.io.InputStream class could
be used instead. This principal applied to dozens of classes. Hence the main difference in the
runtime class libraries are:
Large numbers of ewe.xxx classes have been discarded and replaced with standard java.xxx classes.
This immediately relieves the vast majority of porting problems when writing applications for the
Eve VM. Not only do far fewer new classes have to be learned (standard Java classes would work
most of the time) but existing applications and libraries could be far more easily ported to the VM.
The other major change is in the GUI. The functionality and look of the GUI in the new VM is
almost identical to that of Ewe but with a much better naming convention and better organization of
classes into separate independent packages.
What is Eve?
Eve is a portable Virtual Machine that executes Java byte code (i.e. Java “.class” files) and has been
specifically designed to run well on mobile devices as well as on the desktop. Standard Java started
on the desktop and attempted to scale down to mobile devices with (unsurprisingly) little success.
Eve started on mobile devices and scaled up. The result is VM that performs well on all devices and
provides APIs that are mobile aware instead of forcing mobile devices to emulate desktop systems.
Other mobile oriented features include:

Awareness of the pop-up software Input Panel present on many mobile devices (such as
PocketPCs). You actually have control over how your UI elements respond to this instead of
being forced to accept the default behavior.

Awareness of devices that may not support multiple windows. The standard Eve UI library
allows you to program as if multiple windows are supported and will simulate the windows
if necessary. Or you can determine the graphics capabilities at runtime and alter your user
screens accordingly.

The VM’s Java class library is embedded in the VM after being modified in a way that
allows for direct memory mapping. This means that running a second instance of the VM
takes up far less memory than would be using standard Java classes.

An API that allows Eve applications running on Windows Mobile devices to communicate
with applications on the desktop over the ActiveSync connection.
Eve Compared to Standard Java
Like other Java Virtual Machines, Eve comprises of a natively executable VM along with a library
of Java classes. On Windows and Linux desktop systems the VM is a single executable called
eve.exe (on Windows) or eve (on Linux). On Windows Mobile systems it is split between a small
executable launcher eve.exe and the VM itself in a DLL called eve.dll.
The Eve VM executes Java class files according to the Java Virtual Machine specifications.
However the standard class library associated with the Eve VM does not conform to any of the
numerous Java standards. The decision to not follow any particular standard was taken since Eve
was designed to be portable from devices as small as Smart Phones to full desktop computers.
Currently, no Java standard works well on all devices. Java Micro Edition (JME) is too limited for
any device larger than a Smart Phone, the Java Standard Edition (JSE) simply cannot fit and run on
the vast majority of mobile devices, and PersonalJava has apparently been abandoned.
The only Java standard that Eve will attempt to eventually implement is MIDP. The class library of
the Eve VM is currently being extended to implement the MIDP 2.0 standard since the MIDP’s
limited functionality is already a subset of the capabilities of the VM and there are many MIDP
applications currently in use.
Important Information on the Eve Class Library
All classes in the Eve class library are either in java.xxx packages or in eve.xxx packages. It is
important to note the following points about the java.xxx library.

Although the library contains a number of java.xxx packages, all the classes available in the
standard Java 2 package will not necessarily be available in packages of the same name in
the Eve library. For example the Java 2 java.util package is very large and only a limited
number of those classes are available in the java.util package on the Eve VM.

A java.xxx class in the Eve library may not implement all the methods in the
corresponding class in the Java 2 library but will generally implement the majority of the
methods.

A java.xxx class in the Eve library will not have methods that are not in the Java 2
library thereby retaining full compatibility.

All methods in java.xxx classes in the Eve library will operate in the same way as those
methods in the Java 2 library.
In short, the java.xxx packages and the classes contained therein in the Eve class library can be
thought of as a compatible subset of the Java 2 library. This means that a Java application
written to specifically target the Eve VM can also be run by a standard Java 2 VM. But
applications targeting the Java 2 library will not necessarily work on the Eve VM.
A JAR file is provided (“eve.jar”) which contains all the eve.xxx classes customized as necessary to
run on a standard Java 2 VM. Therefore it is possible to use a standard Java 2 VM (such as Sun’s
Java VM) to run Eve applications by placing this JAR file within the classpath. In fact, this is the
recommended way to test Eve applications during development.
Using a command line you would run an Eve application with the main() method in the class called
my.class.name using the native Eve VM like this:
eve -cp <my/class/path> <my.class.name> [program arguments] …
Or you could run the same application using a Java VM like this:
java -cp eve.jar;<my/class/path> Eve <my.class.name> [program arguments] …
Note that the only differences are the inclusion of eve.jar in the classpath and the fact that class that
you must always run is Eve (case sensitive). The “Eve” class (contained in eve.jar) initializes
critical parts of the Eve VM library and then proceeds to execute the specified application class file
my.class.name.
Writing Applications for the Eve VM.
Writing applications for the Eve VM is no different from writing for any other Java VM and
virtually any Java IDE or command line compiler will compile Eve applications. Eclipse is
recommended because it is powerful, cross-platform and free.
In order to write and compile Eve applications you will need two items, both of which are
included in the SDK.
1. The javadoc generated Eve API documentation.
2. The file CompileEve.zip which contains the class libraries needed for compilation.
In order to debug Eve applications using a Standard Java VM you will need the file:
JavaEve.zip. This file contains the pure Java versions of all classes in the CompileEve.zip file
along with their source code. This allows you to trace into the source code of the Eve library if
needed. Additionally the DLLs java_eve.dll (on Windows) and libjava_eve.so (on Linux) are
included and are needed to allow the Java VM to provide functionality provided by the Eve VM but
not in standard Java libraries.
In order to run Eve applications using the native Eve VM you will need to install the desktop
version of the Eve VM on your computer.
In order to run Eve applications using the Standard Java VM you can again use JavaEve.zip but
you can also use eve.jar which contains the same classes as JavaEve.zip but without the source
code and without debugging information. It therefore provides the same runtime library but is much
smaller.
For details on how you would setup an IDE such as Eclipse to compile and debug Eve applications
please see this file and select Beginners Programming Guide.
Capabilities of the Eve VM.
General Capabilities

Applications (class files) and program resources may be loaded from standard Zip and Jar
(Java Archive) files as well as via specially formatted “.eve” files. The Ewe VM would allow
an application to access files in a Zip/Jar file at runtime – but these files could not be placed
in the “classpath”.

Full TCP/IP network support including support for IPv6. Infra-Red communication over
IR-Sockets is also implemented. The Ewe VM did not support IPv6.

A Native Method Interface which allows applications written in Java to access device
specific C/C++ APIs via the native Java keyword and a dynamic linked library. This works
in the same was as the Java Native Interface (JNI) and applications written in the Eve Native
Interface automatically provide a JNI API for use with Java 1.2 compliant VMs.
Mobile Device Specific Features

A full Desktop-to-Portable Device Communication API is provided along with
synchronization utility classes. This API works the same over ActiveSync (used for
syncing WindowsCE/Windows Mobile devices on a Windows PC) or over a network (used
for synchronizing Linux devices). This API allows your Java/Eve applications on the
desktop to automatically run, connect to and communicate with your Eve applications on
your mobile device through a standard Socket.

Built-in Application Launcher. The Ewe VM contains a built in launcher that lets you add
to and edit a list Eve applications and then run them directly from the launcher. There is no
need to type or fiddle with cumbersome command lines. After your application is packaged
in an .eve (or .zip) file you simply use the VM and built in file browser to locate the
application and add it to the list.

Eve applications can be loaded remotely (across a network or across the ActiveSync
connection) onto any other computer or device with the Eve VM installed. The Eve VM
Launcher (which works on all platforms) can act as a Remote Application Server and
provide application classes and resources to another Eve VM running as a Remote
Application Loader. This means you can test your application on your mobile device without
having to package it or copy it across as individual classes or .eve files.

Mobile devices have very different display and user input issues than desktop computers.
Unfortunately, standard Java does not provide you with methods of customizing the drawing
surface before the VM begins and sets up its GUI system. In the Eve VM, a pre-main
method (called eveMain()) can be defined which will run before the normal main() method.
Within this method you have a chance to setup the GUI and windowing system as you wish
before the standard UI initializes. This can be used, for example, to rotate the screen (if
possible) and disable multiple window support.

Native Text Input is available (and is the default when run on certain devices) allowing the
use of the device’s “native” text input methods instead of Java versions of text widgets. This
is useful for devices like Smart Phones where simulating the different text inputs natively
available on the phone would be too tedious.

Runtime discovery of device capabilities is fully supported. For example you can
determine at runtime if the device supports a mouse, or if a stylus and touch screen is used
or if only keyboard entry is supported. Many other aspects of the device are also
discoverable.
Unique Developer Tools

Mobile Devices can be simulated on the desktop. The desktop version of the VM accepts
command line switches that cause it to simulate different devices. Switches like /p indicate
that it should emulate a PocketPC (along with simulation of the pop-up input panel) and /s
indicate that it should emulate a Microsoft SmartPhone with soft keys. You can also specify
the size of the screen and the type of input that is supported.

Native Windows and Linux executables can be created from Eve applications. After you
write your Eve application in Java and package it in a “.eve” or “.zip” file you can link with
the VM to create a self contained native Windows .exe complete with its own icon. This
works for both desktop and mobile versions of Windows.

Automatic conversion to Applets. An Eve application can be run automatically within a
Web Browser as an Applet without modifications. A virtual file system can even be set up so
that you can fully demo desktop applications over the web complete with file loading and
saving. Standard Applet security restrictions on access to local resources and networks will
still apply.
Architecture of the Eve VM.
The Eve VM and library is designed to be very modular – far more so than the Ewe VM. The
libraries for the VM and the types of devices that are available generally suggest four possible
incremental versions of the VM.

Eve Embedded (Command Line)– a version with no GUI interface that would either have
no user interaction or interact via a device specific interface (e.g. a serial port). This version
is generally only of interest to manufacturers or developers for specific devices.

Eve Mobile Edition – this consists of Eve Embedded along with the full Eve GUI library.
This would be suitable for PDAs, PocketPCs and advanced Windows Mobile 5
SmartPhones.

Eve Desktop Edition – this consists of Eve Pocket along with a simple database API, a
Printing API and some advanced GUI features.
A Mobile Edition can be upgraded to the full Desktop Edition by simply installing a single eveextended.eve file into the same installation directory as the VM itself.
The Eve Libraries
Eve Embedded (Command Line) – This include the following packages:

java.lang – Standard Java classes, including Thread, StringBuilder, etc.

java.lang.ref – Standard Weak/Soft/Phantom Reference support.

java.lang.reflect – Standard Reflection API including Proxy.

java.math – Support for BigInteger and BigDecimal classes.

java.io – Standard Java I/O classes.

java.net – Standard Java Network classes (including IPv6 support).

java.util – Standard Java Utility/Collection classes. Not as extensive as the Java 2 java.util
library, but with sufficient classes for most applications.

java.util.zip – Standard Java compression classes.

eve.data – Utility Data Classes.

eve.io – File access and extra utility I/O classes.

eve.sys – Eve VM specific system classes.

eve.util – Extra Utility/Collection classes.

eve.math – Alternative support for BigInteger and BigDecimal.

eve.net – Extra Network classes.

eve.zipfile – ZipFile access with a smaller and more flexible API than java.util.zip.
Eve Mobile Edition, contains these additional packages:

eve.fx, eve.fx.gui, eve.fx.sound – These packages provide low-level access to the device
graphics, sound and user interface.

eve.net – Provides extra networking utilities as well as the PC to Device communication
API.

eve.ui, eve.ui.event – A full GUI implementation, complete with multiple windows, frames,
and standard widgets.

eve.ui.table – Support for Table and Tree controls.

eve.ui.filechooser – Support for an advanced FileChooser dialog.

eve.ui.data – Support for the Editor class – a type of Form that allows for direct transfer of
data between widgets and class fields.

eve.ui.game – Support for Forms designed to be used with animated images and board
based games.

eve.math, eve.security – Together provide classes for encrypted data storage and
communication.
Eve Desktop Edition, contains these additional packages:

eve.database, eve.database.implement, eve.ui.advanced.database – Provides the
specifications for the simple Eve Database API and a simple file based implementation.

eve.fx.points, eve.fx.print – Provide support for printing documents.

eve.ui.table.registry – Provides more advanced GUI elements and a very simple HTML
viewer.
Customizing the Eve VM
Replacing/Reducing the Java Library
The Eve VM consists of a native executable which will vary in size between 700KB to 1MB
depending on the target platform, and a Java class library 3MB in size for the full desktop version (a
full 1MB of which is used by the eve.ui libraries). In devices where space is at a premium,
customized versions of the VM can be produced with a more limited class Library. Utilities to do
this customizing will soon be made available to developers.
Customizing/Replacing the UI
The Eve VM implements the user interface in two distinct layers. The lowest levels are contained in
the packages: eve.fx, eve.fx.gui, eve.fx.sound. These are very basic APIs and expose the lowest
levels of user interface provided by the underlying system. This includes classes representing a
Window (or the devices drawing area if true windows are not present), Graphics contexts for
drawing onto Windows and Images, and simple event trapping for Window changes, pen/pointer
events, key-press events, resize events, paint events, macro text events and input panel events.
The eve.ui packages are all built on top of the eve.fx and eve.fx.gui libraries but are not used by any
other packages, allowing them to be completely replaced by any other GUI library. An AWT or
SWT or even SWING library could be implemented over the eve.fx packages.
Eve Application Development
Michael L Brereton - 02 February 2008, http://www.ewesoft.com/
Beginners Guide - Contents
>> Next: Starting Your Application
Setting up the Eve SDK
Eve Application Development
Setting up the Eve SDK
Introduction
What's in the SDK
Compiling and Running Eve Applications – Expert
Compiling and Running Eve Applications - Part 1, Quick Start
Compiling and Running Eve Applications - Part 2, Advanced Topics
Configuring Eclipse for Eve Development
Introduction
Thank you for downloading and installing the Eve SDK. This document will help you set up your
standard Java SDK tools and IDE to be able to easily write, compile, execute and debug Eve
applications.
By this time you would have already read about Eve, its capabilities and its differences from a
standard Java VM. However at this point it is best to briefly mention some important aspects of
the Eve platform.
o
A Native Eve VM is one that has been written specifically for a particular
platform. Native Eve VMs are available for Microsoft PocketPC, Microsoft Smartphone,
Microsoft Windows, Linux Desktop and some Linux Mobile systems. On Windows
desktop the VM is a single executable (eve.exe) while on Windows mobile systems, the
VM consists of a small launcher executable (eve.exe) and the VM itself compiled as a
dynamic linked library (eve.dll).
o
Native Desktop Eve VMs contain all classes in the Eve library (the eve.xxx
packages) as well as some classes in java.xxx packages. The mobile versions of the Eve
VM have some packages removed but these packages are available in the file eveextended.eve which can be optionally installed with the VM to provide full desktop
functionality.
o
Because the native Eve VM does not contain the full standard Java library, it is
therefore not a fully functional, standard Java VM. It can be thought of a VM that
implements some Java classes, plus an extensive Java compatible library (the eve.xxx
packages).
o
An Eve Application or Eve Compatible Library is one that targets the Eve VM.
That is, it only uses classes which are in the Eve library, or which are from another Eve
Compatible Library.
o
An Eve Application can therefore be run on any native Eve VM, since the classes
required by the application will be within the native Eve VM.
o
An Eve Application can also be run on any Java VM with the use of an external
JAR or ZIP file which contains the Eve library specially written for a true Java VM. There
are two such external library files provided by: eve.jar and JavaEve.zip. The use of these
two files and the differences between them are discussed in later sections.
It is important to also note that an Eve Application is a true Java application. All classes and
methods used by an Eve Application and which are contained within the external Eve library
JAR/ZIP files are true Java classes. The Eve VM only runs classes compiled by a standard Java
compiler – no extensions to the language itself are used. This is why an Eve Application can be
run on any system with a Java VM and can also be converted easily into an Applet without any
additional programming.
What's in the SDK
The SDK contains within it several directories. These are:
o
classes - which contains JAR/ZIP libraries need to compile and debug Eve
Applications.
o
programs - which contains the EveMaker program builder along with its
associated support files. This is used to create .eve files from your applications and other
distributable file types for your application.
o
include - which contains the eve_eni2.h file used for writing DLLs to interface to
both Native Eve VMs and Java VMs to implement native Java methods.
o
docs – which include the API documentation and the basic Programming Guide.
Compiling and Running Eve Applications – Expert
This Expert section is intended for developers who are experienced in Java development and in
configuring their Integrated Development Environment (IDE), or who have had prior experience
using Eve. Other users may wish to go to the following sections for more detailed explanations on
compiling and running Eve applications.
Compiling Eve Applications - CompileEve.zip
The file CompileEve.zip is within the classes directory of the SDK and it is used as the library
for compiling Eve applications. When compiling Eve applications, you must instruct your
compiler/IDE to:
1.
Disregard standard Java libraries - i.e. tell the compiler not to use the standard
Java SDK libraries since they will contain common Java classes which are not
supported by the Eve VM.
2.
Include the file CompileEve.zip as a class library - i.e. tell the compiler to look
within this file for all classes. Of course you would add the project class directory and
any other Eve compatible libraries that you may have.
Under IDEs like Eclipse this would mean removing the reference to JRE System Library in the
Libraries tab of the Java Build Path of the Project Properties, and adding instead the file
CompileEve.zip as an External Jar.
Running/Debugging Eve Applications with a Java VM - JavaEve.zip
The file JavaEve.zip is also in the classes directory of the SDK and it is used when you wish to
run a Eve application using a standard Java VM (Java 2 or better). The JavaEve.zip file contains:
1.
Debug builds of the Eve class library for Java VMs. This allows a Java VM to
execute a Eve application.
2.
Full source code to all the classes in the zip file. This allows you to trace/step
into the Eve library itself when debugging your Eve applications.
So, in order to use a Java VM to run an Eve application, you must provide JavaEve.zip as an
external library (i.e. you must include it in the classpath for the VM). You must note however that
you never execute your main runnable Eve class directly - you must always run the class Eve
(which is within the JavaEve.zip library) and then provide your main Eve class as an argument.
For example, if you wished to run the class tests.HelloWorld you would have to run:
EveSDK\classes>java -cp JavaEve.zip;./ Eve tests.HelloWorld
Note the Java VM will actually execute the class Eve (contained in JavaEve.zip). The Eve class
will setup the Eve library, then load the class specified as the argument to Eve (tests.HelloWorld)
and then pass execution on to that class, including any further arguments provided.
Any special Eve VM Command Line Switches (e.g. "/p" to simulate a PocketPC, or "/s" to
simulate a Smartphone) must be placed between Eve and the target class name. e.g.:
EveSDK\classes>java -cp JavaEve.zip;./ Eve /s tests.HelloWorld
Therefore to configure your IDE to execute/debug a Eve Application you must:
1.
2.
3.
Include the file JavaEve.zip in the classpath of the Java VM you are using. Under
Eclipse this means adding it in as an External Jar.
Always specify the target class as Eve (with no package specifiers).
Provide the actual target class you wish to execute as an argument to Eve using full
dot notation.
Compiling and Running Eve Applications - Part 1, Quick Start
The classes directory within the SDK is the location for the important SDK library files and for
the example Java packages. Within this directory you will find two ZIP files JavaEve.zip and
CompileEve.zip, and two subdirectories containing sample Eve classes, solitaire and tests. We
will now try to compile and run the HelloWorld.java file located in the tests directory. The
examples given show how to do this from the command line, but doing this from an IDE is
discussed later.
Java Class Organizations
Remember that under Java, classes are organized into packages and sub-packages and that these
packages are represented on the file system as directories (folders). The full class name is written
in dot notation like: eve.net.Link. So the Java source file for this class would be expected to be a
file called Link.java in a directory called net which itself would be in a directory called eve.
The file HelloWorld.java is in a directory called tests (within the classes directory) because the
HelloWorld class has been placed in the tests package. This is indicated by the first line of code in
HelloWorld.java: package tests;
The JavaEve.zip Library
The zip file JavaEve.zip contains all the class files needed to both compile and run a Eve
application on a Java VM that is at least Java 2 compliant. It contains all the classes in the Eve
library as documented in the Eve API, plus some classes and packages that support the Eve library
when running on a Java VM. These additional classes exist in the library but they are not part of
the public Eve API and so you should not directly use them in your applications.
Compiling using JavaEve.zip
We will now attempt to compile the tests/HelloWorld.java file using the JDK installed on your
system. To do so the current working directory for the command line should be the classes
directory of the SDK. Then you use the command:
EveSDK\classes><path_to_javac>/javac -classpath JavaEve.zip;./ tests/HelloWorld.java
The part that reads: -classpath JavaEve.zip;./ tells the compiler to look both in JavaEve.zip and
in the current directory to find classes during compilation.
If this compilation was successful there will be no messages generated by the compiler and a file
called HelloWorld.class will be created within the tests directory.
Running using a Eve VM
If there is a native Eve VM available for your platform you can run the class files like this:
EveSDK\classes><path_to_eve>/eve tests.HelloWorld
Note the notation for the class - tests.HelloWorld. When we were compiling we specified a file
name for the compiler to work on. Here we are specifying a class name - so we provide the full
package name and the class name, but we do not put a .class at the end. Note that we do not need
to specify a class path, since the Eve VM will look in the current directory by default for classes.
Since the class name is tests.HelloWorld the VM will look automatically for a HelloWorld.class
file within a tests directory in the current directory. On my system the command line would be:
EveSDK\classes>c:\"program files"\eve\eve tests.HelloWorld
If you get a java/lang/NoClassDefFoundError this indicates that you are probably within the
wrong directory - ensure that you are in the classes directory of the SDK.
Note that there are some special Eve VM Command Line Switches that can be used to alter the
runtime behavior of the VM. These include:
/p or /p5 or /p6 - to simulate a Microsoft PocketPC version 2003 or Mobile 5 or Mobile 6
(all windows will appear at the top left of the desktop in this mode - do not move them
from there.)
/s or /s5 or /s6 - to simulate a Microsoft SmartPhone version 2003 or Mobile 5 or Mobile 6
(all windows will appear at the top left of the desktop in this mode - do not move them
from there.)
/w <width> - to specify a specific device screen width in pixels.
/h <height> - to specify a specific device screen height in pixels.
/n - to specify a system where multiple windows are not allowed (you should specify a
screen width and height with this option).
/r - to specify that the VM should consider itself running on a mobile system.
These switches must be placed immediately before the name of the class to execute. For example
try this:
EveSDK\classes>c:\"program files"\eve\eve /s tests.HelloWorld
Running using a Java VM
To run a Eve application with the Java VM you run the command line:
EveSDK\classes>java -cp JavaEve.zip;<extra_class_paths> Eve <package_and_class>
Please note the Eve that is before the full class name that you wish to run. So we would run
HelloWorld by doing this:
EveSDK\classes>java -cp JavaEve.zip;./ Eve tests.HelloWorld
Please not that the Java VM does not run the class you specified directly. In fact the Java VM is
directed to run the class called Eve (which is within the JavaEve.zip file). This class then starts up
the Eve library and then locates, loads and runs the target class file (e.g. tests.HelloWorld).
Note that the Eve VM command line switches described above also work here. You specify the
switches between the Eve and the target class name. e.g.:
EveSDK\classes>java -cp JavaEve.zip;./ Eve /s tests.HelloWorld
Why would you want to run your application using a Java VM when you intend to run on
an Eve VM? The most important reason to do this during application development is to take
advantage of the debugging features afforded by a true Java VM. A true Java VM allows you to
step through your code line by line if needed and also allows you to view the values of fields and
variables as you step through the application. These features are currently not provided by an Eve
VM.
What is the eve.jar file? In the classes directory you will also find the file: eve.jar. eve.jar can also
be used to compile and run Eve programs, however unlike JavaEve.zip the eve.jar file does not
contain any source code and the class files may not have debugging information in them. Therefore
when you are debugging your Eve application you will not be able to trace into the classes in the
Eve library. However eve.jar is much smaller than JavaEve.zip and so when you are distributing
your application, you may wish to include eve.jar instead of JavaEve.zip.
Compiling and Running Eve Applications - Part 2, Advanced Topics
Problems compiling with JavaEve.zip
When you tell your compiler to use JavaEve.zip during the compiling of your applications, it will
look for classes that are in JavaEve.zip and, by default, classes which are in the standard JDK.
This is necessary because JavaEve.zip does not contain the java.xxx classes which are part of the
Eve library. When the compiler comes across java.xxx classes it will use the classes from the
standard JDK library. While this is normally OK to do, there are two potential problems with
doing this.
1.
The standard JDK library will contain a number of other class files which are not
supported by a native Eve VM. So it will be possible for you to refer to classes like
java.awt.Color in your Eve application, which will compile correctly but which will
generate a “class not found” run-time error when run on a native Eve VM.
2.
Not all of the methods in the java.xxx packages are implemented by the Eve VM.
Compiling using CompileEve.zip
To ensure that the compiler only allows the compiling of classes and methods which actually are
supported by the native Eve VMs, you should tell the compiler to look in the file CompileEve.zip
which also exists in the classes directory along with JavaEve.zip. This zip file contains only the
classes that are actually supported by a native Eve VM.
When running your applications under Java, you should continue to use JavaEve.zip so that you
can use the debug features of your IDE.
Configuring Eclipse for Eve Development
Eclipse is the recommended IDE for Eve developers. It is extremely powerful and has an
extensive number of tools for Java developers.
Configuring Compiling (Build) using CompileEve.zip
The image below shows the modification of an Eclipse Project telling the compiler to use
CompileEve.zip as the source for classes during build. Note that originally the Libraries tab
contained an entry for JRE System Library which was removed before the Add External JARs
button was pressed to add in CompileEve.zip.
Configuring Running/Debugging using a Java VM and JavaEve.zip
The image below shows the creation of a Run entry for an Eve application. Note that the Main
Class is set to be Eve.
Next we set the actual target class we wish to run in the Arguments tab. For this example the
target class is evesamples.ui.GridControl and I have specified the /p5 Eve command line switch
which tells the Eve Library to simulate a PocketPC Mobile 5.
Last in the Classpath tab we have to include the JavaEve.zip file and exclude the
CompileEve.zip file. The image below shows how the Classpath tab should look when correctly
configured.
Note that when this Run entry was first created for this project, the CompileEve.zip file was
included in the classpath - as a dependancy of the project under the UserEntries section. The
IDE automatically includes all libraries used for compiling into the classpath used for running.
Under most Java circumstances this is the right thing to do, but for our purposes, we need to
remove CompileEve.zip. However there was no way to remove the CompileEve.zip entry alone the entire project under User Entries had to be removed and then the Add Projects button was
used to add the project back into the User Entries. This time, when the project was added,
CompileEve.zip was not included, and this is what we want.
After that the Add External JARs button was used to add JavaEve.zip under User Entries.
Once this was done, the Run or Debug button could be used to execute the application using the
Java VM.
Configuring Running using the native Eve VM.
To run a Eve application in Eclipse using the native Eve VM, you must configure an External
Tool. To do this you select Run >External Tools >External Tools... from the main menu. Then
select Program in the Configurations section and press New. You must give the entry a unique
name and then locate the Eve VM by pressing Browse File System in the Location directory. The
Working Directory should be set to be the directory where the application classes will be found.
This may be set correctly by default, but if not use Browse Workspace or Browse File System to
select the correct directory. The Arguments section should be set to the target class, with any VM
command line switches placed before it as normal.
Once this has been configured then the external tool will execute the native Eve VM and it should
run the target application.
Eve Application Development
Michael L Brereton - 31 December 2007, http://www.ewesoft.com/
Beginners Guide - Contents
<< Previous: Setting up the SDK
>> Next: Using EveMaker
Starting Your Application
Eve Application Development
Starting Your Application
The main() method
The eveMain() method
Displaying a User Interface – Forms
Form Exit Buttons
Form Validation
Form Display Options
Displaying SoftKeyBars in Forms
This chapter deals with some issues that will be important to experienced Java programmers who
wish to get working on Eve as quickly as possible. Since the Eve VM can execute applications
from separate class files, just like a Java VM, the use of the EveMaker Program Builder during
initial program development is not necessary. EveMaker is really used for packaging your
application for deployment. Hence programmers can start writing and testing programs without
reading the chapter on EveMaker.
The main() method
You can start your Eve application using a static void main(String []args) method as in standard
Java, however you must begin and end the method with two special function calls. GUI
Applications should do this:
import eve.ui.Application;
import eve.ui.MessageBox;
public class HelloWorld {
public static void main(String[] args)
{
Application.startApplication(args);
new MessageBox("Hello World","This is my first message box!",
MessageBox.MBOK).execute();
Application.exit(0);
}
}
As you can see, the first line of the main() method should be Application.startApplication
(String args[]). This starts and initializes the Eve System and GUI library. This is only
necessary if you are using the main() method for starting your Eve application. It is not
necessary if you are starting your Eve application by overriding the Application class or any of
the other allowed starting classes. You should also use Application.exit(int code) to exit your
Eve application.
A non-GUI, command line only application should start like this:
import eve.sys.Vm;
public class HelloWorld {
public static void main(String[] args)
{
Vm.startEve(args);
System.out.println("Hello World!");
Vm.exit(0);
}
}
Note that the System.out.println() method has no effect when run on a mobile system that does
not support consoles (e.g. WindowCE/PocketPC).
Also, avoid using System.exit(), System.loadLibrary() and System.getProperty() - they will
fail when running as an Applet. Instead use Application.exit() or Vm.exit(), Vm.loadLibrary()
and Vm.getProperty().
The eveMain() method
This is a special optional method very similar to the main() method and has the same form:
public static void eveMain(String[] args);
This method is called before main() is called and can be used to customize the behavior of the
VM before it creates the Application’s default main window. The first window created by the
VM becomes the Application’s main window and if one is not created within eveMain() then the
VM creates a default window that is appropriate for the current platform. In eveMain() you are
given a chance to explicitly create the window yourself thereby achieving behavior that may be
required by your particular Application.
Displaying a User Interface – Forms
Like most GUI systems, Eve UI elements consist of Control objects placed within Container
objects (which are themselves types of Control objects) to form a tree of displayable Controls. In
order for the Controls to be displayed, the top-level Container must be placed in a Frame which
must be then placed in a Window. While this is a fairly complicated process, you will most
likely never have to do it. Instead you would have a Form as your top level Control.
A Form “knows” how to place itself correctly in a Frame and then how to display that Frame
within a Window (and whether or not a new Window should be opened or whether an existing
Window should be used instead). You would simply call one of the show() or execute() methods
of the Form to display the Form on the screen. All options regarding the Frame and Window
used by the Form (e.g. if the created Window should be maximized) are set using the Form itself.
After the Form is displayed it receives UI events (user input, repainting and resizing) from its
containing Window. Each Window has its own Event Thread which is used to receive native UI
events from the underlying OS and then convert them to Eve events which are sent to the correct
Control objects within the Window. When writing Event Handlers (covered in a later chapter)
you must be careful not to block the Thread that called the Event Handler for any
significant amount of time, as this will block the Window from receiving UI Events and make
the application unresponsive. The only exception to this is the execute() method, which is
explained below.
In the first example above we did this:
new MessageBox("Hello World","This is my first message box!",
MessageBox.MBOK).execute();
A MessageBox is a type of Form and you can see that we used the execute() method which
displays it modally and then waits for it to close. There are three ways of displaying a Form:
1.
execute() – which displays a Form modally and waits for the Form to close (via the
exit(int exitCode)) method. When a Form is opened modally it blocks user input to other
Forms until it closes or until another Form is also opened modally.
2.
exec() – which displays a Form modally but returns immediately. You can call
waitUntilClosed() at any time after to wait until the Form has been closed and to get the
exitCode value.
3.
show() – which displays a Form non-modally and returns immediately.
Note that execute() and exec() will create a new Event Thread to handle UI events for the current
Window, so that even though waitUntilClosed() blocks the current Thread (which may have been
the original Event Thread) the original Window will still receive UI events because of the newly
created Event Thread. This is why it is safe to call execute() at any time, including within an
Event Handler.
You can specify that you wish the Form to be displayed within an existing Window instead of in
a new Window by using execute(Frame parent) or show(Frame parent). To get the containing
Frame for any Control call getFrame() on the Control.
Form Exit Buttons
Here is an example of a Form that we add a Panel to containing a number of Controls. The
addNext(), addLast() and other methods will be explained in the next Chapter.
package evesamples.ui;
import eve.fx.Insets;
import eve.fx.gui.WindowConstants;
import eve.ui.Button;
import eve.ui.Form;
import eve.ui.Panel;
import eve.ui.Gui;
//##################################################################
public class TestForm extends Form{
//##################################################################
//===================================================================
public TestForm()
//===================================================================
{
title = "Testing Panel";
Panel p = new Panel();
p.setText("Testing the panel");
p.addNext(new Button("Hello"));
p.addLast(new Button("There!"));
p.addNext(new Button("How"));
p.addLast(new Button("are you?"));
addLast(p);
doButtons(OKB|DEFCANCELB);
}
//##################################################################
}
//##################################################################
Note that there is no main() method in this class. Because it inherits from Form the VM will
automatically create it using the public default constructor and then call execute() on it. When
the Form exits the application will automatically exit as well. We can therefore run this by doing:
eve evesamples.ui.TestForm
or:
java -cp eve.jar;./ Eve evesamples.ui.TestForm
The doButtons(int buttons) method is a quick way of adding buttons to the bottom of a Form.
The values you can use here are:
OKB – the OK button.
CANCELB – the Cancel button.
DEFOKB – the OK Button where Enter is the hotkey for the button.
DEFCANCELB – the Cancel button where Esc (or Cancel on a mobile device) is the
hotkey for the button.
YESB – the Yes button.
NOB – the No button.
Pressing these buttons results in a call to exit() with the following constant int values:
IDOK – the OK button was pressed.
IDCANCEL – a Cancel button was pressed.
IDYES – (same as IDOK) the Yes button was pressed.
IDNO – the No button was pressed.
If the user presses the ‘X’ button for the Window then exit() is called with a value of
IDCANCEL.
Form Validation
It is possible to validate a Form before exiting (and possibly aborting the exit) by overriding the
boolean canExit(int exitCode) method. For example if we add this method to the Form above:
//===================================================================
protected boolean canExit(int exitCode)
//===================================================================
{
if (exitCode != IDCANCEL){
Gui.flashMessage("Please cancel!",this);
return false;
}
return true;
}
Run this new code and the Form will not exit unless the Cancel or ‘X’ button is pressed. The
Gui.flashMessage(String message, Control parent) is a convenient way of flashing a simple
message to the user – particularly appropriate for mobile devices.
Form Display Options
The main way of controlling how a Form is displayed is by adjusting the windowFlagsToSet
and windowFlagsToClear fields of the Form. Normally a new Window is displayed in a way
that is consistent with the underlying OS – however you can explicitly turn on or off certain
Window properties by setting bits in either or both of these two fields.
The bit values to use should be any of the eve.fx.gui.WindowConstants.FLAG_XXX values
(see the API). For example if we modify the constructor by adding the following two lines:
…
doButtons(OKB|CANCELB);
windowFlagsToSet |= WindowConstants.FLAG_MAXIMIZE;
windowFlagsToClear |= WindowConstants.FLAG_HAS_CLOSE_BUTTON;
}
If you run this using the native Eve VM – the Form will be maximized on the screen (since we
set the FLAG_MAXIMIZE bit) and will not display an ‘X’ button (since we clear the
FLAG_HAS_CLOSE_BUTTON bit). However under Java the ‘X’ button is still displayed since
there does not seem to be any way to remove it under Java.
The title of a Form can be set and the Window title will normally be set to this value when the
Form is displayed. There is also a windowTitle for a Form. If this is not null it will override the
title field and it will be used for the Window title instead. However if windowTitle has the
special constant value WINDOW_TITLE_DONT_CHANGE then the Window will not have
its title changed by the display of the Form.
The windowIcon can be set for a Form to be an ImageData object (explained later) or a
DeviceIcon (a better choice) created by eve.sys.Device.createIcon().
Some other options for a Form include (their names explain their purpose):
public boolean inheritSoftKeys = false;
public boolean resizable = !Gui.isPDA;
public boolean moveable = !Gui.isPDA;
public boolean hasTitle = true;
public boolean resizeOnSIP = false;
public boolean keepFrame = true;
public boolean noBorder = Gui.hasSoftKeys;
public boolean hasTopBar = !Gui.hasSoftKeys;
public boolean exitSystemOnClose = false;
Displaying SoftKeyBars in Forms
SoftKeys refer to the two Button/Menus that appear at the bottom of Windows Mobile 5/6
devices and other mobile devices. This is the preferred method for selection options and actions
for modern mobile devices. The Eve UI library fully supports the use of SoftKey bars in any
number of configurations and SoftKeyBar setup and event handling should normally be done in
your application Forms.
The image below shows the Eve Launcher running simulating a Windows Mobile 5 device. The
SoftKeyBar displayed for the active Form has a menu assigned to the first key (on the left) and a
single button (Exit) assigned to the right.
When you create a Form you should determine what type of SoftKeyBar (if any) is available on
the system using: SoftKeyBar.getType(). This returns:
o
TYPE_NONE – for no SoftKeyBar support.
o
TYPE_SINGLE – for a SoftKeyBar with only one button/menu item.
o
TYPE_DOUBLE – for a SoftKeyBar with two buttons/menu items (the
maximum supported by Eve).
o
TYPE_MENU – for a SoftKeyBar with only one item that must be a menu and
which may be hidden until the user presses the special “Menu” key (e.g. on Android)
Once you determine the type and number of keys you can begin setting up the SoftKeyBar for
your Form. You do this by creating a new SoftKeyBar object and then setting the one or two
keys for the bar using one of the setKey() methods. Then you assign the created SoftKeyBar to
the Form using the Form.setSoftKeyBarFor() method.
Forms actually allow you to specify different SoftKeyBars for different Controls on the Form
depending on which Control has the keyboard focus. The Form method:
public void setSoftKeyBarFor(Control c, SoftKeyBar bar)
Will set up the Form such that when the specified Control has the focus the assigned SoftKeyBar
will be displayed. If the c parameter is null, then this will be the default SoftKeyBar for the Form
and is displayed for all Controls which do not have a specific SoftKeyBar assigned to them. The
method below is used to assign a SoftKeyBar to a number of Controls in a Vector.
public void setSoftKeyBarForAll(Vector controls, SoftKeyBar bar)
The simplest way to handle SoftKey commands is to assign an Action to each Button or
MenuItem used within a SoftKeyBar. When the Button is pressed or when the MenuItem is
selected the handleAction(String action) method of the Form will be called. Note that
MenuItems and Buttons have an individual field called action. If this field is not set the default
behaviour is for the normal text for the Button or MenuItem to be used as the action.
To summarize, to correctly use the SoftKeyBar in a Form you should follow these steps:
1.
Determine how many keys, if any, are available on the current platform.
2.
Create Menus and/or Buttons for assignments to the SoftKeys.
3.
Create a SoftKeyBar and assign the created Menus and Buttons to the keys.
4.
Assign the SoftKeyBar to the Form.
5.
If there are different Menus/Button combinations for different active Controls in the
Form, then create SoftKeyBars for the different Controls as necessary.
6.
Override the handleAction() method to handle the user selection of the SoftKeyBar
Menus and Buttons.
There is also a special method call: Gui.simulateSoftKeysOnPDA(int softKeyBarType). This
method will ensure that, if run on a PDA (or a simulated PDA – a device with a touch screen, no
mouse pointer and no keyboard) which does not normally use SoftKey bars (e.g. PocketPC
200x) the Eve library will run as if the system did in fact have a SoftKey bar. This is useful when
writing software targeting Windows Mobile 5/6 devices but which may also be run on a
PocketPC or other PDA which does not normally have native SoftKey bars. The Eve VM
Launcher uses this method so that its interface on a PocketPC is exactly the same as on Windows
Mobile devices.
This method, if used, must be called in the eveMain() method so as to have effect before any
native windows are created.
All these concepts are shown in the example shown below:
package evesamples.ui;
import eve.ui.Application;
import eve.ui.Button;
import eve.ui.Control;
import eve.ui.Form;
import eve.ui.Gui;
import eve.ui.Input;
import eve.ui.InputStack;
import eve.ui.Menu;
import eve.ui.MenuItem;
import eve.ui.SoftKeyBar;
public class SoftKeyDemo extends Form{
Input myInput, secondInput;
public SoftKeyDemo()
{
title = "SoftKeyBar Demo";
maximizeOnPDA();
InputStack is = new InputStack();
myInput = is.addInput("Input:","");
secondInput = is.addInput("Another:","");
addLast(is).setCell(HSTRETCH);
addLast(new Button("Hello Button")).setCell(HSTRETCH);
//
Button b = new Button("Exit Now","eve/exitsmall.png");
b.action = "exit_action";
//
if (SoftKeyBar.getType() != SoftKeyBar.TYPE_NONE){
//
if (true){
//
// Create a bar that is different for the inputs.
//
SoftKeyBar sk = new SoftKeyBar();
Menu left = new Menu();
//
// Make a menu with three items.
//
Menu fixedText = new Menu(new String[]
{"One","Two","Three"},"Fixed Text");
left.addItem(fixedText);
left.addItem(new MenuItem("Clear","clear_action"));
if (SoftKeyBar.numberOfKeys() == 1){
left.addItem("-");
left.addItem(sk.createMenuItem(b));
}else{
sk.setKey(2,b);
}
sk.setKey(1, "Actions", left);
setSoftKeyBarFor(myInput, sk);
setSoftKeyBarFor(secondInput, sk);
}
if (true){
//
//Create a default bar for all other controls.
//
SoftKeyBar sk = new SoftKeyBar();
sk.setKey(SoftKeyBar.numberOfKeys(),b);
setSoftKeyBarFor(null, sk);
}
}else{
addButton(b);
}
}
public static void eveMain(String[] args)
{
//
// Try using these other values: TYPE_SINGLE, TYPE_MENU
//
Gui.simulateSoftkeysOnPDA(SoftKeyBar.TYPE_DOUBLE);
}
public boolean handleAction(String action)
{
Control c = Gui.focusedControl();
if (action.equals("clear_action")){
if (c != null) c.setText("");
return true;
}else if (action.equals("exit_action")){
exit(IDOK);
return true;
}else{
if (c != null) c.setText(action);
return true;
}
}
public static void main(String[] args)
{
Application.startApplication(args);
new SoftKeyDemo().execute();
Application.exit(0);
}
}
Here is how it looks when run as a PDA. The left image is what is displayed when one of the
inputs has the focus, the center image is what is displayed when the user selects the “Actions”
SoftKey and the right image is what is displayed when none of the inputs have the focus.
eve /p5 evesamples.ui.SoftKeyDemo
and here is how it looks when run as a normal desktop application:
eve evesamples.ui.SoftKeyDemo
You will note that in the line:
Menu fixedText = new Menu(new String[]{"One","Two","Three"},"Fixed Text");
I created a Menu from an array of Strings. This creation method does not allow me to specify at
that time what the action String should be. I could have instead created an empty Menu and then
added MenuItems to it instead. When creating the individual MenuItem objects I can specify
what the action for each item should be. There is also a convenience SoftKeyBar method called
createMenuItem(String label, String action, Iimage icon) that can be used to create a new
MenuItem for adding to a Menu that will be set to a SoftKeyBar.
Eve Application Development
Michael L Brereton - 30 December 2007, http://www.ewesoft.com/
Beginners Guide - Contents
<< Previous: Starting Your Application
>> Next: Laying Out Controls
Using EveMaker
Eve Application Development
Using EveMaker
Creating a .eve File
Creating Executable Targets
Advanced Executable Targets
Developer Tools
Advanced Command Line Options
Edit Install File
The EveMaker application is written entirely in Eve and is located in the /programs subdirectory
of the Eve SDK. Because it is very processing and I/O intensive it will perform significantly
better when run using Sun’s Java VM. The RunEveMaker.bat file in the /programs directory can
be used to run it with an installed Java VM (you may have to edit the bat file to correctly identify
the path of the Java VM). The batch file has the single line:
java -cp ../classes/eve.jar Eve EveMaker.eve
The purpose of the EveMaker application is to:
1.
Package the classes and resources needed by an application into a .eve file – the normal
method of distributing Eve applications in a completely platform independent way. Not
only can you specify the starting class but you can also specify a full set of command line
arguments for the application.
2.
Produce a native executable file that contains all your programs classes and resources.
This effectively combines your application with a stub VM executable to produce a single
executable file. For all Windows and Windows Mobile targets, you can additionally
specify a Windows icon to be assigned to the executable. Linux executables are not
assigned icons.
3.
Provide a number of developer utilities including:
o
Producing a Windows icon from individual images.
o
Extracting individual images from a Windows icon.
o
Producing a PNG image with transparent areas from an individual image.
o
(Advanced) Producing Eve Native Interface code from native methods in
a compiled Java class.
o
(Advanced) Attaching the entire Eve library to a VM executable that was
produced by building the VM from source.
Creating a .eve File
Here is what the EveMaker application looks like when run.
The icons at the top are used to switch between the various functions EveMaker (similar to a
Tabbed panel). The first tab, as displayed, is used to specify the classes and other files that will
make up the application.
The Program Name is used as the base name for executables produced, so you should not have
any spaces or other characters not allowed on the destination file system. The Starting Class is
the class that is used to start the application.
The Edit Command Line button is used to edit more advanced command line options (detailed
here) and the Single Class File button is a convenience function that lets you select a single class
through a file chooser box and EveMaker will then fill out the rest of the information based on
that class (you will always have a chance to edit the information afterwards).
The Program Directories/Files Entries is where you specify the classes and other files that make
up your application. The files specified here are placed in the output .eve file (and executable files
if chosen) and will be available to the application at run time.
NOTE: File names in .eve files are case sensitive. An application that works when run from
individual classes and individual files on an Windows system may stop working when placed in
a .eve file because the Windows file system is not case sensitive (the Linux file system is). This
will be the most common problem you will have when packaging your application.
You use the
1.
button to add a new entry into the list. Each entry specifies:
A Source Path – a directory that holds files for the application.
2.
A File/Mask value – which is a semi-colon separated list of file masks. All files in the
directory that match those masks will be placed in the .eve file.
3.
A Path in Eve value – which specifies the virtual directory within the .eve file assigned
to those files.
4.
An Include Subdirectories flag – which specifies whether the File/Mask should be
applied to the subdirectories of the Source Path as well as the directory itself.
You can use the Preview Files button to see what files will be included and what they will be
named in the .eve file. Here is what the included files look like for the example above:
The Eve File Options is where you specify the name of the final output .eve file and other
options:
1.
Add Command Line – specifies that a command line should be added to the .eve file.
This is almost always the case unless you are creating an .eve file that is meant to be a
library (a collection of classes for use by other applications).
2.
Use String Pool – a special option that reduces the size of the final .eve file by pooling
all the Strings in the .eve file into a single pool instead of individual String pools in each
class (the default).
3.
Create Zip File Also – this specifies that a .zip file containing the exact same files as
the .eve file should also be created. This can be useful for tracking deployment problems.
4.
Add Install File – this is used by various target platforms to install the application.
Currently the following platforms use this file:
o
the Eve VM Launcher uses the Icon specified in the install file to display
the link to the application.
o
Qtopia Based devices use all the information to determine how to place the
application within the application tabs.
The Edit Install File is used to specify the information placed in this file, and this is detailed
here.
The final section on this screen contains the Create Eve File button which is used to place all the
specified files into the output .eve file. The Show Files button will show in a tree structure all the
files that will be placed in the .eve file. The Run Eve File button will use the Eve VM to run the
created .eve file. You should always use this to test your application before attempting to create
executable targets. The Check Dependencies button is used to make sure that there are no
classes that are referred to from within the .eve file but which are not present in the final file.
Make sure that you save your project information with the File->Save menu option. The project
is saved as a .enf file at whatever location you choose.
Creating Executable Targets
You should only attempt this once you have confirmed that you can create a working .eve file
that is correct and contains all the resources needed by your application. The screen shot below
shows the EveMaker tab that is used to create executable targets.
Here are some important things to note about the executable targets.
o
On Windows or Linux desktop systems, the executable produced
contains the native VM, the Eve class library and your application classes and
resources. No other executables or shared libraries are necessary and the Eve VM
does not need to be installed on the computer that will be running the application.
o
On Windows Mobile systems (and soon for Linux mobile systems), the
standard Eve VM is a small launcher (eve.exe) and a dynamic linked library
(eve.dll) which contains the actual VM. Mobile targets created by EveMaker (like
PocketPC or Smartphone) do not contain the Eve library or the VM. Mobile
targets contain only your application combined with a simple launcher that will
load the eve.dll library at runtime. They are therefore much smaller executables
but they require that either the VM be installed on the target device or that you
distribute the eve.dll along with the created target. As long as the eve.dll is in the
same location as your created target at runtime it will successfully find and load
the dll.
o
The Jar target is a single .jar file containing your application, the Eve
library and the command line and can therefore be executed directly using an
installed Java 2 (or better) VM. The .jar file also contains the Java support libraries
java_eve.dll and libjava_eve.so (for Linux desktops).
o
The Exe-Jar Launcher target is a special Windows executable that is
similar to the standard Windows executable target but at runtime, if the program
detects that Suns Java VM (version 2 or better) is installed on the system it will
dynamically create a .jar file for your application and then run the application
using the Java VM. If a Java VM is not found, the native Eve VM embedded in the
executable will be used instead.
o
The Applet target is used to create a small HTML page containing an
<APPLET> tag for your application. The application classes are placed in a .jar
file while the resources (images and non-class files) are either placed in
appropriate directories or are placed in a single _resources.zip file depending on
the option chosen in Applet Options.
There are a few Target Options available.
o
Windows Program Icon: You use this to assign an icon to Windows and
Windows Mobile based targets. You can use the default, use an existing icon or
create your own from any number of individual images.
o
Which Eve classes to put in the target? You can choose to place the
entire Eve library into the executable or you can choose to place only classes
referenced by your application. The second option produces smaller executables
but you must be sure that you do not reference essential classes through
Class.forName() method calls, since these will not be included in the list of
referenced classes.
o
Compress classes in the target? Normally classes are not compressed in
the target (the .eve file is not compressed) but you can choose to compress (zip)
them either for desktop targets or all targets. It is not normally recommended that
you compress classes for mobile targets since extra time is taken to unzip the
classes at runtime and extra memory is taken to hold the uncompressed class bytes.
o
Applet Options – Display: The Applet may be displayed completely
embedded within the web page (in which case the application will not popup any
extra frames at runtime) or it may be displayed in a single external frame or it may
be displayed as a regular application, displaying as many frames as it needs. For
the first two options you must supply the Width and Height for the Applet or
Frame. The third option does not require width/height specifications since each
displayed Form determines its own size.
o
Applet Options – Place Resources in Zip File: Normally non-class
resources are placed in directories along with the HTML file and the web server
will provide the resources individually as needed. However you can specify that all
resources be placed in a separate zip file that will be named _resources.zip and
which will be downloaded only once when the Applet is loaded. This has the
advantage of only a single file access at runtime but will have the disadvantage of a
longer start time while this is loaded.
o
Always Check Class Tree: This will do a class dependency check when
generating targets to ensure that no classes required by the application are missing.
o
Place Classes in External Zip: This will produce the executable but the
classes are placed in an external zip file called eve-classes.zip instead of being
placed within the target itself. This is useful for troubleshooting your target if it is
not working as expected.
o
Create Targets in Program Info Directory: If this is checked the targets
are placed in the same directory as the .enf file that specifies the current project.
Each target is placed in a directory named for the target (e.g. “Exe” or “Jar” or
“Applet”) and the Program Name specified in the previous section is used as the
name of the executable (or .jar or .html) itself. If this in unchecked the targets are
created in the same directory as the output .eve file created in the previous section.
Once you have selected the targets and the options you want, press the Build Targets button to
create the targets. Any errors will be reported in a dialog box. On success the various targets are
placed in the destination folders and are immediately ready for running or distribution.
Advanced Executable Targets
This feature lets you build executable targets for your porgram with a higher degree of control
than the previous screen. You specify options for each target individually instead of having
options applied to all targets. Here is what this screen looks like:
For each entry you can specify an individual Executable Stub on your system that has been
compiled and converted to a an appropriate stub – or you can choose one of the already
available stubs supplied with the SDK. The one shown in the example is the Linux x86
command line version. You must also provide a unique Directory Name that will contain the
created executable. The following options are applied to the target creation:
o
Always Append: There are two methods for attaching your application
classes to executable stubs. The normal method for Windows executables involves
inserting the application classes as standard Win32 resources. This allows for the
inclusion of a program icon. The normal method for Linux executables is to
append the classes to the end of the executable. This append method also works
on Windows executables (but the resource method does not work on Linux
executables) but the append method does not allow for the inclusion of a program
icon. If you choose this Always Append option, then the application classes and
resources will be appended to the executable even if it is a Windows executable.
o
Is Command Line Version: This option should be used if you know that
the executable stub was a command line only build of the VM. This tells the
system to use the command line only classes to build the target executable. All the
eve.fx and eve.ui packages are not included.
o
Is Java Hybrid: This option is used to generate a Jar Launcher as
described in the previous section. Note that this will only work on Windows
desktop executables at this time.
o
External Resources: This option is used to indicate that the application
classes and resources should be placed in an external file called eve-classes.zip.
This is only useful for troubleshooting your application.
o
Zip Resources: This option indicates that the application classes and
resources should be compressed when being placed in the target executable. This
makes the executable smaller but will result in longer startup times and will use
more memory.
o
Referenced Classes Only: This option indicates that only classes
referenced by your application classes should be placed in the application instead
of the entire Eve library.
The Build Target button will build all the targets specified. Any errors will be reported in a
dialog box.
Developer Tools
There are a number of Developer Tools provided by EveMaker. These are described below:
o
Windows Icon Maker – This allows you to create a Windows Icon from a
number of other standard images of various sizes. There must be at least one image
within the icon but there is no upper limit on the number of images that can be in
the icon. It is recommended that it contain images of sizes 16x16, 32x32 and
48x48 (and 12x12 and 64x64 if possible).
o
Windows Icon Extractor – This allows you to select an individual icon
from a Windows Icon and then save that image as a single PNG image.
o
PNG Icon Maker – This allows you to create a PNG image with
transparent areas from a BMP image or other image by selecting an individual
color within the image to be transparent.
o
ENI DLL Maker – This allows you to generate C++ code necessary for
creating ENI (Eve Native Interface) code to implement native Java methods in a
class. The generated C++ code, when compiled, produces a DLL that is compatible
with the Eve VM and with Sun’s Java VM. The generated code is also compatible
with both Windows and Linux. The code produced is a simple skeleton and you
must provide the functionality by implementing the provided functions using the
Eve ENI specifications. This is explained in a separate programmers guide.
o
Eve VM Maker – This takes an Eve VM compiled from source code and
produces a stub executable for use in producing application targets and it
combines the compiled VM with the Eve library to produce a stand-alone Eve VM.
Note that for Windows executables, you should include at least one resource in the
executable (you can add an icon, for example). This resource is stripped out when
making the stub. Note that if the VM you compile ends with “_stub” or
“_stub.exe” it is assumed to already be a stub and no new stub will be created.
Advanced Command Line Options
Here is what the Advanced Command Line options looks like:
The Eve Files is used to specify extra .eve files which will be used as class libraries at run time.
Extra Commands are placed after the start class and VM Options are placed before the start
class. You can specify the Width and Height of the application (when simulating a mobile
device with /p, /p5 or /p6 or /s, /s5 or /s6. You can also specify a default locale for the
application using a two character locale specifier (e.g. us).
The example above will generate a final command line that looks like this:
/p5 /w 320 /h 240 evesamples.jigsaw.JigsawPuzzle “c:\My Pictures\GoodPicture.jpg”
Edit Install File
This is used to edit the data placed in the Install File which may optionally be placed in the .eve
file. Here is an example:
o
Program Title specifies the name to be displayed with the Icon for the
application when installed in the Eve VM launcher or on the Qtopia device.
o
Install Location specifies the location the .eve file will be placed on a
Qtopia device.
o
Arguments and VM Options specify extra arguments and VM Options to
be used with the application (these are not normally used).
o
Category specifies which Qtopia category the application should be
placed in.
o
Program Icon specifies the name of a PNG icon within the .eve file that
should be used to represent the application in the VM Launcher or on the Qtopia
device.
Eve Application Development
Michael L Brereton - 30 December 2007, http://www.ewesoft.com/
Beginners Guide - Contents
<< Previous: Using EveMaker
>> Next: Event Handling
Laying Out Controls
Eve Application Development
Laying Out Controls
Panel – The Basic Container
Adding Controls to the Panel
The Default Layout
Stretching the Layout
The QuickLayout LayoutManager
Implementing a Custom LayoutManager
CellPanel - The Universal Container
Adding Controls
Setting Cell Behavior
Setting Control Behavior
Advanced Control Layout and Modifications
Tagging a Control
Borders
Modifying Controls
Like most GUI systems, Eve UI elements consist of Control objects placed within Container
objects (which are themselves types of Control objects). However, since the primary aim of Eve is
to provide a universal programming system for deployment across desktop and mobile systems,
emphasis is placed on laying out controls such that the final interface will work on a variety of
different screen sizes. Although absolute layout of controls is possible (i.e. specifying their
absolute x, y position and size within their parent containers), this practice is discouraged. Instead,
controls are placed and sized based on how you add them to the containers and on the preferred
size of the control.
Although most controls will correctly calculate its own preferred size (which is usually based on
its contents or whatever text or data it is displaying) you can always explicitly set its preferred
size via setPreferredSize().
Panel – The Basic Container
A Panel container should be viewed as the most basic container for laying out controls, although
it actually inherits from Canvas, which inherits from Container. A Panel uses a LayoutManager
object to organize its child controls. The job of the LayoutManager is to:
1. Calculate the area required by child controls when they are laid out in their preferred size.
2. Set the location and size of each control when the Panel is displayed or resized on screen.
The first task is done via the getPreferredSize() method and the second by the layout() method.
By default the LayoutManager of a newly created Panel is the Panel itself but this can be changed
by modifying the layoutManager field of the Panel.
Adding Controls to the Panel
A Panel will place its child controls in a 2-D grid, and you do not need to set the dimensions of
the grid before adding controls to the Panel. The methods that you use to add to the Panel will
shape the dimensions of the final grid.
Controls are added to the Panel from left to right, top to bottom. You use addNext() to add a
control to the current row, while leaving the row still open for the addition of more controls. You
use endRow() to close the row, causing the next control to be added to a new row below the
previous one. You can alternatively use addLast() to add a control to a row and then immediately
close the row. Closing a row that has no controls added to it yet will have no effect.
NOTE: The addNext() and addLast() methods do not actually add the controls as children of
the CellPanel container. It merely puts the controls into an eve.util.Grid object (essentially a 2-D
Vector) in the panel and at this point they are considered to be sub-controls. Before the panel is
displayed on the screen a make() is called on the top control, which causes a make() to be called
to all sub-controls. Only during the make() method call will the controls be added as a true child
of the container. You do not usually have to explicitly call make() on your controls - this is
automatically done when the control is about to be displayed in a Form or Frame.
It is acceptable to have differing numbers of controls on each row in the Panel. The number of
columns that are considered to be in the Panel will be equal to the number of controls in the row
with the most controls. The same applies to the number of rows in the Panel.
The Default Layout
By default, the Panel lays out its controls such that all the controls within the same column have
the same width and all the controls within the same row have the same height. This usually will
keep the controls lined up both vertically and horizontally. The width of each column is
determined by the largest preferred width of all the controls in that column. Similarly the height of
each row is determined by the largest preferred height of all the controls in that row.
In this situation, the controls will be placed adjacent to each other without any space in between
them. However you can modify this by giving each child controls an INSETS Tag or by setting
the default INSETS tag of the entire Panel. This is shown in the example below, with the lines in
bold showing where the default and individual insets of controls are set. An eve.fx.Insets object
specifies the top, left, bottom and right spacing given to a control within the Panel.
package samples.ui;
import eve.ui.*;
import eve.fx.*;
//##################################################################
public class TestPanel extends Form{
//##################################################################
//===================================================================
public TestPanel()
//===================================================================
{
title = "Testing Panel";
Panel p = new Panel();
p.defaultTags.set(TAG_INSETS,new Insets(2,2,0,0));
p.setText("Testing the panel");
p.addNext(new Button("Hello"));
p.addLast(new Button("There!"));
p.addNext(new Button("How"));
p.addLast(new Button("are you doing?")).setTag(TAG_INSETS,new
Insets(4,4,4,4));
p.addLast(new Button("I'm alone"));
addLast(p);
}
//##################################################################
}
//##################################################################
You can execute this using: eve evesamples.ui.TestPanel and you will see the following screen.
You will note that each control is separated from the other by 2 pixels (from the default INSETS)
but the one labeled "are you doing?" is inset differently from the others.
Stretching the Layout
The example shown above shows the Panel when displayed at exactly its preferred size. But what
happens if the Panel is resized so that it is no longer at the preferred size? By default the member
variables stretchLastRow and stretchLastColumn are both true. These flags tell the Panel to
stretch or shrink the last row/column if the Panel is resized so that it is no longer at its preferred
height/width. All other rows and columns retain their preferred size. You can alternately set
stretchFirstRow and stretchFirstColumn to be true if you prefer to have the first row/column to
stretch or shrink as the Panel is resized.
The picture below shows what happens to the TestPanel when it is resized on-screen.
This default simplistic handling of Panel stretching may be adequate for most of your panels, but
may not be so for some of your more complex forms. If you need better handling of such situations
you should either write your own LayoutManager or use a CellPanel container; both of which are
explained in later sections in this chapter.
The QuickLayout LayoutManager
This is the only other LayoutManager provided by Eve. It provides the fastest possible layout but at
the price of having a fixed preferred width and height that apply to all Controls added to the Panel.
It is the fastest because the getPreferredSize() method of the child controls is not called since the
preferred size of these controls are overridden by the QuickLayout's unitPreferredWidth and
unitPreferredHeight variables. Other than this fact, the QuickLayout manager works very
similarly to the default Panel LayoutManager
Implementing a Custom LayoutManager
This is very easy to do since only two methods need be implemented. These will be explained
below:
public Dimension getPreferredSize(Grid controls, Panel panel, Dimension destination);
This is used to determine the preferred size of the area needed for the child controls. This method is
usually called when the Panel is asked to calculate its preferred size, which is usually done during
the make() process of the Panel or containing Form. However it may be the case that this method is
never called (for example, if the Panel's parent uses QuickLayout). Do not assume that it will be
called.
In getPreferredSize() the first line should determine if the destination parameter is null and if it is,
then a new Dimension object should be created and assigned to destination. At the end of the
method you should return the destination object.
The controls that have been added to the Panel via addNext() and addLast() are provided in the
controls parameter. This is an eve.util.Grid object which you can traverse to access the individual
added controls. There are two things to note about this parameter:
1. It may be null if no controls were added to the Panel at all.
2. Controls at any cell within the grid may be null.
The example below shows a skeleton implementation of getPreferredSize().
//===================================================================
public Dimension getPreferredSize(Grid controls, Panel panel, Dimension
destination)
//===================================================================
{
if (destination == null) destination = new Dimension();
destination.set(0,0);
if (controls == null) return destination;
for (int row = 0; row < controls.rows; row++){
for (int col = 0; col < controls.columns; col++){
Control c = (Control)controls.objectAt(row,col);
if (c == null) continue;
//
// Do your calculations here.
//
}
}
return destination;
}
public void layout(Grid controls, Panel panel, Rect panelRect)
This method is called when the Panel is displayed or resized. The controls and panel parameters are
the same as for getPreferredSize(). The panelRect parameter represents the area within the Panel
that has been assigned for the child controls. Note that this area is not necessarily the full area of the
Panel. The panel may have reserved space for its borders or text label.
Within the layout() method, depending on the space allocated for the child controls, you should set
the location and size of each Control by calling its setRect(int x, int y, int width, int height)
method.
Here is a skeleton implementation of layout()
//===================================================================
public void layout(Grid controls, Panel panel, Rect panelRect)
//===================================================================
{
if (controls == null) return;
for (int row = 0; row < controls.rows; row++){
for (int col = 0; col < controls.columns; col++){
Control c = (Control)controls.objectAt(row,col);
if (c == null) continue;
//
// Do your calculations here and call setRect
// on the control.
c.setRect(x, y, width, height);
}
}
}
CellPanel - The Universal Container
A CellPanel is a very versatile container and should be the main container you should use for your
controls (unless performance issues require you to use a Panel). Note that, although CellPanel
inherits from Panel, a CellPanel does not use a LayoutManager, it implements its own layout
scheme.
A CellPanel also places controls in a grid of cells but additionally allows you to define how cells
may grow and shrink with the CellPanel, and how the controls are placed within their allocated
cells. Controls are also allowed to span multiple cells horizontally and vertically. By placing
CellPanels within other CellPanels you can achieve virtually any layout you could want without
having to specify a single XY coordinate. CellPanels work similarly to the Java GridBag layout but
are considerably easier to use.
CellPanels work because each Control can report its preferred size. This is the same concept as
preferred sizes for Java controls. Controls can also report their minimum and maximum size
(although the maximum size of Controls has not yet been implemented and at this time has no effect
on layout). Based on these sizes the CellPanel attempts to place controls so that they are as close to
their preferred size as is possible.
Adding Controls
Controls are added to the CellPanel left to right, top to bottom. You use addNext() to add a control
to the current row, while leaving the row still open for the addition of more controls. You use
endRow() to close the row, causing the next control to be added to a new row below the previous
one. You can alternatively use addLast() to add a control to a row and then immediately close the
row. Closing a row that has no controls added to it yet will have no effect.
A CellPanel will declare its own preferred size to be the sum of the preferred sizes of its child
controls. However when it is finally placed on screen it may by stretched or shrunk depending on
the actual area allocated to it. You can specify how the sizes of cells in the grid are affected if this
should happen. You can also specify how the control within a cell should behave if its containing
cell is not its preferred size.
It is important to note that the "cells" are not actual objects or controls. They are merely logical
rectangles that specify the space allocated to a particular control. It is also important to note that the
grid always maintains its horizontal and vertical alignment. ALL cells in a particular column will be
the same width (even though the controls within them may be sized differently) and ALL cells in a
particular row will be the same height.
Setting Cell Behavior
You specify how a cell behaves when its CellPanel is resized by calling the setCell(int) method on
the control placed in the cell. A cell will either "grow" and/or "shrink" with the CellPanel and you
can specify its horizontal behavior separate to its vertical behavior. If it both grows and shrinks in a
particular direction, it is said to "stretch" in that direction. The value that you pass to setCell() must
be one of the following which may be bitwise OR'ed together. They are:
HGROW - Allow the cell to grow horizontally.
HSHRINK - Allow the cell to shrink horizontally.
VGROW - Allow the cell to grow vertically.
VSHRINK - Allow the cell to shrink vertically.
There are also some convenience values:
HSTRETCH - This is equal to (HGROW|HSHRINK)
VSTRETCH - This is equal to (VGROW|VSHRINK)
STRETCH - This is equal to (HSTRETCH|VSTRETCH)
DONTSTRETCH - This is equal to 0.
By default the cell will fully grow and shrink in both directions.
Please note the following:

If at least one cell in a column will shrink or grow horizontally, then ALL the cells in the
column will shrink or grow horizontally.

If at least one cell in a row will shrink or grow vertically, then ALL the cells in the row will
shrink or grow vertically.
Setting Control Behavior
The control within a cell does not necessarily change its size along with its containing cell. You
specify how the control behaves when its cell is resized by calling the setControl(int) method.
There are two aspects of the behavior that you must specify:
1.
How the control is resized (if at all) along with the cell.
2.
How the control is placed within the cell.
The control can either "expand" or "contract" with the cell, and its behavior in the vertical and
horizontal directions are controlled independently. If the control both expands and contracts, it is
said to "fill" the cell. The following values can be OR'ed together to specify the behavior:
HEXPAND - Expand the control with the cell horizontally.
HCONTRACT - Contract the control with the cell horizontally.
VEXPAND - Expand the control with the cell vertically.
VCONTRACT - Contract the control with the cell vertically.
There are also some convenience values:
HFILL - This is equal to (HEXPAND|HCONTRACT)
VFILL - This is equal to (VEXPAND|VCONTRACT)
FILL - This is equal to (HFILL|VFILL)
DONTFILL - This is equal to 0.
By default, the control will fill the cell in both directions.
Now if the control does not fully fill the cell, there may be instances where the width of the control
is less than the width of the cell, or the height of the control is less than the height of the cell. You
can specify how the control is to be aligned in the cell vertically and horizontally should this happen
by OR'ing together the following values (their names are self explanatory)
TOP, BOTTOM, LEFT, RIGHT
If no horizontal specifier is used, it will default to horizontally centering the control. Alternatively
you can use HCENTER to explicitly do this. Similarly if no vertical specifier is used, it will default
to vertically centering the control. You can use VCENTER to explicitly do this as well. There are
some alternatives to these values that can also be used:
NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST
The fill values are OR'ed together with the alignment values for the call to setControl().
The addLast()/addNext() methods, as well as the setControl() and setCell() methods, all return the
control being added to the panel. That way you can conveniently string together a set of method
calls; e.g.
addLast(new Button("Hello")).setCell(HSTRETCH).setControl(HFILL|VCENTER);
You can also use the convenience addNext() and addLast() methods which also take the cell
constraints and control constraints as additional arguments:
addLast(new Button("Hello"),HSTRETCH,HFILL|VCENTER);
Let's try an example. When you run this program you will get a resizable frame. Try resizing it and
see how the controls respond. Try modifying the FILL values and see how the controls react.
package samples.ui;
import eve.ui.*;
//##################################################################
public class FirstLayout {
//##################################################################
//
===================================================================
public static void main(String args[])
//
===================================================================
{
Application.startApplication(args);
Form f = new Form();
f.title = "First layout!";
f.exitSystemOnClose = true;
f.resizable = true;
f.moveable = true;
// First Row.
f.addNext(new Button("One"),Form.DONTSTRETCH,Form.FILL);
f.addLast(new Button("Two-wide"),Form.HSTRETCH,Form.FILL);
// New Row.
f.addNext(new Button("Three"),Form.VSTRETCH,Form.FILL);
f.addLast(new Button("Four"),Form.STRETCH,Form.FILL);
//Try uncommenting the line below to see its effect.
//f.equalWidths = true;
f.execute();
Application.exit(0);
}
//##################################################################
}
//##################################################################
Advanced Control Layout and Modifications
Tagging a Control
A tag is an integer value (which represents some tag name) coupled with an object value
(represented by eve.util.Tag). It is a way of adding specific values to any object with a TagList
(including Controls) in a very memory conservative way. You can set tags on a Control by calling
the setTag() method. There are tags which are used by controls to specify how they behave in
CellPanels. These are explained below.
TAG_INSETS
This specifies the distance between the cell boundary and the start of the area allocated in
the cell for the control and you must use an eve.fx.Insets object to specify the values. The
constructor for Insets is Insets(int top,int left,int bottom,int right) For example:
addLast(new Button("Hello")).setTag(TAG_INSETS, new Insets(2,3,2,3));
The button will now be placed two pixels below and above the cell top and bottom
boundaries, and three pixels after and before the left and right boundaries. By default, no
INSETS tag is specified which defaults to no inset values.
TAG_SPAN
This specifies the number of cells a control occupies horizontally and vertically. You must
provide an eve.fx.Dimension object to specify the horizontal (width) and vertical span
(height) of the control. By default the span will be set to (-1, -1). A value of less than zero
for the width or height indicates that it should initially try to be 1, but if it is placed adjacent
to an empty cell on its right or below it, it should automatically extend the control into these
empty cells. Consider the following lines:
addLast(new Button("Hello"));
addNext(new Button("There"));
addLast(new Button("OK?"));
The first row only introduces one cell horizontally. The second row introduces two. The end
result is that the CellPanel will be a grid of two cells across and two cells down. However
the top right cell is unoccupied. Because by default the span of the first button would be (-1,
-1) during the make() process it will be set to span across into the empty cell so that the
whole grid of controls is still properly aligned. This is called auto-spanning. Auto-spanning
is done both horizontally and vertically. If you don't want a cell to be auto-spanned, set its
span to be explicitly (1,1) like so:
addLast(new Button("Hello")).setTag(TAG_SPAN,new Dimension(1,1));
addNext(new Button("There"));
addLast(new Button("OK?"));
Now the first button will be aligned directly over the second button only, and will not be
stretched over the third button.
TAG_PREFERREDSIZE, TAG_MINIMUMSIZE, TAG_MAXIMUMSIZE
This sets the preferred, minimum and maximum sizes of the control, and must be specified
by Dimension objects. Because these are so often called they have their own methods as
well:
Control.setPreferredSize(Dimension), Control.setMinimumSize(Dimension),
Control.setMaximumSize(Dimension)
However, remember that the maximum size of a control does not have any effect on layout
yet.
TAG_TEXTSIZE
This sets the preferred size of the control to be a certain number of characters wide and high.
You can also call Control.setTextSize(Dimension)
Borders
The int borderWidth member specifies how far the grid of cells is placed from the edge of each
side of the CellPanel. The int borderStyle member specifies the type of border that will be drawn
around the CellPanel. The values you can use are:
BDR_RAISEDOUTER, BDR_SUNKENOUTER, BDR_RAISEDINNER,
BDR_SUNKENINNER
You should have one OUTER style OR'ed with one INNER style. Together with these, or alone, you
can also OR with:
BDR_OUTLINE, BDR_NOBORDER, BDR_DOTTED
BDR_OUTLINE specified that a flat single line should be drawn around the panel. This can be in
addition to the raised or sunken inner/outer borders - or it can be used without them.
BDR_NOBORDER specifies that no border should be drawn. BDR_DOTTED specifies a dotted
border is to be drawn. When this option is specified all other border options (except for
NOBORDER) are overridden.
You can also OR these values with border flags that specify which sides of the border should be
drawn:
BF_TOP, BF_BOTTOM, BF_RIGHT, BF_LEFT, BF_RECT (all sides)
There is also BF_FLAT that overrides the 3D effect and forces a flat border, and BF_MONO
which forces black and white border drawing only.
There are some convenience values defined for you:
EDGE_RAISED
EDGE_SUNKEN
EDGE_ETCHED
EDGE_BUMP
= (BDR_RAISEDOUTER | BDR_RAISEDINNER)|BF_RECT
= (BDR_SUNKENOUTER | BDR_SUNKENINNER)|BF_RECT
= (BDR_SUNKENOUTER | BDR_RAISEDINNER)|BF_RECT
= (BDR_RAISEDOUTER | BDR_SUNKENINNER)|BF_RECT;
NOTE: If you specify a border style you must also specify a border width that is wide enough to
display the border without overlapping onto the cells. A value of two is the minimum value you
should use for the 3D border styles.
NOTE ALSO: If you specify a non-zero border width, and the border style is set to zero, then a
solid border WILL be drawn around your control. If you want a border spacing but do not want the
border to be drawn, then set borderStyle to be BDR_NOBORDER.
In addition to Panels, borders also apply to many other controls.
A Text Border is a special type of border that can be placed around Panels and CellPanels. This is
basically a single line of text and an etched border around the Panel. To display such a border
around a Panel, simply call setText() on the Panel. The example below (from the previous chapter)
shows a text border "Testing the panel" around a Panel.
Modifying Controls
There are a number of ways the look and behavior of a Control can be modified. This is
accomplished by setting or clearing bit flags in the modifier field. For convenience there are a
number of methods which are used to set and clear flags without affecting the state of the other
flags.
To get a list of the available modifiers, check the API for eve.ui.ControlConstants
public int modify(int flagsToSet, int flagsToClear);
This sets and clears the flags specified. The return value is the value of the flags (both the set and
clear flags) before the operation is done. You can use this to restore the flags to its original values
by using:
public void restore(int valueReturnedByModify,int flagsToSetORedWithFlagsToClear);
For example:
int oldValue = aControl.modify(Invisible,Disabled);
// do some code...
aControl.restore(oldValue,Invisible|Disabled);
public void modifyAll(int toSet,int toClear,boolean doThisOne);
This modifies all the sub-controls of the control. If doThisOne is true, then it also modifies the
control itself.
To check on the status of the modifier use this:
public boolean hasModifier(int which,boolean inheritFromParent);
There are many others, check the API for more details and check the API for the class
ewe.ui.ControlConstants to get the complete list of flags.
Eve Application Development
Michael L Brereton - 02 January 2008, http://www.ewesoft.com/
Beginners Guide - Contents
<< Previous: Laying Out Controls
>> Next: Handles And Tasks
Event Handling
Eve Application Development
Event Handling
The PRESSED ControlEvent and DATA_CHANGED DataChangeEvent
Event Handling Example
Event Listeners
Correctly using show() and exec()
Using newEventThread()/resumeEventThread()
Menu and List Events
SELECTED and DESELECTED Events
PRESSED Events
Like most GUI based systems, Eve uses an event-based model for receiving user input and for
generating control actions. You react to user input in your applications by trapping and handling the
appropriate events. The Events used by Eve are mostly contained in the package eve.ui.event.
The main events received by your controls will be pen/mouse events (as represented by a PenEvent
object) and keyboard events (as represented by a KeyEvent object). These events are sent directly
to a single Control object as determined by the Window manager. For PenEvents the Window
manager determines the actual Control object being pressed and sends the appropriate event directly
to it, adjusting x and y co-ordinates of the on-screen mouse/pen location to be relative to the target
Control. For KeyEvents the Window manager determines which Control has the current keyboard
“focus” and sends the KeyEvent directly to it.
In each case the appropriate Event is sent synchronously to the target by calling the Control’s
postEvent() method. This method will call the onEvent() method of the Control which it then uses
to adjust its internal state and, if necessary, generate further Events in reaction to the user input.
The Events generated by the Control objects themselves are then passed to its parent container via a
call to the parent’s onEvent() method. The event is passed all the way up the parent chain until it
reaches the top-level Window. The parent can determine which control generated the Event by
looking at the target member of the Event. Most of the events that you will be handling will be
those generated by Controls in reaction to user input.
NOTE: Pen/Mouse and Key events are not passed up the Gui control tree unless you modify the
Control to set the SendUpUIEvents modifier bit.
The PRESSED ControlEvent and DATA_CHANGED
DataChangeEvent
There are two main event types that are generated by Control objects in reaction to user input and
these are the two that you will be handling the most.
Some Controls, such as Button controls, do not have an internal state but will react to a pen press.
When the appropriate user action is performed on the Control they generate a ControlEvent with
the type of the Event set to ControlEvent.PRESSED.
Most other Controls, such as Input and CheckBox controls, keep some form of data as an internal
state. User input may cause that state to change. These Controls generate a DataChangeEvent with
the type of the Event set to DataChangEvent.DATA_CHANGED.
Using these two events alone, you will be able to handle the majority of user input in your
programs. Note that some Controls (such as CheckBox controls) will generate both a PRESSED
and DATA_CHANGED event, but you should handle only one of them.
An example of handling events is given below. It is a simple Form that takes as input two numbers
and calculates another value from them. The numbers are either multiplied or divided depending on
what the user chooses in the Choice.
You will note that the calculation is done and the result updated once the user changes any of the
inputs. This is caused by handling the DataChange event.
Event Handling Example
package evesamples.ui;
import eve.sys.Convert;
import eve.sys.Event;
import eve.ui.*;
import eve.ui.event.ControlEvent;
import eve.ui.event.DataChangeEvent;
//##################################################################
public class Events extends Form{
//##################################################################
Input firstNumber, secondNumber, result;
Choice operation;
Button message,quit;
//===================================================================
public Events()
//===================================================================
{
title = "Form Demo";
resizable = true;
addLast(firstNumber = new Input(),HSTRETCH,FILL);
addLast(secondNumber = new Input(),HSTRETCH,FILL);
addLast(result = new Input(),HSTRETCH,FILL);
result.modify(DisplayOnly,0);
addLast(operation = new Choice(new String[]
{"Multiply","Divide"},0),HSTRETCH,FILL);
addNext(message = new Button("Message"),HSTRETCH,FILL);
addNext(quit = new Button("Exit"),HSTRETCH,FILL);
}
//------------------------------------------------------------------private void calculate()
//------------------------------------------------------------------{
try{
double one = Convert.toDouble(firstNumber.getText());
double two = Convert.toDouble(secondNumber.getText());
double answer =
operation.getInt() == 0 ? one*two : one/two;
result.setText(""+answer);
}catch(Exception e){
result.setText(e.getMessage());
}
}
//===================================================================
public void onEvent(Event ev)
//===================================================================
{
if (ev instanceof DataChangeEvent && ev.type ==
DataChangeEvent.DATA_CHANGED){
calculate();
}else if (ev instanceof ControlEvent && ev.type == ControlEvent.PRESSED){
if (ev.target == quit){
Application.exit(0);
}else if (ev.target == message){
new MessageBox("Hello","Hello there!",MBOK).execute();
}
}
super.onEvent(ev); //Make sure you call this.
}
//##################################################################
}
//##################################################################
This example shows how you normally handle “Form-centric” data input. There is another method
of handling input which is more “Object-centric” and uses a eve.ui.data.Editor to automatically
update variables in an Object from data entered by the user. This is discussed in a later chapter.
Event Listeners
As we have seen, the event model allows parent Controls to capture events generated by their child
controls. However it is also possible for an object which is not a container for a Control to handle
the Control’s events. Any object which implements the EventListener interface can receive such
events. You can do this by calling the addListener(EventListener listener) method on a Control.
You can also call removeListener() later to remove the listener.
Correctly using show() and exec()
As mentioned before exec()/execute() display a Form modally and create a new Event Thread for a
window if they are called within a window’s Event Thread. This allows the blocking of the old
Event Thread but still allow the original window to receive repaint/resize events normally.
The show() method displays a Form non-modally. The new Form is displayed and the method
returns immediately, but a new Event Thread for the old window will not be created. Therefore
you should not block the event delivery thread if you call the show() method since there will be no
other delivery thread to handling incoming user events.
Here is an example that shows how you can display new Forms and the correct and incorrect way to
handle waiting for the Form to close.
import eve.sys.Event;
import eve.sys.EventListener;
import eve.ui.*;
import eve.ui.event.ControlEvent;
//##################################################################
public class ShowExec extends Form{
//##################################################################
//===================================================================
public ShowExec()
//===================================================================
{
title = "Show/Exec Demo";
Button b;
addLast(b = new Button("Execute a Message Box!"));
//
//This functions correctly.
//
b.addListener(new EventListener(){
public void onEvent(Event ev){
if (ev.type == ControlEvent.PRESSED){
Control c = ((Control)ev.target);
String txt = c.getText();
c.setText("Waiting...");
new MessageBox("Executed","This will execute()
OK!",MBOK).execute();
c.setText(txt);
}
}
});
//
//This functions correctly as well.
//
addLast(b = new Button("Show a Message Box!"));
b.addListener(new EventListener(){
public void onEvent(Event ev){
if (ev.type == ControlEvent.PRESSED){
Control c = ((Control)ev.target);
String txt = c.getText();
c.setText("Waiting...");
new MessageBox("Shown","This will show()
OK!",MBOK).show();
c.setText(txt);
}
}
});
//
//This is wrong! It calls a show, which does NOT spawn a new event thread,
//and then attempts to wait, which blocks all Gui events. It eventually
gives
//up and returns after 5 seconds.
//
addLast(b = new Button("Bad Show and Wait for Message Box!"));
b.addListener(new EventListener(){
public void onEvent(Event ev){
if (ev.type == ControlEvent.PRESSED){
Control c = ((Control)ev.target);
String txt = c.getText();
c.setText("Waiting...");
MessageBox mb = new MessageBox("Shown","This will
show() OK!\nBut will block for 5 seconds.",MBOK);
mb.show();
//This will block this window's event thread for 5
seconds.
try{
Form.waitUntilClosed(mb.handle,new
eve.sys.TimeOut(5000));
}catch(Exception e){}
c.setText(txt);
mb.exit(0);
}
}
});
//
// If you want to wait on a non-modal form to close or you
// want to block for any other reason, you will have to call
// newEventThread()
//
addLast(b = new Button("Good Show and Wait for Message Box!"));
b.addListener(new EventListener(){
public void onEvent(Event ev){
if (ev.type == ControlEvent.PRESSED){
final Control c = ((Control)ev.target);
final String txt = c.getText();
c.setText("Waiting...");
show() OK!",MBOK);
MessageBox mb = new MessageBox("Shown","This will
mb.show();
if (false){
//
// This is one way to do it.
//
Object oldEvent = c.newEventThread(c);
try{
mb.waitUntilClosed();
}finally{
c.resumeEventThread(oldEvent);
}
}else{
//
// This uses a convenience method to wait on
a Handle
//
c.waitEventThread(mb.handle);
}
c.setText(txt);
}
}
});
}
public static void main(String[] args)
{
Application.startApplication(args);
Form f = new ShowExec();
f.exitSystemOnClose = true;
f.show();
// The application will not exit here.
}
//##################################################################
}
//##################################################################
Using newEventThread()/resumeEventThread()
This method in Control is used if you are (or may be) within an Event Thread but
wish to wait on some external thread or event without blocking events being
delivered to the Window. You would use it like this:
// Here I am in an Event handler for the Control c
Object oldEvent = c.newEventThread(c);
try{
//
// Now I can block for as long as I need.
// The Control c will not receive user events, only repaint/resize events.
//
}finally{
c.resumeEventThread(oldEvent);
}
If the parameter to newEventThread() is null then all Controls will continue to receive events as
normal, otherwise the specified control will not receive pen/keyboard events, only repaint and
resize events.
The method pauseEventThread() is a convenience method that calls newEventThread(this) , i.e. it
calls newEventThread() and disables itself.
You can safely call newEventThread() even if the current thread is not a Window Event Thread.
Menu and List Events
These events are a little more complicated than the PRESSED and DATA_CHANGED events and
need some explaining.
SELECTED and DESELECTED Events
Menu and List Controls generate MenuEvent and ListEvent event objects. In fact ListEvent
inherits from MenuEvent and so is very similar, just as List inherits from Menu. These events have
a field called selectedItem and this will indicate the item selected (or deselected) that caused the
event to be generated. This field is of type Object because it can be either a MenuItem object
(which is usually the case) or a String representing the text of the item. So when you are handling a
MenuEvent/ListEvent you should always check what type this value is at run-time and not assume
it is one or the other.
Menu events also have a field called menu. This indicates the Menu or List that the selectedItem
belongs to. This may be different from the target of the event, which is the usual way you
determine the source of the event. However the target of a MenuEvent may change as the event
propagates up chains of sub-menus. Each parent menu that detects a MenuEvent coming from one
of its sub-menus will modify the target field so that it appears to come from itself. However, it will
not change the menu field, so this always refers to the original generator of the event. This also
happens with the MenuBar control – it too changes the target field so that any events generated by
menus that it contains will look as though they come from the MenuBar itself.
PRESSED Events
Menus are generally used for the selection of a single item within a menu or a set of menus. As a
result they will also generate a standard PRESSED event when an item (which is not associated
with a sub-menu) is selected via the keyboard or pen/mouse press.
Lists are generally used differently, and frequently multiple item selection is allowed. Therefore the
PRESSED event is generally not generated unless an item is double clicked with the pen/mouse.
However a List will generate a DataChangedEvent when the user changes the currently selected
item.
Eve Application Development
Michael L Brereton - 02 January 2008, http://www.ewesoft.com/
Beginners Guide - Contents
<< Previous: Event Handling
>> Next: Dynamic Controls
Handles and Tasks
Eve Application Development
Handles and Tasks
TimeOut Objects
Handles
Handle State
Handle Progress
Other Handle methods
Return Values
Tasks
Monitoring a Handle/Task with a ProgressBarForm
Many of the methods in the Eve library use or return eve.sys.Handle Objects. It is important to
understand how they work and how to use them.
TimeOut Objects
An eve.sys.TimeOut object specifies a length of time allowed for some operation to execute
with millisecond precision. You create a TimeOut object by calling its constructor with the length
of time in milliseconds as the parameter. Once this is done you can call the following methods on
it:

boolean hasExpired() – This reports whether or not the specified time-out period has
elapsed.

void reset() – This tells the TimeOut to restart the countdown with the same period as
before.

int remaining() – This reports the number of milliseconds left before expiring. If it returns
<= 0 then the timeout has expired.

int elapsed() – This reports the number of milliseconds that has elapsed since creation or
since the last reset().

void expire() – Forces an early expiration of the TimeOut.
There are also two constant TimeOut objects that are predefined – TimeOut.Forever which
never expires, and TimeOut.Immediate which is always expired. These are useful when you
want to either wait indefinitely or not at all.
Handles
A Handle is an object that allows you to monitor the status of an arbitrary asynchronous task. A
task will set various flags and values on a handle to give an indication as to the progress and
status of the running task. Any thread can monitor the progress and status of the running task by
polling the handle or by waiting for certain conditions to occur.
Handle State
The state of a Handle is used to report whether the task the Handle is monitoring is running or
has stopped, whether it was successful or if it failed, and when the task has attained certain
critical points along its progress. The state of the Handle is really a 32-bit integer with each bit
representing a different condition. The top six bits are reserved and are defined to be: Changed
(indicates some kind of change of state), Running (indicates that the task has begun and is still
running), Stopped (indicates that the task has stopped and no further state changes will occur),
Success (indicates that the task has successfully done what it had set out to do), Failure
(indicates that the task failed to do what it was supposed to), Aborted (indicates that the task was
aborted due to an outside – possibly user – intervention). All other bits are free to be programmer
defined.
A running task can change the state of a Handle by calling set(int newState) or by calling
setFlags(int bitsToSet, int bitsToClear). A monitoring thread can call the check() method to
check the current state of the handle. This is a non-blocking call which simply returns the current
state of the handle.
A Thread can call one of the many waitOn() methods to wait until certain bits of the Handle
state have been set. Check the API for a description of the various waitOn() methods. A Thread
can also call waitUntilStopped() or waitUntilCompletion() to wait for the handle to report that
it has stopped.
Handle Progress
The progress of a Handle is meant to represent a fractional value that indicates how close the
task is to completion. A value of 0.0 indicates that the task has now begun and a value of 1.0
indicates that the task is complete. This value is only an indication of the task progress and
should not be used to determine the running/stopped state of the task. During the execution of a
Task the progress may be reset to zero several times due to the execution of sub-tasks within the
main task. The progress of a Handle is set using setProgress(float progress). Using this method
ensures that any Threads monitoring the task gets alerted to the change in progress. You can also
use a value of -1 which is used to indicate that work is being done even though the exact
progress towards completion may be unknown. If a ProgressBar control is monitoring the
Handle setProgress(-1) causes the ProgressBar to show a small “bouncing” block instead of a
steadily increasing bar.
An additional variable – doing is also accessible. This is a String that can be used to give an
indication as to what the task is doing at any point in time. Use of this variable is completely
optional. The method startDoing(String task) is a convenient way to set the progress to zero
and set the doing variable at the same time.
Other Handle methods
The void stop(int reason) method is used to request that the task be stopped. However whether
or not this works depends on the Handle and the task being monitored. You should never assume
that as soon as this method is called the task will stop. You should always wait until the Stopped
bit has been set.
Return Values
A Handle can also be used to return a value from the asynchronous task. The task can set the
returnValue variable to be any kind of object. There is also an error value that can be set to a
Throwable Object if an error occurs in processing that may have caused it to abort operations.
Usually the Handle will set the Failed status bit after setting the error value.
The method succeed(Object returnValue) is a convenience method used to set the returnValue
and then set the flags to Stopped|Success. The method fail(Throwable t) is a convenience
method used to set the error value and then set the flags to Stopped|Failure.
Tasks
The class eve.sys.Task can be thought as a Handle that can run itself within a new Thread or it
can be thought of as a Thread with its own Handle (itself). To create a new Task extend the Task
Object and then override the method doRun(). The start() method of Task will ensure that it first
sets the Running status bit before calling doRun(). When doRun() returns it will automatically
ensure that the Running bit is cleared and that the Stopped bit is set.
Monitoring a Handle/Task with a ProgressBarForm
The example below shows how to use a Task to do a process in a separate Thread and monitor the
progress of the process.
package evesamples.ui;
import java.io.IOException;
import eve.sys.Handle;
import eve.sys.Task;
import eve.ui.Button;
import eve.ui.Form;
import eve.ui.MessageBox;
import eve.ui.ProgressBarForm;
import eve.ui.ReportException;
public class TestProgress extends Form{
//
// Do some work in the current Thread.
//
public String doSomething(Handle h) throws IOException
{
if (h == null) h = new Handle();
h.startDoing("Preparing...");
int max = 10;
for (int i = 0; !h.shouldStop && i<max; i++){
//
// Should do some real work here.
//
try{Thread.sleep(300);}catch(InterruptedException e){}
h.setProgress((float)(i+1)/max);
}
if (h.shouldStop) return null;
h.startDoing("Doing...");
for (int i = 0; !h.shouldStop && i<max; i++){
try{Thread.sleep(100);}catch(InterruptedException e){}
h.setProgress((float)(i+1)/max);
}
if (h.shouldStop) return null;
// Fake an error.
if ((int)(Math.random()*2) == 0) throw new IOException("Fake error!");
return "Did my work!";
}
//
// Create a Handle to run doSomething() in a separate Thread (Task).
// You must call start() on the returned Handle to begin the process.
//
public Handle doSomethingInThread()
{
return new Task(){
protected void doRun(){
try{
String got = doSomething(this);
// If doSomething() returned null, then
// the stop() method was called on this task
// which set the shouldStop field to true.
if (got == null) set(Aborted|Stopped);
else succeed(got);
}catch(IOException e){
fail(e);
}
}
};
}
public TestProgress()
{
title = "Test Progress";
maximizeOnPDA();
Button b = new Button("Do something now.");
addLast(b).setCell(HSTRETCH);
b.action = "doSomething";
doButtons(CANCELB);
}
public boolean handleAction(String action)
{
if (action.equals("doSomething")){
Handle h = doSomethingInThread();
ProgressBarForm pbf = new ProgressBarForm();
pbf.title = "Progress...";
pbf.maximizeOnPDA();
pbf.setMainTask("Doing something!");
pbf.showStop = true;
pbf.horizontalLayout = false; //Put cancel below the bar.
Object got = pbf.executeAndGetReturnValue(h);
if (got == null){
if (h.error != null)
new ReportException(h.error).execute();
else
new MessageBox("Aborted","You aborted the
process!",MessageBox.MBOK).execute();
}else{
new MessageBox("Success","Process returned:
"+got,MessageBox.MBOK).execute();
}
return true;
}
return super.handleAction(action);
}
}
The method doSomething(Handle h) performs a lengthy task in the current Thread. It returns a
String on success, or null if the stop() method is called on the provided Handle (because
h.shouldStop will be set true). It can also throws an IOException (we will fake one randomly).
Doing the method doSomethingInThread() wraps the doSomething() method within a Task.
When start() is called on the returned Handle (which is a Task in this case) it calls the run()
method on the Task which then calls doRun(). The doRun() method calls the doSomething()
method, passing itself as the Handle. If doSomething() returns a valid String it reports success and
sets the returnValue to be the returned String by calling succeed(). If an exception is thrown it
calls fail() to set the error value to that error. If doSomething() returns null it can only mean that
the stop() method was called on the Task and so it sets the Aborted and Stopped bits.
The handleAction() method actually starts the task running and monitors it using a
ProgressBarForm. There are a number of useful methods in ProgressBarForm one of which is
executeAndGetReturnValue(Handle h). This method calls start() on the Handle (if it is already
started this has no effect) and then monitors and displays the progress of the Handle. When
complete it will return the returnValue of the Handle if any. Our code then checks that return
value and if it is null it checks the error value to determine if this is due to an Exception or due to
the user aborting.
Eve Application Development
Michael L Brereton - 02 January 2008, http://www.ewesoft.com/
Beginners Guide - Contents
<< Previous: Handles And Tasks
>> Next: Images and Pictures
Dynamic Controls
Eve Application Development
Dynamic Controls
Hiding/Showing Controls
The SingleContainer Control
The MultiPanel Interface
The MultiCardPanel Interface
The CardPanel
The TabbedPanel
Containers such as Panel and CellPanel are not meant to be easily changed after being displayed.
Although this is possible, it can be quite complicated. If you need to change a Control or set of
Controls you should either use a SingleContainer or use one of the containers that implement
MultiPanel.
Hiding/Showing Controls
It is possible to hide and reveal controls programmatically, while adjusting the size of the containing
Window appropriately. To hide a Control and its children call hide(boolean update) on the Control.
You can then call unhide(boolean update) to show the Control again. For every call of hide() there
must be a matching call of unhide(). Two calls of hide() will require two calls of unhide() to get the
Control to reappear.
When a Control is hidden or unhidden the relayout() method is invoked on a parent of the Control
if the update parameter is true (this should usually be set true). The parent Container that is used
defaults to the containing Frame for the Control (determined by getFrame() at runtime) but this
can be changed to a different parent using the method void setHiddenParent(boolean
startHidden, Container parent). This method indicates whether the Control should be initially
hidden (if startHidden is true) and which parent is to be used for hiding the Control (if it is null it
will default to getFrame()). If the hide parent is not the Frame for the Control, then the containing
Window will not be resized to adjust to the new layout.
The example below illustrates how hide() and unhide() work.
package evesamples.ui;
import eve.ui.*;
import eve.ui.event.*;
/**
* This class tests the HideControl class.
*/
//####################################################
public class DynamicPanel extends Form{
private Input searchFor, replaceWith;
private Button moreDetails, search;
private CellPanel details;
private boolean showingDetails;
private CheckBox matchCase, wholeWord, regularEx, wrapSearch;
private final String less = " Less Details ";
private final String more = " More Details ";
public DynamicPanel()
{
title = "Search and Replace";
InputStack is = new InputStack();
is.inputLength = 30;
searchFor = is.addInput("Search For:","Anything");
replaceWith = is.addInput("Replace With:","Nothing");
addLast(is).setCell(HSTRETCH);
//
details = is = new InputStack();
is.columns = 2;
matchCase = is.addCheckBox("Match Case");
wholeWord = is.addCheckBox("Whole Words");
regularEx = is.addCheckBox("Regular Expression");
wrapSearch = is.addCheckBox("Wrap Search");
is.setText("Search Options");
addLast(details).setCell(HSTRETCH);
//
// Set the details to be initially hidden.
//
details.setHiddenParent(true,null);
//
addButton(moreDetails = new Button(more));
moreDetails.modify(NoFocus,0);
showingDetails = false;
addButton(search = new Button("Search"));
//
doButtons(DEFCANCELB);
}
public void onControlEvent(ControlEvent ev)
{
if (ev.type == ControlEvent.PRESSED){
if (ev.target == moreDetails){
if (showingDetails){
moreDetails.setText(more);
details.hide(true);
}else{
moreDetails.setText(less);
details.unhide(true);
}
showingDetails = !showingDetails;
}
}
super.onControlEvent(ev);
}
}
//####################################################
This is how the Form looks when created. The details.setHiddenParent(true,null); line tells the
Form that the details control should initially be hidden, and that the parent for unhiding the control
should be the full Frame of the Form at runtime (because the parent parameter was null).
After pressing the “More Details” button the Form is updated and the Window is resized to look
like this:
The SingleContainer Control
This is a container that you add a single control to but which will allow that control to be changed
dynamically on-screen. You add the first control and change the displayed control using the
setControl() methods. That single control can, of course itself be a container, and so you can
effectively add and remove any number of controls dynamically by placing them all in a Panel
which you then place in a SingleContainer.
The example below shows how to use a SingleContainer using an Editor as the base Form. The
Editor class will be explained in a later chapter.
package evesamples.ui;
import eve.fx.Insets;
import eve.sys.Locale;
import eve.ui.*;
import eve.ui.data.Editor;
import eve.util.mVector;
public class TestSingleContainer extends Editor{
Panel one, two, three;
SingleContainer single;
//
===================================================================
public TestSingleContainer()
//
===================================================================
{
title = "Testing Single Container";
Panel p = new Panel();
p.defaultTags.set(Panel.TAG_INSETS,new Insets(2,2,0,0));
p.setText("Testing the panel");
Label l;
p.addNext(l = new MessageArea("Hello\nGood to meet you."));
l.alignment = l.anchor = RIGHT;
p.addLast(new Button("There!"));
p.addNext(l = new MessageArea("How\nNice to see you,\nI trust you
are well"));
l.alignment = CENTER; l.anchor = LEFT;
p.addLast(new Button("are you doing?")).setTag(TAG_INSETS,new
Insets(4,4,4,4));
p.addLast(new Button("I'm alone"));
one = p;
single = new SingleContainer();
single.setControl(one);
addLast(single);
addNext(addField(new Button("One"),"uno")).setCell(HSTRETCH);
addNext(addField(new Button("Two"),"dos")).setCell(HSTRETCH);
addNext(addField(new Button("Three"),"tres")).setCell(HSTRETCH);
two = new CellPanel();
p = two;
p.addLast(new Button("are you doing?")).setTag(TAG_INSETS,new
Insets(4,4,4,4));
p.addLast(new Button("I'm alone"));
three = getLocaleList();
}
//
===================================================================
public void action(String name,Editor ed)
//
===================================================================
{
if (name.equals("uno")) {
ed.getWindow().setTitle("First set!");
single.setControl(one,true);
}
if (name.equals("dos")) {
ed.getWindow().setTitle("Second set!");
single.setControl(two,true);
}
if (name.equals("tres")) {
ed.getWindow().setTitle("Third set!");
single.setControl(three,true);
}
}
//
------------------------------------------------------------------static Panel getLocaleList()
//
------------------------------------------------------------------{
int [] ids = Locale.getAllIDs(0);
String [] locales = new String[ids.length];
Locale locale = new Locale();
for (int i = 0; i<ids.length; i++){
locale.set(ids[i]);
locales[i] = locale.toString();
}
List list = new List(10,40,false);
mVector.addAll(list.items,locales);
return new ScrollBarPanel(list);
}
}
The MultiPanel Interface
Containers which implement this interface allow you to change the control it displays between a set
of controls that you add via addItem(Control c, String tabName, String longName). The
tabName specifies the on-screen display name for that control. For a tabbed display, this will be
displayed in the tab. The longName is an optional name (it may be null) for display in special
circumstances and gives a more detailed description of the control being added.
The Control being added may itself be placed in another container before being added as an item to
the MultiPanel – this is completely up to the implementing MultiPanel. The various select()
methods are used to switch between the added controls.
The MultiCardPanel Interface
This is an extension of the MultiPanel interface and it can only be implemented by controls that use
a CardPanel as the underlying dynamic control display. It allows for the specifying of more
advanced data (such as icons for individual items) and for providing access to the underlying
CardPanel.
There are two MultiCardPanel implementations provided: a CardPanel and a TabbedPanel.
The CardPanel
This is a MultiCardPanel that can only have its displayed control changed programmatically via the
select() methods. That is, the CardPanel provides no user controls to switch between the added
controls.
The CardPanel is usually used as the main implementation of a MultiPanel – the only extra controls
needed is a control or set of controls to allow the user to switch between the available items.
The CardPanel, by default, will place all added controls in a ScrollBarPanel, thereby ensuring that
its contents will always be fully accessible. However if you do not wish this to happen you can set
the autoScroll field to be false.
The TabbedPanel
This is a MultiCardPanel that actually uses a CardPanel for its implementation. In fact you can
access its contained CardPanel through the cardPanel field (you would need to do this if you want
to switch off the autoScroll feature). It then uses a set of tabs as the user method for switching
between the various added controls.
The Eve TabbedPanel actually provides significant functionality and flexibility. An entire Chapter
on the TabbedPanel class will be done for the advanced UI programming tutorial.
Eve Application Development
Michael L Brereton - 30 December 2007, http://www.ewesoft.com/
Beginners Guide - Contents
<< Previous: Dynamic Controls
>> Next: Drawing On Controls
Images and Pictures
Eve Application Development 1
Images and Pictures 1
Images and Pictures 1
PixelBuffers 2
The interface ImageData (eve.sys) represents a 2D Object that has a particular width and height
and can provide pixel data as raw scan lines and as ARGB formatted int values (Alpha-RedGreen-Blue at 8 bits per channel). It is used to represent an abstract Object that contains image
pixel data but makes no assumptions about how that data is stored or how it is to be rendered to
any graphics entity. You will generally not need to implement objects of this class unless you are
doing specialized image processing.
The interface Drawable (eve.fx) represents a 2D Object that has a particular width and height and
“knows” how to draw itself onto a Graphics object.
The interface IImage (eve.fx) inherits from ImageData and Drawable and represents an object
with pixel data that knows how to draw itself onto a Graphics object. IImage is the base
interface implemented by most of the image based classes in the Eve library.
Images and Pictures
The class Image (eve.fx) is a standard bitmapped image that can be drawn on using a Graphics
object – similar to the java.awt.BufferedImage class. Its implementation is related closely to the
native OS and it usually is linked to a native object that represents a drawable bitmap (e.g. an
HBITMAP on Windows).
You can draw to an Image by creating a Graphics object for the image using new
Graphics(Image im) and then drawing onto that Graphics. You can convert an Image to a Picture
by calling toPicture() on the Image.
The class Picture (eve.fx) is an immutable bitmapped image that is optimized for fastest drawing
to a Graphics object. You cannot draw on a Picture so it must be created from:
1.
Decoding a formatted image (in JPEG, GIF, BMP or PNG format).
2.
From raw pixel data from any ImageData source (e.g. an Image).
The main constructors used for decoding from formatted image bytes are:
public Picture(String imageName);
The imageName is the name of the image resource (which may reside as a file, applet resource or
eve file resource). This method actually creates a FormattedDataSource using the imageName
as the source of the bytes and then uses the constructor below:
public Picture(FormattedDataSource source,Object maskObject,int options);
All the other Picture() constructors that decode formatted bytes eventually call this constructor.
The source parameter is the source of the formatted image bytes. The maskObject can be null, it
can be a Color (specifying the color that should be considered transparent), it can be a Mask
object (which defines a transparency bitmap) or it can be the name of a bitmapped image resource
which will then be converted to a Mask object.
It is recommended that you store images in PNG format, in which case you will not need to use
maskObject at all since PNG images can specify transparency (alpha) values for each pixel.
No options are currently defined for Picture constructors so you should set options to 0.
You can create a Picture from any ImageData object using:
public Picture(ImageData source,Object maskObject,int options);
MaskObject is the same as described above and no options are currently defined.
If you only wish to create a Picture from a section of an existing image then you should first
create a PixelBuffer and then convert the PixelBuffer to a Picture.
PixelBuffers
A PixelBuffer is an IImage that stores its pixels completely in an int array – with no underlying
OS specific image resource. It can be used for a number of purposes:
1.
To access and directly manipulate the pixel data for an image or a section of an image.
2.
To transform pixel data in some way.
3.
To compose images with arbitrary alpha values.
The third function is necessary because you cannot “paint” pixel data with transparency values to a
Graphics object. Although the Graphics object can alpha-blend an Image into the destination
drawing surface, the final pixels of the destination surface will always be fully opaque regardless of
the alpha value of the source image. A PixelBuffer provides methods that you can use to create an
image with transparent sections using standard Graphics drawing primitives. However this will be
covered in a chapter on advanced 2D imaging.
The pixel int array values for an PixelBuffer is available through the int[] getBuffer() method.
The size of this array will always be a minimum of width*height with each int value representing
a pixel in ARGB format. You can create a PixelBuffer from an existing image, a section of an
existing image, a scaled section of an existing image or from formatted image bytes. Once you
create the PixelBuffer you can access the pixels directly, perform a transformation on the pixels,
scale or set the transparency values of pixels or use drawing primitives to add to the image. Once
complete you can call toPicture() or toImage() to convert to a Picture or Image object.
Eve Application Development
Michael L Brereton - 02 February 2008, http://www.ewesoft.com/
Beginners Guide - Contents
<< Previous: Images and Pictures
>> Next: Table Controls
Drawing to Controls
Eve Application Development
Drawing to Controls
Example Custom Control
You will want to draw on controls either:
1. In response to a repaint request by the platform.
2. Directly to reflect changes in the control state or for animation.
The main method calls to request a repaint of a control are the repaintNow() or
repaintNow(Graphics g, Rect area) methods. These result in a call to doPaint(Graphics g, Rect
area) for that control and all its child controls. This is a synchronous call – the doPaint() method is
called in the same thread as the call to repaintNow(). To request a repaint that occurs within the
Window event thread you should call repaint() or repaint(int x, int y, int width, int height)
instead. You do not need to supply a Graphics for repaintNow(), one will be created if the Graphics
parameter is null.
When parts of the Window are invalidated the Window calls a repaintNow() in the event thread for
the area that needs repainting which then results in a call to doPaint().
Custom drawing of a Control will therefore require you to override the doPaint(Graphics g, Rect
area) method. The supplied area parameter is the area within the Control that needs repainting. The
Graphics provided will be a fully buffered Graphics object, i.e. it draws to an off-screen Image
which is then drawn on the screen when all drawing is done.
If you need to draw directly to an area on the Control when not in the doPaint() method you should
call:
BufferedGraphics getGraphics(int x, int y, int width, int height);
If the Control is not visible on the screen (it may not be in a visible Window) then this returns null.
Otherwise the method returns a BufferedGraphics object from which you get a Graphics object by
calling getGraphics(). You draw on the returned Graphics object (you must draw on the entire
requested area) and then call release() on the BufferedGraphics when complete. This will update
the area on the Control with what you have drawn on the Graphics.
Note that the Graphics returned by BufferedGraphics.getGraphics() has a coordinates relative to
the Control, and not relative to the x and y parameters specified in Control.getGraphics(). For
example the calls:
BufferedGraphics bg = getGraphics(10,20,100,50);
if (bg == null) return;
Graphics g = bg.getGraphics();
g.drawLine(10,20,50,50);
//Do other drawing.
bg.release();
The g.drawLine(10,20,50,50); will start at (10,20) within the control. Even though you have only
requested to draw in a section of the control, the Graphics will still be relative to the entire control –
however any drawing outside of the requested area will not affect the on-screen control.
Remember to draw the background for the control as well. By calling getGraphics() you invalidate
the requested area and so must repaint all data within it.
Example Custom Control
Here is an example of a simple custom control. It will consist of a grid of cells with some simple
text within each cell. Initially, each cell will be invisible until the user presses the pen/mouse over
that cell. First we’ll do the constructor and then an example main() method to display the control
within a Form.
package evesamples.ui;
import eve.fx.BufferedGraphics;
import eve.fx.Color;
import eve.fx.FontMetrics;
import eve.fx.Graphics;
import eve.fx.Rect;
import eve.ui.Application;
import eve.ui.CellPanel;
import eve.ui.Control;
import eve.ui.Form;
import eve.ui.event.PenEvent;
public class GridControl extends Control{
int numRows, numCols, cellWidth, cellHeight;
boolean[] visible;
public GridControl(int rows, int cols)
{
numRows = rows;
numCols = cols;
visible = new boolean[rows*cols];
backGround = Color.White;
}
public static void main(String args[])
{
Application.startApplication(args);
Form f = new Form();
f.title = "Test GridControl";
CellPanel cp = new CellPanel();
cp.setText("GridControl");
cp.addLast(new GridControl(4,4));
f.addLast(cp);
f.doButtons(Form.OKB);
f.execute();
Application.exit(0);
}
}
Now we would like for the control to be able to report a preferred size – but we would like it to
calculate the size based on the number of rows and columns in the grid. To do that we override
calculateSizes() and within that method we set the fields preferredWidth and preferredHeight.
This method is usually only called once (unless it is forced by a relayout() call) and when the
method is called you can call getFont() or getFontMetrics() to determine the font that has been
assigned to it.
/**
* This method is called (usually only once) and is used
* by the Control to calculate its preferred,minimum and maximum sizes.
*/
protected void calculateSizes()
{
FontMetrics f = getFontMetrics();
cellWidth = f.getTextWidth("(00,00)")+4;
cellHeight = f.getHeight()+4;
preferredWidth = cellWidth*numCols;
preferredHeight = cellHeight*numCols;
}
/**
Use this to determine the area of a particular cell in the control.
**/
public Rect getCellRect(int row, int col, Rect dest)
{
if (dest == null) dest = new Rect();
dest.x = col*cellWidth;
dest.y = row*cellHeight;
dest.width = cellWidth;
dest.height = cellHeight;
return dest;
}
Now we use this method to paint the control.
public void doPaint(Graphics g, Rect area)
{
for (int r = 0; r<numRows; r++)
for (int c = 0; c<numCols; c++)
paintCell(r,c,g);
}
The doPaint() method is called in response to a repaint() or repaintNow() call which can be called
explicitly or is called when the window needs refreshing because part of it has been covered and
then exposed. The method paintCell() has not been defined yet so we need to define it now:
private void paintCell(int row, int col, Graphics g)
{
boolean alwaysPaint = true;
Rect r = Rect.getCached();
try{
getCellRect(row, col, r);
//
BufferedGraphics bg = null;
if (g == null){
bg = getGraphics(r.x, r.y, r.width, r.height);
if (bg == null) return;
g = bg.getGraphics();
}
//
g.setColor(getBackground());
g.fillRect(r.x, r.y, r.width, r.height);
//
if (alwaysPaint || visible[row*numCols+col]){
g.setColor(getForeground());
g.drawRect(r.x,r.y,r.width,r.height);
String myLabel = "("+row+","+col+")";
int w = getFontMetrics().getTextWidth(myLabel);
g.setFont(getFont());
g.drawText(myLabel,r.x+((cellWidth-w)/2),r.y);
}
if (bg != null) bg.release();
}finally{
r.cache();
}
}
The first line defines a Boolean variable alwaysPaint and sets it true. We use this initially to display
all the cells regardless of the state of the visible[] flag for the cell. As long as alwaysPaint is true,
then all cells will always be painted.
Note the line Rect r = Rect.getCached(); This gets a Rect object for temporary use that will later
be released via a cache() call. Objects can be fetched out of the cache very quickly (quicker than
allocating a new one) and as long as it is replaced back in the cache it will cause no object to be
garbage collected. Any object can be cached using the eve.sys.Cache class but commonly used
objects like Rect and Point have their own methods to do this for convenience.
Using this Rect we get the area for the cell we are painting with the getCellRect() method and now
we have to paint within that area. Now when the method is called from doPaint() the Graphics
object g will already be a valid Graphics, but there will be another circumstance when we call this
method without a valid Graphics object. In that case the parameter g will be null and we will have
to create our Graphics for the Control.
BufferedGraphics bg = null;
if (g == null){
bg = getGraphics(r.x, r.y, r.width, r.height);
if (bg == null) return;
g = bg.getGraphics();
}
That is the purpose of the call to getGraphics(). It creates and returns a (cached) BufferedGraphics
object for the specified area. We first make sure it is not null (which would indicate that the control
is not actually on screen) and then we get a Graphics object by calling getGraphics() on the
BufferedGraphics.
Now we can draw the cell using the Graphics g which we know will now be valid. First we make
sure to paint the background of the Control.
g.setColor(getBackground());
g.fillRect(r.x, r.y, r.width, r.height);
Then we paint the inside of each cell. Later we will set alwaysPaint to false.
if (alwaysPaint || visible[row*numCols+col]){
g.setColor(getForeground());
g.drawRect(r.x,r.y,r.width,r.height);
String myLabel = "("+row+","+col+")";
int w = getFontMetrics().getTextWidth(myLabel);
g.setFont(getFont());
g.drawText(myLabel,r.x+((cellWidth-w)/2),r.y);
}
if (bg != null) bg.release();
When we run this we get this window:
Now we’ll set alwaysPaint to false and when we run it we will see only a blank white rectangle. So
now we will add code to react to a pen/mouse press. Pressing the mouse on a particular cell will
make it visible or invisible.
public void onPenEvent(PenEvent pen)
{
if (pen.type == PenEvent.PEN_DOWN){
int r = pen.y/cellHeight;
int c = pen.x/cellWidth;
if (r >= 0 && r <numRows && c >= 0 && c <numCols){
visible[r*numCols+c] = !visible[r*numCols+c];
paintCell(r, c, null);
}
}
super.onPenEvent(pen);
}
The onPenEvent() is called by the default onEvent() method of Control. Here we are reacting to
PEN_DOWN but we could also react to PEN_UP in this method. Note that normally, for
performance reasons, a Control will not receive PEN_MOVED events. We will show how to
receive and react to these events a little later.
Now when we run the application we get a blank rectangle again, but pressing the mouse on the
control will reveal and hide the cells.
Now we will modify the program to handle the mouse cursor moving over the control by displaying
the cell under the mouse in a light blue color. First we will add a field to hold the cell the mouse
was last over. This is used so that we don’t continuously repaint the same cell if the mouse is
moving within a single cell. Then we will request that the control receive PEN_MOVE events in the
constructor.
Point mouseOver;
public GridControl(int rows, int cols)
{
numRows = rows;
numCols = cols;
visible = new boolean[rows*cols];
backGround = Color.White;
PenEvent.wantPenMoved(this, PenEvent.WANT_PEN_MOVED_ONOFF|
PenEvent.WANT_PEN_MOVED_INSIDE, true);
}
Now the control will receive PEN_MOVE and PEN_MOVED_OFF events. We will alter the
paintCell() method to paint the cell that the mouse is over differently.
boolean over = (mouseOver != null && mouseOver.x == col &&
mouseOver.y == row);
if (over || alwaysPaint || visible[row*numCols+col]){
getForeground());
g.setColor(over ? Color.LightBlue :
g.drawRect(r.x,r.y,r.width,r.height);
String myLabel = "("+row+","+col+")";
int w = getFontMetrics().getTextWidth(myLabel);
g.setFont(getFont());
g.drawText(myLabel,r.x+((cellWidth-w)/2),r.y);
}
And lastly we will modify the onPenEvent() method to handle the PEN_MOVE and
PEN_MOVED_OFF events.
public void onPenEvent(PenEvent pen)
{
int oldx = -1, oldy = -1;
if (mouseOver != null){
oldx = mouseOver.x;
oldy = mouseOver.y;
}
if (pen.type == PenEvent.PEN_MOVED_OFF && mouseOver != null){
mouseOver = null;
paintCell(oldy,oldx,null);
}
int r = pen.y/cellHeight;
int c = pen.x/cellWidth;
if (r < 0 || r >= numRows || c < 0 || c >= numCols) {
super.onPenEvent(pen);
return;
}
if (pen.type == PenEvent.PEN_DOWN){
visible[r*numCols+c] = !visible[r*numCols+c];
paintCell(r, c, null);
}else if (pen.type == PenEvent.PEN_MOVE){
if (oldx != c || oldy != r){
if (mouseOver != null){
mouseOver = null;
paintCell(oldy,oldx,null);
}
mouseOver = new Point(c,r);
paintCell(r,c,null);
}
}
super.onPenEvent(pen);
}
Eve Application Development
Michael L Brereton - 02 February 2008, http://www.ewesoft.com/
Beginners Guide - Contents
<< Previous: Drawing On Controls
>> Next: Tree Controls
Table Controls
Eve Application Development 1
Table Controls. 1
Extending the TableModel 1
The eve.ui.table.TableControl is one of the most useful and powerful controls in the Eve GUI
library. It can be used as is for displaying tabulated data, or it can be extended to provide more
advanced controls (e.g. the eve.ui.table.TreeControl inherits from the TableControl).
A Table rendered on screen is actually made up of two parts. The TableControl control itself is the
on-screen UI component responsible for laying out the table on the screen and for interpreting user
pen presses. A TableModel is used by the TableControl to specify a number of different aspects of
the table, including:
ü
The number of rows and columns in the table, and whether row and column
headers are to be used.
ü
The width of columns and the height of rows.
ü
The data, textual or otherwise, to be displayed in each cell.
ü
The display attributes for each cell, including fill (background) color, border
style, font, insets, etc.
Apart from the first set of data listed above, all the rest of the information is provided through
method calls in TableModel that you can override to provide your own functionality and
appearance. You do not need to extend TableModel to display your own data in a Table. You can use
a GridTableModel to display a simple grid of textual data (see the API on how to use a
GridTableModel). However there are two disadvantages of this method:
1.
All of the data to be displayed must be pre-created and placed in a Grid. For very
large tables, this will use a lot of memory.
2.
You still will not be able to customize the appearance of individual cells without
overriding the GridTableModel.
Overall it will usually be best to override the TableModel, especially when the data to be displayed
is very large, or is easily generated dynamically given the row and column of a particular cell.
Extending the TableModel
Row and Column Attributes
numRows and numCols specify the number of rows and columns in the table. This value does not
include the row and column headers – set the hasRowHeaders and hasColumnHeaders to be true
or false depending if these headers are being used.
Override calculateColWidth(int col) and calculateRowHeight(int row) to return the size (in
pixels) of a particular column and row. The values of col and row will start from 0. A col or row
value of –1 indicates the row headers width or column headers height. You may still be given a
parameter of –1 even if you have hasRowHeaders/hasColumnHeaders set to false. Therefore you
should always check if the parameter is –1 and return 0 in that instance if you are not using
headers.
Cell Data
There are two methods that are called to determine the data to be displayed in a cell – either of
which has the option of returning null. The first is boolean getCellText(int row,int col,StringList
destination). This method should append text data for the cell to the destination StringList using
one of the add() methods of StringList. If there is no text data for that cell, this method should
return false. If you are displaying nothing but text data in your table, then you just need to override
this method.
The second method is getCellData(int row,int col). This method can return any data – however the
default implementation of TableModel is only able to render a String, an array of Strings, an IImage
object, a Control or a ControlProxy. If you return null from getCellText() and return a value from
getCellData() which is not one of the ones listed above, you will have to override paintCellData()
to paint your custom data.
Cell Attributes
This is a very important aspect – it determines the appearance of a particular cell and the text within
it. This is determined through the method: getCellAttributes(int row,int col,boolean
isSelected,TableCellAttributes ta).
This method returns a TableCellAttributes object that can be the same one provided in the
parameter, or it can be a completely different one. The TableCellAttributes object contains all the
information needed by the TableControl to render the cell – including the cell text/data. In fact, it is
in the default getCellAttributes() method that the getCellText() and getCellData() methods are
called.
When overriding the getCellAttributes() method you should (in most cases) call the superclass
implementation of the method to setup the attributes to be the default values. You should then
modify the individual elements of the attributes to customize the appearance of particular cells.
Miscellaneous Methods
The method canSelect(int row,int col) should return true or false to determine whether a particular
cell can be selected or not. Note that if it returns true for cases where row or col values are –1, then
the entire row/column will be selected.
The method made() gets called when a make() is called on the containing TableControl. This gives
you an opportunity to do preparations for display. At this stage you will be able to get FontMetrics
from the Table that is displaying the model, for example.
The TableControl itself has some useful methods that you can use and override. Most of these are
fairly simple to use and are documented in the API.
Eve Application Development
Michael L Brereton - 02 February 2008, http://www.ewesoft.com/
Beginners Guide - Contents
<< Previous: Table Controls
Tree Controls
Eve Application Development 1
Tree Controls. 1
Object Representation organization vs. Object Composition organization.. 1
Object Representation in Trees – Using eve.data.TreeNode. 1
Object Composition in Trees – Using the TreeModelAdapter Class. 4
This chapter gives information on using Tree UI controls. These are very powerful controls that can
handle cut/paste operations as well as drag and drop operations. This chapter will discuss two
methods of organizing your data so that it may be displayed in a tree control, as well as the methods
needed to detect when the user interacts with the tree.
Object Representation organization vs. Object Composition
organization
Trees, tables and lists are controls that display a range of data. There may be circumstances where
this range of data is fairly small – say less than 100 data elements, or where the range is very large –
say tens or hundreds of thousands of elements. In the case where the data range is small it may be
convenient to have each element represented in memory as an Object organized in some sort of
structure. For trees, this structure would be a tree type data structure, for tables a grid data structure
would be appropriate, and for lists a single dimensional vector would be appropriate. This allows
the programmer to create the entire structure once and then allow the control to access the various
elements within the structure (depending on the view the user is interacting with) with no further
programmer assistance. This approach can be thought of as Object Representation.
However when dealing with large ranges of data this approach will not be appropriate. In addition
to the extended time required to read and create the structure, there will also be the problem of
memory limitations. Generally in these cases, the actual data to be displayed would be stored on a
local file system, and in these instances it is usually better to only read, decode and construct
objects to represent the data when it is necessary to display that data on screen. Because only a
small range of data is normally visible at a time, this approach usually works well at the expense of
a slight delay in display refresh time. This approach can be thought of as on-demand Object
Composition.
In the Eve UI library you can use either of these approaches when displaying data in trees, tables
and lists, usually be inheriting from one of two base control classes. Tree and Table controls
separate the “physical” user interface elements of the control from the data representation of the
control. They do this by having a separate object representing the control (eve.ui.TableControl and
eve.ui.TreeControl) and separate object representing the data (eve.ui.TableModel and
eve.ui.TreeTableModel). You should note that TreeControl inherits from TableControl and
TreeTableModel inherits from TableModel.
Object Representation in Trees – Using eve.data.TreeNode
This is very easy to do, since the basic TreeTableModel is geared towards easy display of tree data
using the eve.data.TreeNode interface. This interface represents data elements that can have a
single parent and several children. The interface consists of methods that allow a tree structure of
such elements to be traversed and to be displayed in a TreeControl.
Node Traversing Methods
These include: int getChildCount(), TreeNode getChild(int childIndex) and TreeNode
getParent(). These are the primary methods used to go up and down a tree data structure consisting
of TreeNode objects.
Tree Control Methods
These are methods that are primarily used by a TreeControl when displaying a tree data structure.
They include:
boolean canExpand(), boolean isLeaf(), boolean expand() and boolean collapse()
isLeaf() tells the control whether to consider the TreeNode to be a leaf (which will not contain
children) or a node (which may contain children). canExpand() tells the control whether or not it
should display a ‘+’ symbol next to the node icon, used by the user to expand the node to display its
children. expand() tells the node that the user has requested the node to be expand, and that the
node, if necessary, should gather and organize its children. After calling this method, the methods
getChildCount() and getChild() will be called to display the node’s children on-screen. This is
useful for data structures that prefer to delay the gathering and construction of children until the
user requests them to be displayed on-screen. Similarly, the collapse() method tells the node that the
user has collapsed the node and that, if it wishes, it can free its children until the next expand() call.
The MutableTreeNode Interface
This interface is an extension of TreeNode and represents a data object that can have its children
and parent manipulated. This is in contrast to TreeNode that only has methods to report the state of
the tree data structure, but has none to modify it. Most implementations of a TreeNode will actually
implement this interface as well.
The LiveTreeNode Object
This class is in the package eve.ui.data and is a full implementation of MutableTreeNode that also
implements the eve.ui.data.LiveData object, a very useful application-oriented object. You can
inherit from LiveTreeNode to build your data tree and then provide the TreeTableModel of the
TreeControl that you are using with the root object of your data tree. The tree will then be viewable
within the TreeControl.
Some methods you will want to override for this include:
String getName() – This returns the name of the object and will be displayed next to its icon.
IImage getIcon() – This should return a 16x16 image representing an icon to be displayed for
the node. By default it returns null which indicates to the tree control that default images
should be used.
boolean isLeaf() – By default this returns true if the node’s child count is zero.
boolean canExpand() – By default this returns true if the node’s child count is not zero.
Using TreeNode in a TreeControl
To set the root object in the tree control use TreeControl.getTreeTableModel() to get the
TreeTableModel for the control and then call TreeTableModel.setRootObject(TreeNode root).
After doing this you can then display the tree control, but remember to place it in a ScrollBarPanel
like this:
…
TreeControl tc = new TreeControl();
Form f = new Form();
f.addLast(new ScrollBarPanel(tc));
tc.getTreeTableModel().setRootObject(myRootObject);
…
Displaying the form will then display the tree control with its associated data structure.
Lines and Indexes of a TreeControl
One of the properties of a TreeControl is that each row in the display represents exactly one node
or leaf on-screen. This row is referred to as the line or index of the TreeControl (the two terms are
used interchangeably). There is a direct mapping between a line and a single displayed node or leaf.
Similarly, if a leaf or node is exposed on-screen, it is associated with a unique line/index.
There are several methods of TreeTableModel which use or return line/index values. In fact, this is
the primary method of manipulating the TreeControl’s display. There are two methods that allow
you to map TreeNode objects to line/index values.
int indexOf(TreeNode node)
This returns the line/index of the TreeNode if it is on-screen. If it is not (i.e. its parent has
not been expanded) it will return –1.
TreeNode getTreeNodeAt(int index)
This returns the associated TreeNode at a particular index in the TreeControl.
Here are some useful TreeTableModel methods, most of which use line/index values.
void paintLine(int line)
This immediately repaints the specified line.
void reExpandNode(int line)
This causes the node on the specified line to collapse and be re-expanded. This is useful if
you have made major changes to the child list of a particular node. However please note that
the methods described in the section “Modifying the Tree Structure” may be more
appropriate to update the display under some circumstances.
Modifying the Tree Structure
Modifying a tree of MutableTreeNode objects is very easy using the addChild() or removeChild()
methods. However changes that you make to your object tree will not automatically be reflected in
the TreeControl. In order for this to happen you must inform the tree control of what changes you
have made so it will know which nodes and which children to refresh on-screen. Here are some
TreeTableModel methods that you can use to update the display based on changes made to the tree
data structure.
boolean inserted(TreeNode parent, TreeNode child, boolean selectChild)
You should call this method after you have inserted a new TreeNode as a child of a parent
TreeNode. It prompts the display to re-expand the parent as needed to include the new child.
You can set selectChild to be true if you want the new child to be automatically selected.
This call re-organizes the tree controls internal data structure but does not refresh the
display. You must call update() on the TreeTableModel to refresh the on-screen display.
boolean deleted(TreeNode parent, int indexOfDeletedChild)
You should call this method after you have deleted a child TreeNode from a parent
TreeNode. It prompts the display to re-expand the parent as needed to include the new child.
You must provide it with the index of the deleted child (i.e. its index when it was still a child
of the parent). You must call update() on the TreeTableModel to refresh the on-screen
display.
These two methods, inserted and deleted are the most memory and time efficient way to update the
tree control when you have made changes to the underlying tree data structure. You can call them
several times before calling update() since they do not refresh the on-screen display themselves.
Here is a simple example of using a TreeControl with TreeNodes.
package evesamples.ui;
import eve.sys.Event;
import eve.ui.Form;
import eve.ui.Label;
import eve.ui.ScrollBarPanel;
import eve.ui.table.TreeControl;
import eve.ui.table.TreeEvent;
public class TestTrees extends Form{
TreeControl tree;
Label txt;
public TestTrees()
{
title = "Tree Control with Nodes";
maximizeOnPDA();
PersonTreeNode home = new PersonTreeNode("Springfield");
home.addOneChild(
new PersonTreeNode("Abe").addOneChild(
new PersonTreeNode("Homer")
PersonTreeNode("Bart"))
.addOneChild(new
.addOneChild(new
PersonTreeNode("Lisa"))
PersonTreeNode("Maggie"))
.addOneChild(new
)
);
tree = new TreeControl();
tree.getTreeTableModel().setRootObject(home);
addLast(new ScrollBarPanel(tree));
addLast(txt = new Label(" ")).setCell(HSTRETCH);
setPreferredSize(200, 200);
}
public void onEvent(Event ev)
{
if (ev instanceof TreeEvent && ev.target == tree){
int i = tree.getSelectedLine();
if (i == -1) txt.setText("No selection.");
else{
String s = i+" = ";
PersonTreeNode pt =
(PersonTreeNode)tree.getTreeTableModel().getTreeNodeAt(i);
s += pt.getName();
txt.setText(s);
}
}else
super.onEvent(ev);
}
}
Object Composition in Trees – Using the TreeModelAdapter
Class
The TreeModelAdapter class is an extension of the TreeTableModel class that you can use to
display tree data that do not need to be organized in a structure of TreeNode objects. The way this
class works is as follows:

An object is not created for a node until the node is expanded. When it is expanded the class
must create an object to represent the node, based on the parent node object (which would
have been previously constructed) and the index of the node within its parent.

The data for an unexpanded node or a leaf is determined from its parent object and its index
within the parent object. This includes its name, icon and flags.

The root object will always have a parent object of null and a child index of 0.

There is no restriction on the type of object that you use to represent an expanded node. The
object itself is not used except as a reference for accessing the child data and for creating
child nodes.
Here are the methods that must be overridden:
Object createObjectFor(Object parent, int childIndex)
This requests a new Object to represent the data of the child at the specified index in the
specified parent. The only time parent will ever be null is when an object is being created
for the root of the tree. In this case childIndex will only be 0.
int getChildCount(Object parent)
This is used to get the number of children for a particular parent node.
String getDisplayString(Object parent, int childIndex)
This is used to get the name to display for a particular child node.
These three are the absolute minimum that you should override to display your tree data. However
the effect of only overriding this would be:
1. All nodes would have the same icon (folders).
2. All nodes would be considered expandable.
Two important methods that you can override are:
IImage getIcon(Object parent, int childIndex)
This returns the icon for a particular child of a parent node. By default this returns null.
void adjustFlags(Object parent, long [] indexes, byte [] flags)
The indexes and flags parameters are arrays each of the same length equal to the child count
for the parent object. For each element in the flags array, you can switch on or off the bit
values IsNode and CanExpand. This will tell the model whether the child at a particular
index in the parent is a node or a leaf, and whether it can be expanded or not. The method by
default leaves the flags unaltered, each one with the IsNode and CanExpand bits set.
There is an alternative to using the adjustFlags() method. If you set the dynamicCanExpand
member of the TreeModelAdapter to be true, then the flags for each node/leaf in the display will be
queried each time it is displayed. This results in a call to:
byte getFlags(Object parent, int childIndex, byte savedFlags)
This should return an adjusted value of savedFlags with the bits IsNode and CanExpand
set on or off appropriately. This method is only called if dynamicCanExpand is true.