Списки JList, JCombobox

Списки JList используются для визуального отображения данных. Пользователь видит весь список или большую его часть, и может выбрать один элемент списка или несколько. В отличие от обычного списка JList, раскрывающийся список JComboBox используется для выбора одной строки из нескольких вариантов и показывает только выбранный в данный момент элемент. JComboBox позволяет также вводить собственные значения.

JList имеет двух поставщиков данных (модели данных). Первая модель, реализующая интерфейс ListModel, содержит элементы списка. Вторая модель ListSelectionModel реализует интерфейс ListSelectionModel и следит за выделенными элементами. По умолчанию в списке JList используется стандартная реализация модели DefaultListSelectionModel, поддерживающая все самые важные режимы выделения.

Пример использования списка JList и модели DefaultListModel

// Использование списков JList и модели списка DefaultListModel

import javax.swing.*;

import java.awt.event.*;
import java.util.Vector;

public class ListModelTest extends JFrame
{
    // Модель списка
    private DefaultListModel<String> dlm = new DefaultListModel<String>();

    private final String[] data1 = { "Чай" ,"Кофе"  ,"Минеральная","Морс"};
    private final String[] data2 = { "Ясли","Детсад", "Школа"     , "Институт", 
                                     "Университет"};

    public ListModelTest()
    {
        super("Пример со списком JList");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        // Создание панели
        JPanel contents = new JPanel();

        // Наполнение модели данными
        for (String string : data2) {
            dlm.add(0, string);
        }
        // Создание кнопки
        JButton add = new JButton("Добавить");
        add.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                dlm.add(dlm.getSize(), "-- Новая запись --");
                validate();
            }
        });
        JList<String> list1 = new JList<String>(data1);
        JList<String> list2 = new JList<String>(dlm);

        // Вектор данных
        Vector<String> big = new Vector<String>();
        for (int i=0; i < 15; i++) {
            big.add("~ " + i + ". ~");
        }
        JList<String> bigList = new JList<String>(big);
        bigList.setPrototypeCellValue("12345");
        
        // Размещение компонентов в панели
        contents.add(add);
        contents.add(new JScrollPane(list1));
        contents.add(new JScrollPane(list2));
        contents.add(new JScrollPane(bigList));

        setContentPane(contents);
        // Вывод окна
        setSize(400, 200);
        setVisible(true);
    }
    public static void main(String[] args) {
        new ListModelTest();
    }
}

В примере создается небольшое окно, в котором размещаются три списка JList. Первый список создается на основе массива строк. Второй список использует в качестве модели данных DefaultListModel типа String. Третий список формируется динамически на основе объекта Vector. Следует отметить, что списки вставляются в панель прокрутки JScrollPane. Компоненты Swing по умолчанию не предоставляют возможностей прокрутки содержимого и их необходимо помещать в панель прокрутки. Списки JList не являются исключением.

В примере используется полезный метод setPrototypeCellValue() для определения ширины визуального списка. Элементы списка, как правило, имеют разные размеры, поэтому перед их выводом на экран компонент JList проверяет их размеры и выбирает ширину самого большого элемента в списке. Метод setPrototypeCellValue() позволяет определить ширину JList по длине указанной строки.

Интерфейс программы представлен на следующем скриншоте.

Модели списка

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

Модель данных AbstractListModel

Свойства моделей данных списков JList описаны в интерфейсе ListModel и включают четыре метода. Два метода служат для присоединения и удаления слушателей событий, происходящих при обновлении данных списка; один метод возвращает элемент, находящийся в некоторой позиции списка; последний метод позволяет узнать количество элементов модели.

Для работы со списками в библиотеке Swing имеется стандартная модель данных DefaultListModel. В рассмотренном выше примере представлен код добавления строк в модель и создание списка на основе данных модели. Модель позволяет добавлять и удалять данные, вставлять их в любые позиции, производить перебор элементов. Модели и виды (View) компонентов Swing сами заботятся о том, чтобы всё вовремя и правильно появлялось на экране. Для демонстрации динамического поведения данных модели в примере размещена кнопка JButton "Добавить". В методе обработки нажатия кнопки actionPerformed в модель данных добавляется строка, после чего вызывается метод validate(), чтобы привести интерфейс в соответствие с новыми данными списка.

