Eve Application Development - CourseLog
Transcription
Eve Application Development - CourseLog
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.