显示 HTML 格式的大型 JLIST

发布于 2024-09-06 13:17:06 字数 735 浏览 1 评论 0原文

我有一个java小程序,我必须在其中显示大量项目(字典条目)。用户需要能够选择列表中的各个项目,因此它被实现为 JList。生成列表的速度非常快,直到我决定通过使用 HTML 设置各个项目的格式来使显示更加美观。现在,列表看起来很漂亮,但每次用户访问字典时都需要 10 到 15 秒才能生成它(如果没有格式化,它几乎立即发生)。我想我可以通过在用户首次进入应用程序时生成列表并根据需要隐藏和取消隐藏列表来预先解决性能问题。但是,我想知道是否有更好的方法。也许是生成列表的更有效的方法。

这是发生速度减慢的代码部分(在 C 和 D 的显示之间):

DefaultListModel dictCodeModel =  new DefaultListModel();
System.out.println("C");
while (e.hasMoreElements()) {
    String currentEntry = "";
    DEntry dentry = (DEntry) e.nextElement();
    if (!filter)                 
        dictCodeModel.addElement(dentry.theToken); // tokens have embedded html tags

}
System.out.println("D");

正如您所看到的,它非常简单。当“theToken”被格式化为 HTML 时,我得到了真正的性能提升。我能做些什么来解决这个问题吗?

谢谢,

I have a java applet in which I have to display a large amount of items (dictionary entries). The user needs to be able to select individual items in the list, hence it is implemented as a JList. Generating the list was very quick until I decided to make the display more asthetically pleasing by formatting the individual items using HTML. Now the list looks pretty but it takes between 10 and 15 seconds to generate it each time the user accesses the dictionary (without formatting it occurs almost instantly). I suppose I could take the performance hit up front by generating the list when the user first enters the application and just hiding and unhiding the list as needed. But, I'm wondering if there is a better way. Perhaps a more efficient way to generate the list.

Here is the section of code where the slow down occurrs (Between the display of C and D):

DefaultListModel dictCodeModel =  new DefaultListModel();
System.out.println("C");
while (e.hasMoreElements()) {
    String currentEntry = "";
    DEntry dentry = (DEntry) e.nextElement();
    if (!filter)                 
        dictCodeModel.addElement(dentry.theToken); // tokens have embedded html tags

}
System.out.println("D");

As you can see it is pretty simple. When "theToken" is formatted as HTML, I get a real performance hit. Any ideas of what I can do to get around this?

Thanks,

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

庆幸我还是我 2024-09-13 13:17:06

您使用哪种 HTML 格式?如果只是一些文本样式(字体、颜色),您可以使用 JLabel,相应地设置其属性并 将其设置为 JListListCellRenderer代码>.

What kind of HTML formatting are you using? If it's just some text styling (font, color), you can use a JLabel, set its properties accordingly and set it as ListCellRenderer for the JList.

-黛色若梦 2024-09-13 13:17:06

上面的链接有点过时了,所以这里有一些更新的内容。

简单地使用 JTable 可以极大地提高初始加载速度,但当您第一次开始滚动时,速度会有点慢。您遇到了新问题,行高需要调整。这可以通过实现 TableCellRenderer 在自定义渲染器内部轻松完成,因为 getTableCellRendererComponent 方法使您可以访问行、表和组件。然而,这将触发表的更新,该更新将调用相同的代码。如果你编码得当,这不会成为问题。不过,最好的做法是将其放在其他地方。我向 JViewport 添加了一个侦听器,并且仅更新了当前视图中的行。 代码我基于这里

或者,您可以使用编写一个 ListCellRenderer 来返回一个看起来像 HTML 的 JPanel 。如果您只需要 JTextArea ,那么您需要设置其宽度以确保正确设置其首选高度就像这个答案。同样,您必须更新行的宽度,并且基于 JViewport 执行此操作是有意义的。

如果您对这两种方法的性能感到好奇,那么返回 JPanel 的自定义渲染器比 JLabel 渲染 HTML 更快。即使列表有几千个项目,两者都相当快。如前所述,当您最初滚动时,它们可能会有点慢。

最后,这里有一些代码可以让您自己进行快速比较:

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.Timer;
import java.util.concurrent.ExecutionException;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;

public class JTableHtmlTest extends JFrame {

    protected static final long serialVersionUID = 1L;

    public static class Item {
        public int id;
        public String msg;
    }

    static class TableModel extends AbstractTableModel {

        private static final long serialVersionUID = JListTest.serialVersionUID;
        private Item[] items = new Item[] {};

