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

Окончание работы с GUI

Для окончательного вида нам необходимо сделать еще три вещи:

  1. Сделать диалоговое окно для ввода данных при перемещении студентов из одной группы в другую
  2. Сделать диалог для ввода данных для студента
  3. Сделать форму для вывода отчета по всем студентам
Давайте начнем с формы для перемещения студентов из группы в группу. Сама по себе форма не сложная - давайте просто посмотрим код для класса и после этого код для вызова этой формы и реализации команды. Итак, наш новый класс GroupDialog.
Самое главное находится в его конструкторе - тут мы создаем все необходимые элементы - выпадающий список для групп и спинер для года. Также обратите внимание на то, как мы обрабатываем нажатие кнопок - ловим событие листенером и устанавливаем внутренне поле result в true или false.
Если объявить result как int, то по идее можно устанавливать фактически любое количество возможных результатов - т.е. любая кнопка может закрыть окно и можно потом проверить, какая конкретно была нажата.
Кроме этого мы воспользовались достаточно сложным layout'ом - GridBagLayout. Вы можете почитать о нем в статье Что такое LayoutManager

GroupDialog.java

package students.frame;

import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import java.util.Vector;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;

import students.logic.Group;

public class GroupDialog extends JDialog implements ActionListener {

    private static final int D_HEIGHT = 150;   // высота
    private final static int D_WIDTH = 200;   // ширина
    private JSpinner spYear;
    private JComboBox groupList;
    private JButton btnOk = new JButton("OK");
    private JButton btnCancel = new JButton("Cancel");
    private boolean result = false;

    public GroupDialog(int year, List groups) {
        // Установить заголовок
        setTitle("Перенос группы");

        // Создаем сложный layout для нашего окна
        GridBagLayout gbl = new GridBagLayout();
        setLayout(gbl);
        // Создаем переменную для установки правил размещения
        GridBagConstraints c = new GridBagConstraints();
        // Сразу задаем отступ от границ для каждого элемента
        c.insets = new Insets(5, 5, 5, 5);

        // Первый элемент - заголовок для поля выбора групп
        JLabel l = new JLabel("Новая группа:");
        // После него можно будет еще помещать компоненты
        c.gridwidth = GridBagConstraints.RELATIVE;
        // Не заполняем все пространство, отведенное компоненту
        c.fill = GridBagConstraints.NONE;
        // "Привязываем" компонент к правому краю
        c.anchor = GridBagConstraints.EAST;
        // Устанавливаем это правило для нашего компонета
        gbl.setConstraints(l, c);
        // Добавляем компонент
        getContentPane().add(l);

        // Второй элемент - список групп
        groupList = new JComboBox(new Vector(groups));
        // Элемент занимает всю оставшуюся ширину
        c.gridwidth = GridBagConstraints.REMAINDER;
        // Растягиваем компонент по всему пространству для него
        c.fill = GridBagConstraints.BOTH;
        // "Привязываем" его к левой части
        c.anchor = GridBagConstraints.WEST;
        // Устанавливаем это правило для нашего компонета
        gbl.setConstraints(groupList, c);
        // Добавляем компонент
        getContentPane().add(groupList);

        // Третий элемент - заголовок для поля выбора года
        l = new JLabel("Новый год:");
        // После него можно будет еще помещать компоненты
        c.gridwidth = GridBagConstraints.RELATIVE;
        // Не заполняем все пространство, отведенное компоненту
        c.fill = GridBagConstraints.NONE;
        // "Привязываем" компонент к правому краю
        c.anchor = GridBagConstraints.EAST;
        // Устанавливаем это правило для нашего компонета
        gbl.setConstraints(l, c);
        // Добавляем компонент
        getContentPane().add(l);

        // Сразу увеличиваем группу на один год - для перевода
        spYear = new JSpinner(new SpinnerNumberModel(year + 1, 1900, 2100, 1));
        // Элемент занимает всю оставшуюся ширину
        c.gridwidth = GridBagConstraints.REMAINDER;
        // Растягиваем компонент по всему пространству для него
        c.fill = GridBagConstraints.BOTH;
        // "Привязываем" его к левой части
        c.anchor = GridBagConstraints.WEST;
        // Устанавливаем это правило для нашего компонета
        gbl.setConstraints(spYear, c);
        // Добавляем компонент
        getContentPane().add(spYear);

        c.gridwidth = GridBagConstraints.RELATIVE;
        c.fill = GridBagConstraints.BOTH;
        btnOk.setName("OK");
        // Добавляем листенер для кнопки
        btnOk.addActionListener(this);
        // Устанавливаем это правило для нашего компонета
        gbl.setConstraints(btnOk, c);
        // Добавляем компонент
        getContentPane().add(btnOk);

        btnCancel.setName("Cancel");
        // Добавляем листенер для кнопки
        btnCancel.addActionListener(this);
        // Устанавливаем это правило для нашего компонета
        gbl.setConstraints(btnCancel, c);
        // Добавляем компонент
        getContentPane().add(btnCancel);

        // Устанавливаем поведение формы при закрытии - не закрывать совсем.
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);

