Как создать JList, каждый элемент которого содержит JCheckBox и JLabel с различными событиями при нажатии - PullRequest
2 голосов
/ 24 января 2020

Я пытаюсь создать Jlist, который содержит элементы (пользовательские) с разделенными слушателями и поведением. Под этим я подразумеваю, что когда ячейка загружается, она должна:

  • Отображать флажок, который проверяется, если CustomClass имеет значение «установленный» = true
  • Отображать текст то есть в CustomClass
  • Если я нажму в CheckBox, он изменит состояние Установленного этого Объекта на противоположное его значению
  • Если я нажму на текст, он отобразит информацию CustomClass на другой панели

Прямо сейчас мне удалось создать JList с CustomClass (SimpleTemplate), который рисует флажок с именем SimpleTemplate и, когда Вы нажимаете на нее, отображает информацию о SimpleTemplate на другой панели. Тем не менее, я не знаю, как разделить слушателей и события, предложенные ранее.

Пока мой код выглядит следующим образом:

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import javax.swing.DefaultListModel;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.ListCellRenderer;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;

public class CustomJListExample extends JFrame {

    private static final Dimension SIDE_PANEL_DIMENSION = new Dimension(190, 190);
    private static final Dimension CONTAINER_PANEL_DIMENSION = new Dimension(400, 200);
    private static final Dimension TEMPLATES_LIST_DIMENSION = new Dimension(180, 180);
    private static final Border SIMPLE_BORDER = new JTextField().getBorder();

    private JList<SimpleTemplate> templatesList = new JList<>();
    private JLabel templateName = new JLabel();
    private JLabel templateDescription = new JLabel();


    public CustomJListExample() {
        JPanel rightPanel = prepareRightSide();
        JPanel leftPanel = prepareLeftSide();

        JPanel containerPanel = new JPanel();
        containerPanel.setPreferredSize(CONTAINER_PANEL_DIMENSION);

        containerPanel.add(leftPanel);
        containerPanel.add(rightPanel);
        add(containerPanel);
        pack();
    }

    private JPanel prepareRightSide() {
        JPanel rightPanel = new JPanel();
        rightPanel.setBorder(SIMPLE_BORDER);
        rightPanel.setBackground(Color.GRAY);
        rightPanel.setPreferredSize(SIDE_PANEL_DIMENSION);

        templateName.setText("---");
        templateDescription.setText("---");

        rightPanel.add(templateName);
        rightPanel.add(templateDescription);

        return rightPanel;
    }

    private JPanel prepareLeftSide() {
        JPanel leftPanel = new JPanel();
        leftPanel.setBorder(SIMPLE_BORDER);
        leftPanel.setBackground(Color.GRAY);
        leftPanel.setPreferredSize(SIDE_PANEL_DIMENSION);

        DefaultListModel<SimpleTemplate> templatesListModel = new DefaultListModel<>();
        templatesListModel.addElement(new SimpleTemplate("Template 1", "Description template 1", false));
        templatesListModel.addElement(new SimpleTemplate("Template 2", "Description template 2", true));
        templatesListModel.addElement(new SimpleTemplate("Template 3", "Description template 3", false));

        templatesList.setCellRenderer(new JListRepositoryItem());
        templatesList.addListSelectionListener(e-> displayTemplateInfo());
        templatesList.setPreferredSize(TEMPLATES_LIST_DIMENSION);
        templatesList.setModel(templatesListModel);
        templatesList.repaint();

        leftPanel.add(templatesList);

        return leftPanel;
    }

    private void displayTemplateInfo() {
        SimpleTemplate selectedValue = templatesList.getSelectedValue();
        templateName.setText(selectedValue.getName());
        templateDescription.setText(selectedValue.getDescription());
    }

    class JListRepositoryItem extends JCheckBox implements ListCellRenderer {
        @Override
        public Component getListCellRendererComponent(JList list, Object value, int index,
            boolean isSelected, boolean cellHasFocus) {
            setComponentOrientation(list.getComponentOrientation());
            setFont(list.getFont());
            setBackground(list.getBackground());
            setForeground(list.getForeground());

            if (value instanceof SimpleTemplate) {
                SimpleTemplate template = (SimpleTemplate) value;
                setSelected(isSelected);
                setEnabled(list.isEnabled());
                setText(template.getName());
            }

            return this;
        }
    }

    class SimpleTemplate {
        private String name;
        private String description;
        private boolean installed;

        public SimpleTemplate(String name, String description, boolean installed) {
            this.name = name;
            this.description = description;
            this.installed = installed;
        }

        public String getName() {
            return name;
        }

        public String getDescription() {
            return description;
        }

        public boolean isInstalled() {
            return installed;
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new CustomJListExample().setVisible(true));
    }
}

, который генерирует следующий пример.

enter image description here

Тем не менее, мне не удается настроить Text на собственное поведение, а CheckBox - собственное поведение.

1 Ответ

2 голосов
/ 24 января 2020

