Как запретить JList делать выбор за пределами границ ячейки?

"Есть ли способ предотвратить выбор JList последнего элемента, когда пользователь нажимает его ниже в списке?"

Это вопрос, который кто-то спросил здесь, и у меня такая же проблема. Этот парень нашел так называемое решение (переопределив processMouseEvent()), но я хочу знать, есть ли лучший/более элегантный способ сделать это.

[изменить]

Хорошо, подробнее. Если у вас есть JList и есть какое-то пространство, не занятое какой-либо ячейкой/элементом, и вы нажимаете это пространство, тогда выбирается последний элемент в JList.

Для реального примера попробуйте этот пример JList Swing Tutorial, щелкните пробел и убедитесь, что Rollo выбран.

Ответ 1

См. https://forums.oracle.com/forums/thread.jspa?threadID=2206996

import java.awt.EventQueue;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JScrollPane;

public class TestJList {
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                JList list = new JList(new Object[] { "One", "Two", "Three" }) {
                    @Override
                    public int locationToIndex(Point location) {
                        int index = super.locationToIndex(location);
                        if (index != -1 && !getCellBounds(index, index).contains(location)) {
                            return -1;
                        }
                        else {
                            return index;
                        }
                    }
                };

                list.addMouseListener(new MouseAdapter() {

                    @Override
                    public void mouseClicked(MouseEvent e) {
                        JList list = (JList) e.getSource();
                        if (list.locationToIndex(e.getPoint()) == -1 && !e.isShiftDown()
                                && !isMenuShortcutKeyDown(e)) {
                            list.clearSelection();
                        }
                    }

                    private boolean isMenuShortcutKeyDown(InputEvent event) {
                        return (event.getModifiers() & Toolkit.getDefaultToolkit()
                                .getMenuShortcutKeyMask()) != 0;
                    }
                });

                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                frame.getContentPane().add(new JScrollPane(list));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
}

Ответ 2

Глядя на стек вызовов, вы не можете делать то, что хотите, не вступая в AWT-события:

Thread [AWT-EventQueue-0] (Suspended (breakpoint at line 384 in DefaultListSelectionModel)) 
    DefaultListSelectionModel.changeSelection(int, int, int, int, boolean) line: 384    
    DefaultListSelectionModel.changeSelection(int, int, int, int) line: 415 
    DefaultListSelectionModel.setSelectionInterval(int, int) line: 459  
    TestJList$1(JList<E>).setSelectionInterval(int, int) line: 2067 
    BasicListUI$Handler.adjustSelection(MouseEvent) line: 2739  
    BasicListUI$Handler.mousePressed(MouseEvent) line: 2695 
    AWTEventMulticaster.mousePressed(MouseEvent) line: 280  
    TestJList$1(Component).processMouseEvent(MouseEvent) line: 6502 
    TestJList$1(JComponent).processMouseEvent(MouseEvent) line: 3321    
    TestJList$1.processMouseEvent(MouseEvent) line: 24  
    TestJList$1(Component).processEvent(AWTEvent) line: 6270    
    TestJList$1(Container).processEvent(AWTEvent) line: 2229    
    TestJList$1(Component).dispatchEventImpl(AWTEvent) line: 4861   
    TestJList$1(Container).dispatchEventImpl(AWTEvent) line: 2287   
    TestJList$1(Component).dispatchEvent(AWTEvent) line: 4687   
    LightweightDispatcher.retargetMouseEvent(Component, int, MouseEvent) line: 4832 
    LightweightDispatcher.processMouseEvent(MouseEvent) line: 4489  
    LightweightDispatcher.dispatchEvent(AWTEvent) line: 4422    
    JFrame(Container).dispatchEventImpl(AWTEvent) line: 2273    
    JFrame(Window).dispatchEventImpl(AWTEvent) line: 2713   
    JFrame(Component).dispatchEvent(AWTEvent) line: 4687    
    EventQueue.dispatchEventImpl(AWTEvent, Object) line: 707    
    EventQueue.access$000(EventQueue, AWTEvent, Object) line: 101   
    EventQueue$3.run() line: 666    
    EventQueue$3.run() line: 664    
    AccessController.doPrivileged(PrivilegedAction<T>, AccessControlContext) line: not available [native method]    
    ProtectionDomain$1.doIntersectionPrivilege(PrivilegedAction<T>, AccessControlContext, AccessControlContext) line: 76    
    ProtectionDomain$1.doIntersectionPrivilege(PrivilegedAction<T>, AccessControlContext) line: 87  
    EventQueue$4.run() line: 680    
    EventQueue$4.run() line: 678    

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

Для чего это стоит (это делается путем переопределения методов processXXXEvent), вот небольшой фрагмент, который я на самом деле считаю не таким уродливым и не позволяет выбирать объекты при нажатии вне их границ:

import java.awt.event.MouseEvent;

import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

public class TestJList {

    private JList list;

    protected void initUI() {
        JFrame frame = new JFrame("test");
        list = new JList(new Object[] { "Hello", "World", "!" }) {

            private boolean processEvent(MouseEvent e) {
                int index = list.locationToIndex(e.getPoint());
                return index > -1 && list.getCellBounds(index, index).contains(e.getPoint());
            }

            @Override
            protected void processMouseEvent(MouseEvent e) {
                if (processEvent(e)) {
                    super.processMouseEvent(e);
                }
            }

            @Override
            protected void processMouseMotionEvent(MouseEvent e) {
                if (processEvent(e)) {
                    super.processMouseMotionEvent(e);
                }
            }
        };
        list.setVisibleRowCount(10);

        frame.add(new JScrollPane(list));
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new TestJList().initUI();
            }
        });
    }
}