5

我创建了应用程序,它的行为与预期的差不多。只要数据库查询正在运行,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 在创建自定义面板时可能会冻结。

解决或最小化这个问题的最优雅的方法是什么?


4

3 回答 3

2

您可能应该首先考虑限制显示的结果数量!

除此之外,Swing 组件的创建非常高效。事实上,我希望它能够在结果从数据库返回时一样快地创建屏幕外组件实例,如果不是更快的话。你确定这真的是瓶颈吗?

最后,如果您担心冻结,您可以随时使用 invokeLater 为每个自定义面板触发单独的事件。然后将有机会在每个面板创建之间发生事件处理,这应该消除“冻结”效果。

于 2013-01-07T11:43:24.663 回答
1

你想在不干扰数据库的情况下运行数据库查询EDT,所以总是欢迎单独运行。在里面运行你的程序invokeLater,然后是一个用于数据库查询的新线程。巧合的是,我也处理过与你类似的问题,所以有看看我做了什么。

  ////start a Thread  for DB query on a button click or whatever you want
   private void jButton2ActionPerformed(java.awt.event.ActionEvent evt)   
{                                         
 new Thread(new thread1()).start();

} 

内线:

public class thread1 implements Runnable
{

    public void run() {
     insert ins=new insert();/*make object of class if needed(class to handle Db operations)*/

    try {
        ins.insert(path); //call the function to insert into database or any other query
    } 

   catch (ClassNotFoundException ex) {
        Logger.getLogger(forminsert.class.getName()).log(Level.SEVERE, null, ex);
    } catch (SQLException ex) {
        Logger.getLogger(forminsert.class.getName()).log(Level.SEVERE, null, ex);
    }

    JOptionPane.showMessageDialog(frame, "Successfully inserted into database");
    ///show it is inserted

    getRootPane().updateUI();//may work without this(optional)

    }
}

有了这个,我从来没有遇到过任何冻结问题。它插入数据库没有任何问题。

于 2013-01-07T12:52:12.177 回答
1

我的计划是使用 SwingWorker 在单独的线程上运行数据库查询。然后使用 invokeLater() 在 Event Dispatch Thread 上安排一个任务来创建自定义面板,因为必须在 EDT 中创建和访问 Swing 组件。

使用这种方法,如果结果集包含很多行,则 gui 在创建自定义面板时可能会冻结。

它不应该冻结,我认为您的方法是正确的。

如果您仍然觉得它会冻结,请尝试告诉我们。

于 2013-01-07T11:39:48.127 回答