The user interface of a Pivot application is often defined using XML markup rather than Java code. The hierarchical structure of an XML document more closely parallels the structure of the component hierarchy, which makes it easier to visualize the resulting output.
Pivot allows developers to split the definition of the user interface into multiple WTKX files. This allows each piece to vary independently and makes the overall structure easier to manage. The layout of the Stock Tracker application is divided into two WTKX documents; stocktracker.wtkx, which sets up the main structure of the application, and stocktracker.detail.wtkx, which defines the layout for the quote detail. We'll take a look at the top-level markup in stocktracker.wtkx first, concentrating on the main page elements, TablePane, SplitPane, and TableView. Then we'll investigate the markup for the stock quote detail, which uses the Form container and lays the ground work for the data binding support we'll discuss in a later section.
The root node of stocktracker.wtkx is a TablePane element. A table pane is a type of layout container that arranges its children in a manner similar to an HTML table.
Like HTML tables, TablePane contents are organized into rows and columns. Unlike HTML, however, which may attempt to infer a table structure from its contents, a table pane's structure must be explicitly defined, as shown below:
<TablePane styles="{padding:8, horizontalSpacing:6, verticalSpacing:6}"> <columns> <TablePane.Column width="1*" /> </columns> <rows> <TablePane.Row height="-1"> <Label ... /> </TablePane.Row> <TablePane.Row height="1*"> <SplitPane splitRatio="0.4"> ... </SplitPane> </TablePane.Row> <TablePane.Row height="-1"> <BoxPane ... > ... </BoxPane> </TablePane.Row> <TablePane.Row height="-1"> <TablePane> ... </TablePane> </TablePane.Row> </rows> </TablePane>
TablePane row heights and column widths can be specified as either automatic, absolute, or relative:
A value of -1 specifies an automatic size; the table pane will determine the height of the row as the maximum preferred height of all components in that row; the same applies to automatic column widths.
An explicit, non-negative value defines an absolute width or height, specified in pixels.
A number followed by an asterisk specifies a relative size; the width or height is determined by applying a weighted percentage of the space left over after subtracting the width or height of all automatic and absolute columns and rows (which may be 0).
In the example above, the single column will be given 100% of the width allocated to the TablePane. The heights of rows 1, 3, and 4 will be determined automatically based on the preferred heights of their contents, and the second row will be given 100% of the vertical space left over after the other rows are accounted for.
The following graphic shows the resulting layout (the blue lines represent the table cell boundaries):
Stock Tracker table pane cells.
Note the styles attribute of the TablePane element. Styles are used to customize the appearance of a component, and are specified in WTKX as a JSON-formatted collection of name/value pairs. All WTKX elements support the styles attribute, but each element may support a different set of styles depending on its current skin.
In this example, the table pane is assigned a "padding" value of 8 and a "horizontalSpacing" value of 6. Padding represents the amount of space that the table pane will leave around its components, and horizontal spacing is the size of the gap the table pane will leave between columns.
<TablePane.Row height="-1"> <Label text="%stockTracker" styles="{font:'Verdana bold 14', verticalAlignment:'center'}"/> </TablePane.Row>
Note the use of the percent symbol in the text attribute value. This tells the WTKX component loader that the value of the label's text property should be provided by a string resource with the specified name, minus the percent sign. This is discussed in more detail in the Localization section.
The SplitPane container is used to divide an area into two resizable regions. Split panes can be either vertical or horizontal (the default). Vertical split panes contain top and bottom components, and horizontal split panes contain left and right components, as shown below:
<SplitPane splitRatio="0.4"> <left> <Border styles="{padding:0, color:10}"> ... </Border> </left> <right> <Border styles="{padding:6, color:10}"> ... </Border> </right> </SplitPane>
In this example, both the left and right components are instances of the Border class. Since split panes themselves don't draw any kind of divider, borders are often used to delineate content boundaries. Given a split ratio value of 0.4, as shown, the left side of the split pane will initially be allocated 40% of the split pane's width, and the right side will receive 60%.
The TableView component is used to present tabular data in a grid of rows and columns. While it bears a superficial resemblance to TablePane, the purpose of the two components is quite different: TablePane is a container; its primary purpose is to lay out sub-components, and its structure is most often defined at design time. TableView is a component that acts as a view on a set of model data and is most often populated at run time.
Like TablePane, TableView's columns must be defined up front. However, since the contents of a table view are generally not known until run time, it is not necessary to define a table view's row structure - this is provided by the table view's model data when the table view is populated.
TableView is used by the Stock Tracker demo to display a selectable list of stock quote data. The following listing shows the markup used by the demo to create the table view:
<TableView wtkx:id="stocksTableView" selectMode="multi" styles="{showHorizontalGridLines:false}"> <columns> <TableView.Column name="symbol" headerData="%symbol" width="80"/> <TableView.Column name="value" headerData="%value" width="80"> <cellRenderer> <content:TableViewNumberCellRenderer styles="{horizontalAlignment:'right'}" numberFormat="$$0.00"/> </cellRenderer> </TableView.Column> <TableView.Column name="change" headerData="%change" width="80"> <cellRenderer> <stocktracker:ChangeCellRenderer styles="{horizontalAlignment:'right'}" numberFormat="+0.00;-0.00"/> </cellRenderer> </TableView.Column> </columns> </TableView>
Note that the TableView element includes a wtkx:id attribute value with a value of "stocksTableView". This is used to associate the table view with an instance of TableViewHeader, which is discussed in the next section. ID values are also used to gain access to WTKX components from Java code when wiring up event handlers. This is discussed in more detail in the Event Handling section.
Setting the selectMode attribute to "multi" specifies that a user may select multiple rows in this table view. Other possible values for selectMode are "single" and "none". Setting showHorizontalGridLines to false tells the table view not to draw horizontal grid lines separating its cells. Vertical grid lines will still be drawn; however, these can be turned off as well by setting showVerticalGridLines to false.
The application defines three columns for the table view: one for the stock ticker symbol, another for the stock's current value, and a third for the change percentage. The name attribute defines the name of the column. It is not visible to the user, but is used as a key to map values in each row to their appropriate place in the table view; the value of the headerData attribute is what is actually shown to the user in the table view header. Note that the header data for each column is specified as a resource name so the header text can be localized.
The width of each column in this example is set to a relative width of "1*". This means that each column will be allocated one third of the total width of the table view. Like TablePane, TableView columns can also be assigned an absolute or an automatic pixel width.
The "value" and "change" columns define several additional attributes related to cell renderers. By default, table cells are drawn using a renderer that presents the cell's contents as a text string. Custom cell renderers can be used to present cell data formatted as a number, date, or otherwise. The Stock Tracker demo uses the built-in number cell renderer to format the value column and a custom renderer to display the values in the change column using either red or green text depending on the change direction. A complete discussion of cell renderers is outside the scope of this tutorial, but the source code for the custom renderer is as follows:
package pivot.tutorials.stocktracker; import java.awt.Color; import pivot.wtk.TableView; import pivot.wtk.content.TableViewNumberCellRenderer; public class ChangeCellRenderer extends TableViewNumberCellRenderer { public static final Color UP_COLOR = new Color(0x00, 0x80, 0x00); public static final Color DOWN_COLOR = new Color(0xff, 0x00, 0x00); public void render(Object value, TableView tableView, TableView.Column column, boolean rowSelected, boolean rowHighlighted, boolean rowDisabled) { super.render(value, tableView, column, rowSelected, rowHighlighted, rowDisabled); if (value != null && !rowSelected) { StockQuote stockQuote = (StockQuote)value; float change = stockQuote.getChange(); getStyles().put("color", change < 0 ? DOWN_COLOR : UP_COLOR); } } }
Pivot's TableView component does not include built-in header support; rather, support for column headings and resizing is provided by a separate TableViewHeader component. This is because TableView (and other similar components including ListView and TreeView) do not include built-in support for scrolling. Scrolling is provided by the ScrollPane class, discussed below.
Stock Tracker table view and table view header.
The following markup is used to declare the table view header component for the Stock Tracker demo:
<TableViewHeader tableView="$stocksTableView"> <tableViewHeaderPressListeners> <TableView.SortHandler/> </tableViewHeaderPressListeners> </TableViewHeader>
That's it! The header automatically associates itself with the table view instance identified by the tableView attribute - no additional markup or coding is necessary.
Note that an instance of TableView.SortHandler is added as a header press listener on the table view header. This class can be used to provide default sorting behavior in response to a column header click.
Pivot components do not manage scrolling internally - all scrolling in Pivot is handled by the ScrollPane class (more specifically, by an instance of the Viewport class, but the distinction is not relevant to this discussion). A scroll pane provides a windowed view onto another component and allows a user to select which part of the component is shown by dragging a horizontal or vertical scroll bar.
The component being scrolled is called the "view", and is specified as a child of the scroll pane's
The stocktracker.wtkx file defines the overall structure of the application, and the layout of the quote detail content is defined separately in stocktracker.detail.wtkx. Yet, when the application is run, it appears as though the entire application was defined in a single WTKX file. How does this work? The key is the wtkx:include tag:
<wtkx:include namespace="detail" src="stocktracker.detail.wtkx"/>
The wtkx:include tag tells the WTKX serializer that a component should be placed here, but that the component's content is not defined in this file. The location of the actual content is specified by the src tag and is either an absolute (beginning with a slash character) or relative (no slash) path to another WTKX file. Relative paths are resolved in the context of the current WTKX file, and absolute paths are resolved in the context of the application's classpath. Fully-qualified URLs (those that begin with a protocol) are not supported.
In addition to the src attribute, the wtkx:include tag accepts an optional wtkx:id attribute. This is used to partition the ID values defined in each WTKX file into separate namespaces, allowing developers to avoid naming collisions. Components are referenced by concatenating the include IDs with the component's ID; for example, the path "detail.changeLabel" refers to the Label with ID "changeLabel" defined in the stocktracker.detail.wtkx file.
Though the top-level element of the detail page is a FlowPane, most of the layout is handled by the Form container. Forms arrange their children in a vertical stack, similar to a vertical FlowPane, prepending a text label to each field.
Stock Tracker detail view using Form container.
A field's label is defined by a Form.name attribute attached to the child element, as shown below:
<Form styles="{rightAlignLabels:true, fill:true}"> <sections> <Form.Section> <Label Form.name="%value" textKey="value" styles="{horizontalAlignment:'right'}"/> <Label wtkx:id="changeLabel" Form.name="%change" textKey="change" styles="{horizontalAlignment:'right'}"/> <Label Form.name="%openingValue" textKey="openingValue" styles="{horizontalAlignment:'right'}"/> <Label Form.name="%highValue" textKey="highValue" styles="{horizontalAlignment:'right'}"/> <Label Form.name="%lowValue" textKey="lowValue" styles="{horizontalAlignment:'right'}"/> <Label Form.name="%volume" textKey="volume" styles="{horizontalAlignment:'right'}"/> </Form.Section> </sections> </Form>
Since the quote data is read-only, the "fields" in this example are actually themselves Label instances. Their form label values are specified as string resources so they can be easily translated.
Each Label in the form defines a textKey attribute. This attribute specifies the name of the property from which the label will obtain its text value during data binding. Specifically, it is the name of the value's key in the "bind context", an instance of Dictionary
Though data binding is often used to populate a Form's contents, there is nothing special about the Form class itself that supports data binding - it is supported by all container types and many component classes.
Next: Event Handling