        public int getRowCount() {
            return items.length;
        }

        public int getColumnCount() {
            return 1;
        }

        public Object getValueAt(int rowIndex, int columnIndex) {
            return items[rowIndex];
        }

        @Override
        public String getColumnName(int column) {
            return "";
        }

        public void updateItems() {
            SwingWorker<Item[], Void> worker = new SwingWorker<Item[], Void>() {

                @Override
                protected Item[] doInBackground() throws Exception {
                    final Item[] tempList = new Item[3000];
                    for (int i = 0; i < tempList.length; i++) {
                        Item item = new Item();
                        item.id = (int) (Math.random() * 10000);
                        item.msg = "This is the default message that has to be"
                                + " long enough to wrap around a few times so that"
                                + " we know things are working. It's rather tedious to write.";
                        tempList[i] = item;
                    }
                    return tempList;
                }

                @Override
                protected void done() {
                    try {
                        items = get();
                        fireTableDataChanged();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (ExecutionException e) {
                        e.printStackTrace();
                    }

                }
            };
            worker.execute();
        }

    }

    public static class TableRenderer implements TableCellRenderer {

        private static final String strColor = "#EDF5F4";
        private static final Color strideColor = Color.decode(strColor);

        JLabel htmlLabel = new JLabel();
        JPanel noHtmlPanel = new JPanel();
        JLabel noHtmlLabel = new JLabel();
        JTextArea noHTMLTextArea = new JTextArea();
        Item toRender = null;
        boolean useHtml = false;

        public TableRenderer() {
            noHTMLTextArea.setWrapStyleWord(false);
            noHTMLTextArea.setLineWrap(true);
            noHTMLTextArea.setOpaque(false);

            Font defaultFont = noHtmlLabel.getFont();
            Font boldFont = defaultFont.deriveFont(Font.BOLD);
            noHtmlLabel.setFont(boldFont);
            noHtmlLabel.setOpaque(false);

            noHtmlPanel.setLayout(new BorderLayout());
            noHtmlPanel.add(noHtmlLabel, BorderLayout.NORTH);
            noHtmlPanel.add(noHTMLTextArea, BorderLayout.SOUTH);
        }

        public void setUseHtml(boolean useHtml) {
            this.useHtml = useHtml;
        }

        public Component getJlabelRenderer(JTable table, Item value, int row) {

            String colorString = "";
            if (row % 2 == 0) {
                colorString = "background-color:" + strColor + ";";
            }
            if (toRender != value) {
                toRender = value;
                htmlLabel.setText("<html><div style='padding:2px;" + "width:"
                        + table.getWidth() + ";" + colorString
                        + "color:black;'>"
                        + "<div style='padding:2px;font-weight:500;'>"
                        + "Item " + value.id + "</div>" + value.msg
                        + "</div></html>");
            }

            return htmlLabel;
        }

        public Component getNoHtmlRenderer(JTable table, Item value, int row) {
            if (toRender != value) {
                toRender = value;
                noHtmlLabel.setText("Item " + value.id);
                noHTMLTextArea.setText(value.msg);

                if (row % 2 == 0) {
                    noHtmlPanel.setBackground(strideColor);
                    noHtmlPanel.setOpaque(true);
                } else {
                    noHtmlPanel.setOpaque(false);
                }
            }

            return noHtmlPanel;
        }

        public Component getTableCellRendererComponent(JTable table,
                Object value, boolean isSelected, boolean hasFocus, int row,
                int column) {
            if (useHtml) {
                return getJlabelRenderer(table, (Item) value, row);
            } else {
                return getNoHtmlRenderer(table, (Item) value, row);
            }
        }

    }

