Статьи

JavaFX: восстановить Swing JTable

В этой записи блога вы действительно объясните, как заново представить Swing JTable в приложение JavaFX. Мы рассмотрим, почему этот ценный компонент был удален, и превознесем его достоинства. Я покажу некоторые из моего кода клиента Gmail.

Почему JTable был удален?

JTable был удален из JavaFX, так как графический интерфейс команда FX хочет переписать пользовательский интерфейс , используя его с API проекта дерева сцены . Граф сцены основан на модульной структуре дерева графического объекта, в то время как компоненты в компонентах Swing и AWT в Java были построены из Java 2D и, таким образом, способны воспроизводить только графику непосредственного режима .

В чем преимущество графа объектов? Ну, это позволяет легко рассматривать каждый рисунок как узел. Каждый узел может объединяться в группу узловых объектов. Для каждого отдельного узла применяется аффинное преобразование, в котором можно перевести узел на расстояние, повернуть узел на угол и сдвинуть узел в параллелограмм. Вы можете применить те же аффинные преобразования к группе объектов узлов. Поймите, тогда, что вы можете достичь, когда у вас есть группа из группы узлов. Scenegraph имеет все это, а также добавляет легкое затенение, эффекты рисования и фильтры и обрезку формы на каждом узле ( SGNode ).

Еще один способ думать об узлах, это взглянуть на область компьютерного проектирования (САПР). Когда инженеры-автомобилисты проектируют автомобиль, они, очевидно, используют графические элементы для представления каждой части автомобиля. У автомобиля есть ось, три или более колеса, двигатель и обычно рулевое колесо. Каждый компонент автомобиля может быть представлен в трансформации, цвете и деталях, которые ему требуются. Однако вы можете полностью повернуть автомобиль и увидеть все составляющие его движения или изменения в целом.

Очевидно, что JTable нужно будет переработать для Project Scenegraph.

Каковы положительные стороны JTable?

Есть много положительных моментов для почтенного JTable, который пережил суматоху дизайна Java UI. Мы живем с JTable, возможно, с 1998 года, когда он был раскрыт в имени com.sun.java.swing.JTable . Даже тогда было действительно потрясающе иметь возможность перетаскивать столбец таблицы из одного конца представления в другой столбец. Это была удивительная техника.

Он реализует довольно хороший шаблон проектирования FLYWEIGHT (см. TableCellRenderer , DefaultTableCellRenderer и TableCellEditor ).
Он может обрабатывать огромное количество данных, брошенных на него.
Модель данных находится в TableModel , которая полностью отделена от пользовательского интерфейса компонента.
Он следует за Model View Controller, как любой другой компонент Swing.

Добавление Swing JTable

Иногда вы просто не можете жить без этих преимуществ. JTable отлично подходит, если вы хотите отображать строки и столбцы из таблицы базы данных. Если у вас уже есть знания Swing, вы уже знаете, как их использовать и программировать. Так почему бы не использовать его?

Однако вы можете получить JTable обратно в свои программы JavaFX. Теперь достаточно просто повторно использовать любой компонент Swing внутри выпуска JavaFX Preview SDK и Subversion. Вы можете обернуть любой JComponent внутри javafx.ext.swing.ComponentView . Поэтому вы можете легко добавить JTable в программу JavaFX на основе сценографа. Под капотом магия управляется SGComponent (от Project SceneGraph).

В этом примере я быстро покажу свою собственную версию JTable, основанную на изучении кода в демонстрации Assortis. Демонстрация Assortis является частью проекта openjfx-компилятора Subversion . Вот мой XenonTable.

/*
* XenonTable.fx
*
* Created on 22-Jul-2008, 00:31:28
*/

package com.xenonsoft.gmailclient.ui;

/**
* @author Peter Pilgrim
*/

import javafx.ext.swing.Component;
import javax.swing.JTable;
import javax.swing.JComponent;
import javax.swing.JScrollPane;
import javax.swing.table.TableModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import java.lang.System;
import java.lang.Exception;

