О сайте Начало Java Студенческий отдел кадров Статьи Курсы по Java Вопросы/Ответы

Список - model и listeners. Пример попроще

Давайте рассмотрим постепенно все необходимые нам части пользовательского интерфейса. И начнем мы со списка групп. Здесь нас будет интересовать два момента:

  • Возможность реагировать на изменение выбранной строки в списке. Нам необходимо показывать список студентов выделенной группы.
  • Возможность добавить/удалить элемент списка.
В нашем "отделе кадров" мы не ставили себе задачу редактировать именно список - коллекция студентов будет представлен в виде таблицы. Список будет содержать группы, который мы не собирались редактировать. Но для понимания основ лучше будет пока рассмотреть именно список. Идеологически редактирование данных в таблице будет очень похоже. В данной части мы пока оставим наш "отдел кадров" в стороне, чтобы не было сразу сложно. Нам ведь предстоит немало потрудиться. И если писать сразу весь код, то скорее всего Вы запутаетесь. Поэтому рассмотрим все на тестовом примере - создадим простую форму и положим на нее всего лишь один объект - список. В SWING для этого существует специальный класс - JList. Позже добавим еще пару кнопок и панель.
Наше самое простое приложение будет выглядеть вот так:

import java.util.*;
import javax.swing.*;

public class Test extends JFrame {

    private JList list;

    public Test() {
        // Для инициализации списка нам потребуется вектор - посмотрите коллекции
        Vector v = new Vector();
        v.add("1");
        v.add("2");
        v.add("3");
        v.add("4");
        v.add("5");
        list = new JList(v);

        // Добавляем список на панель формы
        getContentPane().add(new JScrollPane(list));

        // Устанавливаем границы
        setBounds(100, 100, 200, 200);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                Test t = new Test();
                t.setDefaultCloseOperation(t.EXIT_ON_CLOSE);
                t.setVisible(true);
            }
        });
    }
}
        

В общем ничего сложного пока нет. Давайте теперь добавим в наш список возможность реагировать на события - а именно на перемещение указателя. Теперь мы вплотную подошли в слушателям (listeners). По сути они и являются теми самыми контроллерами в паттерне MVC (Model-View-Controller). Если Вы внимательно читали первую часть посвященную SWING, то помните, что механизм листенеров (автор приносит свои извинения за русский вариант английского слова, но в программистской среде понятие "слушатель" встречается крайне редко) основан на простой идее - объект, который принимает какие-либо события может содержать список объектов, которые "подписались" на "прослушку" событий. Т.е. для примера - кнопка при своем нажатии проверяет список листенеров и каждому посылает сообщение, что ее нажали. Предварительно каждый, кто хочет знать о нажатии добавляет себя в список листенеров для кнопки. Чтобы принять сообщение от кнопки листенер должен реализовать интерфейс. Давайте посмотрим это на примере.
Для списка листенер должен реализовать интерфейс ListSelectionListener. В нашем случае листенером будет сама форма. Также мы сразу введем режим выделения для списка - только одна строка. Список может выделять несколько пунктов сразу, но нас этот режим не интересует. Кто хочет рассмотреть подробно - читайте документацию.

import java.util.*;
import javax.swing.*;
import javax.swing.event.*;

// Обратите внимание, что теперь наша форма реализует интерфейс
public class Test extends JFrame implements ListSelectionListener {

    private JList list;

    public Test() {
        // Для инициализации списка нам потребуется вектор - посмотрите коллекции
        Vector v = new Vector();
        v.add("1");
        v.add("2");
        v.add("3");
        v.add("4");
        v.add("5");
        list = new JList(v);

        // Вот здесь выставляем режим выделения одного пункта
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        // Список будет посылать сообщения форме
        list.addListSelectionListener(this);

        // Добавляем список на панель формы
        getContentPane().add(new JScrollPane(list));

        // Устанавливаем границы  
        setBounds(100, 100, 200, 200);
    }

    // Единственный метод, который реализует интерфейс ListSelectionListener
    public void valueChanged(ListSelectionEvent e) {
        System.out.println("New index:" + list.getSelectedIndex());
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                Test t = new Test();
                t.setDefaultCloseOperation(t.EXIT_ON_CLOSE);
                t.setVisible(true);
            }
        });
    }
}
        

