I have a JMenuBar
for my Swing app which helps the user carry out some common tasks. One of the JMenu
s in the menu bar is a "Font" menu, which loads all of the system's available fonts and lets the user change the font on a JTextArea
. For each font, a separate JMenuItem
is added to the JMenu
whose font is respective to its name. I load the font in a SwingWorker
's doInBackground
method and then publish
each item.
The class might help you understand: Edit: here's the minimum reproducible example:
public class MRE extends JFrame {
public MRE() {
setLayout(new BorderLayout());
setSize(500, 500);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JMenuBar menu = new JMenuBar();
JMenu view = new JMenu("View");
JMenu font = new JMenu("Font");
JTextArea area = new JTextArea();
final String[] fonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
new SwingWorker<Void, JMenuItem>() {
@Override
protected Void doInBackground() {
JMenuItem item;
for (String font : fonts) {
item = new JMenuItem(font);
Font f = new Font(font, Font.PLAIN, 14);
item.setFont(f);
item.addActionListener(new Listener(f, area));
publish(item);
}
return null;
}
@Override
protected void process(List<JMenuItem> chunks) {
for (JMenuItem item : chunks) {
font.add(item);
}
}
}.execute();
view.add(font);
menu.add(view);
add(menu, BorderLayout.NORTH);
add(area, BorderLayout.CENTER);
}
private record Listener(Font f, JTextArea area) implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
area.setFont(f);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new MRE().setVisible(true));
}
}
The problem is that the UI blocks for a second or two when the user hovers over the menu whilst it's loading the font.
Edit: here's what it looks like on my Windows laptop. It is slow to load the first time, but all times after that it loads almost instantly:
I suspect the problem is due to this reason:
"multiple invocations to the publish method might occur before the process method is executed. For performance purposes all these invocations are coalesced into one invocation with concatenated arguments."
-- this is part of the Javadoc for the publish()
method inside the SwingWorker
class; it states that, because there are many publish()
calls in a short amount of time, they are chained together, thus the UI blocks because it's loading everything at once.
I've even tried loading each JMenuItem
with its respective font into a List
inside a static block, and then inside the constructor, I loop over the list and add the item, but this also blocks.
For both versions, removing the item.setFont(...)
makes the menu open almost instantly, like it should (hence why I abstracted the action listener to a comment because it does not noticeably affect performance).
Edit:
I added the action listener logic. As per @Hovercraft Full Of Eels
's comment, a List<Font>
could've worked, but users can also change the font style and size, so I cannot preload the fonts because these elements will be reset (unfortunately, Font
does not have setters for the style and size). LiveAppStore
is a class with some synchronized live data which is shared across a few files. MySyntaxTextArea
is a subclass of JTextArea
with a singleton implementation.
If it makes a difference, I'd like to note that I am writing and testing this program on my Windows laptop. On my Macbook Pro with the M1 Pro chip, the menu does load almost instantly but, of course, I cannot assume everyone has a very fast and efficient laptop. Some of my peers on Windows -- even with very high-spec computers -- notice the slight lag.
I want to ask if there's a way to overcome this, thanks in advance.