Как на самом деле фокус на JButtons работает в Java? - PullRequest
4 голосов
/ 09 июля 2020

Я обнаружил странную аномалию в Java Swing. Первая кнопка JButton, добавленная в пользовательский интерфейс в хронологическом порядке, всегда срабатывает, когда пользователь нажимает клавишу пробела, при условии, что перед этим он не нажал другую кнопку. Такое поведение возникает даже при вызове getRootPane().setDefaultButton(JButton) и JButton.requestFocus(). При запросе фокуса на JButton, кажется, есть как минимум 2 различных типа «фокуса». Один из «фокусов» или выделений - это пунктирный прямоугольник вокруг текста на кнопке, а другой - более толстый контур вокруг указанной кнопки.

Кнопка с пунктирным текстом срабатывает всякий раз, когда клавиша пробела нажата. Кнопка с толстой рамкой срабатывает всякий раз, когда нажимается клавиша ввода.

Я подготовил компилируемый минимальный пример, иллюстрирующий это поведение. Здесь вообще нет привязки / привязки клавиш.

import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.WindowConstants;

public class ButtonFocusAnomalyExample extends JFrame {
    public ButtonFocusAnomalyExample() {
        super();
        setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        int frameWidth = 300;
        int frameHeight = 300;
        setSize(frameWidth, frameHeight);
        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        int x = (d.width - getSize().width) / 2;
        int y = (d.height - getSize().height) / 2;
        setLocation(x, y);
        setTitle("Any Frame");
        setResizable(false);
        Container cp = getContentPane();
        cp.setLayout(null);
        setVisible(true);
        new DialogMinimal(this, true); // Runs the Dialog
    }

    public static void main(String[] args) {
        new ButtonFocusAnomalyExample();
    }

    static class DialogMinimal extends JDialog {
        private final JTextField output = new JTextField();

        public DialogMinimal(final JFrame owner, final boolean modal) {
            super(owner, modal);
            setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
            int frameWidth = 252;
            int frameHeight = 126;
            setSize(frameWidth, frameHeight);
            Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
            int x = (d.width - getSize().width) / 2;
            int y = (d.height - getSize().height) / 2;
            setLocation(x, y);
            setTitle("Minimal Button Focus Example");
            Container cp = getContentPane();
            cp.setLayout(null);
            JButton bYes = new JButton();
            bYes.setBounds(0, 0, 100, 33);
            bYes.setText("Yes (Space)");
            bYes.addActionListener(this::bYes_ActionPerformed);
            JPanel buttonPanel = new JPanel(null, true);
            buttonPanel.add(bYes);
            JButton bNo = new JButton();
            bNo.setBounds(108, 0, 120, 33);
            bNo.setText("No (Enter/Return)");
            getRootPane().setDefaultButton(bNo); // Set "No" as default button
            bNo.requestFocus(); // Get focus on "No" button
            bNo.addActionListener(this::bNo_ActionPerformed);
            buttonPanel.add(bNo);
            buttonPanel.setBounds(8, 8, 400, 92);
            buttonPanel.setOpaque(false);
            cp.add(buttonPanel);
            output.setBounds(8, 50, 220, 32);
            cp.add(output);
            setResizable(false);
            setVisible(true);
        }

        public void bYes_ActionPerformed(final ActionEvent evt) {
            output.setText("Yes"); // Still fires on every space bar press
        }

        public void bNo_ActionPerformed(final ActionEvent evt) {
            output.setText("No"); // Only fires on every return/enter press
        }
    }
}

Вот как это выглядит:

Button Focus Example

The executable code can also be found здесь .

Мои вопросы:

  1. Что это за разные фокусы?
  2. Как можно изменить фокус, который отображается в виде пунктирного контура вокруг текста кнопки, чтобы пробел и клавиша ввода вызовет событие кнопки «Нет»?

1 Ответ

0 голосов
/ 15 июля 2020

По вопросу 1:

Нет 2-х видов "фокуса". Оба метода делают то, что говорят их соответствующие имена:

JButton.requestFocus() (еще лучше JButton.requestFocusInWindow()) запрашивает фокус на кнопке, а getRootPane().setDefaultButton(JButton) устанавливает selected кнопка, которую LAF обрабатывает отдельно.

Относительно вопроса 2:

Проблема в модальности диалогового окна.

Поэтому возможными решениями могут быть:

  1. Установить модальность на false при создании Диалог, например, с new DialogMinimal(this, false);, и получить фокус, вызвав bNo.requestFocusInWindow() вместо getRootPane().setDefaultButton(bNo); и / или bNo.requestFocus();, но это не решение, если диалог должен быть модальным.
  2. Реализовать RequestFocusListener найдено в Dialog Focus по предложению пользователя camickr.
public DialogMinimal(final JFrame owner, final boolean modal) {
    Button bNo = new JButton();
    [...]
    // bNo.requestFocusInWindow(); // obsolete now
    getRootPane().setDefaultButton(bNo); // To fire on enter key
    bNo.addAncestorListener(new RequestFocusListener()); // To fire on space bar
    [...]
}

...