I faced this problem recently, so I will share what I tried and what it worked for me. Note that I needed to implement a change-font action at runtime as well.
In a Swing application, we usually expand container classes for the core containers. Let's say we want to create Facebook for desktop. Someone could create 3 core classes (extending JPanel or JScrollPane). Let's say: LeftPanel extends JPanel
, MiddlePanel extends JPanel
and RightPanel extends JPanel
. Left panel will represent the left menu, middle panel will represent the main scrolling view and finally the right panel will represent the ads area. Of course, every single of this panels will have inherited JPanel
s (and probably some of them will have a new class as well, e.g PostPanel
, CommentSectionPanel
etc.)
Now, assuming you have read The Use of Multiple JFrames: Good or Bad Practice?, your application uses only one JFrame
and it hosts every single component in it. Even modal JDialog
s are based on it (have it as its parent). So, you can keep it somewhere like a Singleton
.
In order to change components text, we will have to call setText
for each one of them. Calling JFrame
's getComponents
will give us all its components. If one of them is container, we will have to call getComponents
for it, since it probably has components in it as well. The solution is a recursion to find all of them:
private static <T extends Component> List<T> getChildren(Class<T> clazz, final Container container) {
Component[] components;
if (container instanceof JMenu)
components = ((JMenu) container).getMenuComponents();
else
components = container.getComponents();
List<T> compList = new ArrayList<T>();
for (Component comp : components) {
if (clazz.isAssignableFrom(comp.getClass())) {
compList.add(clazz.cast(comp));
}
if (comp instanceof Container)
compList.addAll(getChildren(clazz, (Container) comp));
}
return compList;
}
Calling this method with arguments java.awt.Component.class
and myJFrame
will give you all the components your JFrame
has.
Now, we have to separate which of them can be "refreshed" (change language) and which of them can't. Let's create an Interface
for that:
public static interface LocaleChangeable {
void localeChanged(Locale newLocale);
}
Now instead of giving this ability to each one of them, we give it to the big containers (the example with facebook):
LeftPanel extends JPanel implements LocaleChangeable
, RightPanel extends JPanel implements LocaleChangeable
because they have components with text property.
These classes now are responsible for changing the text of their components. A pseduo-example would be:
public class LeftPanel extends JPanel implements LocaleChangeable {
private JLabel exitLabel;
@Override
public void localeChanged(Locale newLocale) {
if (newLocale == Locale.ENGLISH) {
exitLabel.setText("Exit");
} else if (newLocale == Locale.GREEK) {
exitLabel.setText("Έξοδος"); //Greek exit
}
}
}
(Of course instead of a bunch of if-else
conditions, the ResourceBundle
logic will take place).
So... Let's call this method for all classes-containers:
private void broadcastLocaleChange(Locale locale) {
List<Component> components = getChildren(Component.class, myFrame);
components.stream().filter(LocaleChangeable.class::isInstance).map(LocaleChangeable.class::cast)
.forEach(lc -> lc.localeChanged(locale));
}
That's it! A full example would be:
public class LocaleTest extends JFrame {
private static final long serialVersionUID = 1L;
public LocaleTest() {
super("test");
setDefaultCloseOperation(EXIT_ON_CLOSE);
getContentPane().setLayout(new BorderLayout());
add(new MainPanel());
pack();
setLocationRelativeTo(null);
setVisible(true);
}
private class MainPanel extends JPanel implements LocaleChangeable {
private JLabel label;
private JButton changeLocaleButton;
public MainPanel() {
super(new FlowLayout());
label = new JLabel(Locale.ENGLISH.toString());
add(label);
changeLocaleButton = new JButton("Change Locale");
changeLocaleButton.addActionListener(e -> {
broadcastLocaleChange(Locale.CANADA);
});
add(changeLocaleButton);
}
@Override
public void localeChanged(Locale newLocale) {
label.setText(newLocale.toString());
System.out.println("Language changed.");
}
private void broadcastLocaleChange(Locale locale) {
List<Component> components = getChildren(Component.class, LocaleTest.this);
components.stream().filter(LocaleChangeable.class::isInstance).map(LocaleChangeable.class::cast)
.forEach(lc -> lc.localeChanged(locale));
}
}
private static <T extends Component> List<T> getChildren(Class<T> clazz, final Container container) {
Component[] components;
if (container instanceof JMenu)
components = ((JMenu) container).getMenuComponents();
else
components = container.getComponents();
List<T> compList = new ArrayList<T>();
for (Component comp : components) {
if (clazz.isAssignableFrom(comp.getClass())) {
compList.add(clazz.cast(comp));
}
if (comp instanceof Container)
compList.addAll(getChildren(clazz, (Container) comp));
}
return compList;
}
public static interface LocaleChangeable {
void localeChanged(Locale newLocale);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new LocaleTest().setVisible(true));
}
}