        // Получаем размеры экрана
        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        // А теперь просто помещаем его по центру, вычисляя координаты на основе полученной информации
        setBounds(((int) d.getWidth() - GroupDialog.D_WIDTH) / 2, ((int) d.getHeight() - GroupDialog.D_HEIGHT) / 2,
                GroupDialog.D_WIDTH, GroupDialog.D_HEIGHT);
    }

    // Возврат года, который установлен на форме
    public int getYear() {
        return ((SpinnerNumberModel) spYear.getModel()).getNumber().intValue();
    }

    // Возврат группы, которая установлена на форме
    public Group getGroup() {
        if (groupList.getModel().getSize() > 0) {
            return (Group) groupList.getSelectedItem();
        }
        return null;
    }

    // Получить результат, true - кнопка ОК, false - кнопка Cancel
    public boolean getResult() {
        return result;
    }

    // Обработка нжатия кнопок
    public void actionPerformed(ActionEvent e) {
        JButton src = (JButton) e.getSource();
        if (src.getName().equals("OK")) {
            result = true;
        }
        if (src.getName().equals("Cancel")) {
            result = false;
        }
        setVisible(false);
    }
}
        

Пока не будем приводить полностью код StudentsFrame. Покажем только наш новый вариант метода moveGroup

    // метод для переноса группы
    private void moveGroup() {
        Thread t = new Thread() {
            public void run() {
                // Если группа не выделена - выходим. Хотя это крайне маловероятно
                if (grpList.getSelectedValue() == null) {
                    return;
                }
                try {
                    // Получаем выделенную группу
                    Group g = (Group) grpList.getSelectedValue();
                    // Получаем число из спинера
                    int y = ((SpinnerNumberModel) spYear.getModel()).getNumber().intValue();
                    // Создаем наш диалог
                    GroupDialog gd = new GroupDialog(y, ms.getGroups());
                    // Задаем ему режим модальности - нельзя ничего кроме него выделить
                    gd.setModal(true);
                    // Показываем диалог
                    gd.setVisible(true);
                    // Если нажали кнопку OK - перемещаем в новую группу с новым годом
                    // и перегружаем список студентов
                    if (gd.getResult()) {
                        ms.moveStudentsToGroup(g, y, gd.getGroup(), gd.getYear());
                        reloadStudents();
                    }
                } catch (SQLException e) {
                    JOptionPane.showMessageDialog(StudentsFrame.this, e.getMessage());
                }
            }
        };
        t.start();
    }
        