public class XenonTable extends Component {
private attribute table: JTable;

public attribute selectedRows: Integer[];

public attribute selectedColumns: Integer[];

public attribute onRowLeadSelection: function( row: Integer, column: Integer ): Void;
public attribute onColumnLeadSelection: function( row: Integer, column: Integer ): Void;

public attribute rowSelectionAllowed: Boolean = true on replace {
if ( table != null ) {
table.setRowSelectionAllowed( rowSelectionAllowed );
}
};
public attribute columnSelectionAllowed: Boolean = false on replace {
if ( table != null ) {
table.setColumnSelectionAllowed( columnSelectionAllowed );
}
};

public attribute tableDataModel: XenonTableModel on replace {
if ( tableDataModel!= null ){
table.setModel(tableDataModel);
}
};

override function createJComponent(): JComponent {
table = new JTable();
table.setFillsViewportHeight(true);
if ( tableDataModel != null ) {
table.setModel(tableDataModel);
}

var rowListener = ListSelectionListener {
override function valueChanged(e:ListSelectionEvent): Void {
if (e.getValueIsAdjusting()) {
return;
}
System.out.print("ROW SELECTION EVENT ");
debugSelection();

// Compiler bug convert int[] to Integer[]?
var sRows:Integer[] = table.getSelectedRows();
selectedRows = sRows;
fireOnRowLeadSelection( e );
}
};
table.getSelectionModel().addListSelectionListener(rowListener);

var columnListener = ListSelectionListener {
override function valueChanged(e:ListSelectionEvent): Void {
if (e.getValueIsAdjusting()) {
return;
}
System.out.print("COLUMN SELECTION EVENT ");
debugSelection();

// Compiler bug convert int[] to Integer[]?
var sColumns = table.getSelectedColumns();
selectedColumns = sColumns;
fireOnColumnLeadSelection( e );
}
};
table.getColumnModel().getSelectionModel().addListSelectionListener(columnListener);

var scrollPane = new JScrollPane(table);
return scrollPane;
// return table;
}

protected function fireOnRowLeadSelection( e: ListSelectionEvent ): Void {
var row:Integer = table.getSelectionModel().getLeadSelectionIndex();
var column:Integer = table.getColumnModel().getSelectionModel().getLeadSelectionIndex();
if (this.onRowLeadSelection != null ) {
this.onRowLeadSelection( row, column );
}
}

protected function fireOnColumnLeadSelection( e: ListSelectionEvent ): Void {
var row:Integer = table.getSelectionModel().getLeadSelectionIndex();
var column:Integer = table.getColumnModel().getSelectionModel().getLeadSelectionIndex();
if (this.onColumnLeadSelection != null ) {
this.onColumnLeadSelection( row, column );
}
}

protected function debugSelection(): Void {

System.out.print("Lead: {%d table.getSelectionModel().getLeadSelectionIndex()} ");
System.out.println("{%d table.getColumnModel().getSelectionModel().getLeadSelectionIndex()} ");

System.out.print("Selected Rows: ");
var selectedRows: Integer[] = table.getSelectedRows();
for ( r in selectedRows ) {
System.out.print("{%d r} ")
}
System.out.println();
System.out.print("Selected Columns: ");
var selectedColumns: Integer[] = table.getSelectedColumns();
for ( c in selectedColumns ) {
System.out.print("{%d c} ")
}
System.out.println();
}
}


 


Хотя это выглядит сложно, приведенный выше код действительно прост. Чтобы создать компонент в JavaFX, один подкласс из специального узла с именем Component, а затем один реализует функцию createJComponent (). CreateJComponent создает JTable и возвращает его в виде JScrollPane .


Чтобы сделать таблицу FX более полезной, необходимо прослушивать определенные события, когда пользователь нажимает на таблицу. Это то, что делают подклассы ListSelectionListener. JavaFX позволяет легко создавать подклассы для любого существующего интерфейса или класса Java. В недавнем FX-компиляторе вам нужно использовать ключевые слова функции переопределения для переопределения методов и атрибутов суперкласса. Давайте рассмотрим таблицу модели класса

Модель данных таблицы

Модель таблицы является подклассом AbstractTableModel из библиотеки Java Swing.

/*
* 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("--------------------------");
}
}



Быстрый читатель будет знать, что я использую функцию индексации для сопоставления координат строки и столбца с линейным массивом. Смотрите мою другую статью: JavaFX Обходной путь для последовательностей последовательностей .

Читатель должен знать, что ключевое слово static удаляется из скомпилированного JavaFX. Обратите внимание, как переменная уровня сценария xlass используется в приведенном выше XenonTableModel. Теперь это правильный способ объявления глобальной переменной модуля, которая заменяет статическую. На самом деле переменная xlass захватывает значение java.lang.String.class. Тогда эта табличная модель обрабатывает только строки. Это нормально, потому что JavaFX имеет отличную подстановку строк, которая позволяет преобразовать объект в строку.

В методе setValue () вы все равно должны вызывать метод суперкласса fireTableCellUpdated () как обычно.

Слушатели

Как я показал выше, слушатели выбора списка выполняют клиент XenonTable для фактического использования таблицы.


Слушатели защищены, и они просто обновляют последовательности в компоненте таблицы, а именно rowSelected, columnsSelected. Они также выполняют пользовательские функции
onLeadRowSelection и
onLeadColumnSelection , если они используются. Логика, обрабатываемая триггерами JavaFX.

Демо-класс

Теперь этого достаточно, вот пример программы.

/*
* 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}"
]
}

}
}
}



 

Путь отсюда

В этой статье показано, как повторно применить JTable к JavaFX, если вам это крайне необходимо. Код не показывает, как обрабатывать отрисовку отдельных ячеек таблицы или как связать больше функций из JTable. Тем не менее, вполне приемлемо показывать сотни строк текстовой информации из таблицы базы данных или злой и дикий обширный JSON-запрос.

Один из способов расширения таблицы — добавить возможность рендеринга фона, переднего плана и цветов для каждой отдельной ячейки таблицы. Это относительно простое упражнение для читателя — добавить средство визуализации ячеек в XenonTable. (Так что это расширение, если вам это нужно, на вас)

Во-первых, средство визуализации ячеек в 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;
}
}

 


Вам необходимо зарегистрировать средство визуализации ячеек FX непосредственно в JTable или в каждом TableColumn .

Второй фрагмент кода, вам нужно начать использовать объект ячейки, чтобы сгруппировать кучу атрибутов, а именно: текст, цвета и шрифт.

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 */
}


 

Третий фрагмент кода. Это частичный код для объекта FX ячейки таблицы. Триггер необходимо заполнить:

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);
}
}
}

 


Каждая ячейка берет ссылку на модель таблицы, поэтому, если любое из значений атрибута изменилось, мы можем обновить пользовательский интерфейс таблицы, вызвав соответствующее событие. Вы также должны выяснить, как записать строку и столбец с каждой ячейкой таблицы, прежде чем вы сможете запустить событие.

Наконец, вы можете написать что-то вроде этой основной программы:

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}"
}
]
}

}
}
}


 

Повеселись! Теперь вы можете снова отобразить эти строки базы данных.

С http://jroller.com/peter_pilgrim/