Стандартная модель DefaultListModel очень удобна и позволяет гибко хранить и изменять любые данные. Однако иногда лучше определить собственную модель данных; особенно там, где данные хранятся в нестандартных структурах или их необходимо получить из сетевого соединения или базы данных. Полностью реализовывать модель «с нуля» не нужно. Создатели Swing уже позаботились о поддержке любыми моделями механизма оповещения (встроили списки слушателей и обеспечили рассылку событий) в абстрактном классе AbstractListModel. Нужно лишь унаследовать от этого класса и получить данные списка.

Пример использования AbstractListModel для создания модели данных

public class DatabaseListModel extends AbstractListModel<String>
{
    // Коллекция для хранения данных
    private ArrayList<String> data = new ArrayList<String>();
    // Загрузка данных из БД
    public void setDataSource(ResultSet rs, String column) 
    {
        try {
            // Очистка коллекции
            data.clear();
            // Перебор в цикле данных
            while ( rs.next() ) {
                synchronized ( data ) {
                    // Добавление данных в коллекцию
                    data.add(rs.getString(column));
                }
                // Оповещение видов о добавлении, если они есть
                fireIntervalAdded(this, 0, data.size());
            }
            rs.close();
            // Оповещение видов об изменении
            fireContentsChanged(this, 0, data.size());
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }
    // Функция размера массива данных в списке
    public int getSize() {
        synchronized ( data ) {
            return data.size();
        }
    }
    // Функция извлечения элемента
    public String getElementAt(int idx) {
        synchronized ( data ) {
            return data.get(idx);
        }
    }
}

Модель DatabaseListModel наследуется от абстрактного класса моделей списков AbstractListModel и хранит данные в динамическом массиве ArrayList. Основной код определен в методе setDataSource(), который позволяет модели получать данные из соединения с базой данных. Для правильной работы этому методу необходимо передать результат запроса к базе данных ResultSet, а также название столбца column, данные которого будут использованы для заполнения списка.

При получении очередной записи из базы данных модель данных оповещает об этом своих слушателей. Для оповещения присоединенных к модели слушателей, а это чаще всего виды, отображающие ее данные, используются возможности базового класса AbstractListModel: вызываются методы fireXXX(), каждый из которых сообщает о некотором событии. Для ускорения работы с коллекциями в классе ArrayList отключена синхронизация. Поэтому методы коллекции add(), size(), get() синхронизируются "в ручную".

Можно было бы сначала загрузить из БД данные, а затем поместить их в стандартную модель DefaultListModel. Но в созданной в примере модели демонстрируется способность оповещать слушателей при изменении данных.

Модель выделения DefaultListSelectionModel

Список JList включает используемую по умолчанию модель выделения DefaultListSelectionModel. Данная модель поддерживает три режима выделения элементов списка и предоставляет полную информацию о текущем выделении. В следующей таблице представлены режимы выделения элементов списка.

РежимОписание
SINGLE_SELECTION Выделение одного элемента списка.
SINGLE_INTERVAL_SELECTION Выделение нескольких смежных элементов списка.
MULTIPLE_INTERVAL_SELECTION Выделение нескольких элементов списка в произвольном порядке.

Наиибольшее распространение получили следующие методы модели выделения элементов списка :

