Displaying Large JLIST formatted as HTML

前端 未结 2 973
花落未央
花落未央 2021-01-23 08:02

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 imp

相关标签:
2条回答
  • 2021-01-23 08:46

    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 讨论(0)
  • 2021-01-23 08:52

    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.

    0 讨论(0)
提交回复
热议问题