Peter Pilgrim is professional software developer, designer and architect. Since 1998 he has worked in the financial services industry, investment banking mainly, developing IT for clients. He is a well known specialist in Java Enterprise Edition (Java EE) technology, focused on the server-side and the implementation of electronic commerce. Peter has built professional Java EE apps for top-tier investment banks such as Lloyds Banking Group, UBS, Credit Suisse, Royal Bank of Scotland and Deutsche Bank. Peter is the 91st Oracle Java Champion. Peter is a DZone MVB and is not an employee of DZone and has posted 34 posts at DZone. You can read more from them at their website. View Full User Profile

JavaFX: Reintroduce Swing JTable

09.01.2008
| 31422 views |
  • submit to reddit

In order to make the FX table more useful, one needs to listen to certain events, when the user clicks on the table. This is what the ListSelectionListener subclasses do. JavaFX makes it easy to subclass any existing Java interface or class. In the recent FX compiler you need to use override function keywords to override superclass methods and attributes. Let us view the table model class

The Table Data Model

The table model is a subclass of the AbstractTableModel from the Java Swing library.

/*
* XenonTableModel.fx
*
* Created on 22-Jul-2008, 01:32:27
*/

package com.xenonsoft.gmailclient.ui;

import javax.swing.JTable;
import javax.swing.table.TableModel;
import javax.swing.table.AbstractTableModel;
import java.lang.Class;
import java.lang.Object;
import java.lang.System;

/**
* @author Peter Pilgrim
*/

/*
* JTable uses this method to determine the default renderer/
* editor for each cell. If we didn't implement this method,
* then the last column would contain text ("true"/"false"),
* rather than a check box.
*/
var xlass: Class = null;

public class XenonTableModel extends AbstractTableModel {

public attribute DEBUG: Boolean = false;
public attribute columnNames: String[];
public attribute data: String[];

public function getColumnCount(): Integer {
return sizeof columnNames;
}

public function getRowCount(): Integer {
return ((sizeof data) / (sizeof columnNames)) as Integer;
}

override function getColumnName(col:Integer): String {
return columnNames[col as Integer];
}

public function getValueAt(row:Integer, col:Integer): Object {
if (DEBUG) {
System.out.println("getValue({row},{col}) = {data[getIndex(row,col)]}")
}
return (data[getIndex(row,col)]);
}

protected function getIndex( row:Integer, col:Integer): Integer {
var colSize:Integer = sizeof columnNames;
return ( row * colSize + col) as Integer;
}

override function getColumnClass(c:Integer):Class {
if ( xlass == null ) {
xlass = Class.forName("java.lang.String");
}
return xlass; // getValueAt(0, c).getClass();
}

/*
* Don't need to implement this method unless your table's
* editable.
*/
override function isCellEditable(row:Integer, col:Integer): Boolean {
return false;
}

/*
* Don't need to implement this method unless your table's
* data can change.
*/
override function setValueAt(value:Object, row:Integer, col:Integer): Void {
if (DEBUG) {
System.out.println("Setting value at {row},{col} to {value} (an instance of {value.getClass()})");
}

data[getIndex(row,col)] = value.toString();
fireTableCellUpdated(row, col);

if (DEBUG) {
System.out.println("New value of data:");
printDebugData();
}
}

public function printDebugData(): Void {
var numRows: Integer = getRowCount() as Integer;
var numCols: Integer = getColumnCount() as Integer;

for (i in [0..numRows-1]) {
System.out.print(" row {i}:");
for (j in [0..numCols-1]) {
System.out.print(" {data[getIndex(i,j)]}");
}
System.out.println();
}
System.out.println("--------------------------");
}
}



The quick reader will be aware that I am using the indexing function to map the row and column coordinate to a linear array. See my other article : JavaFX A Workaround For Sequences of Sequences.

The reader should be aware that the static keyword is being removed from compiled JavaFX. Note how the script level variable xlass is used in the above XenonTableModel. This is now the correct way to declare a global module variable that replaces static. Actually the xlass variable captures the java.lang.String.class value. This table model, then, only handles Strings for now. This is ok, because JavaFX has great string substitution that permits conversion of an object to String.

In the setValue() method you still have to call the super class fireTableCellUpdated() method as normal.

Listeners

As I showed above, the list selection listeners perform the client of the XenonTable to actually use the table.

The listeners are protected and they simply update the sequences in the table component, namely rowSelected, columnsSelected. They also execute the user defined onLeadRowSelection and onLeadColumnSelection functions, if there are used. The logic handled by JavaFX triggers.

The Demo Class

Now that is enough talk, here is the example program.

