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