在本章中你将学到如何在单个应用程序中混合Swing表格和JavaFX柱状图。
本章以一个Swing应用程序开始,提供了一个通过添加JavaFX功能来丰富Swing应用程序的样例。
Swing应用程序样例
许多现实世界的项目使用Swing应用程序来处理表格。你可以继续使用已有的代码并好好利用JavaFX APIs。对于本例来说,你可以增加一个JavaFX柱状图,以此为表格数据提供一个彩色插图。本章提供了SwingInterop例子,其中处理了一个Swing表格和一个JavaFX柱状图。当你改变表格里面的数据时,柱状图会立刻更新。
从只包含Swing表格的样例应用程序开始,如图4-1。
图4-1 Swing JTable应用程序窗口
这个应用程序包含两个类
SampleTableModel.java类继承了AbstractTableModel类并定义了表格。
SwingInterop类继承了JApplet类,是这个应用程序的基础类。其main方法在事件分发线程(EDT)上调用了run方法,其中创建了一个JFrame对象和一个JApplet对象,并用SwingInterop类的实例初始化了JApplet对象。然后调用init方法,创建了表格并将其加入到applet的内容面板中。
整合JavaFX柱状图
为了给柱状图提供数据,修改SampleTableModel类,增加一个新的类变量(bcData)和一个新的方法来返回适合柱状图的数据格式。getBarChartData方法的实现如例4-1所示。
例4-1
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.chart.BarChart;
public class SampleTableModel extends AbstractTableModel {
private static ObservableList<BarChart.Series> bcData;
public ObservableList<BarChart.Series> getBarChartData() {
if (bcData == null) {
bcData = FXCollections.<BarChart.Series>observableArrayList();
for (int row = 0; row < getRowCount(); row++) {
ObservableList<BarChart.Data> series =
FXCollections.<BarChart.Data>observableArrayList();
for (int column = 0; column < getColumnCount(); column++) {
series.add(new BarChart.Data(getColumnName(column),
getValueAt(row, column)));
}
bcData.add(new BarChart.Series(series));
}
}
return bcData;
}
//rest of the SampleTableModel class code
}
SwingInterop类重写了JApplet.init方法,以创建应用程序的内容面板。修改init方法来创建一个保存JavaFX柱状图的JFXPanel对象和一个组织JavaFX图表和表格的JSplitPane对象。Init方法中所需要的修改如例4-2中的加粗代码所示。
例4-2
@Override
public void init() {
tableModel = new SampleTableModel();
// create javafx panel for charts
chartFxPanel = new JFXPanel();
chartFxPanel.setPreferredSize(new Dimension(PANEL_WIDTH_INT, PANEL_HEIGHT_INT));
//create JTable
JTable table = new JTable(tableModel);
table.setAutoCreateRowSorter(true);
table.setGridColor(Color.DARK_GRAY);
SwingInterop.DecimalFormatRenderer renderer =
new SwingInterop.DecimalFormatRenderer();
renderer.setHorizontalAlignment(JLabel.RIGHT);
for (int i = 0; i < table.getColumnCount(); i++) {
table.getColumnModel().getColumn(i).setCellRenderer(renderer);
}
JScrollPane tablePanel = new JScrollPane(table);
tablePanel.setPreferredSize(new Dimension(PANEL_WIDTH_INT,
TABLE_PANEL_HEIGHT_INT));
JPanel chartTablePanel = new JPanel();
chartTablePanel.setLayout(new BorderLayout());
//Create split pane that holds both the bar chart and table
JSplitPane jsplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
jsplitPane.setTopComponent(chartTablePanel);
jsplitPane.setBottomComponent(tablePanel);
jsplitPane.setDividerLocation(410);
chartTablePanel.add(chartFxPanel, BorderLayout.CENTER);
//Add the split pane to the content pane of the application
add(jsplitPane, BorderLayout.CENTER);
}
为了避免语法错误,在SwingInterop类中增加一个引入声明和charFxPanel成员变量,如例4-3所示。
例4-3
import javafx.embed.swing.JFXPanel;
import javax.swing.*;
public class SwingInterop extends JApplet {
private static JFXPanel chartFxPanel;
// rest of the SwingInterop class code here
}
你已经准备好了用来呈现JavaFX数据的Swing应用程序的UI。下一步是创建JavaFX场景。因为JavaFX场景必须由JavaFX应用程序线程创建,将代码封装为一个Runnable对象,如例4-4所示。在init方法的末尾加入这些代码。
例4-4
Platform.runLater(new Runnable() {
@Override
public void run() {
createScene();
}
});
在SwingInterop类中加入如下引入声明,如例4-5所示。
例4-5
import javafx.application.Platform;
实现SwingInterop类的createScene方法,如例4-6。增加引入声明并定义chart 成员变量。
例4-6
import javafx.scene.Scene;
import javafx.scene.chart.Chart;
private void createScene() {
chart = createBarChart();
chartFxPanel.setScene(new Scene(chart));
}
createBarChart方法创建了图表,并为表格添加了一个的改变监听器。注意所有JavaFX数据的改变都必须发生在JavaFX线程上。因此,将事件处理器中负责更新JavaFX图表的代码封装进一个Runnable对象中,并传入Platform.runLater方法。createBarChart方法的实现如例4-7所示。
例4-7
private BarChart createBarChart() {
CategoryAxis xAxis = new CategoryAxis();
xAxis.setCategories(FXCollections.<String>observableArrayList(tableModel.
getColumnNames()));
xAxis.setLabel("Year");
double tickUnit = tableModel.getTickUnit();
NumberAxis yAxis = new NumberAxis();
yAxis.setTickUnit(tickUnit);
yAxis.setLabel("Units Sold");
final BarChart chart = new BarChart(xAxis, yAxis, tableModel.getBarChartData());
tableModel.addTableModelListener(new TableModelListener() {
public void tableChanged(TableModelEvent e) {
if (e.getType() == TableModelEvent.UPDATE) {
final int row = e.getFirstRow();
final int column = e.getColumn();
final Object value =
((SampleTableModel) e.getSource()).getValueAt(row, column);
Platform.runLater(new Runnable() {
public void run() {
XYChart.Series<String, Number> s =
(XYChart.Series<String, Number>) chart.getData().get(row);
BarChart.Data data = s.getData().get(column);
data.setYValue(value);
}
});
}
}
});
return chart;
}
增加如例4-8所示的引入声明。
例4-8
import javafx.collections.FXCollections;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
重命名框架的标题为“Swing JTable and Bar Chart”并运行SwingInterop程序。
应用程序窗口如图4-2所示。
图4-2 SwingInterop程序窗口
应用程序文件
源代码
NetBeans工程