0

It seems like the only way to display text with multiple styles in a text area is to use a JEditorPane. To explore this, I wrote a small app to list all the fonts in their font.

Everything works, and the list is displayed in a JEditorPane, which is inside a JScrollPane.

However, it takes a few seconds to launch and to repopulate, because it generates the entire new list before displaying it. The code is below, following MCV. Minimalizing it to just the message and text made less work so the lag is no longer nearly as noticeable, but my question (below) was more about rendering and keeping track of JScrollPane's position anyway than about this app.

FontFrame.java

public class FontFrame extends JFrame {
    private FontFrame() {
        JFrame frame = new JFrame("Fonts Displayer");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        FontPanel fontPanel = new FontPanel();
        frame.getContentPane().add(fontPanel);
        frame.getRootPane().setDefaultButton(fontPanel.getDefaultButton());

        frame.setMinimumSize(new Dimension(600, 600));
        frame.setPreferredSize(new Dimension(1000, 700));
        frame.pack();
        frame.setVisible(true);
    }


    public static FontFrame launchFontFrame() {
        return new FontFrame();
    }

    public static void main(String[] args) {
        FontFrame.launchFontFrame();
    }
}

FontPanel.java

class FontPanel extends JPanel {
    private String message = "ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz";

    private JEditorPane fontPane;
    private JTextField messageInput;
    private JButton changeButton;

    protected FontPanel() {
        buildPanel();
    }

    private void buildPanel() {
        setLayout(new BorderLayout());

        // Build message input panel
        JPanel inputPanel = new JPanel();
        messageInput = new JTextField(message);
        messageInput.setFont(new Font("SansSerif", Font.PLAIN, 14));
        messageInput.setColumns(40);

        JLabel messageLabel = new JLabel("Message:");

        changeButton = new JButton("Change");
        changeButton.addActionListener((ActionEvent e) -> {
            String text = messageInput.getText();
            if (!text.isEmpty() && !text.equals(message)) {
                message = text;
                refreshFontList();
            }
        });

        inputPanel.add(messageLabel);
        inputPanel.add(messageInput);
        inputPanel.add(changeButton);


        // Build scrolling text pane for fonts display
        fontPane = new JEditorPane();
        fontPane.setContentType("text/html");
        fontPane.setEditable(false);
        fontPane.setVisible(true);
        JScrollPane scrollPane = new JScrollPane(fontPane);
        refreshFontList();

        // Add components to main panel
        add(inputPanel, BorderLayout.NORTH);
        add(scrollPane, BorderLayout.CENTER);
    }


    private void refreshFontList() {
        String[] list = GraphicsEnvironment.getLocalGraphicsEnvironment()
                                                .getAvailableFontFamilyNames();
        StringBuilder messages = new StringBuilder();

        // Set global table style settings
        messages.append("<table>");

        // Make a row for each font
        for (String font : list) {
            messages.append("<tr>");

            //Append data cells
            messages.append("<td>").append(font).append("</td>")
                    .append("<td style=\"font-family:").append(font).append("\">")
                        .append(message)
                    .append("</td>");

            messages.append("</tr>");
        }

        messages.append("</table>");

        fontPane.setText(messages.toString());
    }

    JButton getDefaultButton() {
        return changeButton;
    }
}

Is there any way of redoing refreshFontList so it can generate only what fontPane would show in its viewport first, possibly by using JScrollBar, and then generate the rest of the list right after so the computation time doesn't cause lag in display? Is there a different component that would work better?

BrainFRZ
  • 437
  • 3
  • 15
  • 1
    *"so it can generate only what fontPane would show in its viewport first"* That effect can be created automatically by popping the font list into a `JList` or `JComboBox` and using an appropriate renderer. Here is an [example using a `JComboBox`](http://stackoverflow.com/a/6965149/418556). Here is an [example using a `JList`](http://stackoverflow.com/questions/25683630/how-to-determine-if-2-fonts-have-equivalent-glyphs/25683631#25683631). – Andrew Thompson Apr 28 '17 at 16:13
  • Thanks for the quick response! Just to be sure I'm understanding though, it sounds like I'd want to build my rows into a JList, and then learn how to use a renderer to make it show the needed cells to the JEditorPane? – BrainFRZ Apr 28 '17 at 16:20
  • *"..show the needed cells to the JEditorPane?"* Nope. I'm saying **forget the editor pane** and display the font list to the user in a Swing combo or list! As they scroll down either, they can see the fonts rendered as they might appear in an editor pane. If the `JEditorPane` comes into it at all, I would suggest displaying a string in it like *"the quick brown fox jumps over the lazy dog" in both lower and upper case, as well as the digits 0-9 and a selection of other symbols, and change the font depending on what font is currently selected. But that can be done in a `JTextArea` anyway. – Andrew Thompson Apr 28 '17 at 16:26
  • While this does answer the immediate question for this specific app, my question was meant to be broader in how to generate first what I need only for the section of the JScrollPane into a JEditorPane. There are tons of instances where using a JComboBox wouldn't make any sense, but I'd still want to be able to optimize, such as allowing someone to change the view styles of different message types in a text game, or something. That'd need a JEditorPane to have the text area look/feel, wouldn't it? Or is there a different component that would work better? – BrainFRZ Apr 28 '17 at 16:33
  • Should I edit the question to be more explicitly general? – BrainFRZ Apr 28 '17 at 16:34
  • *"Should I edit the question to be more explicitly general?"* Yes. – Andrew Thompson Apr 28 '17 at 16:36
  • To display only visible content, you’ll need to paint it yourself. Instead of a JEditorPane or JList or JComboBox, create a subclass of JPanel, and override its `paintComponent` and `getPreferredSize` methods. See https://docs.oracle.com/javase/tutorial/uiswing/painting/ . – VGR Apr 28 '17 at 17:40
  • Sorry for the delay, but I've edited the question to be more general. Although it sounds like @VGR came up with the only possibility in the mean time? If that's the only answer, could you make it an actual answer so I can vote on it and accept it? – BrainFRZ Apr 28 '17 at 17:45
  • @AndrewThompson's examples leverage the flyweight pattern, also available in `JTable`, to render only _visible_ cells. – trashgod Apr 29 '17 at 06:30
  • @trashgod *"leverage the flyweight pattern"* I knew there was some design pattern it used, but couldn't think of the name for it! Ta. – Andrew Thompson Apr 29 '17 at 06:37
  • 1
    More on the _flyweight pattern_ [here](http://stackoverflow.com/a/7776211/230513). – trashgod Apr 29 '17 at 06:46

0 Answers0