Теперь посмотрим, каким образом нам сделать класс для редактирования данных о студенте. Этот класс может служить как для добавления так и для редактирования - поля будут одинаковые. Отметим, что ИД студента должно где-то храниться и если этот ИД будет больше 0 - это может служить нам знаком, что студента надо редактировать - т.е. выполнить обновление данных. Если же ИД равен 0, то это значит, что данные надо будет добавить. Также у нас есть два варианта передачи данных:

  1. Передавть отдельно по всем полям, т.е. разбивать данные о студенте на строки, цифры и даты и передавать каждое поле отдельно своим сеттером и геттером.
  2. Передавать сразу всего студента целиком как экземпляр класса и получать его также
Мы выберем второй способ. Первый может быть привлекателен только если мы хотим сделать некое универсальное окно, которое потом использовать в других проектах. Но я думаю, что это больше затруднит нам работу. Количество методов будет велико и, на мой взгляд, не оправдано. Таким образом мы сделаем два метода setStudent/getStudent. setStudent распределит данные о студенте по всем полям, а getStudent соберет данные со всех полей и отдаст нам уже готовый экземпляр типа Student. Значение поля StudentId в экземпляре Student скажет нам, добавлять нового студента или изменить данные о существующем.
Также необходимо отметить, что нам потребуется конструктор для класса Student - сейчас он конструируется прямо из ResultSet. Нам же необходимо сделать конструктор без параметров. Он в общем-то совсем ничего не делает, но правила языка требуют. Мы ранее упоминали, что при добавлении студента программа будет позволять добавлять студента без закрытия окна - это удобно, когда набирают данные о новой группе. В этом случае мы оставляем данные о группе и годе такими же, а остальные поля очищаем. Всего у нас будет 7 полей:
  • Три обычных тескстовых поля - Фамилия, Имя, отчество
  • Спинер для выбора даты рожения - не очень он мне нравится, но вы можете его исправить до того, чтобы Вам хотелось
  • Радио-кнопки для выбора пола - муж/жен
  • Выпадающий список с группами
  • Спинер для выбора года обучения
Автор не претендует на высокий уровень дизайна данного окна - возможно специалисты по GUI выявят много недостатков. Моя цель - показать возможности GUI. Так что вы можете самостоятельно подправить неудачные места. Кроме этого отметим, что нам необходимо будет перегружать данные о студентах не выходя из диалогового окна - значит нам, во-первых, потребуется сделать метод StudentsFrame.reloadStudents() доступным (public). И также мы в конструкторе передаем ссылку на главную форму StudentsFrame чтобы можно было вызвать данный метод. Итак, вот наш класс для ввода данных для студента.

StudentDialog.java

package students.frame;

import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Vector;

import javax.swing.AbstractButton;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JRadioButton;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerDateModel;
import javax.swing.SpinnerNumberModel;

import students.logic.Group;
import students.logic.ManagementSystem;
import students.logic.Student;

public class StudentDialog extends JDialog implements ActionListener {

    private static final int D_HEIGHT = 200;   // высота окна
    private final static int D_WIDTH = 450;   // ширина окна
    private final static int L_X = 10;      // левая граница метки для поля
    private final static int L_W = 100;     // ширина метки для поля
    private final static int C_W = 150;     // ширина поля
    // Владелец нашего окна - вводим для вызова нужного нам метода
    private StudentsFrame owner;
    // Результат нажатия кнопок
    private boolean result = false;
    // Параметры студента
    private int studentId = 0;
    private JTextField firstName = new JTextField();
    private JTextField surName = new JTextField();
    private JTextField patronymic = new JTextField();
    private JSpinner dateOfBirth = new JSpinner(new SpinnerDateModel(new Date(), null, null, Calendar.DAY_OF_MONTH));
    private ButtonGroup sex = new ButtonGroup();
    private JSpinner year = new JSpinner(new SpinnerNumberModel(2006, 1900, 2100, 1));
    private JComboBox groupList;

