4 用JavaFX功能丰富Swing应用程序
在本章中,您将学习如何在单个应用程序中混合使用Swing表格和JavaFX柱状图。
本章从一个Swing应用程序开始,并提供了一个示例,演示如何通过添加JavaFX功能来丰富Swing应用程序。
示例Swing应用程序
许多真实项目使用处理表格的Swing应用程序。您可以继续使用现有代码,并利用JavaFX API。例如,您可以添加一个JavaFX柱状图,以提供表格数据的彩色图示。本章提供了一个名为SwingInterop
的示例,该示例处理了一个Swing表格和一个JavaFX柱状图。当您更改表格单元格中的数据时,柱状图会立即更新。
从只有Swing表格的示例应用程序开始,如图4-1所示。
该应用程序由两个类组成:
SampleTableModel
类继承自AbstractTableModel
类,并定义了表格。
SwingInterop
类继承自JApplet
类,是应用程序的基本类。它的main
方法在事件分派线程(EDT)上调用run
方法来创建图形用户界面(GUI)。run
方法创建一个JFrame对象和一个JApplet对象,并使用SwingInterop
类的实例初始化JApplet对象。然后它调用init
方法,该方法创建表格并将表格添加到applet的内容面板中。
您可以通过点击上面的链接查看这两个类的实现。
集成JavaFX柱状图
要为柱状图提供数据,需要通过添加一个新的类变量(bcData
)和一个从表格中检索数据并以适合柱状图的格式返回数据的方法来修改SampleTableModel
类。 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; } //SampleTableModel类的其余代码 }
SwingInterop
类重写了JApplet.init
方法来创建应用程序的内容面板。修改init
方法以创建一个JFXPanel对象来容纳JavaFX柱状图,并创建一个JSplitPane对象来容纳JavaFX图表和表格。 示例4-2中显示了对init
方法所需的更改。
示例4-2
@Override public void init() { tableModel = new SampleTableModel(); // 创建用于图表的JavaFX面板 chartFxPanel = new JFXPanel(); chartFxPanel.setPreferredSize(new Dimension(PANEL_WIDTH_INT, PANEL_HEIGHT_INT)); // 创建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()); // 创建包含柱状图和表格的分割窗格 JSplitPane jsplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); jsplitPane.setTopComponent(chartTablePanel); jsplitPane.setBottomComponent(tablePanel); jsplitPane.setDividerLocation(410); chartTablePanel.add(chartFxPanel, BorderLayout.CENTER); // 将分割窗格添加到应用程序的内容面板中 add(jsplitPane, BorderLayout.CENTER); }
为了消除语法错误,在SwingInterop
类中添加导入语句和chartFxPanel
类变量的定义,如示例4-3所示。
示例4-3
import javafx.embed.swing.JFXPanel; import javax.swing.*; public class SwingInterop extends JApplet { private static JFXPanel chartFxPanel; // 其他 SwingInterop 类代码在这里 }
您已经准备好了Swing应用程序的UI来渲染JavaFX数据。下一步是创建JavaFX场景。因为JavaFX场景必须在JavaFX应用程序线程上创建,所以将代码包装到一个Runnable对象中,如示例4-4所示。将此代码添加到init
方法的末尾。
将示例4-5中的导入语句添加到SwingInterop
类中。
按照示例4-6中所示的方式实现SwingInterop
类的createScene
方法。添加导入语句并定义实例变量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("年份"); double tickUnit = tableModel.getTickUnit(); NumberAxis yAxis = new NumberAxis(); yAxis.setTickUnit(tickUnit); yAxis.setLabel("销售数量"); 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 和 Bar Chart",然后运行SwingInterop
应用程序。
应用程序窗口显示在图 4-2中。