We get this question every now and again and generally our response is that we don't but would like to get such a contribution. However, looking at our samples it seems that most of our component building explanations revolve around "small" components rather than composites which would fit a tree much better.
A composite component is really a Container which we would assemble into a specialized component by tailoring together smaller components. Naturally we could also perform custom painting for such a composite but that often defeats the purpose...
These components aren't suitable for everything but for things such as a tree we gain several huge advantages by using Container nesting to build a tree:
- We represent the nodes using custom components allowing subclasses to manipulate the look of the tree to a great degree.
- We hardly need to write any code and styles, touch support, keyboard navigation etc. are all built in
- We can leverage transitions and component nesting for really stunning effects
Lets go strait to the code, first we need the model:
/**I think thats pretty self explanitory...
* Arranges tree node objects, a node can essentially be anything
*
* @author Shai Almog
*/
interface TreeModel {
/**
* Returns the child objects representing the given parent, null should return
* the root objects
*/
public Vector getChildren(Object parent);
/**
* Is the node a leaf or a folder
*/
public boolean isLeaf(Object node);
}
Then we would like the implementation:
class Node {Thats a bit long and tedious but its mostly unrelated to LWUIT, the main code is the tree component code which is really rather simple:
Object[] children;
String value;
public Node(String value, Object[] children) {
this.children = children;
this.value = value;
}
public String toString() {
return value;
}
}
TreeModel model = new TreeModel() {
Node[] sillyTree = {
new Node("Root 1", new Node[] {
new Node("Child 1", new Node[] {
new Node("Gand Child 1", new Node[] {
}),
new Node("Gand Child 2", new Node[] {
}),
new Node("Gand Child 3", new Node[] {
}),
new Node("Gand Child 4", new Node[] {
}),
}),
new Node("Child 2", new Node[] {
new Node("Something Else", new Node[] {
}),
new Node("More of the same", new Node[] {
}),
}),
new Node("Child 3", new Node[] {
}),
new Node("Child 4", new Node[] {
}),
}),
new Node("Root 2", new Node[] {
new Node("Something Else", new Node[] {
}),
new Node("More of the same", new Node[] {
}),
}),
new Node("Root 3", new Node[] {
new Node("Something Else", new Node[] {
}),
new Node("More of the same", new Node[] {
}),
}),
new Node("Root 4", new Node[] {
}),
};
/**Then all we need is to use it in order to produce the video on the right:
* LWUIT Tree component sample
*
* @author Shai Almog
*/
public class TreeComponent extends Container {
private static final String KEY_OBJECT = "TREE_OBJECT";
private static final String KEY_PARENT = "TREE_PARENT";
private static final String KEY_EXPANDED = "TREE_NODE_EXPANDED";
private static final String KEY_DEPTH = "TREE_DEPTH";
private ActionListener expansionListener = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
Component c = (Component)evt.getSource();
Object e = c.getClientProperty(KEY_EXPANDED);
if(e != null && e.equals("true")) {
collapseNode(c);
} else {
expandNode(c);
}
}
};
private TreeModel model;
private Image folder;
private Image nodeImage;
private int depthIndent = 15;
public TreeComponent(TreeModel model) {
try {
this.model = model;
folder = Image.createImage("/folder.png");
nodeImage = Image.createImage("/page_white.png");
setLayout(new BoxLayout(BoxLayout.Y_AXIS));
buildBranch(null, 0, this);
setScrollableY(true);
} catch (IOException ex) {
ex.printStackTrace();
}
}
private void expandNode(Component c) {
c.putClientProperty(KEY_EXPANDED, "true");
int depth = ((Integer)c.getClientProperty(KEY_DEPTH)).intValue();
Container parent = c.getParent();
Object o = c.getClientProperty(KEY_OBJECT);
Container dest = new Container(new BoxLayout(BoxLayout.Y_AXIS));
Label dummy = new Label();
parent.addComponent(BorderLayout.CENTER, dummy);
buildBranch(o, depth, dest);
parent.replace(dummy, dest, CommonTransitions.createSlide(CommonTransitions.SLIDE_VERTICAL, true, 300));
}
private void collapseNode(Component c) {
c.putClientProperty(KEY_EXPANDED, null);
Container p = c.getParent();
for(int iter = 0 ; iter < p.getComponentCount() ; iter++) {
if(p.getComponentAt(iter) != c) {
Label dummy = new Label();
p.replaceAndWait(p.getComponentAt(iter), dummy, CommonTransitions.createSlide(CommonTransitions.SLIDE_VERTICAL, false, 300));
p.removeComponent(dummy);
}
}
}
/**
* Adds the child components of a tree branch to the given container.nt
*/
protected void buildBranch(Object parent, int depth, Container destination) {
Vector children = model.getChildren(parent);
int size = children.size();
Integer depthVal = new Integer(depth + 1);
for(int iter = 0 ; iter < size ; iter++) {
Object current = children.elementAt(iter);
Button nodeComponent = createNodeComponent(current, depth);
if(model.isLeaf(current)) {
destination.addComponent(nodeComponent);
} else {
Container componentArea = new Container(new BorderLayout());
componentArea.addComponent(BorderLayout.NORTH, nodeComponent);
destination.addComponent(componentArea);
nodeComponent.addActionListener(expansionListener);
}
nodeComponent.putClientProperty(KEY_OBJECT, current);
nodeComponent.putClientProperty(KEY_PARENT, parent);
nodeComponent.putClientProperty(KEY_DEPTH, depthVal);
}
}
protected Button createNodeComponent(Object node, int depth) {
Button cmp = new Button(node.toString());
if(model.isLeaf(node)) {
cmp.setIcon(nodeImage);
} else {
cmp.setIcon(folder);
}
updateNodeComponentStyle(cmp.getSelectedStyle(), depth);
updateNodeComponentStyle(cmp.getUnselectedStyle(), depth);
return cmp;
}
protected void updateNodeComponentStyle(Style s, int depth) {
s.setBorder(null);
s.setPadding(LEFT, depth * depthIndent);
}
}
Form treeForm = new Form("Tree Test");
treeForm.setLayout(new BorderLayout());
TreeComponent tree = new TreeComponent(model);
treeForm.addComponent(BorderLayout.CENTER, tree);
treeForm.setScrollable(false);
treeForm.show();
This could also make for a super-cool way to implement submenus (although you've already expressed your view on those ;-).
ReplyDeleteIt would be neat to use FIRE_RIGHT to open a submenu 'inside' the normal menu, and be able to close it again using FIRE_LEFT. By having the submenu visually inside the menu, submenus might become much more intuitive...
Not sure I have the expertise level to do it myself, but maybe you would give some pointers as to how it could be done?
Shai, could you please add zip file of all sources in this post? Somehow I can't get examples working. I added some missing bits, but I get blank screen when running it.
ReplyDeleteas you sad when the content of container exceeds the size of the phone screen scrolling crashes. Is there a way to solve that container scrolling bug easily
ReplyDeleteThis doesn't crash with scrolling.
ReplyDeleteCheck out the JavaOne demo we had that features a tree for image selection as part of the demo.
Thanks Shai
ReplyDeletecould you plz tell me how to get the source code? thanks again
This comment has been removed by the author.
ReplyDeleteShai, Please provide the code for the TreeView as a .java or .zip file. Please.
ReplyDeleteThe tree component is officially in the LWUIT SVN.
ReplyDeleteWhy aren't you using the Tree component from LWUIT?
ReplyDeleteIts better tested and there is a sample in the LWUIT demo.
The code above is outdated by now.