Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create swing components at runtime

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?


like image 602
user1952204 Avatar asked Oct 05 '22 10:10

user1952204


2 Answers

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.

like image 182
mikera Avatar answered Oct 10 '22 01:10

mikera


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.

like image 28
vishal_aim Avatar answered Oct 10 '22 01:10

vishal_aim