Фокус владельца временно меняется на ноль - PullRequest
5 голосов
/ 10 сентября 2011

Я довольно новичок в разработке Swing, надеюсь, мой вопрос не глупый.

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

Моя текущая логика пользовательского интерфейса вносит некоторые изменения в элементы управления, когда фокус находится внутри одной из панелей или ее дочерних панелей. Однако получение промежуточного звена null нарушает эту логику.

Я искал в Google информацию об этой проблеме, но не нашел ничего релевантного.

Вопрос в том, является ли это поведение задуманным, и есть ли какой-нибудь способ обойти промежуточные нули.

Вот минимальное приложение, воспроизводящее указанное поведение:

import java.awt.*;
import java.beans.*;
import javax.swing.*;

public class FocusNullTest extends JFrame {

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                FocusNullTest self = new FocusNullTest();
                self.setVisible(true);
            }
        });
    }

    public FocusNullTest() {
        setSize(150, 100);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Container contentPane = getContentPane(); 
        contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.X_AXIS));

        contentPane.add(new JButton("1"));
        contentPane.add(new JButton("2"));

        KeyboardFocusManager focusManager =
            KeyboardFocusManager.getCurrentKeyboardFocusManager();
        focusManager.addPropertyChangeListener(
                "permanentFocusOwner",
                new PropertyChangeListener() {
                    @Override
                    public void propertyChange(PropertyChangeEvent e) {
                        System.out.println("permanentFocusOwner changed from: "
                                           + e.getOldValue());
                        System.out.println("permanentFocusOwner changed to  : "
                                           + e.getNewValue());
                    }
                });
    }
}

Выходные данные журнала:

(запуск программы, фокус устанавливается на кнопку 1 автоматически)
переменная перманентного фокуса изменена с: ноль
Параметр перманентного фокуса изменен на: javax.swing.JButton [, 0,18,41x26, (пропущено)]
(нажал на кнопку 2)
перманентFocusOwner изменен с: javax.swing.JButton [, 0,18,41x26, (пропущено)]
constantFocusOwner изменен на: null
перманентныйFocusOwner изменен с: ноль
Параметр перманентного фокуса изменен на: javax.swing.JButton [, 41,18,41x26, (пропущено)]


(необязательная часть, на коде намерения)
Моя цель - сделать что-то похожее на представление списка, где записи расширяются и отображают больше информации, когда они получают фокус (и сворачиваются обратно, когда теряют его). Расширенный вид содержит несколько дополнительных кнопок.

JList не представляется подходящим элементом управления, потому что (1) он не позволяет нажимать кнопки, и (2) его записи имеют постоянную высоту, тогда как я хочу, чтобы записи динамически расширялись в фокусе , JTable с его режимом редактирования, кажется, также не является подходящим решением, по крайней мере, из-за постоянного размера записи.

Поэтому я использую обычный JPanel с вертикальным расположением блоков в качестве контейнера, подписываюсь на изменения модели и обновляю визуальные элементы вручную. Проблема в том, что когда я нажимаю на кнопку, содержащийся элемент списка теряет фокус. Я мог бы обнаружить, что фокус все еще остается в пределах элемента списка, если фокус не изменится на null временно.

Ответы [ 4 ]

2 голосов
/ 10 сентября 2011
My goal is to make something looking like a list view, where the entries 
expand and display more information when they get focus (and collapse back 
when they lose it). The expanded view contains some additional buttons.

enter image description here enter image description here

ButtonModel может сделать это с помощью JButton, очень хороший вывод - с помощью JToggleButton или все еще есть оригинальная идея с удерживаемым там JPanel + MouseListener ()

import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class CollapsablePanelTest {

    public static void main(String[] args) {
        CollapsablePanel cp = new CollapsablePanel("test", buildPanel());
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().add(new JScrollPane(cp));
        f.setSize(360, 300);
        f.setLocation(200, 100);
        f.setVisible(true);
    }

    public static JPanel buildPanel() {
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.insets = new Insets(2, 1, 2, 1);
        gbc.weightx = 1.0;
        gbc.weighty = 1.0;
        JPanel p1 = new JPanel(new GridBagLayout());
        p1.setBackground(Color.blue);
        gbc.gridwidth = GridBagConstraints.RELATIVE;
        p1.add(new JButton("button 1"), gbc);
        gbc.gridwidth = GridBagConstraints.REMAINDER;
        p1.add(new JButton("button 2"), gbc);
        gbc.gridwidth = GridBagConstraints.RELATIVE;
        p1.add(new JButton("button 3"), gbc);
        gbc.gridwidth = GridBagConstraints.REMAINDER;
        p1.add(new JButton("button 4"), gbc);
        return p1;
    }

    private CollapsablePanelTest() {
    }
}

