i created the application and it behaves pretty much as expected. the gui keeps responsive as long as the database query is running. when creating the custom panels with SwingUtilities.invokeLater() the gui freezes for a very short amount of time.
when i use SwingUtilities.invokeAndWait() its runs very smoothly on a high end gaming pc. (probably not the best machine to code...) but on a relatively slow machine (dualcore, 2GB RAM) the gui "lags"
i created a very minimal version of the program that reproduces the behaviour. increase the value of TEST_NUMBER_OF_PANELS when testing it.
it set it to an insanely large value to reproduce the behavour on my current pc without any fancy look and feel and additional components. but i dont want to post it like this. so i reduced it to 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);
}
}
}
i guess i should run as much code as possible with SwingWorker rather than on the EDT. im aware that i should access the TableModel in a seperate thread. if there is only one JTable in the application its simple: i create the table on the EDT and update the data on the thread.
in that case im not sure wich approach is advisable. right now everything tabe-related is managed in the TestPanel class. which runs entirely on the EDT.
of course i could run a thread in TestPanel which takes care of the TableModel but creating a new thread for every single panel doesnt sound like a smart idea to me.
another "idea" was to create the models directly in TestTab on the same thread as the database query is running on. but since it belongs to TestPanel this approach sounds like bad design.
the by far dirtiest idea i had was to use invokeAndWait() and let the Tread.sleep() for a while so the ETD doesnt get slapped by new invokes every few milliseconds. but i dont actually want to code it that way.
what design approach could be the most advisable?
i want to create several custom panels in a JTabbedPane at runtime. the number of custom panels depends on the resultset of a database query.
each custom panel holds a JTable with a chunk of data from the resultset and a few JButtons.
my plan is to run the database query on a seperate thread with SwingWorker. then schedule a task on the Event Dispatch Thread with invokeLater() to create the custom panels since swing components must be created and accessed in the EDT.
with this approach its possible that the gui freezes while creating the custom panels if the resultset contains a lot of rows.
what could be the most elegant way to solve or minimize this problem?
You should probably think about limiting the number of results displayed first and foremost!
Apart from that, Swing component creation is pretty efficient. In fact, I'd expect it to be able to create off-screen component instances as fast as the results come back from the database, if not faster. Are you sure this is really the bottleneck?
Finally, if you are worried about freezing, you can always fire off separate events with invokeLater for each custom panel. Then there will be an opportunity for event handling to happen between each panel creation, which should eliminate the "freeze" effect.
my plan is to run the database query on a seperate thread with SwingWorker. then schedule a task on the Event Dispatch Thread with invokeLater() to create the custom panels since swing components must be created and accessed in the EDT.
with this approach its possible that the gui freezes while creating the custom panels if the resultset contains a lot of rows.
It should not freeze, I think your approach is correct.
If you still feel it will freeze, please try and let us know.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With