    // Второй параметр содержит знак - добавление студента или исправление
    public StudentDialog(List<Group> groups, boolean newStudent, StudentsFrame owner) {
        // После вставки студента без закрытия окна нам потребуется перегрузка списка
        // А для этого нам надо иметь доступ к этому методу в главной форме
        this.owner = owner;
        // Установить заголовок
        setTitle("Редактирование данных студента");
        getContentPane().setLayout(new FlowLayout());

        groupList = new JComboBox(new Vector<Group>(groups));

        JRadioButton m = new JRadioButton("Муж");
        JRadioButton w = new JRadioButton("Жен");
        // Сделаем имя такое же, как требуется в баще данных - М/Ж
        m.setActionCommand("М");
        w.setActionCommand("Ж");
        // Добавим радио-кнопки в группу
        sex.add(m);
        sex.add(w);

        // Не будем использовать layout совсем
        getContentPane().setLayout(null);

        // Разместим компоненты по абсолютным координатам
        // Фамилия
        JLabel l = new JLabel("Фамилия:", JLabel.RIGHT);
        l.setBounds(L_X, 10, L_W, 20);
        getContentPane().add(l);
        surName.setBounds(L_X + L_W + 10, 10, C_W, 20);
        getContentPane().add(surName);
        // Имя
        l = new JLabel("Имя:", JLabel.RIGHT);
        l.setBounds(L_X, 30, L_W, 20);
        getContentPane().add(l);
        firstName.setBounds(L_X + L_W + 10, 30, C_W, 20);
        getContentPane().add(firstName);
        // Отчество
        l = new JLabel("Отчество:", JLabel.RIGHT);
        l.setBounds(L_X, 50, L_W, 20);
        getContentPane().add(l);
        patronymic.setBounds(L_X + L_W + 10, 50, C_W, 20);
        getContentPane().add(patronymic);
        // Пол
        l = new JLabel("Пол:", JLabel.RIGHT);
        l.setBounds(L_X, 70, L_W, 20);
        getContentPane().add(l);
        m.setBounds(L_X + L_W + 10, 70, C_W / 2, 20);
        getContentPane().add(m);
        // Сделаем по умолчанию женщину - из уважения
        w.setBounds(L_X + L_W + 10 + C_W / 2, 70, C_W / 2, 20);
        w.setSelected(true);
        getContentPane().add(w);
        // Дата рождения
        l = new JLabel("Дата рождения:", JLabel.RIGHT);
        l.setBounds(L_X, 90, L_W, 20);
        getContentPane().add(l);
        dateOfBirth.setBounds(L_X + L_W + 10, 90, C_W, 20);
        getContentPane().add(dateOfBirth);
        // Группа
        l = new JLabel("Группа:", JLabel.RIGHT);
        l.setBounds(L_X, 115, L_W, 25);
        getContentPane().add(l);
        groupList.setBounds(L_X + L_W + 10, 115, C_W, 25);
        getContentPane().add(groupList);
        // Год обучения
        l = new JLabel("Год обучения:", JLabel.RIGHT);
        l.setBounds(L_X, 145, L_W, 20);
        getContentPane().add(l);
        year.setBounds(L_X + L_W + 10, 145, C_W, 20);
        getContentPane().add(year);

        JButton btnOk = new JButton("OK");
        btnOk.setName("OK");
        btnOk.addActionListener(this);
        btnOk.setBounds(L_X + L_W + C_W + 10 + 50, 10, 100, 25);
        getContentPane().add(btnOk);

        JButton btnCancel = new JButton("Cancel");
        btnCancel.setName("Cancel");
        btnCancel.addActionListener(this);
        btnCancel.setBounds(L_X + L_W + C_W + 10 + 50, 40, 100, 25);
        getContentPane().add(btnCancel);

        if (newStudent) {
            JButton btnNew = new JButton("New");
            btnNew.setName("New");
            btnNew.addActionListener(this);
            btnNew.setBounds(L_X + L_W + C_W + 10 + 50, 70, 100, 25);
            getContentPane().add(btnNew);
        }

        // Устанавливаем поведение формы при закрытии - не закрывать совсем.
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);