  • setSelectedIndex (int idx) - выделение элемента списка;
  • setSelectionInterval (int anchor, int lead) - выделение нескольких смежных элементов;
  • addSelectionInterval (int anchor, int lead) - добавление к уже имеющемуся выделению еще одного интервала выделения;
  • setSelectedIndices (int[] rows) - выделение нескольких элементов списка в произвольном порядке.

Методы setSelectionInterval() и addSelectionInterval() в качестве параметров используют позиции первого и последнего выделяемых элементов. Если они совпадают, то выделяется один элемент. Необязательно, чтобы первое число было бы больше второго. В модели выделения хранятся начало интервала выделения anchor и конец интервала выделения lead.

Пример различных режимов выделения элементов списка

// Пример использования различных режимов выделения
import javax.swing.*;

public class ListSelectionTest extends JFrame
{
    private String[] data = { "Футбол", "Хоккей", "Баскетбол", 
                              "Биатлон", "Лыжи"};
    public ListSelectionTest()
    {
        super("Пример модели выделения");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        // Модель данных
        DefaultListModel<String> dlm = new DefaultListModel<String>();
        for (String string : data) 
            dlm.addElement(string);
        // Cписки для разных типов выделения
        JList<String> lst_single   = new JList<String>(dlm);
        JList<String> lst_interval = new JList<String>(dlm);
        JList<String> multiple     = new JList<String>(dlm);
        // Определение максимальной ширины списка
        lst_single.setPrototypeCellValue("Установленный размер");
        // Модели выделения
        lst_single  .setSelectionMode(
                          ListSelectionModel.SINGLE_SELECTION);
        lst_interval.setSelectionMode(
                          ListSelectionModel.SINGLE_INTERVAL_SELECTION  );
        multiple    .setSelectionMode(
                          ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        // Модель выделения можно установить и как в следующей 
        // закомментированной строке
        // multiple .getSelectionMode().setSelectionMode(
        //                ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        // Выделение строки списков
        lst_single  .setSelectedIndex(2);
        lst_interval.setSelectionInterval(1, 1);
        lst_interval.addSelectionInterval(2, 3);
        // Список выделяемых строк
        int[] rows = {0, 2, 4};
        multiple.setSelectedIndices(rows); 
        // Добавляем компоненты в интерфейс
        JPanel contents = new JPanel();
        contents.add(new JScrollPane(lst_single  ));
        contents.add(new JScrollPane(lst_interval));
        contents.add(new JScrollPane(multiple    ));
        // Вывод окна
        setContentPane(contents);
        setSize(360, 200);
        setVisible(true);
    }
    public static void main(String[] args) {
        new ListSelectionTest();
    }
}

В примере заполняется стандартная модель DefaultListModel данными из массива и на её основе создается три списка. Для списков устанавливаются различные режимы выделения элементов. Режим выделения можно установить двумя способами: вызовом метода setSelectionMode() или вызовом метода с точно таким же названием для самой модели выделения.

Интерфейс программы представлен на следующем скриншоте.

Один элемент списка может быть выбран щелчком мыши или нажатием клавиш управления курсором. Интервалы и дополнительные элементы могут быть выбраны с помощью вспомогательных клавиш Shift и Ctrl.

В следующей таблице представлены методы получения информации о выделенных элементах.

МетодНазначение
isSelectedlndex() Функция проверки наличия выделенных элементов списка.
getSelectedlndex(),
getSelectedIndices()
Функции получения позиции первого выделенного элемента (если выделений нет, то возвращается -1) или массив позиций выделенных элементов.
getSelectedValue(),
getSelectedValues()
Получение выбранного элемент списка или массива элементов.

Как правло, возможностей стандартной модели выделения DefaultListSelectionModel вполне достаточно для большей части приложений. Но если возникнет потребность в разработке собственной модели выделения, то можно использовать следующий шаблон для создания собственной модели.

class CustomListSelectionModel extends DefaultListSelectionModel
{
    // Добавление интервала выделения
    public void addSelectionInterval(int anchor, int lead)
    {
        super.addSelectionInterval(anchor, lead);
        ...
    }
    // Определение интервала выделения
	public void setSelectionInterval(int anchor, int lead)
    {
        super.setSelectionInterval(anchor, lead);
        ...
    }
}

Шаблон модели выделения унаследован от DefaultListSelectionModel и в нем переопредены два метода : метод addSelectionInterval() добавляет к выделению новый интервал, метод setSelectionInterval() определяет интервал выделения.

Внешний вид списка

Внешний вид списка JList, как и у всех унаследованных от JComponent компонентов Swing, можно настраивать - менять цвет фона, цвет шрифта и сам шрифт, а также цвет выделенного элемента.

В таблице представлены свойства и методы определения интерфейса списка.

Свойство (и методы get/set)Описание
background, foreground, font Определение фона, цвета символов и шрифт соответственно.
selectedBackground Определение цвета фона выделенных элементов.
selectedForeground Определение цвета символов выделенных элементов.

С помощью этих свойств можно установить определеный стиль представления списков в интерфейсе приложения.

Слушатели событий JList

Список JList включает стандартные события от мыши и клавиатуры базового компонента JComponent. Дополнительные события происходят в его моделях. События в модели выделения элемента списка ListSelectionModel позволяют узнать об изменениях в выделении элементов списка.

Пример использования слушателя ListSelectionListener

// Пример тестирования слушателя ListSelectionListener

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

public class ListSelectionListenerTest extends JFrame
{
    // Данные списка
    private final String[]   dataList = { "Бакалея", "Напитки", "Хлеб"};
    private final String[][] dataText = {{"Сахар", "Мука", "Соль"},
                                         {"Чай"  , "Кофе", "Морс"},
                                         {"Батон", "Пшеничный", 
                                          "Бородинский"}};
    private JTextArea content;