Теперь если мы запустим наше приложение из командной строки, то при перемещении по списку мы увидим на экране строку с номером индекса. Но если Вы попробуете изменять строки мышкой, то заметите, что наш метод valueChanged вызывается ДВА раза. Это связано с тем, что список может выделять сразу много строк. В нашем случае нам этот эффект больше мешает, чем помогает. Но мы можем найти на него управу - оказывается объект класса ListSelectionEvent имеет метод getValueIsAdjusting который возвращает true если событие вызывается по причине выбора нескольких пунктов. Нам интересен момент, когда этот метод возвращает false - нам же не надо отслеживать многострочные выделения внутри нашего списка. Поэтому наш пример немного изменится.

import java.util.*;
import javax.swing.*;
import javax.swing.event.*;

// Обратите внимание, что теперь наша форма реализует интерфейс
public class Test extends JFrame implements ListSelectionListener {

    private JList list;

    public Test() {
        // Для инициализации списка нам потребуется вектор - посмотрите коллекции
        Vector v = new Vector();
        v.add("1");
        v.add("2");
        v.add("3");
        v.add("4");
        v.add("5");
        list = new JList(v);

        // Вот здесь выставляем режим выделения одного пункта
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        // Список будет посылать сообщения форме
        list.addListSelectionListener(this);

        // Добавляем список на панель формы
        getContentPane().add(new JScrollPane(list));

        // Устанавливаем границы
        setBounds(100, 100, 200, 200);
    }

    // Единственный метод, который реализует интерфейс ListSelectionListener
    public void valueChanged(ListSelectionEvent e) {
        if (!e.getValueIsAdjusting()) {
            System.out.println("New index:" + list.getSelectedIndex());
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                Test t = new Test();
                t.setDefaultCloseOperation(t.EXIT_ON_CLOSE);
                t.setVisible(true);
            }
        });
    }
}
        

Теперь давайте попробуем изменять наш список динамически. Если вы будете пробовать просто добавлять в наш вектор какие-либо значения, то эффекта на экране вы не увидите. Вернее может и увидите, но что там точно будет гарантировать я не могу. Как уже упоминалось, нам надо действовать через модель. Модель можно получить вызовом JList.getModel(). Этот метод возвращает нам не класс, а интерфейс ListModel. Конечно же за ним кроется какой-то настоящий класс, но в том удобство и состоит, что вы можете написать свою модель, которая обязана реализовать интерфейс ListModel по своему вкусу, и расширить его теми функциями, которые Вам необходимы. Давайте напишем пример, который будет добавлять в наш список строки. Предварительно сделаем вот какое замечание - наш список на самом деле уже имеет некоторую модель. Но она не является стандартной и что она умеет точно - мы не знаем. В пакете SWING есть уже готовая модель, которая умеет то, что нам надо - а именно добавлять строки. Это DefaultListModel. Давайте ее и используем. Кроме этого мы добавим кнопку, которая будет давать команду на добавление. Для "прослушивания" событий от кнопки нам надо реализовать интерфейс ActionListener. В нем тоже всего один метод - actionPerformed.

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

// Обратите внимание, что теперь наша форма реализует интерфейс
public class Test extends JFrame implements ListSelectionListener, ActionListener {

    private JList list;
    private JButton add = new JButton("Add");

    public Test() {
        list = new JList();
        // Вот здесь выставляем режим выделения одного пункта
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        // Создадим модель и отдади ее нашему списку вместо его стандартной
        DefaultListModel dlm = new DefaultListModel();
        list.setModel(dlm);

        // Список будет посылать сообщения форме
        list.addListSelectionListener(this);

        // Кнопка тоже будет посылать сообщения форме
        add.addActionListener(this);

        // Добавляем список на панель формы
        getContentPane().add(new JScrollPane(list), BorderLayout.CENTER);

        // Добавляем кнопку в нижнюю часть
        getContentPane().add(add, BorderLayout.SOUTH);

        // Устанавливаем границы
        setBounds(100, 100, 200, 200);
    }