class CollapsablePanel extends JPanel {

    private static final long serialVersionUID = 1L;
    private boolean selected;
    private JPanel contentPanel_;
    private HeaderPanel headerPanel_;

    private class HeaderPanel extends JButton /*JToggleButton //implements MouseListener*/ {

        private static final long serialVersionUID = 1L;
        private String __text;
        private Font __font;
        private BufferedImage open, closed;
        private final int OFFSET = 30, PAD = 5;

        public HeaderPanel(String text) {
            //addMouseListener(this);
            __text = text;
            setText(__text);
            __font = new Font("sans-serif", Font.PLAIN, 12);
            // setRequestFocusEnabled(true);
            setPreferredSize(new Dimension(200, 30));
            int w = getWidth();
            int h = getHeight();
            /*try {
            open = ImageIO.read(new File("images/arrow_down_mini.png"));
            closed = ImageIO.read(new File("images/arrow_right_mini.png"));
            } catch (IOException e) {
            e.printStackTrace();
            }*/
            getModel().addChangeListener(new ChangeListener() {

                @Override
                public void stateChanged(ChangeEvent e) {
                    ButtonModel model = (ButtonModel) e.getSource();
                    if (model.isRollover()) {
                        toggleSelection();
                    } else if (model.isPressed()) {
                        toggleSelection();//for JToggleButton
                    }
                }
            });
        }

        /*@Override
        protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        int h = getHeight();
        ///if (selected)
        //g2.drawImage(open, PAD, 0, h, h, this);
        //else
        //g2.drawImage(closed, PAD, 0, h, h, this);
        // Uncomment once you have your own images
        g2.setFont(font);
        FontRenderContext frc = g2.getFontRenderContext();
        LineMetrics lm = font.getLineMetrics(__text, frc);
        float height = lm.getAscent() + lm.getDescent();
        float x = OFFSET;
        float y = (h + height) / 2 - lm.getDescent();
        g2.drawString(__text, x, y);
        }
        @Override
        public void mouseClicked(MouseEvent e) {
        toggleSelection();
        }

        @Override
        public void mouseEntered(MouseEvent e) {
        }

        @Override
        public void mouseExited(MouseEvent e) {
        }

        @Override
        public void mousePressed(MouseEvent e) {
        }

        @Override
        public void mouseReleased(MouseEvent e) {
        }*/
    }

    public CollapsablePanel(String text, JPanel panel) {
        super(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.insets = new Insets(1, 3, 0, 3);
        gbc.weightx = 1.0;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.gridwidth = GridBagConstraints.REMAINDER;
        selected = false;
        headerPanel_ = new HeaderPanel(text);
        setBackground(Color.orange);
        contentPanel_ = panel;
        add(headerPanel_, gbc);
        add(contentPanel_, gbc);
        contentPanel_.setVisible(false);
        JLabel padding = new JLabel();
        gbc.weighty = 1.0;
        add(padding, gbc);
    }

    public void toggleSelection() {
        selected = !selected;
        if (contentPanel_.isShowing()) {
            contentPanel_.setVisible(false);
        } else {
            contentPanel_.setVisible(true);
        }
        validate();
        headerPanel_.repaint();
    }
}
2 голосов
/ 10 сентября 2011

В качестве обходного пути сохраните последнего «настоящего» предыдущего владельца фокуса как участника в обработчике событий.

if ((e.getOldValue() != null) && (e.getNewValue() == null))
 prev_owner = e.getOldValue();

Тогда у вас будет ручка для этого объекта, когда вы на самом деле сфокусируетесь на цели. Обрабатывать изменения выделения только тогда, когда реальный компонент действительно получает фокус (т.е. когда getNewValue() не равен нулю).

(Поведение похоже на то, что описано в Подсистема фокуса AWT , в том смысле, что предыдущий компонент сначала теряет фокус, а затем целевой компонент получает его. Он не атомарный, поэтому период времени, когда на самом деле ничто не имеет фокуса. Но я не эксперт, так что это может измениться.)

2 голосов
/ 10 сентября 2011

Моя цель - сделать что-то похожее на представление списка, где записи расширяются и отображают больше информации, когда они получают фокус.

В качестве альтернативы я иногда использую JSplitPane: слева я помещаю (фокусируемую) кнопку расширения в JTable, Outline или вертикальные Box панелей; справа я поставил расширенный вид.

2 голосов
/ 10 сентября 2011

KeyboardFocusManager запускает два события для большинства свойств (что касается спецификации бинов, он не должен - никогда не выяснял причину, просто предполагая, что причиной может быть асинхронная природа фокуса)

   firePropertyChange(someProperty, oldValue, null)
   firePropertyChange(someProperty, null, newValue)

для работы в зависимости от newVaue, подождите секунду

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...