How to use the TreeGridView component
The TreeGridView class renders a table of data from a data collection similarly to a GridView, except that the data is hierarchical, and branches may be opened and closed to reveal nested data. It includes support for selecting an item, scrolling, and custom layouts. Each item in the data provider is displayed as a row, divided into columns for the item's fields. Tree grid views support selecting a row, scrolling, and custom cell renderers for each column.
TreeGridView component⚠️ Beta Notice: This component is still quite new to Feathers UI. It was included in the latest release because it should be stable enough for production use. However, some APIs may go through minor changes in upcoming releases — based on feedback from developers like you. Learn more about Beta APIs.
The Basics
Start by creating a TreeGridView control, and add it to the display list.
var treeGridView = new TreeGridView();
addChild(treeGridView);
Data provider and columns
To render some data in the tree grid view, pass in a hierarchical collection that contains an object for each row.
var collection = new ArrayHierarchicalCollection([
{
dept: "Bakery",
children: [
{item: "Whole Wheat Bread", dept: "Bakery", price: "2.49"},
{item: "English Muffins", dept: "Bakery", price: "2.99"},
]
},
{
dept: "Dairy",
children: [
{item: "2% Milk", dept: "Dairy", price: "2.49"},
{item: "Butter", dept: "Dairy", price: "4.69"},
]
},
{
dept: "Meat",
children: [
{item: "Chicken breast", dept: "Meat", price: "5.90"},
{item: "Bacon", dept: "Meat", price: "4.49"},
]
},
{
dept: "Produce",
children: [
{item: "Lettuce", dept: "Produce", price: "1.29"},
{item: "Broccoli", dept: "Produce", price: "2.99"},
]
},
]);
treeGridView.dataProvider = collection;
Set the collection's itemToChildren() method to get the children from each branch that need to be rendered by the tree grid view.
collection.itemToChildren = (item:Dynamic) -> item.children;
Next, define the columns in the tree grid view, so that it knows which fields from the data provider's items to display. One of the items from the data provider appears below.
{ item: "Broccoli", dept: "Produce", price: "2.99" }
The item has three fields, item, dept, and price. Create a separate TreeGridViewColumn for each of the fields in the item, and pass them to the columns property.
treeGridView.columns = new ArrayCollection([
new TreeGridViewColumn("Item", (data) -> data.item),
new TreeGridViewColumn("Department", (data) -> data.dept),
new TreeGridViewColumn("Unit Price", (data) -> data.price)
]);
The first parameter of the TreeGridViewColumn is the text to display in each column header. The second parameter is passed to the itemToText() property, which is a function that returns the text to display in a cell renderer.
Items in the collection are not required to be anonymous structures, like
{ item: "Bacon", dept: "Meat", price: "4.49" }in the example above. Class instances are allowed too (and encouraged as a best practice; you should prefer classes over anonymous structures).
Selection
Add an event listener for Event.CHANGE to perform an action when the user selects a different item.
treeGridView.addEventListener(Event.CHANGE, treeGridView_changeHandler);
Check for the new value of the selectedItem property in the listener.
function treeGridView_changeHandler(event:Event):Void {
var treeGridView = cast(event.currentTarget, TreeGridView);
trace("TreeGridView selectedItem change: " + treeGridView.selectedItem.text);
}
Alternatively, the value of the selectedLocation property references the location of the items in the tree grid view's collection as an Array of integers.
function treeGridView_changeHandler(event:Event):Void {
var treeGridView = cast(event.currentTarget, TreeGridView);
trace("TreeGridView selectedLocation change: " + treeGridView.selectedLocation);
}
Add or remove items
To add a new item at a specific location, pass an object to the data provider's addAt() method.
var newRow = { item: "Gala Apple", dept: "Produce", price: "1.00" };
var newLocation = [3, 1];
treeGridView.dataProvider.addAt(newRow, newLocation);
In the example above, a new tab is added to the beginning.
Similarly, to remove an item, call remove() or removeAt() on the collection.
var locationToRemove = [3, 1];
treeGridView.dataProvider.removeAt(locationToRemove);
Cell renderers
An cell renderer is a Feathers UI component that one of the fields from a single row displayed in a TreeGridView or GridView component.In other words, a TreeGridView typically contains many cell renderers in a two-dimensional grid — with each one rendering a different field from each row in the collection.
Feathers UI provides a default HierarchicalItemRenderer class, which can display data in many different ways that cover a variety of common use-cases. However, components like TreeGridView also support custom cell renderers, which allow developers to render the tree grid view's data in infinite unique ways.
Consider a collection of items with the following format.
{ item: "Gala Apple", dept: "Frozen", price: "3.99", icon: "https://example.com/img/pizza.png" }
While the default HierarchicalItemRenderer class can easily display some text and an image, creating a custom cell renderer for this simple data will be a good learning exercise.
A custom cell renderer designed to display this data might use a Label to display one of the strings, and an AssetLoader to display the image. The following example creates a DisplayObjectRecycler which instantiates these components and adds them to a LayoutGroupItemRenderer — a special base class for custom cell renderers.
var recycler = DisplayObjectRecycler.withFunction(() -> {
var cellRenderer = new LayoutGroupItemRenderer();
var layout = new HorizontalLayout();
layout.gap = 6.0;
layout.paddingTop = 4.0;
layout.paddingBottom = 4.0;
layout.paddingLeft = 6.0;
layout.paddingRight = 6.0;
cellRenderer.layout = layout;
var openedToggle = new ToggleButton();
openedToggle.name = "openedToggle";
cellRenderer.addChild(openedToggle);
var icon = new AssetLoader();
icon.name = "loader";
cellRenderer.addChild(icon);
var label = new Label();
label.name = "label";
cellRenderer.addChild(label);
return cellRenderer;
});
Developers are not required to use the
LayoutGroupItemRendererclass. In fact, a custom cell renderer may be created from any OpenFL display object, including primitives likeopenfl.display.Spriteand all other Feathers UI components.
Both TreeGridView and TreeGridViewColumn define cellRendererRecycler properties. On TreeGridViewColumn, the cellRendererRecycler property may be used to customize the cell renderers in that specific column. On TreeGridView, the cellRendererRecycler property may be used to customize the default cell renderers used when a particular column doesn't have a specific cell renderer.
var column = new TreeGridViewColumn("Item", (data) -> data.item);
column.cellRendererRecycler = recycler;
So far, the DisplayObjectRecycler creates the cell renderer, but it doesn't understand how to interpret the data yet. A custom update() method on the recycler can do that.
recycler.update = (cellRenderer:LayoutGroupItemRenderer, state:TreeGridViewCellState) -> {
var openedToggle = cast(cellRenderer.getChildByName("openedToggle"), ToggleButton);
var label = cast(cellRenderer.getChildByName("label"), Label);
var loader = cast(cellRenderer.getChildByName("loader"), AssetLoader);
openedToggle.visible = state.branch && state.columnIndex == 0;
openedToggle.selected = state.branch && state.opened;
label.text = state.text;
loader.source = state.data.icon;
};
When the update() method is called, it receives the cell renderer and an TreeGridViewCellState object. TreeGridViewCellState has a number of useful properties.
columnis theTreeGridViewColumnthat contains the item.columnIndexis the position of the column within the row.datais the row from the collection.branchindicates if the row is a branch or not.enabledindicates if the cell renderer should be enabled or not.locationis the position of the item within the collection.openedindicates if a branch is opened or not.owneris theTreeGridViewthat contains the row.rowIndexis the position of the row within the collection.selectedis populated by comparing toselectedItem.textis populated usingitemToText()
In this case, the value of text is displayed by the Label, and the icon field from data (remember the example item from above, with name and icon fields) is displayed by the AssetLoader. The values of branch, columnIndex, and opened are used with a ToggleButton to display whether a branch is opened or not. Obviously, we'll need an itemToText() function to populate the text value from the name field.
It's always a good practice to provide a reset() method to the DisplayObjectRecycler, which will clean up a custom cell renderer when it is no longer used by the TreeGridView.
recycler.reset = (cellRenderer:LayoutGroupItemRenderer, state:TreeGridViewCellState) -> {
var openedToggle = cast(cellRenderer.getChildByName("openedToggle"), ToggleButton);
var label = cast(cellRenderer.getChildByName("label"), Label);
var loader = cast(cellRenderer.getChildByName("loader"), AssetLoader);
openedToggle.visible = false;
openedToggle.selected = false;
label.text = "";
loader.source = null;
};
Warning: A
DisplayObjectRecyclerwithout areset()method could potentially cause memory leaks or other unexpected behavior, if the same data needs to be used again later.
Styles
A number of styles may be customized on a TreeGridView component, including an optional background skin and the appearance of the grid view's scroll bars.
Background skin
Optionally give the tree grid view a background using the backgroundSkin property. The following example sets it to a RectangleSkin instance.
var skin = new RectangleSkin();
skin.border = SolidColor(1.0, 0x999999);
skin.fill = SolidColor(0xcccccc);
skin.width = 16.0;
skin.height = 16.0;
treeGridView.backgroundSkin = skin;
The border and fill properties of the RectangleSkin are used to adjust its appearance. They support a variety of values — from solid colors to gradients to bitmaps.
The grid view automatically calculates its preferred size based on the initial dimensions of its background skin (accounting for some other factors too, like the layout and scroll bars), so it's important to set a skin's width and height properties to appropriate values to use in this calculation.
See Skinning with common shapes for more details about how to use
RectangleSkinwith theLineStyleandFillStyleenums that change its border and fill appearance.
The appearance of the grid view's border or fill may be customized to change when the tree grid view is disabled. In the next example, setting the skin's disabledFill method makes it switch to a different fill when the tree grid view is disabled.
skin.disabledFill = SolidColor(0xffcccc);
Similarly, use the skin's disabledBorder property to change the border when disabled.
skin.disabledBorder = SolidColor(2.0, 0x999999);
In the examples above, the tree grid view uses the same RectangleSkin for all states, and that skin listens for changes to the grid view's current state. Alternatively, the tree grid view's disabledBackgroundSkin method allows the tree grid view to display a completely different display object when it is disabled.
var defaultSkin = new RectangleSkin();
// ... set border, fill, width, and height
treeGridView.backgroundSkin = defaultSkin;
var disabledSkin = new RectangleSkin();
// ... set border, fill, width, and height
treeGridView.disabledBackgroundSkin = disabledSkin;
In the example above, the tree grid view will have a separate skins when enabled and disabled.
Scroll bars
The scroll bars in a TreeGridView component are of type HScrollBar and VScrollBar. Their appearance may be customized globally in a theme, or they may be customized outside of a theme on an specific, individual tree grid view.
See How to use the
HScrollBarandVScrollBarcomponents for complete details about which styles are available for the scroll bars.
Style scroll bars globally
Use the HScrollBar and VScrollBar classes in a theme to provide a function that globally styles all scroll bars in your project.
styleProvider.setStyleFunction(HScrollBar, null, setHScrollBarStyles);
styleProvider.setStyleFunction(VScrollBar, null, setVScrollBarStyles);
The functions should use the following signatures.
function setHScrollBarStyles(scrollBar:HScrollBar):Void {
// ... set styles here
}
function setVScrollBarStyles(scrollBar:VScrollBar):Void {
// ... set styles here
}
Style scroll bars in a specific TreeGridView
The scrollBarXFactory and scrollBarYFactory properties may be used to customize the creation of an individual tree grid view's scroll bars.
treeGridView.scrollBarXFactory = () -> {
var scrollBar = new HScrollBar();
// ... set styles here
return scrollBar;
};
treeGridView.scrollBarYFactory = () -> {
var scrollBar = new VScrollBar();
// ... set styles here
return scrollBar;
};