/*
* XenonTableDemo.fx
*
* Created on 22-Jul-2008, 02:35:07
*/

package com.xenonsoft.gmailclient;

/**
* @author Peter
*/
import javafx.ext.swing.*;
import com.xenonsoft.gmailclient.ui.XenonTable;
import com.xenonsoft.gmailclient.ui.XenonTableModel;
import java.lang.System;

class PersonModel {
public attribute firstName: String;
public attribute lastName: String;
public attribute activity: String;
public attribute years: Integer;
public attribute vegetarian: Boolean;
}

var persons:PersonModel[] = [
PersonModel{firstName:"Mary", lastName:"Campione",
activity:"Snowboarding", years: 5, vegetarian: false},
PersonModel{firstName:"Alison", lastName:"Huml",
activity:"Rowing", years:3, vegetarian: true},
PersonModel{firstName:"Kathy", lastName:"Walrath",
activity:"Knitting", years:2, vegetarian:false},
PersonModel{firstName:"Sharon", lastName:"Zakhour",
activity:"Speed reading", years:20, vegetarian:true},
PersonModel{firstName:"Philip", lastName:"Milne",
activity:"Pool", years:10, vegetarian:false},
];


SwingFrame {
width: 400;
height: 400;
visible: true;
title: "XenonTableDemo"

content:
XenonTable {
onRowLeadSelection: function ( row:Integer, column: Integer): Void {
System.out.println("*YAHOO* handler {row} {column}");
}

onColumnLeadSelection: function ( row:Integer, column: Integer): Void {
System.out.println("*YIPPEE* handler {row} {column}");
}

columnSelectionAllowed: false
rowSelectionAllowed: true

tableDataModel:
XenonTableModel {
DEBUG: true;

columnNames: [
"First Name",
"Last Name",
"Activity",
"# of Years",
"Vegetarian?"
]

data: bind for ( person in persons ) {
[
person.firstName,
person.lastName,
person.activity,
"{person.years}",
"{person.vegetarian}"
]
}

}
}
}



 

The Way To Go From Here

This article showed how to reapply to the JTable to JavaFX, if you are in desperate need for it. The code does not show to handle individual table cell rendering or how to bind more of functionality from the JTable. However, it is perfectly acceptable to show hundreds of rows of text information from a database table or a wicked and wild extensive JSON query.

One way to extend the table would be to add the ability to render the background, foreground and colours for each individual table cell. It is a relative simple exercise for the reader to add a cell renderer to XenonTable. (So this extension, if you require it, is on you)

First, the cell renderer in FX.

public class XenonTableCellRenderer extends DefaultTableCellRenderer {

public attribute xenonTable: XenonTable;
//...

override function getTableCellRendererComponent(
table: JTable, value: Object,
isSelected: Boolean, hasFocus: Boolean,
row:Integer, column:Integer): Component
{
var text:String = value as String;

var tableModel:XenonTableModel = xenonTable.tableDataModel;
var cell: XenonTableCell = tableModel.getTableCell(row,column);
if ( cell.font != null ) {
this.setFont( cell.font.getAWTFont());
}
if ( cell.foreground != null ) {
//...
}
if ( cell.background != null ) {
//....
}

this.setText( cell.text );
return this;
}
}

 


You need register the FX cell renderer with the JTable directly or each TableColumn.

Second code snippet, you need to start using a cell object to group together a bunch of attributes, namely: text, colours and font.

public class XenonTableModel  extends AbstractTableModel {

public attribute DEBUG: Boolean = false;
public attribute columnNames: String[];
public attribute data: XenonTableCell[] on replace {
for ( cell in data ) {
cell.tableModel = this;
}
};
/* .... as before */
}


 

The third snippet of code. This is the partial code for the table cell FX object. The trigger need to be filled in:

public class XenonTableCell {

public attribute foreground: Color = null on replace oldValue {
if ( foreground != oldValue ) {
fireUpdate();
}
};
public attribute background: Color = null on replace oldValue {
/* ditto */
};
public attribute font: Font = null on replace oldValue {
/* ditto */
};
public attribute text: String = null on replace oldValue {
/* ditto */
};

// Private implementation
protected attribute row: Integer;
protected attribute col: Integer;
protected attribute tableModel: XenonTableModel = null;

protected function fireUpdate() {
if ( tableModel != null ) {
tableModel.fireTableCellUpdated(row, col);
}
}
}

 


Each cell takes a reference to the table model so if any of the values of attribute changed we can update the table UI by firing the appropriate event. You also have to figure how to record the row and column with each table cell before you can fire the event.

