我创建了应用程序,它的行为与预期的差不多。只要数据库查询正在运行,gui 就会保持响应。当使用 SwingUtilities.invokeLater() 创建自定义面板时,gui 会冻结很短的时间。
当我使用 SwingUtilities.invokeAndWait() 时,它在高端游戏 PC 上运行非常流畅。(可能不是最好的编码机器......)但是在相对较慢的机器(双核,2GB RAM)上,gui“滞后”
我创建了一个非常小的版本的程序来重现该行为。测试时增加 TEST_NUMBER_OF_PANELS 的值。
它将它设置为一个非常大的值,以在我当前的 PC 上重现行为,而无需任何花哨的外观和感觉以及其他组件。但我不想这样发布。所以我把它减到100
package test;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Vector;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.event.TableModelEvent;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
//import org.pushingpixels.substance.api.SubstanceLookAndFeel;
//import org.pushingpixels.substance.api.skin.BusinessBlackSteelSkin;
//import org.pushingpixels.substance.api.skin.SubstanceBusinessBlackSteelLookAndFeel;
public class Test extends JFrame {
private static final int TEST_NUMBER_OF_PANELS = 100;
private static JTabbedPane tabbedPane = new JTabbedPane();
Test() {
this.setLayout(new BorderLayout());
this.setSize(1050, 700);
this.setMinimumSize(new Dimension(400,200));
this.add(tabbedPane, BorderLayout.CENTER);
JButton testbutton = new JButton("new tab");
testbutton.addMouseListener(new MouseListener() {
@Override
public void mousePressed(MouseEvent e) {
tabbedPane.addTab("tab x", new TestTabContent());
}
@Override
public void mouseReleased(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseClicked(MouseEvent e) {
}
});
this.add(testbutton, BorderLayout.NORTH);
//tabbedPane.addTab("tab1", new TestTabContent());
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Test().setVisible(true);
/*
try {
UIManager.setLookAndFeel(new SubstanceBusinessBlackSteelLookAndFeel());
} catch (Exception e) {
System.out.println("Substance Business failed to initialize");
}
SubstanceLookAndFeel.setSkin(new BusinessBlackSteelSkin());
new Test()
.setVisible(true);
*/
}
});
}
private class TestTabContent extends JPanel {
TestTabContent() {
final JPanel boxContainer = new JPanel();
boxContainer.setLayout(new BoxLayout(boxContainer, BoxLayout.Y_AXIS));
JPanel boxContainerOuter = new JPanel();
boxContainerOuter.setLayout(new BorderLayout());
boxContainerOuter.add(boxContainer, BorderLayout.NORTH);
JScrollPane mainScrollPane = new JScrollPane(boxContainerOuter);
// create toolbar
JPanel toolBar = new JPanel();
toolBar.setLayout(new BorderLayout());
//east
JPanel InfoPanel = new JPanel();
InfoPanel.setLayout(new BoxLayout(InfoPanel, BoxLayout.X_AXIS));
InfoPanel.add(new JLabel("test: some info ..."));
toolBar.add(InfoPanel, BorderLayout.WEST);
//west
JPanel viewOptionPanel = new JPanel();
viewOptionPanel.setLayout(new BoxLayout(viewOptionPanel, BoxLayout.X_AXIS));
viewOptionPanel.add(new JLabel("some controls.."));
toolBar.add(viewOptionPanel, BorderLayout.EAST);
// set main panel´s layout
GroupLayout layout = new GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(toolBar, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(mainScrollPane)
);
layout.setVerticalGroup(
layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(toolBar, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
.addGap(0, 0, 0)
.addComponent(mainScrollPane, GroupLayout.DEFAULT_SIZE, 413, Short.MAX_VALUE))
);
// create controls
SwingWorker<String, Void> worker = new SwingWorker<String, Void>() {
@Override
protected String doInBackground() throws Exception {
// RUN DATABASE QUERY
// ------------------
//Thread.sleep(1000);
// ------------------
// CREATE TESTPANELS
ArrayList<ArrayList<ArrayList<String>>> dataFromQuery = createDummyData();
for (final ArrayList<ArrayList<String>> tableData : dataFromQuery) {
//SwingUtilities.invokeAndWait(new Runnable() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
// create panels on edt
TestPanel newTestPanel = new TestPanel(tableData);
JPanel seperator = new JPanel(new BorderLayout());
seperator.setBackground(Color.black);
seperator.add(newTestPanel);
boxContainer.add(seperator);
}
});
}
return "";
}
};
worker.execute();
}
private ArrayList<ArrayList<ArrayList<String>>> createDummyData () {
ArrayList<String> columns = new ArrayList<String>();
ArrayList<ArrayList<String>> rows = new ArrayList<ArrayList<String>>();
ArrayList<ArrayList<ArrayList<String>>> tables = new ArrayList<ArrayList<ArrayList<String>>>();
for (int i = 0; i < 15; i ++) {
columns.add("test213124");
}
for (int i=0; i < 8; i++) {
rows.add(columns);
}
for (int i=0; i < TEST_NUMBER_OF_PANELS; i++) {
tables.add(rows);
}
return tables;
}
}
public class TestPanel extends JPanel {
private static final long serialVersionUID = -3853151036184428736L;
public static final int radioButtonWidth = 20;
private JTable table;
private DefaultTableModel tableModel;
private JPanel collapsiblePane;
private JButton collapsingButton;
TestPanel(ArrayList<ArrayList<String>> tableData) {
System.out.println("testpanel constructor");
createTestPanel(tableData);
}
private void createTestPanel(ArrayList<ArrayList<String>> tableData) {
// container with boxLayout for collapsiblePane
JPanel boxContainer = new JPanel();
boxContainer.setLayout(new BoxLayout(boxContainer, BoxLayout.Y_AXIS));
boxContainer.setBorder(BorderFactory.createMatteBorder(0, 1, 1, 1, Color.BLACK));
// set table stuff
tableModel = new DefaultTableModel();
tableModel.setColumnIdentifiers(
new Object[] {
"test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test"});
for (ArrayList<String> rowData : tableData) {
Vector<Serializable> data = new Vector<Serializable>();
for (String columnData : rowData) {
data.add(columnData);
}
tableModel.addRow(data);
}
table = new JTable(tableModel) {
public void tableChanged(TableModelEvent e) {
super.tableChanged(e);
repaint();
}
public Component prepareRenderer(TableCellRenderer renderer, int row, int column){
Component returnComp = super.prepareRenderer(renderer, row, column);
Color alternateColor = new Color(225,225,238);
Color usualColor = Color.WHITE;
if (!returnComp.getBackground().equals(getSelectionBackground())){
Color bg = (row % 2 == 0 ? alternateColor : usualColor);
returnComp .setBackground(bg);
bg = null;
}
return returnComp;
}
};
boxContainer.add(table.getTableHeader(), BorderLayout.NORTH);
boxContainer.add(table, BorderLayout.CENTER);
// other controls / toolbar
JPanel toolbar = new JPanel();
toolbar.setLayout(new BorderLayout());
// buttons to the right
JPanel toolbarButtonGroup = new JPanel();
toolbarButtonGroup.setLayout(new BoxLayout(toolbarButtonGroup, BoxLayout.X_AXIS));
// test button
JButton button = new JButton("test");
JPanel sepPanel = new JPanel();
sepPanel.add(button);
toolbarButtonGroup.add(sepPanel);
// test button
button = new JButton("test");
sepPanel = new JPanel();
sepPanel.add(button);
toolbarButtonGroup.add(sepPanel);
// test button
button = new JButton("test");
sepPanel = new JPanel();
sepPanel.setBorder(BorderFactory.createEmptyBorder(0,0,0,5));
sepPanel.add(button);
toolbarButtonGroup.add(sepPanel);
toolbar.add(toolbarButtonGroup, BorderLayout.EAST);
boxContainer.add(toolbar);
JPanel subPanel = new JPanel();
subPanel.setLayout(new BoxLayout(subPanel, BoxLayout.Y_AXIS));
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new BorderLayout());
buttonPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 24));
collapsingButton = new JButton(tableModel.getValueAt(0, 8).toString());
collapsingButton.setName("toggleButton");
collapsingButton.setHorizontalAlignment(SwingConstants.LEFT);
collapsingButton.setBorderPainted(false);
collapsingButton.setFocusPainted(false);
buttonPanel.add(collapsingButton, BorderLayout.CENTER);
buttonPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
subPanel.add(buttonPanel);
collapsiblePane = new JPanel();
collapsiblePane.setName("collapsiblePane");
collapsiblePane.setLayout(new CardLayout());
collapsiblePane.add(boxContainer, "");
subPanel.add(collapsiblePane);
add(subPanel);
}
}
}
我想我应该使用 SwingWorker 而不是在 EDT 上运行尽可能多的代码。我知道我应该在单独的线程中访问 TableModel。如果应用程序中只有一个 JTable,它很简单:我在 EDT 上创建表并更新线程上的数据。
在那种情况下,我不确定哪种方法是可取的。现在所有与 tabe 相关的东西都在 TestPanel 类中管理。它完全在 EDT 上运行。
当然,我可以在 TestPanel 中运行一个线程来处理 TableModel,但是为每个面板创建一个新线程对我来说听起来不是一个聪明的主意。
另一个“想法”是在运行数据库查询的同一线程上直接在 TestTab 中创建模型。但由于它属于 TestPanel,这种方法听起来像是糟糕的设计。
到目前为止,我最肮脏的想法是使用 invokeAndWait() 并让 Tread.sleep() 一段时间,这样 ETD 就不会每隔几毫秒就被新调用打耳光。但我实际上并不想那样编码。
什么设计方法可能是最可取的?
我想在运行时在 JTabbedPane 中创建几个自定义面板。自定义面板的数量取决于数据库查询的结果集。
每个自定义面板都包含一个 JTable,其中包含来自结果集中的大量数据和一些 JButton。
我的计划是使用 SwingWorker 在单独的线程上运行数据库查询。然后使用 invokeLater() 在 Event Dispatch Thread 上安排一个任务来创建自定义面板,因为必须在 EDT 中创建和访问 Swing 组件。
使用这种方法,如果结果集包含很多行,则 gui 在创建自定义面板时可能会冻结。
解决或最小化这个问题的最优雅的方法是什么?