Обновить язык элементов Swing во время выполнения - PullRequest
0 голосов
/ 04 октября 2019

Приложение 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));
}

An ActionListenerна JComboBox вызывает это, что мгновенно меняет язык всех элементов GUI:

private void changeLanguage() {
    Locale locale = comboBox.getItemAt(comboBox.getSelectedIndex());
    resourceBundle = ResourceBundle.getBundle("Bundle", locale);
    myLabel.setText(resourceBundle.getString("myLabelText"));
}

В моем приложении намного больше ярлыков и кнопок, язык которых установлен через changeLanguage (не всехотя из них используется локализация, например, кнопка «дом» со значком дома), и я намерен добавить еще. С увеличением количества элементов графического интерфейса, также становится проще просто забыть добавить один в функцию, поэтому мой вопрос:

Есть ли способ "зарегистрировать" JLabel, ... и его ключ к некоторому классу (непосредственно после его создания) и последующего изменения языка (путем загрузки другого Locale) также автоматически изменяет текст JLabel, ...? Есть ли обычно используемый подход, который отличается от того, как я это делаю?

Ответы [ 2 ]

0 голосов
/ 17 октября 2019

Чем я закончил:

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, ..., которым нужно изменить язык, без необходимости вручную перечислять всеих в методе (и, возможно, забыв один).

0 голосов
/ 04 октября 2019

Я недавно столкнулся с этой проблемой, поэтому я поделюсь тем, что я пробовал и что у меня сработало. Обратите внимание, что мне нужно было также реализовать действие изменения шрифта во время выполнения.

В приложении Swing мы обычно расширяем классы контейнеров для основных контейнеров. Допустим, мы хотим создать Facebook для рабочего стола. Кто-то может создать 3 основных класса (расширение JPanel или JScrollPane). Допустим, LeftPanel extends JPanel, MiddlePanel extends JPanel и RightPanel extends JPanel. Левая панель будет отображать левое меню, средняя панель будет представлять основной вид прокрутки и, наконец, правая панель будет представлять область рекламы. Конечно, каждая из этих панелей будет наследовать JPanel с (и, возможно, у некоторых из них также будет новый класс, например PostPanel, CommentSectionPanel и т. Д.)

Теперь, если выпрочитали Использование нескольких JFrames: хорошая или плохая практика? , ваше приложение использует только один 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.

Теперь мы должны отделить, какие из них можно «обновить» (изменить язык), а какие нет. Давайте создадим Interface для этого:

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

Теперь вместо того, чтобы дать эту возможность каждому из них, мы даем ее большим контейнерам (пример с Facebook): LeftPanel extends JPanel implements LocaleChangeable, RightPanel extends JPanel implements LocaleChangeable потому что у них есть компоненты с текстовым свойством.

Эти классы теперь отвечают за изменение текста своих компонентов. Псевдо-пример будет:

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