Implementing TreeCell Events to Trigger Content View[]
Submitted by RCasey on Aug 1 2007
I will be showing some code here that demonstrates the ability to draw on an outside Java class container, which holds hundreds of indexed objects indexed via an iterator, populate a Tree with that data model, and allow viewing of the object contents by clicking on a TreeCell.
So, the overarching SplitFrame fx file contains the following:
var myVolume = MyVolume{
filepath: "/data/my_data_file"
};
var mySelectModel = MySelectModel { };
var rootCell = MyVolumeTreeCell {
text: "DemoVolume"
volume: myVolume
selectionModel: mySelectModel
};
var editPanel = MyEditPanel {
var: myself
selectionModel: mySelectModel
};
var myTree = Tree {
root: rootCell
rootVisible: true
};
Frame {
title: "This is my object viewer"
height: 800
width: 1000
content: SplitPane {
orientation: HORIZONTAL
content:
[SplitView {
weight: 0.30
content: myTree
},SplitView {
weight: 0.70
content: ScrollPane {
view: CenterPanel {
background: white
content: editPanel
}
}
}]
}
visible: true
}
From this top level view, the important thing I want you to see are the two class instances (rootCell and editPanel) that share a common selection model class. This selection model is simply a class that holds onto a tree cell representing the current one selected in the Tree.
public class MySelectModel {
public attribute selectedNode: TreeCell;
}
Why am I doing this? Why don't I act off of some trigger from the Tree class itself?
Truth is, I tried that. I tried to have something trigger where it indicated that a TreeCell had been selected. However, Tree seems to merely have placeholders for events that are not yet implemented, at least for the public attributes. So capturing events in Tree is out.
What this means is that I needed a new central class to act as a bridge to indicate the currently selected TreeCell, which could then be intercepted by my viewer class, which happens to be a simple Table at this stage.
So what does throw an event trigger when a TreeCell is selected? The Tree Cell itself!
The data to go into the tree cell is hierarchical, thus the desire to use a Tree in the first place. By extending TreeCell with numerous species of data types, I pave the way to creating the Tree from my data model:
public class MyVolumeTreeCell extends TreeCell {
public attribute volume: MyVolume;
public attribute selectionModel: MySelectModel;
}
trigger on MyVolumeTreeCell.volume = newValue {
cells = [MyDictionaryTreeCell {
var: myself
text: "Dictionary"
parent: this
cells: bind lazy loadDictionaryData(newValue,myself)
selectionModel: bind this.selectionModel
},
MyStationTreeCell {
var: myself
text: "Station"
parent: this
cells: bind lazy loadStationData(newValue,myself)
selectionModel: bind this.selectionModel
}];
}
public class MyDictionaryTreeCell extends TreeCell {
public attribute selectionModel: MySelectModel;
}
public class MyStationTreeCell extends TreeCell {
public attribute selectionModel: MySelectModel;
}
[...further code omitted...]
What's important here are the following:
- the trigger to populate the tree, starting at the root cell, once the data model has been identified
- the extension that adds the selectonModel handle to all levels of the tree, in fact in the full code this is passed down two more levels.
The lazy binding to the load operations are nothing special, excepting for the fact that they contain subclassed TreeCell nodes with a handle to MySelectModel. The load operation recursively runs the volume container's iterator to create new instances of TreeCell with the attributes
parent: par
selectionModel: bind par.selectionModel
object: nextObj
Let's call this a MyObjectCell class (extends TreeCell) for the example here. These serve as my actual data cells.
In this way, the child cells bind to its parent selection model, and the parent to its parent, and so on. When any one of these cells is selected, it can immediately talk to that single bridge selection model, notifying it of the selection.
How does that happen? By putting a trigger on each of the cell classes. For instance, on MyDictionaryTreeCell, we can do the following:
trigger on MyDictionaryTreeCell.selected = newValue {
if (newValue == true) {
selectionModel.selectedNode = this;
System.err.println("Dictionary Cell has been selected");
} else {
System.err.println("Dictionary Cell has been deselected");
}
}
The current pattern for TreeCell events is that the previously selected TreeCell reports a false condition in its selected attribute and the newly selected TreeCell reports true for the selected attribute. Trapping these means that we can direct other behaviors to occur, but we want to centralize that triggering through a single bind point. Hence the use of a bridge class for capturing the selection.
So now that the selectionModel instance has a particular TreeCell identified, we want to pass this on to other other side of the split frame, which contains a placeholder for a Table in the form of a CenterPanel.
public class MyEditPanel extends CenterPanel {
public attribute selectionModel:MySelectModel;
public attribute treeCell:TreeCell;
public attribute editTable:Table;
}
attribute MyEditPanel.treeCell = bind selectionModel.selectedNode;
attribute MyEditPanel.content = bind editTable;
trigger on MyEditPanel.treeCell = newValue {
System.err.println("TreeCell selected is {newValue.text}");
if (newValue instanceof MyObjectCell) {
var obj:MyDataObject = ((MyObjectCell)newValue).object;
var numFields = getNumberOfFields(obj);
this.editTable = Table {
columns: [TableColumn {text: "Field #"},
TableColumn {text: "Field Name"},
TableColumn {text: "Field Value"},
TableColumn {text: "Special"}]
cells: bind foreach (f in [1..numFields])
[TableCell {
text: "{f}"
},
TableCell {
text: "{obj.getFieldName(f)}"
},
TableCell {
text: "{obj.getFieldVal(f)}"
},
TableCell {
}]
};
}
}
operation getNumberOfFields (obj:MyDataObject) {
var type = obj.getType();
if (type < 0) {
return -1;
} else {
return obj.getNumFields();
}
}
So, what do we have happening here? We have a panel that will create a new instance of Table every time it detects a change in treeCell, which is bound to our central selectedNode instance. The selected TreeNode's handle has been passed all of the way to the Table where we can now grab its associated data object, find the number of fields in that object, and display each field (name,value) in turn.
The key to making this work is to propagate bindings from each TreeCell, through a central class, and down to every display panel that you wish to react to the selection. In this example, we just have one display panel for brevity.