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:

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       = (BDR_RAISEDOUTER | BDR_RAISEDINNER)|BF_RECT
EDGE_SUNKEN     = (BDR_SUNKENOUTER | BDR_SUNKENINNER)|BF_RECT
EDGE_ETCHED     = (BDR_SUNKENOUTER | BDR_RAISEDINNER)|BF_RECT
EDGE_BUMP          = (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.