Finally you can write something like this main program:

import javafx.scene.paint.*;
import javafx.scene.text.*;
// ...

SwingFrame {
title: "XenonTableDemo"
/* ... as before .. */

content:
XenonTable {

// ...

tableDataModel:
XenonTableModel {
DEBUG: false;

// ...

data: bind for ( person in persons ) {
[
XenonTableCell {
font: Font {
name: "Arial",
style: FontStyle.BOLD
}
text: bind person.firstName
},
XenonTableCell {
foreground: Color.BLUE
text: bind person.lastName
},
XenonTableCell {
text: bind person.activity
},
XenonTableCell {
text: bind "{person.years}"
},
XenonTableCell {
text: bind "{person.vegetarian}"
}
]
}

}
}
}


 

Have fun! Now you can display those database rows again.

From http://jroller.com/peter_pilgrim/

Published at DZone with permission of Peter Pilgrim, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Andrew McVeigh replied on Mon, 2008/09/01 - 9:44am

i really cannot understand why they can't do zoomable jtables using the scenegraph project.

the scenegraph library replaced the use of Jazz from HCIL. I use Jazz extensively in my work, and it can apply any affine transformation to any swing widget. want a jtable that's double size, rotated 90 degrees and back to front? no problem! how about one that rotates in real-time? again, easy to do with Jazz.

heaven knows why they had to replace Jazz rather than evolving it.  it looks to be a fairly straightforward case of NIH, I think, in making the scenegraph.

Andrew

Chui Tey replied on Mon, 2008/09/01 - 3:57pm

The official word is that mobile devices do not have Swing. Hence, a new node-based Table implementation.

Unfortunately, JavaFX is rather resource intensive. A node-based Table will only be likely suitable for a toy application. I don't think I'd implement my e-mail client using this.

 

Peter Pilgrim replied on Tue, 2008/09/02 - 9:13am in response to: Andrew McVeigh

Andrew

Yes you can do this. If you have the knowledge.

Look at James Weavers blog entries from this month at javafxpert.com http://learnjavafx.typepad.com/weblog/2008/08/tablenode-creat.html for an example of custom TableNode. This is simplified table.

The way the JTable was designed in 1998 is probably not how the JavaSoft [or should that be JavaFXSoft] team would develop it today.

You can create a group container that manages a two grid of Nodes. The problem is how to generalise the concept of a table.

I saw the Dojo Framework struggling with various table implementation version from 0.4 to 0.9 for a number of years. It is difficult to really generalise tables. JTable was a marvellous job with hindsight.

Case #1 Zoomable Table, where you can magnify the whole table of cells.

Case #2 Zoomable Table Cell, where you can manify a cell inside a table.

Case #3 Table with Custom Node. How about a table with video players in row 0 or mp3 players in row 1

I foresee there will special collection tables. Perhaps the Javafxsoft team will create an abstract tablenode concepts and every one else will subclass it, as they did with JTable.

 

 

 

 

 

Peter Pilgrim replied on Tue, 2008/09/02 - 9:19am in response to: Chui Tey

Chui 

This is correct. With the scenegraph CustomNode you can create a table layout component using your grid layout algorithm. For example to draw Text (see javafx.scene.text.Text ). Essentially you write a Group custom container which contains only Text components, then you lay them out in a plane using a coordinate system. You can use the CustomNode#clip facility to constraint overflows. That's the theory.

Chui Tey replied on Wed, 2008/09/03 - 5:55pm in response to: Peter Pilgrim

[quote=peter_pilgrim]The way the JTable was designed in 1998 is probably not how the JavaSoft [or should that be JavaFXSoft] team would develop it today.[/quote]

Interesting comment, Peter.

How would you have tackled JTable differently if you had to do it today? 

Taking a look at data-binding in Netbeans now, and you can see that Sun is now eschewing compilation-time type checking, and instead uses string-based expressions for run-time reflection and binding.

If JTable had originally worked this way, then there would be no TableModel implementation to worry about. Instead JTable could have bound to any sequence object that is indexable. In turn, on JavaFX, it would have made binding to JavaFX classes a cinch.

 

Sergey Surikov replied on Tue, 2009/08/04 - 2:05pm

Too many code

use CRUDfx SDK from http://code.google.com/p/crudfx/
see screenshot with table

Milka Giordano replied on Sun, 2009/11/08 - 11:14pm

What is the use of Crudfx? I never able to learn about CAD and always hire an expert from Freelancer.com. It saves more time and power, though I have to spend some amount of money. They are pretty good anyway. You can use this code "3DPROTOTYPE" to get more benefit from the site. Good luck.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.