    public ListSelectionListenerTest()
    {
        super("Слушатель событий списка");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        // Создание списка
        JPanel contents = new JPanel();
        final JList<String> list = new JList<String>(dataList);
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        list.setPrototypeCellValue("Увеличенный");
        // Создание текстового поля
        content = new JTextArea(5, 20);
        // Подключения слушателя
        list.addListSelectionListener(new listSelectionListener());
        // Подключение слушателя мыши
        list.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                if ( e.getClickCount() == 2 ) {
                    // Получение элемента
                    int selected = list.locationToIndex(e.getPoint());
                    int i = 0;
                    String txt = "";
                    while (i < dataText[selected].length)
                        txt += dataText[selected][i++] + "\n";
                    content.setText (txt);
                }
            }
        });
        
        // Размещение компонентов в интерфейсе
        contents.add(new JLabel("Список разделов"));
        contents.add(new JScrollPane(list));
        contents.add(new JLabel("Содержимое разделов"));
        contents.add(new JScrollPane(content));
        // Вывод окна
        setContentPane(contents);
        setSize(600, 200);
        setVisible(true);
    }
    /*
     * Слушатель списка 
     */
    class listSelectionListener implements ListSelectionListener {
        public void valueChanged(ListSelectionEvent e) {
            // Выделенная строка
            int selected = ((JList<?>)e.getSource()).
                                              getSelectedIndex();
            System.out.println ("Выделенная строка : " + 
                                     String.valueOf(selected));
        }
    }
    public static void main(String[] args) {
        new ListSelectionListenerTest();
    }
}

В примере в интерфейсе окна размещаются список на основе небольшого массива строк и текстовое поле JTextArea для вывода дополнительной информации. К списку подключается слушатель события listSelectionListener, наследуемый свойства модели выделения. Следует отметить, что слушатель подключается к списку JList, хотя точно такой же метод есть и в модели выделения ListSelectionModel. Отличия связаны с источником события, который возвращает слушателю источник getSource(). В слушателе события определяется выделенная строка.

Интерфейс программы представлен на следующем скриншоте.

Список JList не поддерживает двойные щелчки мыши и не предоставляет средства по их определению. Но добавить такой "сервис" не представляет никаких сложностей. В примере к списку JList дополнительно подключен слушатель обработки события клавиши мыши. При двойном нажатии клавиши мыши на списке определяется выделяемая строка и в текстовый компонент загружаются соответствующие данные. Для получения выделенного элемента используется метод locationToIndex(), который позволяет определить принадлежность точки экрана к элементу списка. Метод indexToLocation() решает обратную задачу - определяет место экрана где находится элемент списка.

Раскрывающиеся списки JComboBox, ComboBoxModel

