Окончание работы с GUI
Для окончательного вида нам необходимо сделать еще три вещи:
- Сделать диалоговое окно для ввода данных при перемещении студентов из одной группы в другую
- Сделать диалог для ввода данных для студента
- Сделать форму для вывода отчета по всем студентам
Давайте начнем с формы для перемещения студентов из группы в группу. Сама по себе форма не сложная — давайте просто посмотрим код для класса и после этого код для вызова этой формы и реализации команды. Итак, наш новый класс GroupDialog.
Самое главное находится в его конструкторе — тут мы создаем все необходимые элементы — выпадающий список для групп и спинер для года. Также обратите внимание на то, как мы обрабатываем нажатие кнопок — ловим событие листенером и устанавливаем внутренне поле result в true или false.
Если объявить result как int, то по идее можно устанавливать фактически любое количество возможных результатов — т.е. любая кнопка может закрыть окно и можно потом проверить, какая конкретно была нажата.
Кроме этого мы воспользовались достаточно сложным layout’ом — GridBagLayout. Вы можете почитать о нем в статье Что такое LayoutManager
GroupDialog.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// метод для переноса группы 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, то это значит, что данные надо будет добавить. Также у нас есть два варианта передачи данных:
- Передавть отдельно по всем полям, т.е. разбивать данные о студенте на строки, цифры и даты и передавать каждое поле отдельно своим сеттером и геттером.
- Передавать сразу всего студента целиком как экземпляр класса и получать его также
Мы выберем второй способ. Первый может быть привлекателен только если мы хотим сделать некое универсальное окно, которое потом использовать в других проектах. Но я думаю, что это больше затруднит нам работу. Количество методов будет велико и, на мой взгляд, не оправдано. Таким образом мы сделаем два метода setStudent/getStudent. setStudent распределит данные о студенте по всем полям, а getStudent соберет данные со всех полей и отдаст нам уже готовый экземпляр типа Student. Значение поля StudentId в экземпляре Student скажет нам, добавлять нового студента или изменить данные о существующем.
Также необходимо отметить, что нам потребуется конструктор для класса Student — сейчас он конструируется прямо из ResultSet. Нам же необходимо сделать конструктор без параметров. Он в общем-то совсем ничего не делает, но правила языка требуют. Мы ранее упоминали, что при добавлении студента программа будет позволять добавлять студента без закрытия окна — это удобно, когда набирают данные о новой группе. В этом случае мы оставляем данные о группе и годе такими же, а остальные поля очищаем. Всего у нас будет 7 полей:
- Три обычных тескстовых поля — Фамилия, Имя, отчество
- Спинер для выбора даты рожения — не очень он мне нравится, но вы можете его исправить до того, чтобы Вам хотелось
- Радио-кнопки для выбора пола — муж/жен
- Выпадающий список с группами
- Спинер для выбора года обучения
Автор не претендует на высокий уровень дизайна данного окна — возможно специалисты по GUI выявят много недостатков. Моя цель — показать возможности GUI. Так что вы можете самостоятельно подправить неудачные места. Кроме этого отметим, что нам необходимо будет перегружать данные о студентах не выходя из диалогового окна — значит нам, во-первых, потребуется сделать метод StudentsFrame.reloadStudents()доступным (public). И также мы в конструкторе передаем ссылку на главную форму StudentsFrame чтобы можно было вызвать данный метод. Итак, вот наш класс для ввода данных для студента.
StudentDialog.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
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 — Первые шаги в Интернет
Архив с исходными кодами: Исходный код