    // Единственный метод, который реализует интерфейс ListSelectionListener
    public void valueChanged(ListSelectionEvent e) {
        if (!e.getValueIsAdjusting()) {
            System.out.println("New index:" + list.getSelectedIndex());
        }
    }

    public void actionPerformed(ActionEvent e) {
        // Мы знаем, что у списка модель класса DefaultListModel
        // И поэтому можем ее привести к такому типу
        DefaultListModel dlm = (DefaultListModel) list.getModel();
        // Модель позволяет добавить новый элемент
        dlm.addElement(String.valueOf(dlm.getSize()));
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                Test t = new Test();
                t.setDefaultCloseOperation(t.EXIT_ON_CLOSE);
                t.setVisible(true);
            }
        });
    }
}
        

И наконец напишем пример, которы не только добавляет, но и удаляет. Для этого добавим еще одну кнопку. Заметьте, что обработчик для обеих кнопок будет один и тот же. Для того, чтобы узнать какая кнопка нажата будем использовать методы getName/setName. В примере есть одно неудобство - Вам надо каждый раз при удалении выделять строку которую вы хотите удалить. Но рещение этой проблемки автор оставляет для читателя. А здесь мы приведем окончательный результат нашей работы.

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

// Обратите внимание, что теперь наша форма реализует интерфейс
public class Test extends JFrame implements ListSelectionListener, ActionListener {

    private JList list;

    public Test() {
        list = new JList();
        // Вот здесь выставляем режим выделения одного пункта
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        // Создадим модель и отдади ее нашему списку вместо его стандартной
        DefaultListModel dlm = new DefaultListModel();
        list.setModel(dlm);

        // Список будет посылать сообщения форме
        list.addListSelectionListener(this);

        JButton add = new JButton("Add");
        JButton del = new JButton("Del");

        // Кнопка тоже будет посылать сообщения форме
        add.addActionListener(this);
        del.addActionListener(this);

        // Дадим нашим кнопкам имена, чтобы их можно было различать
        // при обработке
        add.setName("add");
        del.setName("del");

        // Создадим панель для наших кнопок и сделаем ее layout
        // в виде таблицы - 1 строка, 2 столбца
        JPanel p = new JPanel();
        p.setLayout(new GridLayout(1, 2));
        p.add(add);
        p.add(del);

        // Добавляем список на панель формы
        getContentPane().add(new JScrollPane(list), BorderLayout.CENTER);

        // Добавляем панель в нижнюю часть
        getContentPane().add(p, BorderLayout.SOUTH);

        // Устанавливаем границы
        setBounds(100, 100, 200, 200);
    }

    // Единственный метод, который реализует интерфейс ListSelectionListener
    public void valueChanged(ListSelectionEvent e) {
        if (!e.getValueIsAdjusting()) {
            System.out.println("New index:" + list.getSelectedIndex());
        }
    }

    public void actionPerformed(ActionEvent e) {
        // Мы знаем, что у списка модель класса DefaultListModel
        // И поэтому можем ее привести к такому типу
        DefaultListModel dlm = (DefaultListModel) list.getModel();

        JButton sender = (JButton) e.getSource();

        if (sender.getName().equals("add")) {
            dlm.addElement(String.valueOf(dlm.getSize()));
        }
        // Проверяем имя для удаления и проверяем индекс - если он =-1,
        // значит нет выделенной строки
        if (sender.getName().equals("del") && list.getSelectedIndex() >= 0) {
            dlm.remove(list.getSelectedIndex());
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
           public void run() {
                Test t = new Test();
                t.setDefaultCloseOperation(t.EXIT_ON_CLOSE);
                t.setVisible(true);
            }
        });
    }
}
        

Для сборки нам потребуется команда

javac Test.java
Для сборки примера
javac -encoding UTF-8 Test.java

Теперь вы готовы к более сложным испытаниям, а именно к работе с наши отделом кадров. В следующей части мы с вами создадим более интересный интерфейс, чем тот, который был создан ренее. Представляем - Часть 5 - Улучшаем интерфейс и вводим команды

Архив с исходными кодами: Исходный код