    public JTableHtmlTest() {
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel controlPanel = new JPanel();
        JButton updaterControl = new JButton("Update 3000");
        final JCheckBox useHtmlControl = new JCheckBox("Use HTML");
        final TableModel model = new TableModel();
        final JTable table = new JTable(model);
        final TableRenderer renderer = new TableRenderer();
        JScrollPane scrollPane = new JScrollPane(table);
        final JLabel durationIndicator = new JLabel("0");

        controlPanel.add(useHtmlControl, BorderLayout.WEST);
        controlPanel.add(updaterControl, BorderLayout.EAST);

        getContentPane().add(controlPanel, BorderLayout.PAGE_START);
        getContentPane().add(scrollPane, BorderLayout.CENTER);
        getContentPane().add(durationIndicator, BorderLayout.PAGE_END);

        table.setDefaultRenderer(Object.class, renderer);

        // Only update the JTable row heights when they are in view
        final JViewport viewport = scrollPane.getViewport();
        viewport.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                Rectangle viewRect = viewport.getViewRect();
                int first = table.rowAtPoint(new Point(0, viewRect.y));
                if (first == -1) {
                    return;
                }
                int last = table.rowAtPoint(new Point(0, viewRect.y
                        + viewRect.height - 1));
                if (last == -1) {
                    last = model.getRowCount() - 1;
                }

                int column = 0;
                for (int row = first; row <= last; row++) {
                    Component comp = table.prepareRenderer(
                                table.getCellRenderer(row, column),
                                row, column);
                    int rowHeight = comp.getPreferredSize().height;
                    table.setRowHeight(row, rowHeight);
                }
            }
        });

        updaterControl.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                renderer.setUseHtml(useHtmlControl.isSelected());
                model.updateItems();
            }
        });

        Timer counter = new Timer();
        counter.schedule(new TimerTask() {
            @Override
            public void run() {
                String previousCounter = durationIndicator.getText();
                final String newCounter = Integer.toString(Integer
                        .parseInt(previousCounter) + 1);
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        durationIndicator.setText(newCounter);
                        setTitle(newCounter);
                    }
                });
            }
        }, 0, 100);
    }

    public static void main(String args[]) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    JTableHtmlTest jlt = new JTableHtmlTest();
                    jlt.pack();
                    jlt.setSize(300, 300);
                    jlt.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

    }
}

The links above are a bit out of date so here's something more up to date.

Simply using JTable is a huge improvement in speed on initial load but a little slow when you first start scrolling. And you have the new problem that the row height needs adjusting. This can be done inside of a custom renderer easy enough by implementing TableCellRenderer since the getTableCellRendererComponent method gives you access to the row, the table and the component. This will however fire a update of the table which will call the same code. If you code appropriately, this won't be a problem. Still, it's better practice to put it somewhere else. I added a listener to the JViewport and only updated the rows that are currently in view. The code I based this on is here

Alternatively, you can use write a ListCellRenderer that returns a JPanel that looks like the HTML. If you only need a JTextArea then you'll need to set its width to ensure it's preferred height is set correctly like in this answer. Again, you have to update the row's width and it makes sense to do this based on the JViewport.

If you're curious about the performance of both approaches, then a custom renderer returning a JPanel is faster than JLabels rendering HTML. Both are reasonably quick though even with lists with a few thousand items. As mentioned, they can be a little slow when you initially scroll.

Finally, here's some code that lets you make a quick comparison yourself:

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.Timer;
import java.util.concurrent.ExecutionException;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;

public class JTableHtmlTest extends JFrame {

    protected static final long serialVersionUID = 1L;

    public static class Item {
        public int id;
        public String msg;
    }

    static class TableModel extends AbstractTableModel {

        private static final long serialVersionUID = JListTest.serialVersionUID;
        private Item[] items = new Item[] {};

        public int getRowCount() {
            return items.length;
        }

        public int getColumnCount() {
            return 1;
        }

        public Object getValueAt(int rowIndex, int columnIndex) {
            return items[rowIndex];
        }

        @Override
        public String getColumnName(int column) {
            return "";
        }

        public void updateItems() {
            SwingWorker<Item[], Void> worker = new SwingWorker<Item[], Void>() {

                @Override
                protected Item[] doInBackground() throws Exception {
                    final Item[] tempList = new Item[3000];
                    for (int i = 0; i < tempList.length; i++) {
                        Item item = new Item();
                        item.id = (int) (Math.random() * 10000);
                        item.msg = "This is the default message that has to be"
                                + " long enough to wrap around a few times so that"
                                + " we know things are working. It's rather tedious to write.";
                        tempList[i] = item;
                    }
                    return tempList;
                }

                @Override
                protected void done() {
                    try {
                        items = get();
                        fireTableDataChanged();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (ExecutionException e) {
                        e.printStackTrace();
                    }

                }
            };
            worker.execute();
        }

    }

    public static class TableRenderer implements TableCellRenderer {

        private static final String strColor = "#EDF5F4";
        private static final Color strideColor = Color.decode(strColor);

        JLabel htmlLabel = new JLabel();
        JPanel noHtmlPanel = new JPanel();
        JLabel noHtmlLabel = new JLabel();
        JTextArea noHTMLTextArea = new JTextArea();
        Item toRender = null;
        boolean useHtml = false;

