在运行时刷新 Swing 元素的语言
Refresh language of Swing elements at runtime
我的 Java 8/Swing 应用程序使用 ResourceBundle
和几个 .properties
文件在用户从 JComboBox
中选择一个文件时更改语言:
public static ResourceBundle resourceBundle;
private JComboBox<Locale> comboBox;
private JLabel myLabel;
public Main() {
//More GUI setup here
resourceBundle = ResourceBundle.getBundle("Bundle", Locale.ENGLISH); //Set first/default language
comboBox = new JComboBox<Locale>();
comboBox.addItem(Locale.ENGLISH);
comboBox.addItem(Locale.GERMAN);
comboBox.addItem(Locale.FRENCH);
myLabel = new JLabel(resourceBundle.getString("myLabelText"));
myLabel.setFont(new Font("Tahoma", Font.PLAIN, 14));
}
JComboBox
上的 ActionListener
调用此方法,它会立即更改所有 GUI 元素的语言:
private void changeLanguage() {
Locale locale = comboBox.getItemAt(comboBox.getSelectedIndex());
resourceBundle = ResourceBundle.getBundle("Bundle", locale);
myLabel.setText(resourceBundle.getString("myLabelText"));
}
我的应用程序中有更多标签和按钮通过 changeLanguage
设置了语言(但并非所有标签和按钮都使用本地化,例如带有房屋图标的 "home" 按钮)和我打算添加更多。随着 GUI 项目数量的增加,忘记在函数中添加一个也变得更容易,这就是为什么我的问题是:
有没有办法 "register" a JLabel
,... 和一些 class 的关键(直接在创建它之后)然后更改语言(通过加载其他 Locale
) 也会自动更改 JLabel
的文本,...?是否有一种常用的方法与我的做法不同?
我最近遇到了这个问题,所以我将分享我尝试过的方法以及对我有用的方法。请注意,我还需要在运行时实施更改字体操作。
在Swing应用中,我们通常会为核心容器扩展容器classes。假设我们要为桌面创建 Facebook。有人可以创建 3 个核心 classes(扩展 JPanel 或 JScrollPane)。比方说:LeftPanel extends JPanel
、MiddlePanel extends JPanel
和 RightPanel extends JPanel
。左侧面板代表左侧菜单,中间面板代表主滚动视图,最后右侧面板代表广告区域。当然,这个面板中的每一个都将继承 JPanel
(并且可能其中一些也会有一个新的 class,例如 PostPanel
、CommentSectionPanel
等)
现在,假设您已阅读 The Use of Multiple JFrames: Good or Bad Practice?,您的应用程序仅使用一个 JFrame
并且它托管其中的每个组件。甚至模态 JDialog
s 也基于它(将其作为其父级)。所以,你可以把它放在某个地方,比如 Singleton
.
为了更改组件文本,我们必须为每个组件调用 setText
。调用 JFrame
的 getComponents
将为我们提供其所有组件。如果其中之一是容器,我们将不得不为它调用 getComponents
,因为它可能也包含组件。解决方案是通过递归找到所有这些:
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;
}
使用参数 java.awt.Component.class
和 myJFrame
调用此方法将为您提供 JFrame
拥有的所有组件。
现在,我们必须区分哪些可以 "refreshed"(更改语言),哪些不能。让我们为此创建一个 Interface
:
public static interface LocaleChangeable {
void localeChanged(Locale newLocale);
}
现在,我们不再将这种能力赋予每个容器,而是将其赋予大容器(facebook 的示例):
LeftPanel extends JPanel implements LocaleChangeable
、RightPanel extends JPanel implements LocaleChangeable
因为它们的组件带有文本 属性.
这些 classes 现在负责更改其组件的文本。伪示例是:
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
}
}
}
(当然不是一堆if-else
条件,ResourceBundle
逻辑会发生)。
所以...让我们为所有 classes 容器调用此方法:
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));
}
就是这样!一个完整的例子是:
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));
}
}
我得到的结果:
class ComponentInfo {
JComponent component;
String key;
public ComponentInfo(JComponent c,String k) {
component = c;
key = k;
}
public JComponent getComponent() {return component;}
public String getKey() {return key;}
}
要使用它,请创建一个 ArrayList
并在创建后直接添加应更改其语言的每个组件:
List<ComponentInfo> allComponentInfos = new ArrayList<ComponentInfo>();
JLabel label_pw = new JLabel(resourceBundle.getString("pw_key"));
label_pw.setFont(new Font("Tahoma", Font.PLAIN, 14));
allComponentInfos.add(new ComponentInfo(label_pw, "pw_key"));
通过遍历列表更改语言:
private void changeLanguage() {
Locale locale = comboBox_language.getItemAt(comboBox_language.getSelectedIndex());
resourceBundle = ResourceBundle.getBundle("Bundle", locale);
for(ComponentInfo c:allComponentInfos) {
if(c.getComponent() instanceof JLabel) {
((JLabel) c.getComponent()).setText(resourceBundle.getString(c.getKey()));
} else if(c.getComponent() instanceof JButton) {
((JButton) c.getComponent()).setText(resourceBundle.getString(c.getKey()));
}
}
}
我知道这会为每个组件创建一个额外的对象。 :/ 不幸的是,这是我发现的唯一一种方法,可以让我仅更改需要更改语言的 JLabel
s、JButton
s... 的语言,而无需实际更改在一个方法中手动列出所有这些(并且可能忘记一个)。
我的 Java 8/Swing 应用程序使用 ResourceBundle
和几个 .properties
文件在用户从 JComboBox
中选择一个文件时更改语言:
public static ResourceBundle resourceBundle;
private JComboBox<Locale> comboBox;
private JLabel myLabel;
public Main() {
//More GUI setup here
resourceBundle = ResourceBundle.getBundle("Bundle", Locale.ENGLISH); //Set first/default language
comboBox = new JComboBox<Locale>();
comboBox.addItem(Locale.ENGLISH);
comboBox.addItem(Locale.GERMAN);
comboBox.addItem(Locale.FRENCH);
myLabel = new JLabel(resourceBundle.getString("myLabelText"));
myLabel.setFont(new Font("Tahoma", Font.PLAIN, 14));
}
JComboBox
上的 ActionListener
调用此方法,它会立即更改所有 GUI 元素的语言:
private void changeLanguage() {
Locale locale = comboBox.getItemAt(comboBox.getSelectedIndex());
resourceBundle = ResourceBundle.getBundle("Bundle", locale);
myLabel.setText(resourceBundle.getString("myLabelText"));
}
我的应用程序中有更多标签和按钮通过 changeLanguage
设置了语言(但并非所有标签和按钮都使用本地化,例如带有房屋图标的 "home" 按钮)和我打算添加更多。随着 GUI 项目数量的增加,忘记在函数中添加一个也变得更容易,这就是为什么我的问题是:
有没有办法 "register" a JLabel
,... 和一些 class 的关键(直接在创建它之后)然后更改语言(通过加载其他 Locale
) 也会自动更改 JLabel
的文本,...?是否有一种常用的方法与我的做法不同?
我最近遇到了这个问题,所以我将分享我尝试过的方法以及对我有用的方法。请注意,我还需要在运行时实施更改字体操作。
在Swing应用中,我们通常会为核心容器扩展容器classes。假设我们要为桌面创建 Facebook。有人可以创建 3 个核心 classes(扩展 JPanel 或 JScrollPane)。比方说:LeftPanel extends JPanel
、MiddlePanel extends JPanel
和 RightPanel extends JPanel
。左侧面板代表左侧菜单,中间面板代表主滚动视图,最后右侧面板代表广告区域。当然,这个面板中的每一个都将继承 JPanel
(并且可能其中一些也会有一个新的 class,例如 PostPanel
、CommentSectionPanel
等)
现在,假设您已阅读 The Use of Multiple JFrames: Good or Bad Practice?,您的应用程序仅使用一个 JFrame
并且它托管其中的每个组件。甚至模态 JDialog
s 也基于它(将其作为其父级)。所以,你可以把它放在某个地方,比如 Singleton
.
为了更改组件文本,我们必须为每个组件调用 setText
。调用 JFrame
的 getComponents
将为我们提供其所有组件。如果其中之一是容器,我们将不得不为它调用 getComponents
,因为它可能也包含组件。解决方案是通过递归找到所有这些:
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;
}
使用参数 java.awt.Component.class
和 myJFrame
调用此方法将为您提供 JFrame
拥有的所有组件。
现在,我们必须区分哪些可以 "refreshed"(更改语言),哪些不能。让我们为此创建一个 Interface
:
public static interface LocaleChangeable {
void localeChanged(Locale newLocale);
}
现在,我们不再将这种能力赋予每个容器,而是将其赋予大容器(facebook 的示例):
LeftPanel extends JPanel implements LocaleChangeable
、RightPanel extends JPanel implements LocaleChangeable
因为它们的组件带有文本 属性.
这些 classes 现在负责更改其组件的文本。伪示例是:
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
}
}
}
(当然不是一堆if-else
条件,ResourceBundle
逻辑会发生)。
所以...让我们为所有 classes 容器调用此方法:
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));
}
就是这样!一个完整的例子是:
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));
}
}
我得到的结果:
class ComponentInfo {
JComponent component;
String key;
public ComponentInfo(JComponent c,String k) {
component = c;
key = k;
}
public JComponent getComponent() {return component;}
public String getKey() {return key;}
}
要使用它,请创建一个 ArrayList
并在创建后直接添加应更改其语言的每个组件:
List<ComponentInfo> allComponentInfos = new ArrayList<ComponentInfo>();
JLabel label_pw = new JLabel(resourceBundle.getString("pw_key"));
label_pw.setFont(new Font("Tahoma", Font.PLAIN, 14));
allComponentInfos.add(new ComponentInfo(label_pw, "pw_key"));
通过遍历列表更改语言:
private void changeLanguage() {
Locale locale = comboBox_language.getItemAt(comboBox_language.getSelectedIndex());
resourceBundle = ResourceBundle.getBundle("Bundle", locale);
for(ComponentInfo c:allComponentInfos) {
if(c.getComponent() instanceof JLabel) {
((JLabel) c.getComponent()).setText(resourceBundle.getString(c.getKey()));
} else if(c.getComponent() instanceof JButton) {
((JButton) c.getComponent()).setText(resourceBundle.getString(c.getKey()));
}
}
}
我知道这会为每个组件创建一个额外的对象。 :/ 不幸的是,这是我发现的唯一一种方法,可以让我仅更改需要更改语言的 JLabel
s、JButton
s... 的语言,而无需实际更改在一个方法中手动列出所有这些(并且可能忘记一个)。