Planet JFX
Rcasey (talk | contribs)
No edit summary
No edit summary
 
(8 intermediate revisions by one other user not shown)
Line 1: Line 1:
  +
  +
== 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
 
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
 
holds hundreds of indexed objects indexed via an iterator, populate a Tree with that data model, and allow
Line 4: Line 10:
   
   
So, the overarching Frame is similar to what you typically see in demo code:
+
So, the overarching SplitFrame fx file contains the following:
 
<code><pre>
 
<code><pre>
   
Line 19: Line 25:
 
};
 
};
   
var editPanel = MyViewPanel {
+
var editPanel = MyEditPanel {
 
var: myself
 
var: myself
 
selectionModel: mySelectModel
 
selectionModel: mySelectModel
Line 56: Line 62:
   
 
</pre></code>
 
</pre></code>
  +
  +
  +
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.
  +
  +
<code><pre>
  +
  +
  +
public class MySelectModel {
  +
  +
public attribute selectedNode: TreeCell;
  +
  +
}
  +
  +
</pre></code>
  +
  +
  +
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:
  +
  +
<code><pre>
  +
  +
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...]
  +
  +
</pre></code>
  +
  +
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
  +
  +
<code><pre>
  +
parent: par
  +
selectionModel: bind par.selectionModel
  +
object: nextObj
  +
</pre></code>
  +
  +
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:
  +
  +
<code><pre>
  +
  +
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");
  +
}
  +
}
  +
  +
</pre></code>
  +
  +
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.
  +
  +
<code><pre>
  +
  +
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();
  +
}
  +
}
  +
  +
  +
</pre></code>
  +
  +
  +
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.
  +
  +
[[Category:Code Example]]

Latest revision as of 00:13, 10 November 2007

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:

  1. the trigger to populate the tree, starting at the root cell, once the data model has been identified
  2. 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.