Если вы хотите сделать это правильно - вам придется изменить JList Реализацию пользовательского интерфейса, так как оттуда исходит поведение выбора. Это довольно сложно сделать, если вы никогда не работали с ним.

Кроме того, обычно трудно сделать что-то подобное тому, что вы просили, потому что компонент JList не позволяет вам взаимодействовать с компоненты, предоставляемые непосредственно в реализации ListCellRenderer - он просто использует их для многократной «штамповки» своего графического представления с различными настройками. Это заставляет JList работать очень хорошо на больших объемах данных, но блокирует вас от прямого взаимодействия с компонентами рендерера.

Но есть обходной путь, который вы могли бы использовать для простых случаев, таких как ваш - вы можете добавить custom MouseListener в ваш список и «угадайте», где пользователь нажимает. К счастью, JList API предоставляет вам все методы, необходимые для этого:

templatesList.addMouseListener ( new MouseAdapter ()
{
    @Override
    public void mousePressed ( final MouseEvent e )
    {
        final Point point = e.getPoint ();
        final int index = templatesList.locationToIndex ( point );
        if ( index != -1 )
        {
            // Next calculations assume that text is aligned to left, but are easy to adjust
            final SimpleTemplate element = templatesList.getModel ().getElementAt ( index );
            final Rectangle cellBounds = templatesList.getCellBounds ( index, index );
            final JListRepositoryItem renderer = ( JListRepositoryItem ) templatesList.getCellRenderer ();
            final int iconWidth = renderer.getIcon () !=null ? renderer.getIcon ().getIconWidth () : 16;
            final Insets insets = renderer.getInsets ();
            final int iconX = cellBounds.x + insets.left;

            // Ensure that mouse press happened within top/bottom insets
            if ( cellBounds.y + insets.top <= point.y && point.y <= cellBounds.y + cellBounds.height - insets.bottom )
            {
                // Check whether we hit the checkbox icon
                if ( iconX <= point.x && point.x <= cellBounds.x + insets.left + iconWidth )
                {
                    // We hit the checkbox icon
                    element.installed = !element.installed;
                    templatesList.repaint ( cellBounds );
                }
                else
                {
                    // Check whether we hit text
                    final int iconTextGap = renderer.getIconTextGap ();
                    final int textX = cellBounds.x + insets.left + iconWidth + iconTextGap;
                    final FontMetrics fontMetrics = renderer.getFontMetrics ( renderer.getFont () );
                    final int textWidth = fontMetrics.stringWidth ( element.getName () );
                    if ( textX <= point.x && point.x <= textX + textWidth )
                    {
                        // We hit the text
                        templateName.setText ( element.getName () );
                        templateDescription.setText ( element.getDescription () );
                    }
                    else
                    {
                        // Reset values
                        templateName.setText ( "---" );
                        templateDescription.setText ( "---" );
                    }
                }
            }
            else
            {
                // Reset values
                templateName.setText ( "---" );
                templateDescription.setText ( "---" );
            }
        }
        else
        {
            // Reset values
            templateName.setText ( "---" );
            templateDescription.setText ( "---" );
        }
    }
} );

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

Вот разбивка любого базового c компонента, такого как метка, кнопка или флажок:

Component contents breakdown

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

Вам также необходимо удалить ListSelectionListener, так как он будет конфликтовать с MouseListener. Вот полный код:

