【问题标题】:Refresh language of Swing elements at runtime在运行时刷新 Swing 元素的语言
【发布时间】:2020-02-02 15:46:39
【问题描述】:

当用户从 JComboBox 中选择一个时,我的 Java 8/Swing 应用程序使用 ResourceBundle 和几个 .properties 文件来更改语言:

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 设置语言(但并非所有标签和按钮都使用本地化,例如带有房屋图标的“主页”按钮),我打算添加更多。随着 GUI 项目数量的增加,忘记在函数中添加一项也变得更容易,这就是为什么我的问题是:

有没有办法“注册”JLabel,... 以及它对某个类的键(直接在创建它之后)并在之后更改语言(通过加载另一个 Locale)也会自动更改文本JLabel,...?是否有一种常用的方法与我的做法不同?

【问题讨论】:

  • 您可能希望递归检索给定类型的组件,请参阅:stackoverflow.com/questions/694287/…
  • @Arnaud 感谢您的建议,不幸的是它不适用于我的情况。虽然列表有助于跟踪组件,但如果几个标签、按钮……不使用本地化(例如带有房屋图标的“主页”按钮),则将所有组件添加到列表中不会。此外,我的应用程序如何知道用于特定 UI 元素的键?这将需要第二个列表(1 个用于组件,1 个用于键),这将很快与许多元素混淆,或者需要一个跟踪两者的自定义对象,这将是矫枉过正 (imo)。

标签: java swing internationalization properties-file resourcebundle


【解决方案1】:

我最终得到了什么:

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()));
        }
    }
}

我知道这会为每个组件创建一个额外的对象。 :/ 不幸的是,这是我发现的唯一一种方法,可以让我只更改 JLabels、JButtons、... 的语言,这些语言需要更改,而实际上不必手动列出所有它们在一个方法中(并且可能忘记一个)。

【讨论】:

    【解决方案2】:

    我最近遇到了这个问题,所以我将分享我尝试过的方法以及对我有用的方法。请注意,我还需要在运行时实现更改字体操作。

    在 Swing 应用程序中,我们通常为核心容器扩展容器类。假设我们要为桌面创建 Facebook。有人可以创建 3 个核心类(扩展 JPanel 或 JScrollPane)。假设:LeftPanel extends JPanelMiddlePanel extends JPanelRightPanel extends JPanel。左侧面板代表左侧菜单,中间面板代表主滚动视图,最后右侧面板代表广告区域。当然,这些面板中的每一个都将继承JPanels(可能其中一些也会有一个新类,例如PostPanelCommentSectionPanel 等)

    现在,假设您已阅读 The Use of Multiple JFrames: Good or Bad Practice?,您的应用程序仅使用一个 JFrame,并且它托管其中的每个组件。甚至模态的JDialogs 也基于它(将其作为其父级)。因此,您可以将其保存在 Singleton 之类的地方。

    为了更改组件文本,我们必须为每个组件调用setText。调用JFramegetComponents 将为我们提供它的所有组件。如果其中之一是容器,我们将不得不为它调用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.classmyJFrame 调用此方法将为您提供JFrame 拥有的所有组件。

    现在,我们必须区分哪些可以“刷新”(更改语言),哪些不能。让我们为此创建一个Interface

    public static interface LocaleChangeable {
        void localeChanged(Locale newLocale);
    }
    

    现在,我们不再将这种能力赋予每个人,而是赋予大容器(facebook 的例子): LeftPanel extends JPanel implements LocaleChangeable, RightPanel extends JPanel implements LocaleChangeable 因为它们有带有 text 属性的组件。

    这些类现在负责更改其组件的文本。一个伪示例是:

    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逻辑)。

    所以...让我们为所有的类容器调用这个方法:

    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));
        }
    }
    

    【讨论】:

    • 感谢您提供的长示例!在任何时候,我的应用程序中只有一个 JFrame 处于活动状态,尽管它目前使用两个,第一个是登录屏幕,被普通应用程序窗口取代。它们都使用本地化和相同的ResourceBundle,但并非所有 UI 元素都具有应更改的文本。我明白你在做什么:按下按钮触发ActionListener,调用broadcastLocaleChange 并获取所有组件。但是不使用本地化文本的组件会发生什么?localeChanged 如何为每个组件找到正确的键?
    • @Neph 我不确定我是否明白你在问什么。由于您在localeChanged 方法中手动执行此操作,因此您将为每个组件指定其密钥,并且您不会为不使用本地化文本的组件而烦恼。
    • 如果我理解正确,这是否意味着我必须为localeChanged() 中的每个标签/按钮调用myLabel.setText(resourceBundle.getString("myLabelText"))?如果只是一长串组件的文本已更改,那不是我在问题中发布的代码的更长版本吗?还是.forEach(lc -&gt; lc.localeChanged(locale)) 为每个组件调用一次localeChanged()?如果是这样,我怎么知道当前使用哪个组件来调用它(以便我可以使用右键)?
    • 您能否解释一下在您的代码中如何调用实际的语言更改。
    • @Neph 当按下更改语言按钮时,它会调用所有面板的localeChanged。现在每个面板都负责将文本更改为它拥有的每个组件。
    猜你喜欢
    • 2012-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-08
    • 1970-01-01
    • 2016-05-12
    • 1970-01-01
    相关资源
    最近更新 更多