开发者

Displaying Large JLIST formatted as HTML

开发者 https://www.devze.com 2023-01-04 12:57 出处:网络
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.Genera

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(den开发者_StackOverflow中文版try.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,


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.


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();
                }
            }
        });

    }
}
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号