default-default table cell renderer

System.out.println(System.identityHashCode(new JTable().getDefaultRenderer(Object.class)));

System.out.println(System.identityHashCode(new JTable().getDefaultRenderer(Object.class))); // different address

You can see each jtable has a default-default-default render instance. When I say default-default… I mean the renderer for Object.class.

Given this renderer instance is shared by all cells of the jtable, you can customise this instance to control the default appearance of many, many cells. This object is stateful — actually a subclass instance of JLabel.

target a custom cell renderer – laser precision

1) Most examples show how to install a renderer for a particular data type like Integer, but often I don't want to apply a highly customized renderer on all Integer columns

2) To customize for Column 2, use myTableColumn2.setCellRender(). You can do myTableColumn2 = table.getColumn(target column header name used as column identifier)

3) To specify a cell-specific renderer, you need to subclass and override getCellRenderer(int,int). For example, the following code makes the first cell in the first column of the table use a custom renderer:

TableCellRenderer weirdRenderer = new WeirdRenderer();

table = new JTable(…) {

public TableCellRenderer getCellRenderer(int row, int column) {

if ((row == 0) && (column == 0)) {

return weirdRenderer;

}

// else…

return super.getCellRenderer(row, column);

content of a TableColumn instance

Summary – a TC instance holds __presentation_settings__ (width, header value etc) for a particular column[1]. This instance remembers (via the this.modelIndex) which “physical” column the presentation settings apply, but this instance doesn’t know if it’s displayed as 1st or last column, or hidden.

Should be renamed ColumnDescriptor

[1] actually a column in the model, which might be excluded from the view, via the TCM.

— First look at the field listing —
* myTableColumn.modelIndex — The model index of the column “covered” by this TableColumn. As columns are moved around in the view or removed, modelIndex remains constant. As the #1 most important field in TableColumn.java, this is first parameter in every ctor except the no-arg.

* myTableColumn.cellRenderer
* myTableColumn.cellEditor

This is a useful but less explored feature —
* myTableColumn.identifier — with getter/setters. This object is not used internally by the drawing machinery of the JTable; identifiers may be set in the TableColumn as as an optional way to tag and locate table columns.

———–

Now we are ready to look at TableColumnModel. #1 job is to hold an ordered list of TableColumn’s. Order is the display order.

getColumns() — #1 method. Returns the ordered list. Underlies these methods
** TableColumn getColumn(int viewColumnIndex) — should be renamed getColumnDescriptor
** getColumnCount() — used by JTable.getColumnCount()

paging long JTable

<![CDATA[ /** * based on <> */ public class PagingTable extends JFrame { public static void main(String args[]) { final PagingTable frame = new PagingTable(); frame.setSize(300, 200); frame.getContentPane().add( PagingModel.createPagingScrollPaneForTable(new JTable( new PagingModel())), BorderLayout.CENTER); frame.setVisible(true); } } class Record { static String headers[] = { “Record Number”, “Batch Number”, “Reserved” }; static int counter; static long start_ = System.nanoTime(); String[] data; public Record() { data = new String[] { “” + (counter++), “” + (System.nanoTime() – start_), “Reserved” }; } public String getValueAt(int i) { return data[i]; } public static String getColumnName(int i) { return headers[i]; } public static int getColumnCount() { return headers.length; } } class PagingModel extends AbstractTableModel { protected int pageSize; protected int pageOffset; protected Record[] data; public PagingModel() { this(500, 100); } public PagingModel(int realRows, int size) { data = new Record[realRows]; pageSize = size; for (int i = 0; i < data.length; i++) data[i] = new Record(); } @Override public int getRowCount() { return pageSize; } @Override public int getColumnCount() { return Record.getColumnCount(); } // Only works on the visible part of the table @Override public Object getValueAt(int row, int col) { return data[row + (pageOffset * pageSize)].getValueAt(col); } @Override public String getColumnName(int col) { return Record.getColumnName(col); } // use this method to figure out which page you are on int getPageOffset() { return pageOffset; } int getTotalPages() { return (int) Math.ceil((double) data.length / pageSize); } void pageDown() { if (pageOffset 0) { pageOffset–; fireTableDataChanged(); } } // we’ll provide our own version of a scroll pane that includes // the page up and page down buttons by default. public static JScrollPane createPagingScrollPaneForTable(JTable jt) { final PagingModel model = (PagingModel) jt.getModel(); final JButton upButton = new JButton(“^”); upButton.setMargin(new Insets(0, 0, 0, 0)); upButton.setEnabled(false); // starts off at 0, so can’t go up final JButton downButton = new JButton(“v”); downButton.setMargin(new Insets(0, 0, 0, 0)); upButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { model.pageUp(); if (model.getPageOffset() == 0) upButton.setEnabled(false); else downButton.setEnabled(true); }//method }); downButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { model.pageDown(); // If we hit the bottom of the data, disable the down button if (model.getPageOffset() == (model.getTotalPages() – 1)) downButton.setEnabled(false); else upButton.setEnabled(true); }//method }); return new JScrollPane(jt) { { // Turn on the scrollbars; otherwise we won’t get our corners setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); setCorner(ScrollPaneConstants.UPPER_RIGHT_CORNER, upButton); setCorner(ScrollPaneConstants.LOWER_RIGHT_CORNER, downButton); } }; }//method }//class ]]>

each task instance in EventQueue has ptr to 1 listener

EventObject.java doesn’t contain a “handle” [2] on the listener INSTANCE, but look at an event-queue-task (“qtask”) in the Swing EventQueue.

EDT runs all callback methods (such as actionPerformed) sequentially [1]. So if a jtable and a jtree both listen to an event, EDT must execute the 2 callbacks sequentially.

Imagine 2 qtask INSTANCES are in EventQueue. It’s instructional to imagine a qtask having 3 handles
– on that one listener object, sometimes a jcomponent. See http://docs.oracle.com/javase/tutorial/uiswing/events/actionlistener.html
– on the event object, which provides a handle
– on the source object, perhaps a button or table cell

Note in our simple example one source object (button) generates one event (click) at a time. Given the source maintains 2 pointers to 2 listeners, that one event object must be “delivered to” 2 listeners — 1:2 mapping.

Now the English word “deliver-to” is nice but rather vague or (worse) misleading and can permanent damage some learner’s brain. Listener is unlike some jms client process capable of processing event. Instead, Listener is typically
– a stateful object (perhaps a jcomponent) holding a handle on some RESOURCE — DB, bunch of jcomponents..
– exposing a callback like actionPerformed()

So delivery-to means “EDT executing that non-static callback method on the listener instance, passing in the event object”. This is a crucial and non-trivial data-flow worth a hard look and analysis
* Business logic is in … the method source code;
* Data is passed via …. the event object and the event source
* Resources needed to handle the message are in ….. the listener object’s non-static fields

[1] even though a callback method may dispatch the task to a worker thread or back to EDT again – by invokeLater() i.e. packing up the task into a qtask and “enqueue” to the end of the event queue.

[2] basically a reference or pointer, but it’s good to use a generic term here.

A busy live-market data GUI table – realistic

1000 records are batched into each chunk and posted to event queue. Every chuck needs to be displayed on a “main” table. Killer is the re-sort/re-filter, which must happen on EDT. As a result, EDT is overwhelmed — when you re-size or move the app, it responds, but is sluggish and leaves an ugly trail.

My suggestions –
* Break up into smaller tables, so sorting is faster.
* Break up into smaller datasets. In fact most users are only interested in a subset of the live data.
* If sort (filter) is the killer….
… adopt a a sort-free JTable that efficiently accesses pre-sorted underlying data. Underlying data can be swapped in/out efficiently, and sorted on a “mirror-site” using another thread, possibly a swing worker. Take inspiration from the 2 GC survivor spaces and use 2 identical data structures. One live, the other used as a sort worktable. Swap (by pointer) and fire table event. However this increase memory consumption.

We also need to completely disable user-triggered sorts. Use a dialog box to educate users that changing sorting policy is non-trivial, and they should configure and install the new sorting policy and be prepared to wait a while to see its effect.

In general, you can only get any 2 of
E) cell editing
R) real time view of fast changing mkt data
C) full rather than subset of data streams

In mkt data streaming,  R alone is often enough — read-only view. Some users needs R+C. Some needs E+R

my own custom cell renderer-cum-editor — simple, light-weight

Note a single instance of text component is shared by all cells. The text component needs no long term memory. The existing text value is always overwritten by “value” when calling get*Component() methods. We only need its transient memory for getCellEditorValue().

Thread safety — object state i.e. text value is modified on EDT only.

Input validation — add document listeners — textComp.getDocument.addDocumentListener()

class CustomRenderer extends AbstractCellEditor /////// avoid DefaultCellEditor
implements TableCellRenderer, TableCellEditor {
JScrollPane scrollPane;
JTextComponent textComp;

public CustomRenderer() {
textComp = new JTextField(); ///////////// or text area
scrollPane = new JScrollPane(textComp); // if text area
}

public Component getTableCellRendererComponent(JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row, int column) {
System.out.println(System.identityHashCode(textComp) + ” render (b) has — ” + textComp.getText());
textComp.setText((String) value);
System.out.println(System.identityHashCode(textComp) + ” render (a) has — ” + textComp.getText());
return scrollPane;
}

@Override
public Component getTableCellEditorComponent(
JTable table,
Object value,
boolean isSelected,
int row,
int column) {
System.out.println(System.identityHashCode(textComp) + ” editor (b) has — ” + textComp.getText());
textComp.setText((String) value);
System.out.println(System.identityHashCode(textComp) + ” editor (a) has — ” + textComp.getText());
return scrollPane;
}

@Override
public Object getCellEditorValue() {
return textComp.getText();
}
}

row sorter – swing fundamentals #my take

A row sorter instance holds
1) an all-important “row index” mapping, somewhat similar to the “col index” translator in TableColumnModel,
2) the list of sort keys

