Сохранение раскладки клавиатуры в приложении Swing? - PullRequest
7 голосов
/ 12 марта 2012

У меня есть приложение на Java Swing, которое порождает дочерние диалоги с текстовыми элементами управления. И проблема в том, что когда вы изменяете раскладку клавиатуры в дочернем диалоге, она возвращается обратно сразу после закрытия диалога.

Что мне нужно, так это чтобы компоновка keboard оставалась после переключения независимо от того, была ли она включена в основном или дочернем кадре.

Вот SSCCE, который иллюстрирует проблему:

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class InheritInputContext {

    public static void main(String[] arg) {
        final MainFrame mainFrame = new MainFrame();
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                mainFrame.setPreferredSize(new Dimension(300, 400));
                mainFrame.pack();
                mainFrame.setLocationRelativeTo(null);
                mainFrame.setVisible(true);
            }
        });

    }
}


class MainFrame extends JFrame {

    MainFrame() {
        setLayout(new BorderLayout());
        JTextArea textArea = new JTextArea();
        add(textArea, BorderLayout.CENTER);

        JButton dialogBtn = new JButton("Dialog");
        add(dialogBtn, BorderLayout.SOUTH);
        dialogBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                ChildDialog cd = new ChildDialog(MainFrame.this);
                cd.setPreferredSize(new Dimension(200, 200));
                cd.setLocationRelativeTo(MainFrame.this);
                cd.pack();
                cd.setVisible(true);
            }
        });
    }
}


class ChildDialog extends JDialog {

    ChildDialog(Window w) {
        super(w);
        JTextArea textArea = new JTextArea();
        getContentPane().add(textArea);
    }
}

Ответы [ 3 ]

2 голосов
/ 13 марта 2012

Хорошо, я только что остановился на этом решении:

Добавил слушатель java toolkit в методе main () следующим образом:

AWTEventListener awtWindowListener = new AWTEventListener() {
    @Override
    public void eventDispatched(AWTEvent event) {
        if (event instanceof WindowEvent) {
            if (WindowEvent.WINDOW_CLOSED == event.getID()
                    || WindowEvent.WINDOW_CLOSING == event.getID()) {
                Window child = ((WindowEvent) event).getWindow();
                Window parent = SwingUtilities.getWindowAncestor(child);
                if (parent == null) return;
                InputContext childIC = child.getInputContext();
                parent.getInputContext().selectInputMethod(childIC.getLocale());
            }
        }

    }
};

Toolkit.getDefaultToolkit().addAWTEventListener(awtWindowListener, AWTEvent.WINDOW_EVENT_MASK);

Он работает во всех дочерних диалогах, созданных с родителемокно как параметр конструктора.При закрытии события Locale из InputContext дочернего диалога помещается в InputContext его родительского окна.

Может быть, есть более лучший способ.

1 голос
/ 13 сентября 2012

Да и нет: код yggdraa от 13 марта отлично работал в Windows, но не работал в Linux.

Может не быть универсального решения для Linux вообще: нет таких вещей, как Windows GetKeyboardLayout () и ActivateKeyboardLayout (). Однако могут быть возможны некоторые зависящие от конфигурации хаки, такие как синтаксический анализ выходных данных xset ( подробности здесь ) и принудительное расположение, скажем, при нажатии клавиши вверх / вниз.

В приведенном выше примере код выбора входа в eventDispatched () вызывается слишком поздно - когда клавиатура ОС уже переключилась обратно на системный стандарт США.

Несколько попыток грубой силы также не сработали: myParticularJField.setLocale (myForcedLocale) из обработчика фокуса поля немедленно отменяется при первом нажатии клавиши. То же самое для форсирования локали верхнего уровня (JFrame / JDialog).

Обновление:

У нас в производстве только Windows, поэтому выполнение этой работы под Linux нецелесообразно: слишком много усилий.

На всякий случай, побочный продукт. Это правильно определяет, какой макет в данный момент активен: по умолчанию или альтернативный («локальный»). Он не может отличить несколько альтернативных макетов:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class LinuxKeyboardLayoutStatus {

    public enum LayoutType { DEFAULT, LOCAL }

    public LinuxKeyboardLayoutStatus.LayoutType getCurrentKeyboardLayoutType() throws IOException, InterruptedException {
        String[] command = createCommand();
        Process p = Runtime.getRuntime().exec(command);
        BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()));
        String l = r.readLine();
        r.close();
        p.waitFor();
        return decodeLayoutType(l);
    }

    protected String[] createCommand() {
        return new String[] { "/bin/sh", "-c", "xset -q | grep LED | awk '{ print $10 }' | cut -c5" };
    }

    protected LinuxKeyboardLayoutStatus.LayoutType decodeLayoutType(String commandOutput) {
        return
            commandOutput != null && !commandOutput.equals("0") ? LayoutType.LOCAL : LayoutType.DEFAULT;
    }

}

Обновление:

В Ubuntu изменение обратно к макету по умолчанию происходит на уровне окна X (события DBus). Обходной путь: отключить отдельные макеты для каждого окна: Настройки => Клавиатура => Макеты, снимите флажок «Отдельный макет для каждого окна».

1 голос
/ 12 марта 2012

Вы просто ищете способ, чтобы любое изменение макета повлияло на ваше приложение в глобальном масштабе?

Если это так, один из подходов заключается в создании настраиваемого прослушивателя, чтобы различные компоненты, которые заботятся об изменении макета, регистрировали свою заинтересованность в таких событиях, а затем запускали событие изменения макета, которое вызывает изменение во всех компонентахкогда он изменяется в любом из них.

Другой способ сделать это - сохранить свойства макета в объекте, доступном для любого из компонентов, и заставить их периодически обновлять свой макет с помощью таймера.Однако это было бы менее желательно, потому что, вероятно, было бы много ненужных обновлений по сравнению с режимом работы «только обновление по событию».Я предполагаю, что пользователи вашего приложения не будут менять раскладку клавиатуры более одного или двух раз за сеанс (в отличие от каждых 5 секунд)?

Еще один, третий способ сделать это - это иметьНастройки раскладки клавиатуры хранятся на уровне приложения и загружаются при запуске.Затем, когда произойдет изменение раскладки клавиатуры, попросите пользователя перезапустить приложение, чтобы изменения вступили в силу в глобальном масштабе.

...