        // Получаем размеры экрана
        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        // А теперь просто помещаем его по центру, вычисляя координаты на основе полученной информации
        setBounds(((int) d.getWidth() - StudentDialog.D_WIDTH) / 2, ((int) d.getHeight() - StudentDialog.D_HEIGHT) / 2,
                StudentDialog.D_WIDTH, StudentDialog.D_HEIGHT);
    }

    // Установить поля соответственно переданным данным о студенте
    public void setStudent(Student st) {
        studentId = st.getStudentId();
        firstName.setText(st.getFirstName());
        surName.setText(st.getSurName());
        patronymic.setText(st.getPatronymic());
        dateOfBirth.getModel().setValue(st.getDateOfBirth());
        for (Enumeration e = sex.getElements(); e.hasMoreElements();) {
            AbstractButton ab = (AbstractButton) e.nextElement();
            ab.setSelected(ab.getActionCommand().equals(new String("" + st.getSex())));
        }
        year.getModel().setValue(new Integer(st.getEducationYear()));
        for (int i = 0; i < groupList.getModel().getSize(); i++) {
            Group g = (Group) groupList.getModel().getElementAt(i);
            if (g.getGroupId() == st.getGroupId()) {
                groupList.setSelectedIndex(i);
                break;
            }
        }
    }

    // Вернуть данные в виде нового студента с соотвтествующими полями
    public Student getStudent() {
        Student st = new Student();
        st.setStudentId(studentId);
        st.setFirstName(firstName.getText());
        st.setSurName(surName.getText());
        st.setPatronymic(patronymic.getText());
        Date d = ((SpinnerDateModel) dateOfBirth.getModel()).getDate();
        st.setDateOfBirth(d);
        for (Enumeration e = sex.getElements(); e.hasMoreElements();) {
            AbstractButton ab = (AbstractButton) e.nextElement();
            if (ab.isSelected()) {
                st.setSex(ab.getActionCommand().charAt(0));
            }
        }
        int y = ((SpinnerNumberModel) year.getModel()).getNumber().intValue();
        st.setEducationYear(y);
        st.setGroupId(((Group) groupList.getSelectedItem()).getGroupId());
        return st;
    }

    // Получить результат, true - кнопка ОК, false - кнопка Cancel
    public boolean getResult() {
        return result;
    }

    public void actionPerformed(ActionEvent e) {
        JButton src = (JButton) e.getSource();
        // Добавляем студента, но не закрываем окно
        // Здесь мы не будем вызывать в отдельном потоке сохранение.
        // Оно не занимаем много времени и лишние действия здесь не оправданы
        if (src.getName().equals("New")) {
            result = true;
            try {
                ManagementSystem.getInstance().insertStudent(getStudent());
                owner.reloadStudents();
                firstName.setText("");
                surName.setText("");
                patronymic.setText("");
            } catch (Exception sql_e) {
                JOptionPane.showMessageDialog(this, sql_e.getMessage());
            }
            return;
        }
        if (src.getName().equals("OK")) {
            result = true;
        }
        if (src.getName().equals("Cancel")) {
            result = false;
        }
        setVisible(false);
    }
}
        

Теперь нам осталось сделать только форму для показа полного списка студентов. И этот момент мне бы хотелось оставить вам для самостоятельного изучения. Если очень захочется узнать, как это надо сделать - пишите свои отзывы. И если их будет много - я что-нибудь придумаю.

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

javac students/frame/*.java students/logic/*.java
Для сборки примера
javac -encoding UTF-8 students/frame/*.java students/logic/*.java

Для запуска нам надо указать в CLASSPATH файл mysqlJDBC-3.1.13-bin.jar

java -cp .;mysql-connector-java-3.1.13-bin.jar students.frame.StudentsFrame

Итак, мы в общих чертах прошли по возможностям Java в части работы с коллекциями, базами данных и пользовательским интерфейсом. Теперь нас ждет путешествие в мир Web - мы посмотрим, как можно создавать Интернет-приложения, как пользоваться сервлетами, java-server pages (JSP) и другими технологиями Java для мира Интернет. Представляем - Часть 7 - Первые шаги в Интернет

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