import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class CustomJListExample extends JFrame
{

    private static final Dimension SIDE_PANEL_DIMENSION = new Dimension ( 190, 190 );
    private static final Dimension CONTAINER_PANEL_DIMENSION = new Dimension ( 400, 200 );
    private static final Dimension TEMPLATES_LIST_DIMENSION = new Dimension ( 180, 180 );
    private static final Border SIMPLE_BORDER = new JTextField ().getBorder ();

    private JList<SimpleTemplate> templatesList = new JList<> ();
    private JLabel templateName = new JLabel ();
    private JLabel templateDescription = new JLabel ();


    public CustomJListExample ()
    {
        JPanel rightPanel = prepareRightSide ();
        JPanel leftPanel = prepareLeftSide ();

        JPanel containerPanel = new JPanel ();
        containerPanel.setPreferredSize ( CONTAINER_PANEL_DIMENSION );

        containerPanel.add ( leftPanel );
        containerPanel.add ( rightPanel );
        add ( containerPanel );
        pack ();
    }

    private JPanel prepareRightSide ()
    {
        JPanel rightPanel = new JPanel ();
        rightPanel.setBorder ( SIMPLE_BORDER );
        rightPanel.setBackground ( Color.GRAY );
        rightPanel.setPreferredSize ( SIDE_PANEL_DIMENSION );

        templateName.setText ( "---" );
        templateDescription.setText ( "---" );

        rightPanel.add ( templateName );
        rightPanel.add ( templateDescription );

        return rightPanel;
    }

    private JPanel prepareLeftSide ()
    {
        JPanel leftPanel = new JPanel ();
        leftPanel.setBorder ( SIMPLE_BORDER );
        leftPanel.setBackground ( Color.GRAY );
        leftPanel.setPreferredSize ( SIDE_PANEL_DIMENSION );

        DefaultListModel<SimpleTemplate> templatesListModel = new DefaultListModel<> ();
        templatesListModel.addElement ( new SimpleTemplate ( "Template 1", "Description template 1", false ) );
        templatesListModel.addElement ( new SimpleTemplate ( "Template 2", "Description template 2", true ) );
        templatesListModel.addElement ( new SimpleTemplate ( "Template 3", "Description template 3", false ) );

        templatesList.setCellRenderer ( new JListRepositoryItem () );
        templatesList.setPreferredSize ( TEMPLATES_LIST_DIMENSION );
        templatesList.setModel ( templatesListModel );
        templatesList.repaint ();

        templatesList.addMouseListener ( new MouseAdapter ()
        {
            @Override
            public void mousePressed ( final MouseEvent e )
            {
                final Point point = e.getPoint ();
                final int index = templatesList.locationToIndex ( point );
                if ( index != -1 )
                {
                    // Next calculations assume that text is aligned to left, but are easy to adjust
                    final SimpleTemplate element = templatesList.getModel ().getElementAt ( index );
                    final Rectangle cellBounds = templatesList.getCellBounds ( index, index );
                    final JListRepositoryItem renderer = ( JListRepositoryItem ) templatesList.getCellRenderer ();
                    final int iconWidth = renderer.getIcon () !=null ? renderer.getIcon ().getIconWidth () : 16;
                    final Insets insets = renderer.getInsets ();
                    final int iconX = cellBounds.x + insets.left;

                    // Ensure that mouse press happened within top/bottom insets
                    if ( cellBounds.y + insets.top <= point.y && point.y <= cellBounds.y + cellBounds.height - insets.bottom )
                    {
                        // Check whether we hit the checkbox icon
                        if ( iconX <= point.x && point.x <= cellBounds.x + insets.left + iconWidth )
                        {
                            // We hit the checkbox icon
                            element.installed = !element.installed;
                            templatesList.repaint ( cellBounds );
                        }
                        else
                        {
                            // Check whether we hit text
                            final int iconTextGap = renderer.getIconTextGap ();
                            final int textX = cellBounds.x + insets.left + iconWidth + iconTextGap;
                            final FontMetrics fontMetrics = renderer.getFontMetrics ( renderer.getFont () );
                            final int textWidth = fontMetrics.stringWidth ( element.getName () );
                            if ( textX <= point.x && point.x <= textX + textWidth )
                            {
                                // We hit the text
                                templateName.setText ( element.getName () );
                                templateDescription.setText ( element.getDescription () );
                            }
                            else
                            {
                                // Reset values
                                templateName.setText ( "---" );
                                templateDescription.setText ( "---" );
                            }
                        }
                    }
                    else
                    {
                        // Reset values
                        templateName.setText ( "---" );
                        templateDescription.setText ( "---" );
                    }
                }
                else
                {
                    // Reset values
                    templateName.setText ( "---" );
                    templateDescription.setText ( "---" );
                }
            }
        } );

        leftPanel.add ( templatesList );

        return leftPanel;
    }

    class JListRepositoryItem extends JCheckBox implements ListCellRenderer<SimpleTemplate>
    {
        @Override
        public Component getListCellRendererComponent ( JList list, SimpleTemplate value, int index,
                                                        boolean isSelected, boolean cellHasFocus )
        {
            setComponentOrientation ( list.getComponentOrientation () );
            setFont ( list.getFont () );
            setBackground ( list.getBackground () );
            setForeground ( list.getForeground () );

            setSelected ( value.isInstalled () );
            setEnabled ( list.isEnabled () );
            setText ( value.getName () );

            return this;
        }
    }

    class SimpleTemplate
    {
        private String name;
        private String description;
        private boolean installed;

        public SimpleTemplate ( String name, String description, boolean installed )
        {
            this.name = name;
            this.description = description;
            this.installed = installed;
        }

        public String getName ()
        {
            return name;
        }

        public String getDescription ()
        {
            return description;
        }

        public boolean isInstalled ()
        {
            return installed;
        }
    }

    public static void main ( String[] args )
    {
        SwingUtilities.invokeLater ( () -> new CustomJListExample ().setVisible ( true ) );
    }
}

Хотя я хочу еще раз подчеркнуть, что это «взлом», который работает вне JList внутренней логики c, поэтому вы не можете полагаться на JList выбор, так как он будет изменен внутренним lsiteners из JList UI. Но не похоже, что вам действительно нужен выбор JList, так что он может хорошо работать для вас.

В случае, если вы захотите настроить JList пользовательский интерфейс - вы будете необходимо выполнить аналогичные вычисления, но также предоставить пользовательскую реализацию ListUI, которая может быть сложной, если вы используете нативную ОС Look and Feel.

...