Для выбора одного элемента из нескольких доступных вариантов можно использовать раскрывающиеся списки JComboBox, который в нормальном состоянии отображает только один выбранный элемент. Возможные альтернативы появляются в специальном всплывающем окне при открытии списка. Раскрывающиеся списки допускают редактирование текущего элемента. Пользователь может не только выбрать определенный элемент из списка, но и ввести свое собственное значение. Список JComboBox обладает возможностью поиска элементов с клавиатуры, что значительно упрощает работу с большими наборами.

Раскрывающийся список JComboBox использует только одну модель, предоставляющую информацию об элементах списка. Свойства модели JComboBox определены интерфейсом ComboBoxModel, унаследованным от простой модели списка ListModel. Интерфейс ComboBoxModel включает дополнительно два новых метода : getSelectedItem() — для получения выбранного элемента и setSelectedItem() — для смены выбранного элемента. Помимо интерфейса модели ComboBoxModel можно использовать унаследованный от нее интерфейс MutableComboBoxModel, поддерживающий вставку и удаление произвольных элементов списка.

Пример использования JComboBox

// Пример использования раскрывающихся списков

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;

import javax.swing.*;

public class ComboBoxTest extends JFrame
{
    private static final long serialVersionUID = 1L;
    // Массив элементов списка
    public String[] elements = new String[] {"Офис", "Склад", "Гараж", 
                                             "Производство", "Столовая"};

    private JComboBox<String> cbFirst;
    private DefaultComboBoxModel<String> cbModel;

    public ComboBoxTest() {
        super("Пример JComboBox");
        setDefaultCloseOperation(EXIT_ON_CLOSE);

        // Модель данных списка
        cbModel = new DefaultComboBoxModel<String>();
        for (int i = 0; i < elements.length; i++) 
            cbModel.addElement((String)elements[i]);
        // Данные для 2-го списка
        Vector<String> data = new Vector<String>();
        for (int i = 0; i < 10; i++) 
            data.add(String.format("#%d элемент", i));
        // 1-й раскрывающийся список
        cbFirst = new JComboBox<String>(cbModel);
        // Меняем элемент Гараж на Автопарк
        cbModel.setSelectedItem("Гараж");
        int idx = cbModel.getIndexOf(cbModel.getSelectedItem()); 
        cbModel.removeElementAt(idx);
        cbModel.insertElementAt("Автопарк", idx);
        cbModel.setSelectedItem("Автопарк");
        // Определяем размер списка
        cbFirst.setPrototypeDisplayValue("Максимальный размер");
        // 2-й раскрывающийся список
        JComboBox<String> cbSecond = new JComboBox<String>(data);
        /*
         *  Определение свойств списка - редактируемый, количество 
         *  элементов в раскрывающемся окне
         */
        cbSecond.setEditable(true);
        cbSecond.setMaximumRowCount(5);
        // Кнопка добавления элемента в модель данных
        JButton btnAdd = new JButton("Добавить");
        btnAdd.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                // Выбираем позицию предпоследнего элемента
                int pos = cbModel.getSize() - 1;
                cbModel.insertElementAt("~ Добавленная строка ~", pos);
            }
        });

        // Размещение компонентов в интерфейсе и вывод окна
        JPanel contents = new JPanel();
        contents.add  (cbSecond);
        contents.add  (cbFirst );
        contents.add  (btnAdd  );
        setContentPane(contents);
        setSize(450, 180);
        setVisible(true);
    }
    public static void main(String[] args) {
        new ComboBoxTest();
    }
}

В приведенном примере используется стандартная модель DefaultComboBoxModel. Интерфейс примера использования раскрывающихся списков JComboBox представлен на следующих скриншотах.

В примере 2-ой элемент списка "Гараж" меяется на "Автопарк". Для удобства работы со списком было установлено количество видимых на экране элементов с использованием метода setMaximumRowCount(int).

Данные следует добавлять в модель до присоединения к виду с использованием метода addElement. После присоединения модели к списку она начинает оповещать вид о каждом изменении в своих данных, что может замедлить работу приложения.

Скачать примеры

Исходные коды примеров, рассмотренных на странице, можно скачать здесь (5 Кб).

  Рейтинг@Mail.ru