The row sorter updates the mapping whenever underlying content changes or sort key list changes. Javadoc says “RowSorter’s primary role is to provide a mapping between two coordinate systems: that of the view (for example a JTable) and that of the underlying data source, typically a model.

One sorter has multiple sort keys.
One sorter covers all the columns — all potential sort columns

public List getSortKeys() – returns the current sort key list
public setSortKeys()

RowSorter need to reference a TableModel. JTable also have a reference to the model. RowSorter should not install a listener on the model. Instead the view class will get model events, and then call into the RowSorter. For example, if a row is updated in a TableModel, JTable gets notified via the EDT event queue then invokes sorter.rowsUpdated(), which is a void method. The rowsUpdated() internally refreshes row sorter’s internal mapping. I believe after this method returns JTable would query row sorter to re-display the rows in the new order.

Because the view makes extensive use of the convertRowIndexToModel() and convertRowIndexToView(), these methods need to be fast.

When you click a header, mouse click handler calls into the sorter’s instance method toggleSortOrder() to
1) change sort key list,
2) then update internal mapping

JTable renderer to check old/new cell values then set color

I feel there are many broken designs, and a few working designs.

If the requirement is needed only upon user input (not during background update like MOM), then property change event or cell editor could probably pass old/new values to cell renderer.

But let’s assume the requirement is that all changes (by user or behind the scene) be covered. Now, how many ways can the underlying data change? Only 2 — Either by setValueAt() or by direct write to underlying. So I’d make underlying data structure (usually 2D) private and provide a setter as a single choke point. In this setter I would save old value into another 2D. Cell renderer has access to the jtable, so it can retrieve the table model, and access the other 2D.

Note you have to install your render as default for Integer.class (not Object.class) if the column is integer.

Note you must save the default background colour early. That color object is a field of the renderer instance. The same renderer instance is reused and persisted. Once you call renderer.setBackground(), that field is changed forever (and the previos color object unreachable), so you must save the before-value.

What if we force all updates to go through setValueAt(), including MOM updates? Heavy load on EDT given the high volume of MOM. Hard truth — Some updates to model must happen off EDT. However, fireXXX() must always run on EDT. [[java concurrency]] says “data  model  fireXxx  methods always call  the model listeners directly rather than submitting a new event to the event queue, so the fireXxx methods must be called only from the event thread.