Michael L Brereton - 30 December 2007, http://www.ewesoft.com/
<<
Previous: Using EveMaker
>>
Next: Event Handling
Implementing a Custom LayoutManager
CellPanel
- The Universal Container
Advanced
Control Layout and Modifications
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().
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:
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.
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.
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.
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.
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
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:
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);
}
}
}
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.
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.
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:
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);
}
//##################################################################
}
//##################################################################
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.
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.
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.
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)
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.
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.