        public TableRenderer() {
            noHTMLTextArea.setWrapStyleWord(false);
            noHTMLTextArea.setLineWrap(true);
            noHTMLTextArea.setOpaque(false);

            Font defaultFont = noHtmlLabel.getFont();
            Font boldFont = defaultFont.deriveFont(Font.BOLD);
            noHtmlLabel.setFont(boldFont);
            noHtmlLabel.setOpaque(false);

            noHtmlPanel.setLayout(new BorderLayout());
            noHtmlPanel.add(noHtmlLabel, BorderLayout.NORTH);
            noHtmlPanel.add(noHTMLTextArea, BorderLayout.SOUTH);
        }

        public void setUseHtml(boolean useHtml) {
            this.useHtml = useHtml;
        }

        public Component getJlabelRenderer(JTable table, Item value, int row) {

            String colorString = "";
            if (row % 2 == 0) {
                colorString = "background-color:" + strColor + ";";
            }
            if (toRender != value) {
                toRender = value;
                htmlLabel.setText("<html><div style='padding:2px;" + "width:"
                        + table.getWidth() + ";" + colorString
                        + "color:black;'>"
                        + "<div style='padding:2px;font-weight:500;'>"
                        + "Item " + value.id + "</div>" + value.msg
                        + "</div></html>");
            }

            return htmlLabel;
        }

        public Component getNoHtmlRenderer(JTable table, Item value, int row) {
            if (toRender != value) {
                toRender = value;
                noHtmlLabel.setText("Item " + value.id);
                noHTMLTextArea.setText(value.msg);

                if (row % 2 == 0) {
                    noHtmlPanel.setBackground(strideColor);
                    noHtmlPanel.setOpaque(true);
                } else {
                    noHtmlPanel.setOpaque(false);
                }
            }

            return noHtmlPanel;
        }

        public Component getTableCellRendererComponent(JTable table,
                Object value, boolean isSelected, boolean hasFocus, int row,
                int column) {
            if (useHtml) {
                return getJlabelRenderer(table, (Item) value, row);
            } else {
                return getNoHtmlRenderer(table, (Item) value, row);
            }
        }

    }

    public JTableHtmlTest() {
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel controlPanel = new JPanel();
        JButton updaterControl = new JButton("Update 3000");
        final JCheckBox useHtmlControl = new JCheckBox("Use HTML");
        final TableModel model = new TableModel();
        final JTable table = new JTable(model);
        final TableRenderer renderer = new TableRenderer();
        JScrollPane scrollPane = new JScrollPane(table);
        final JLabel durationIndicator = new JLabel("0");

        controlPanel.add(useHtmlControl, BorderLayout.WEST);
        controlPanel.add(updaterControl, BorderLayout.EAST);

        getContentPane().add(controlPanel, BorderLayout.PAGE_START);
        getContentPane().add(scrollPane, BorderLayout.CENTER);
        getContentPane().add(durationIndicator, BorderLayout.PAGE_END);

        table.setDefaultRenderer(Object.class, renderer);

        // Only update the JTable row heights when they are in view
        final JViewport viewport = scrollPane.getViewport();
        viewport.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                Rectangle viewRect = viewport.getViewRect();
                int first = table.rowAtPoint(new Point(0, viewRect.y));
                if (first == -1) {
                    return;
                }
                int last = table.rowAtPoint(new Point(0, viewRect.y
                        + viewRect.height - 1));
                if (last == -1) {
                    last = model.getRowCount() - 1;
                }

                int column = 0;
                for (int row = first; row <= last; row++) {
                    Component comp = table.prepareRenderer(
                                table.getCellRenderer(row, column),
                                row, column);
                    int rowHeight = comp.getPreferredSize().height;
                    table.setRowHeight(row, rowHeight);
                }
            }
        });

        updaterControl.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                renderer.setUseHtml(useHtmlControl.isSelected());
                model.updateItems();
            }
        });

        Timer counter = new Timer();
        counter.schedule(new TimerTask() {
            @Override
            public void run() {
                String previousCounter = durationIndicator.getText();
                final String newCounter = Integer.toString(Integer
                        .parseInt(previousCounter) + 1);
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        durationIndicator.setText(newCounter);
                        setTitle(newCounter);
                    }
                });
            }
        }, 0, 100);
    }

    public static void main(String args[]) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    JTableHtmlTest jlt = new JTableHtmlTest();
                    jlt.pack();
                    jlt.setSize(300, 300);
                    jlt.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文