После прохождения этой главы вы можете почувствовать, что Java всё ещё недостаточно зрелый язык, особенно если помните времена, когда Java впервые был представлен как "стабильный и надёжный" язык программирования компанией Sun Microsystems. Однако сегодня Java имеет отличную модель событий и великолепный дизайн повторного использования компонентов — JavaBeans. Тем не менее, графические интерфейсы пользователя кажутся довольно примитивными, громоздкими и абстрактными.
⑦: При написании этого раздела библиотека Swing была уже "фиксированной" Sun, поэтому при условии, что вы скачали и установили библиотеку Swing, вы должны были иметь возможность правильно скомпилировать и запустить код здесь без каких-либо проблем (вы должны были также скомпилировать сопровождающие демонстрационные программы от Sun, чтобы проверить правильность установки). Если возникли какие-либо проблемы, обратитесь на http://www.BruceEckel.com
, чтобы узнать о последних обновлениях.Именно это пространство представляет собой область, которую займет Swing. Библиотека Swing появилась после Java 1.1, так что мы можем естественно предположить, что она является частью Java 1.2. Однако она была спроектирована таким образом, чтобы работать как дополнение к версии Java 1.1. Это позволяет нам использовать мощную библиотеку UI-компонентов без необходимости ждать поддержки Java 1.2 нашей платформой. Если библиотека Swing не будет поддерживаться вашей версией Java 1.1 и возникнут какие-либо проблемы, вам может потребоваться скачать библиотеку Swing.Swing включает все те компоненты, которых нам не хватало, и в остальной части этой главы: мы будем стремиться понять современный интерфейс пользователя, события, происходящие от кнопок, а также изображения в деревьях и сетках. Это большая библиотека, но в некоторых аспектах она предназначена для выполнения сложных задач — если бы всё было просто, нам не пришлось бы писать больше кода, но наш код постепенно становился бы более сложным. Это означает легкий вход, если нам это требуется, и получение её мощи, если нам это нужно.
Swing достаточно глубоко продуман; этот раздел не попытается полностью объяснить читателям, но представит возможности Swing и то, как он делает использование библиотеки простым. Обратите внимание, что мы осознанно используем всё это для упрощения процесса. Если нам требуется больше возможностей, тогда Swing может предоставить нам то, что нам нужно, если мы готовы углубиться в него, мы можем получить больше информации из онлайн-документации компании Sun.## 13.19.1 Какие преимущества предлагает Swing?
Когда мы начинаем использовать библиотеку Swing, мы замечаем, что это огромный шаг вперед с точки зрения технологии. Компоненты Swing являются Bean, поэтому они могут использоваться во всех средах разработки, поддерживающих Bean. Swing предоставляет полный набор UI-компонентов. Из-за высокой скорости все компоненты очень легкие (не используются "тяжелые" компоненты), и Swing был полностью написан на Java для обеспечения легковесности.
Самое важное заключается в том, что мы хотели бы видеть, как Swing называют "ортогональным использованием"; как только мы применим этот универсальный подход к библиотеке, мы сможем использовать её везде. Это главным образом связано с правилами названия Bean, так что большую часть времени при написании этих примеров программ я могу догадываться о названии метода и правильно его написать с первого раза без необходимости обращаться к чему-либо другому. Это бесспорно свидетельствует о качествах отличной дизайнерской работы библиотеки. Кроме того, мы можем широко вставлять компоненты внутрь других компонентов, а события будут работать должным образом.Клавишные операции поддерживаются автоматически — мы можем использовать Swing-приложения без использования мыши, но нам придётся выполнить дополнительную работу программирования (в старой AWT требовалось огромное количество кода для поддержки клавишных операций). Прокрутка также легко поддерживается — достаточно просто поместить наш компонент в JScrollPane
, затем добавить его в нашу форму. Другие характеристики, такие как подсказки, требуют всего одной строки кода для реализации.
Swing также поддерживает некоторые вещи, известные как "внедряемые внешние виды и эффекты", то есть внешний вид UI может динамически меняться на разных платформах и различных системах операционного уровня в соответствии с ожиданиями пользователя. Можно даже создать свои собственные внешние виды и эффекты.
Если вы долго трудились над созданием нашего интерфейса пользователя с помощью версии Java 1.1, вам не обязательно отказываться от него и переходить к использованию Swing. К счастью, библиотека была спроектирована таким образом, чтобы позволить лёгкую модификацию — во многих случаях можно просто добавить префикс J
перед каждым именем наших старых AWT-компонентов. В следующем примере приведены знакомые черты:```markdown
JButtonDemo
Класс JButtonDemo
является расширением апплета (Applet
). В этом примере используются компоненты Swing: JButton
, JTextField
.
public class JButtonDemo extends Applet {
JButton
b1 = new JButton("JButton 1"),
b2 = new JButton("JButton 2");
JTextField t = new JTextField(20);
public void init() {
ActionListener al = new ActionListener() {
public void actionPerformed(ActionEvent e) {
String name =
((JButton)e.getSource()).getText();
t.setText(name + " была нажата");
}
};
b1.addActionListener(al);
add(b1);
b2.addActionListener(al);
add(b2);
add(t);
}
public static void main(String args[]) {
JButtonDemo applet = new JButtonDemo();
JFrame frame = new JFrame("TextAreaNew");
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
frame.getContentPane().add(applet, BorderLayout.CENTER);
frame.setSize(300, 100);
applet.init();
applet.start();
frame.setVisible(true);
}
}
///:~
Это новый вводный текст, но за исключением добавленной буквы J
, всё остальное выглядит как версия Java 1.1 AWT. Аналогично, мы неправильно используем метод add()
для добавления к окну Swing JFrame
. Однако нам также потребуется подготовить некоторый "content pane", как это было показано выше. Мы можем легко получить выгоду от простых изменений в Swing.
Из-за упакованного кода программы, нам приходится вызывать программу следующим образом:
java c13.swing.JButtonDemo
```
Все программы, представленные в этом разделе, будут требовать одного и того же окна для запуска.
## 13.19.3 Отображение окон
Хотя фрагменты кода и приложения могут стать важными, их использование повсюду может привести к путанице и бесполезности. Оставшаяся часть этого раздела заменяет их примером демонстрационного окна для программы на Swing:
```java
//: Show.java
// Инструмент для отображения демонстраций на Swing
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Show {
public static void
inFrame(JPanel jp, int width, int height) {
String title = jp.getClass().toString();
// Удаление слова "class":
if(title.indexOf("class") != -1)
title = title.substring(6);
JFrame frame = new JFrame(title);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
frame.getContentPane().add(jp, BorderLayout.CENTER);
frame.setSize(width, height);
frame.setVisible(true);
}
} ///:~
```
Классы, которые хотят отображаться самостоятельно, будут наследовать от `JPanel` и затем добавят некоторые визуальные компоненты. В конце концов, они создадут `main()` с содержанием следующей строки:
```
Show.inFrame(new MyClass(), 500, 300);
```
Последние два параметра представляют ширину и высоту окна.
Заметьте, что заголовок окна `JFrame` генерируется с помощью RTTI (Run-Time Type Information).
## 13.19.4 ПодсказкиБольшинство классов, используемых нами для создания пользовательского интерфейса, наследуются от `JComponent` и имеют метод `setToolTipText(String)`. Таким образом, практически любой объект (`jc`) типа `JComponent` можно поместить в окно:```java
jc.setToolTipText("Моя подсказка");
```
И когда указатель мыши находится над `JComponent` более длительное время, чем установлено заранее, появляется маленькое окно с нашей текстовой подсказкой.## 13.19.5 Бордюры
`JComponent` также включает метод `setBorder()`, который позволяет нам помещать различные интересные бордюры на видимые компоненты. В следующем примере программы используется метод `showBorder()`, который создает `JPanel` и помещает бордюр на каждый пример. Этот пример демонстрирует некоторые полезные различия бордюров. Также он использует RTTI для получения названий бордюров (удаляет все пути информации), а затем помещает название бордюра в центральной `JLabel` панели:
```java
//: Borders.java
// Различные границы Swing
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
public class Borders extends JPanel {
static JPanel showBorder(Border b) {
JPanel jp = new JPanel();
jp.setLayout(new BorderLayout());
String nm = b.getClass().toString();
nm = nm.substring(nm.lastIndexOf('.') + 1);
jp.add(new JLabel(nm, JLabel.CENTER),
BorderLayout.CENTER);
jp.setBorder(b);
return jp;
}
public Borders() {
setLayout(new GridLayout(2,4));
add(showBorder(new TitledBorder("Заголовок")));
add(showBorder(new EtchedBorder()));
add(showBorder(new LineBorder(Color.blue)));
add(showBorder(
new MatteBorder(5, 5, 30, 30, Color.green)));
add(showBorder(
new BevelBorder(BevelBorder.RAISED)));
add(showBorder(
new SoftBevelBorder(BevelBorder.LOWERED)));
add(showBorder(new CompoundBorder(
new EtchedBorder(),
new LineBorder(Color.red))));
}
public static void main(String[] args) {
Show.inFrame(new Borders(), 500, 300);
}
} ///:~
```Большинство программных примеров этого раздела используют `TitledBorder`, но мы можем заметить, что остальные бордюры также легко использовать. Мы можем создавать свои собственные бордюры и помещать их внутрь кнопок, меток и так далее — всего, что происходит от `JComponent`.
## 13.19.6 Кнопки
Swing добавляет несколько различных типов кнопок и может модифицировать структуру выборочных компонентов: все кнопки, флажки, радио-кнопки, даже меню, наследуемых от `AbstractButton`. Это потому, что меню обычно содержатся внутри него, они могут быть улучшены и переименованы как `AbstractChooser` или что-то подобное. Мы обратим внимание на удобство использования меню, этот пример демонстрирует различные доступные типы кнопок:
```java
//: Buttons.java
// Различные кнопки Swing
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.plaf.basic.*;
import javax.swing.border.*;
public class Buttons extends JPanel {
JButton jb = new JButton("Кнопка");
BasicArrowButton
up = new BasicArrowButton(
BasicArrowButton.NORTH),
down = new BasicArrowButton(
BasicArrowButton.SOUTH),
right = new BasicArrowButton(
BasicArrowButton.EAST),
left = new BasicArrowButton(
BasicArrowButton.WEST);
public Buttons() {
add(jb);
add(new JToggleButton("Переключатель"));
add(new JCheckBox("Флажок"));
add(new JRadioButton("Радиокнопка"));
JPanel jp = new JPanel();
jp.setBorder(new TitledBorder("Направления"));
jp.add(up);
jp.add(down);
jp.add(left);
jp.add(right);
add(jp);
}
public static void main(String[] args) {
Show.inFrame(new Buttons(), 300, 200);
}
} ///:~
````JButton` выглядит как кнопка AWT, но он имеет меньше возможностей для работы (например, добавление изображений, которое мы рассмотрим позже). В `com.sun.java.swing.basic` есть более подходящий `BasicArrowButton`, но как его протестировать? Есть два типа "стрелок", которые требуют использования стрелковых кнопок: `Spinner`, который изменяет промежуточное значение, и `StringSpinner`, который перемещается через массив строк (и даже автоматически переходит в начало при достижении конца массива). `ActionListeners` прикреплены к стрелковым кнопкам, демонстрирующие эти связанные стрелки: поскольку они являются Beans, мы можем использовать методы с названиями, чтобы захватывать и устанавливать их значения.
Когда мы запустим этот пример программы, мы заметим, что кнопки находятся либо в открытом, либо в закрытом состоянии. Однако флажки и радиокнопки имеют одинаковое поведение при каждом действии — выбраны или нет (они наследуются от `JToggleButton`).
## 13.19.7 Группы кнопок
Если нам нужно, чтобы радиокнопки находились в состоянии "исключающего или" ("xor"), нам следует добавить их в группу кнопок, что почти так же, как это делалось в старом AWT, но гораздо гибче. Пример программы ниже показывает, как некоторые объекты `AbstractButton` могут быть добавлены в `ButtonGroup`.Чтобы избежать повторения кода, программа использует карту для создания различных типов групп кнопок. Это можно будет видеть в методе `makeBPanel`, который создает группу кнопок и `JPanel`, а также добавляет каждый объект класса, указанный первым параметром, в массив второго параметра.
```java
//: ButtonGroups.java
// Использует рефлексию для создания групп различных типов AbstractButton.
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import java.lang.reflect.*;
``````java
public class ButtonGroups extends JPanel {
static String[] ids = {
"June", "Ward", "Beaver",
"Wally", "Eddie", "Lumpy",
};
static JPanel makeBPanel(Class bClass, String[] ids) {
ButtonGroup bg = new ButtonGroup();
JPanel jp = new JPanel();
String title = bClass.getName();
title = title.substring(
title.lastIndexOf('.') + 1);
jp.setBorder(new TitledBorder(title));
for(int i = 0; i < ids.length; i++) {
AbstractButton ab = new JButton("Не удалось создать");
try {
// Получаем динамический конструктор метод,
// который принимает аргумент типа String:
Constructor ctor = bClass.getConstructor(
new Class[] { String.class });
// Создаем новый объект:
ab = (AbstractButton)ctor.newInstance(
new Object[]{ids[i]});
} catch(Exception ex) {
System.out.println("Не удалось создать " +
bClass);
}
bg.add(ab);
jp.add(ab);
}
return jp;
}
public ButtonGroups() {
add(makeBPanel(JButton.class, ids));
add(makeBPanel(JToggleButton.class, ids));
add(makeBPanel(JCheckBox.class, ids));
add(makeBPanel(JRadioButton.class, ids));
}
public static void main(String args[]) {
Show.inFrame(new ButtonGroups(), 500, 300);
}
} ///:~
```
## 13.19.8 Иконки
Мы можем использовать иконку в любом объекте, наследуемом от `JLabel` или `AbstractButton` (включая `JButton`, `JCheckBox`, `JRadioButton` и различные типы `JMenuItem`). Использование иконок в `JLabels` очень просто (мы рассмотрим это подробнее в следующем примере программы). В этом программном примере мы исследуем все возможные способы использования иконок кнопками и их производными.
Можно использовать любой GIF-файл, но в данном примере используется GIF-файл, который является частью этого учебника и доступен для скачивания с сайта `www.BruceEckel.com`. Для открытия файла и получения связанной с ним картинки достаточно создать объект типа `ImageIcon` и передать ему имя файла. После этого можно использовать созданную иконку в программе.```java
//: Faces.java
// Поведение иконок в JButtons
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Faces extends JPanel {
static Icon[] faces = {
new ImageIcon("face0.gif"),
new ImageIcon("face1.gif"),
new ImageIcon("face2.gif"),
new ImageIcon("face3.gif"),
new ImageIcon("face4.gif")
};
JButton
jb = new JButton("JButton", faces[3]),
jb2 = new JButton("Отключить");
boolean mad = false;
public Faces() {
jb.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if(mad) {
jb.setIcon(faces[3]);
mad = false;
} else {
jb.setIcon(faces[0]);
mad = true;
}
jb.setVerticalAlignment(JButton.TOP);
jb.setHorizontalAlignment(JButton.LEFT);
}
});
jb.setRolloverEnabled(true);
jb.setRolloverIcon(faces[1]);
jb.setPressedIcon(faces[2]);
jb.setDisabledIcon(faces[4]);
jb.setToolTipText("Ух ты!");
add(jb);
jb2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if(jb.isEnabled()) {
jb.setEnabled(false);
jb2.setText("Включить");
} else {
jb.setEnabled(true);
jb2.setText("Отключить");
}
}
});
add(jb2);
}
public static void main(String args[]) {
Show.inFrame(new Faces(), 300, 200);
}
} ///:~
```
Иконка может использоваться в нескольких конструкторах, но мы можем использовать метод `setIcon()`, чтобы добавить или заменить иконку. Этот пример также демонстрирует, почему можно установить различные отображаемые иконки при событиях на кнопке `JButton` (или некоторых других `AbstractButton`): когда кнопка нажата, когда она деактивирована или "переключена" (мышь проходит над ней, но не кликает). Это придаёт кнопке анимационный эффект.Обратите внимание, что подсказка также была добавлена к кнопке. ## 13.19.9 Меню
Меню в Swing значительно улучшены и стали более гибкими — например, мы можем использовать их практически в любом месте программы, включая панели и компоненты. Грамматика остаётся такой же, как и в старых AWT, что привело к появлению некоторых проблем, таких как необходимость написания сложного кода для наших меню и отсутствие поддержки некоторых событий (что делает их менее удобными для перехода на другие языки программирования). Кроме того, код меню часто бывает очень длинным и запутанным. Одним из способов решения этой проблемы является использование двумерного массива объектов для хранения информации обо всех пунктах меню (этот метод позволяет нам помещать любую информацию в массив). Этот двумерный массив создаётся при создании меню, поэтому первым значением является имя меню, а последующие значения представляют пункты меню и их свойства. Мы заметим, что столбцы массива не обязательно должны быть одинаковыми — главное, чтобы наш код знал, какие события происходят.```java
//: Menus.java
// Система создания меню; также демонстрирует
// использование иконок в метках и элементах меню.
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Menюs extends JPanel {
static final Boolean
bT = new Boolean(true),
bF = new Boolean(false);
// Вспомогательный класс для создания типовых идентификаторов:
static class MType { MType(int i) {} };
static final MType
mi = new MType(1), // Обычный пункт меню
cb = new MType(2), // Пункт меню с флажками
rb = new MType(3); // Пункт меню с радио кнопками
JTextField t = new JTextField(10);
JLabel l = new JLabel("Выбран значок",
Faces.faces[0], JLabel.CENTER);
ActionListener a1 = new ActionListener() {
public void actionPerformed(ActionEvent e) {
t.setText(((JMenuItem)e.getSource()).getText());
}
};
ActionListener a2 = new ActionListener() {
public void actionPerformed(ActionEvent e) {
JMenuItem mi = (JMenuItem)e.getSource();
l.setText(mi.getText());
l.setIcon(mi.getIcon());
}
};
// Хранение данных меню в виде "ресурсов":
public Object[][] fileMenu = {
// Название меню и ускоренный доступ:
{ "Файл", new Character('F') },
// Название типа ускоренного доступа слушатель активность
{ "Новый", mi, new Character('N'), a1, bT },
{ "Открыть", mi, new Character('O'), a1, bT },
{ "Сохранить", mi, new Character('S'), a1, bF },
{ "Сохранить как", mi, new Character('A'), a1, bF },
{ null }, // Разделитель
{ "Выход", mi, new Character('X'), a1, bT },
};
public Object[][] editMenu = {
// Название меню:
{ "Редактирование", new Character('E') },
// Название типа ускоренного доступа слушатель активность
{ "Вырезать", mi, new Character('T'), a1, bT },
}
``````java
public Object[][] editMenu = {
{ "Копировать", mi, new Character('C'), a1, bT },
{ "Вставить", mi, new Character('V'), a1, bT },
{ null }, // Разделитель
{ "Выделить все", mi, new Character('A'), a1, bT },
};
public Object[][] helpMenu = {
{ "Помощь", new Character('H') },
{ "Индекс", mi, new Character('I'), a1, bT },
{ "Использование помощи", mi, new Character('U'), a1, bT },
{ null }, // Разделитель
{ "О программе", mi, new Character('P'), a1, bT },
};
public Object[][] optionMenu = {
{ "Настройки", new Character('S') },
{ "Настройка 1", cb, new Character('N'), a1, bT },
{ "Настройка 2", cb, new Character('G'), a1, bT },
};
public Object[][] faceMenu = {
{ "Лица", new Character('F') },
{ "Лицо 0", rb, new Character('Z'), a2, bT,
Faces.faces[0] },
};
```
``````markdown
{ "Грань 1", rb, new Character('W'), a2, bT,
Faces.faces[1] },
{ "Грань 2", rb, new Character('E'), a2, bT,
Faces.faces[2] },
{ "Грань 3", rb, new Character('R'), a2, bT,
Faces.faces[3] },
{ "Грань 4", rb, new Character('T'), a2, bT,
Faces.faces[4] },
};
public Object[] menuBar = {
fileMenu, editMenu, faceMenu,
optionMenu, helpMenu,
};
static public JMenuBar createMenuBar(Object[] menuBarData) {
JMenuBar menuBar = new JMenuBar();
for(int i = 0; i < menuBarData.length; i++)
menuBar.add(createMenu((Object[][])menuBarData[i]));
return menuBar;
}
static ButtonGroup bgroup;
static public JMenu createMenu(Object[][] menuData) {
JMenu menu = new JMenu();
menu.setText((String)menuData[0][0]);
menu.setMnemonic(((Character)menuData[0][1]).charValue());
}
``````java
// Создаем заново, в случае наличия
// радиальных кнопок:
bgroup = new ButtonGroup();
for (int i = 1; i < menuData.length; i++) {
if (menuData[i][0] == null)
menu.add(new JSeparator());
else
menu.add(createMenuItem(menuData[i]));
}
return menu;
}
static public JMenuItem createMenuItem(Object[] data) {
JMenuItem m = null;
MType type = (MType) data[1];
if (type == mi)
m = new JMenuItem();
else if (type == cb)
m = new JCheckBoxMenuItem();
else if (type == rb) {
m = new JRadioButtonMenuItem();
bgroup.add(m);
}
m.setText((String) data[0]);
m.setMnemonic(((Character) data[2]).charValue());
m.addActionListener((ActionListener) data[3]);
m.setEnabled(((Boolean) data[4]).booleanValue());
if (data.length == 6)
m.setIcon((Icon) data[5]);
return m;
}
Menus() {
setLayout(new BorderLayout());
add(createMenuBar(menuBar), BorderLayout.NORTH);
JPanel p = new JPanel();
p.setLayout(new BorderLayout());
p.add(t, BorderLayout.NORTH);
p.add(l, BorderLayout.CENTER);
add(p, BorderLayout.CENTER);
}
public static void main(String args[]) {
Show.inFrame(new Menus(), 300, 200);
}
} ///:~
```
Примечание: В данном контексте "Face" было переведено как "Грань". Это наиболее подходящее значение в контексте программирования и графики. Цель этого программного обеспечения — позволить разработчику просто создавать таблицы для описания каждого меню вместо ввода строк кода для создания меню. Каждый ряд таблицы генерирует пункт меню; первая колонка содержит имя пункта меню и клавиатурный быстрый доступ. Остальные колонки содержат данные для каждого пункта меню: строковое значение, которое находится в пункте меню, тип пункта меню, его быстрый доступ, приемник действий, активируемых при выборе пункта меню, а также информация о том, активирован ли пункт меню.
Если начальная часть столбца пуста, она будет рассматриваться как разделитель.
Для предотвращения потери времени и создания множества объектов типа `Boolean`, следующие объекты создаются как `static final`: `bT` и `bF`, которые описывают различные объекты типа `Boolean` и простой класс `MType`. Эти объекты описывают стандартные пункты меню (`mi`), пункты меню с флажками (`cb`) и пункты меню с радиокнопками (`rb`). Вспомните, что группа объектов может иметь единственную ссылку на объект, который больше не является оригинальным значением.Этот пример программы также демонстрирует, как `JLabels` и `JMenuItems` (и их производные) обрабатывают иконки. Иконка помещается в `JLabel` через его конструктор и изменяется при выборе соответствующего пункта меню.Массивы строк меню контролируют отображение всех пунктов меню, которые мы хотим показывать в строке меню. Мы используем этот массив для вызова метода `createMenuBar()`, который группирует массивы данных меню, затем создает меню из каждого отдельного массива данных. Этот подход последовательно использует каждую строку данных меню для создания `JMenu`, а затем вызывает метод `createMenuItem()` для каждой последующей строки данных меню. Наконец, метод `createMenuItem()` анализирует каждую строку данных меню, определяет тип меню и его свойства, и создает соответствующий пункт меню. Как мы видели в конструкторе меню, таблица используется для создания меню, и всё это выполняется рекурсивным способом.
Эта программа не создаёт вложенные меню, но у нас достаточно знаний, чтобы добавить многоуровневые меню, если потребуется.
### 13.19.10 Выпадающие менюВыполнение `JPopupMenu` выглядит немного странно: нам нужно вызвать метод `enableEvents()` и выбрать события мыши вместо использования приемников событий. Можно добавить приемника событий мыши, но `MouseEvent` из `isPopupTrigger()` не вернёт истину — он не знает, что активируется выпадающее меню. Кроме того, когда мы пытаемся использовать приемники событий, они ведут себя непредсказуемо, возможно, из-за одиночного клика мыши. В приведённом ниже примере программы некоторые события вызывают такое поведение выпадающего меню:
```java
//: Popup.java
// Создание всплывающих меню с помощью Swing
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
``````java
public class Popup extends JPanel {
JPopupMenu popup = new JPopupMenu();
JTextField t = new JTextField(10);
public Popup() {
add(t);
ActionListener al = new ActionListener() {
public void actionPerformed(ActionEvent e){
t.setText(((JMenuItem)e.getSource()).getText());
}
};
JMenuItem m = new JMenuItem("Здесь");
m.addActionListener(al);
popup.add(m);
m = new JMenuItem("Там");
m.addActionListener(al);
popup.add(m);
m = new JMenuItem("Далеко");
m.addActionListener(al);
popup.add(m);
popup.addSeparator();
m = new JMenuItem("Останься здесь");
m.addActionListener(al);
popup.add(m);
PopupListener pl = new PopupListener();
addMouseListener(pl);
t.addMouseListener(pl);
}
class PopupListener extends MouseAdapter {
public void mousePressed(MouseEvent e) {
maybeShowPopup(e);
}
public void mouseReleased(MouseEvent e) {
maybeShowPopup(e);
}
private void maybeShowPopup(MouseEvent e) {
if(e.isPopupTrigger()) {
popup.show(e.getComponent(), e.getX(), e.getY());
}
}
}
public static void main(String args[]) {
Show.inFrame(new Popup(),200,150);
}
} ///:~
```
То же `ActionListener`, что и для каждого `JMenuItem`, позволяет извлекать текст из меню и вставлять его в `JTextField`.
## 13.19.11 Списковые поля и выпадающие списки
Списковые поля и выпадающие списки работают в Swing так же, как они работали в старом AWT, но если нам это требуется, они также получили расширенные возможности. Кроме того, они стали еще удобнее в использовании. Например, конструктор `JList` принимает массив строк (`String`). Курьезно, что аналогичная возможность отсутствует в `JComboBox`. Ниже приведен пример использования этих компонентов.
```
//: ListCombo.java
// Списковые поля и выпадающие списки
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
``````В самом начале возникло странное явление, когда `JLists` не предоставляли автоматической поддержки прокрутки — даже если это было бы желательно. Увеличение поддержки прокрутки стало очень простым, как показано выше — достаточно просто поместить `JList` внутрь `JScrollPane`, и все детали будут автоматически учтены.
## 13.19.12 Ползунки и индикаторы прогресса
Ползунки позволяют пользователям вводить данные с помощью перемещения ползунка, что кажется интуитивно понятным во многих случаях (например, регулирование звука). Индикатор прогресса отображает состояние данных от «пустого» до «полного», таким образом, пользователи получают представление о состоянии. Пример программы, который мне нравится больше всего, просто связывает ползунок с индикатором прогресса, так что при перемещении ползунка индикатор прогресса также меняется:
```java
//: Progress.java
// Использование ползунков и индикаторов прогресса
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;
```public class Progress extends JPanel {
JProgressBar pb = new JProgressBar();
JSlider sb =
new JSlider(JSlider.HORIZONTAL, 0, 100, 60);
public Progress() {
setLayout(new GridLayout(2,1));
add(pb);
sb.setValue(0);
sb.setPaintTicks(true);
sb.setMajorTickSpacing(20);
sb.setMinorTickSpacing(5);
sb.setBorder(new TitledBorder("Переместите меня"));
pb.setModel(sb.getModel()); // Share the model
add(sb);
}
public static void main(String args[]) {
Show.inFrame(new Progress(),200,150);
}
} ///:~`JProgressBar` довольно прост, но `JSlider` имеет множество опций, таких как методы, большие или маленькие метки. Обратите внимание, насколько легко добавить рамку с заголовком.
## 13.19.13 Деревья
Использование `JTree` можно представить следующим образом:
```java
add(new JTree(
new Object[]{ "this", "that", "other" }));
```
Эта программа выводит базовое дерево. API дерева очень большой, хотя — конечно, в Swing. Это указывает на то, что мы можем делать всё, что угодно с деревьями, но более сложные задачи могут потребовать значительного исследования и экспериментов. К счастью, библиотека предоставляет компромисс: «по умолчанию» компонент дерева, который обычно является тем, что нам требуется. Таким образом, большую часть времени мы можем использовать эти компоненты, а только в особых случаях нам потребуется более глубокое исследование и понимание. Приведённый ниже пример использует компонент дерева по умолчанию для отображения дерева в панели программы. При нажатии кнопки новый поддерево добавляется к текущему выбранному узлу (если ни один узел не выбран, используется корневой узел):
```java
//: Деревья.java
// Пример простого дерева Swing. Деревья могут быть сделаны
// значительно сложнее, чем это.
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.*;
``````java
// Принимает массив строк и делает первый
// элемент узлом, а остальные — листами:
class Ветка {
DefaultMutableTreeNode r;
public Ветка(String[] данные) {
r = new DefaultMutableTreeNode(данные[0]);
for(int i = 1; i < данные.length; i++)
r.add(new DefaultMutableTreeNode(данные[i]));
}
public DefaultMutableTreeNode узел() {
return r;
}
}
```
```java
public class Trees extends JPanel {
String[][] data = {
{ "Цвета", "Красный", "Синий", "Зелёный" },
{ "Вкус", "Кислый", "Сладкий", "Безвкусный" },
{ "Длина", "Короткая", "Средняя", "Длинная" },
{ "Объём", "Высокий", "Средний", "Низкий" },
{ "Температура", "Высокая", "Средняя", "Низкая" },
{ "Интенсивность", "Высокая", "Средняя", "Низкая" },
};
static int i = 0;
DefaultMutableTreeNode root, child, chosen;
JTree tree;
DefaultTreeModel model;
public Trees() {
setLayout(new BorderLayout());
root = new DefaultMutableTreeNode("корень");
tree = new JTree(root);
// Добавляем его и делаем так, чтобы он управлял прокруткой:
add(new JScrollPane(tree), BorderLayout.CENTER);
// Получаем модель дерева:
model = (DefaultTreeModel) tree.getModel();
JButton test = new JButton("Нажмите меня");
test.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (i < data.length) {
child = new Ветка(data[i++]).узел();
// Какой последний выбранный вами элемент?
chosen = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
if (chosen == null) chosen = root;
// Модель создаст соответствующее событие. В ответ дерево обновится:
model.insertNodeInto(child, chosen, 0);
// Это помещает новый узел на текущий выбранный узел.
}
}
});
// Изменяем цвет кнопки:
``````java
test.setBackground(Color.blue);
test.setForeground(Color.white);
JPanel p = new JPanel();
p.add(test);
add(p, BorderLayout.SOUTH);
}
public static void main(String[] args) {
Show.inFrame(new Trees(), 200, 500);
}
}
///:~
```
```markdown
## Самый важный класс — это `Branch`, который является инструментом для получения массива строк и создания объекта `DefaultMutableTreeNode` как корня для первой строки в этом массиве, а остальные строки в нем используются как листья. Затем вызывается метод `node()`, чтобы создать "ветвь" с корнем. Класс `TreeObject` включает двумерный массив строк, полученный от созданной ветви, а также статическую переменную `i`, используемую для счета массива. Объект `DefaultMutableTreeNode` управляет этим узлом, но его представление на экране контролируется компонентами `JTree` и связанным с ним (`DefaultTreeModel`) моделью. Обратите внимание, что когда `JTree` добавляется к панели программы, он оборачивается в `JScrollPane` — именно так обеспечивается автоматическое прокручивание.
```Контроль над `JTree` осуществляется через его собственную модель. Когда мы изменяем эту модель, она генерирует событие, которое заставляет `JTree` выполнять все необходимые действия для обновления видимого дерева. В методе `init()` модель заполняется путём вызова метода `getModel()`. При нажатии кнопки создаётся новая ветка. Затем находится текущий выбранный узел (или корень, если ничего не выбрано), и метод `insertNodeInto()` модели выполняет все изменения дерева и обновляет его.
Как правило, программа предоставляет нам всё необходимое для работы с деревом, как показано выше. Однако дерево способно делать практически всё, что мы можем себе представить — во всех примерах используется слово "default", но мы можем заменить наши собственные классы для выполнения различных действий. Но обратите внимание: большинство этих классов имеют огромные интерфейсы, поэтому понять эти сложные деревья может потребовать времени.
## 13.19.14 ТаблицыПодобно деревьям, таблицы в Swing являются мощными и гибкими. Они были спроектированы в первую очередь как "сеточные" интерфейсы для взаимодействия с базами данных Java Database Connectivity (JDBC), и поэтому они обладают большой гибкостью, что делает их использование простым. Безусловно, это достаточно для основы полноценного электронного таблицы и могло бы стать предметом целой книги. Однако, если мы понимаем основные принципы, то это позволяет создавать простые `JTable`.```markdown
`JTable` контролирует отображение данных, но `TableModel` управляет самыми данными. Поэтому перед тем, как создать `JTable`, следует создать `TableModel`. Мы можем полностью реализовать интерфейс `TableModel`, но обычно это происходит путем наследования от абстрактного класса `AbstractTableModel`.
```java
//: Table.java
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.event.*;
// Класс DataModel управляет всеми данными:
class DataModel extends AbstractTableModel {
Object[][] data = {
{"один", "два", "три", "четыре"},
{"пять", "шесть", "семь", "восемь"},
{"девять", "десять", "одиннадцать", "двенадцать"}
};
// Вывод данных при изменении таблицы:
class TML implements TableModelListener {
public void tableChanged(TableModelEvent e) {
for(int i = 0; i < data.length; i++) {
for(int j = 0; j < data[0].length; j++)
System.out.print(data[i][j] + " ");
System.out.println();
}
}
}
DataModel() {
addTableModelListener(new TML());
}
public int getColumnCount() {
return data[0].length;
}
public int getRowCount() {
return data.length;
}
public Object getValueAt(int row, int column) {
return data[row][column];
}
public void setValueAt(Object value, int row, int column) {
data[row][column] = value;
// Указывает, что изменения произошли:
fireTableDataChanged();
}
public boolean isCellEditable(int row, int column) {
return true;
}
}
```публичный класс Table расширяет JPanel {
публичный Table() {
setLayout(новый BorderLayout());
JTable table = новый JTable(новый DataModel());
JScrollPane scrollpane = JTable.createScrollPaneForTable(table);
добавляем(scrollpane, BorderLayout.CENTER);
}
публичный статический void main(строка[] аргументы) {
Show.inFrame(новый Table(), 200, 200);
}
} ///:~
## 13.19.15 Карточные диалоговые окна
В начале этой главы нам был представлен старый `CardLayout`, и мы рассмотрели, как управляются все карточки при смене. Интересно, что некоторые люди считают это хорошим дизайном. К счастью, Swing исправил эту ситуацию с помощью `JTabbedPane`, который управляет всеми этими карточками, переключением и другими вещами. Сравнивая `CardLayout` и `JTabbedPane`, можно заметить значительные различия.
Ниже приведён пример программы, которая очень интересна тем, что использует дизайн из предыдущего примера. Все они создаются как производные от `JPanel`, поэтому программа помещает каждый из предыдущих примеров в свой панель `JTabbedPane`. Мы видим, как небольшая программа, использующая RTTI (Run-Time Type Information), становится компактной и элегантной:
```java
//: Tabbed.java
// Использование вкладок
package c13.swing;
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
``````java
public class Tabbed extends JPanel {
static Object[][] q = {
{ "Феликс", Borders.class },
{ "Профессор", Buttons.class },
{ "Конец пути", ButtonGroups.class },
{ "Теодор", Faces.class },
{ "Симон", Menus.class },
{ "Альвин", Popup.class },
{ "Том", ListCombo.class },
{ "Джери", Progress.class },
{ "Бугс", Trees.class },
{ "Дэффи", Table.class },
};
static JPanel makePanel(Class c) {
String title = c.getName();
title = title.substring(
title.lastIndexOf('.') + 1);
JPanel sp = null;
try {
sp = (JPanel)c.newInstance();
} catch(Exception e) {
System.out.println(e);
}
sp.setBorder(new TitledBorder(title));
return sp;
}
public Tabbed() {
setLayout(new BorderLayout());
JTabbedPane tabbed = new JTabbedPane();
for(int i = 0; i < q.length; i++)
tabbed.addTab((String)q[i][0],
makePanel((Class)q[i][1]));
add(tabbed, BorderLayout.CENTER);
tabbed.setSelectedIndex(q.length / 2);
}
public static void main(String[] args) {
Show.inFrame(new Tabbed(), 460, 350);
}
} ///:~
```
```Кроме того, обратите внимание на использованный массивный стиль конструктора: первый элемент — это строка, которую следует поместить на карточку, а второй элемент — это класс `JPanel`, который будет отображаться в соответствующей панели. В конструкторе `Tabbed()`, вы можете увидеть использование двух важных методов `JTabbedPane`: `addTab()` добавляет новую панель, а `setSelectedIndex()` выбирает панель и начинает её отображение (выбор центральной панели показывает, что начинать не обязательно с первой панели). Когда мы вызываем метод `addTab()`, мы предоставляем ему строку (`String`) для вкладки и некоторые компоненты (то есть, один AWT-компонент, а не `JComponent` из AWT). Этот компонент отображается в панели. Как только это сделано, естественно, дальнейшее управление больше не требуется — `JTabbedPane` берёт на себя все остальные задачи.
Метод `makePanel()` получает объект типа `Class` для класса, который мы хотим создать, и создаёт новый экземпляр с помощью `newInstance()` и преобразует его в `JPanel` (конечно, предполагая, что эти классы должны наследовать от `JPanel`, если они хотят быть добавлены, за исключением случаев использования структуры примера программы в этом разделе). Он добавляет границу с названием класса и возвращает результат как `JPanel`, используемый в `addTab()`.При запуске программы можно заметить, что если количество вкладок слишком велико, чтобы поместиться в одной строке, `JTabbedPane` автоматически сворачивает их друг на друга.## 13.19.16 Сообщения Swing
Открытые окружения обычно содержат набор стандартных диалоговых окон, позволяющих быстро передавать сообщения пользователям или получать информацию от пользователей. В Swing такие диалоговые окна включены в класс `JOptionPane`. У нас есть несколько различных вариантов событий (некоторые очень сложные), но основной подход заключается в использовании статических методов `showMessageDialog()` и `showConfirmDialog()` класса `JOptionPane` для вызова диалоговых окон сообщений и подтверждений соответственно.
## 13.19.17 Больше информации о Swing
Этот раздел предназначен для того, чтобы показать нам мощь Swing и начальную точку входа, поэтому мы могли бы осознать простоту наших методов благодаря библиотеке. На данный момент то, что мы видели, может быть достаточно для удовлетворения некоторых наших потребностей в дизайне пользовательского интерфейса. Однако существует много дополнительной информации о Swing — он преднамеренно создан как полный набор инструментов для проектирования пользовательских интерфейсов. Если вы не нашли то, что вам нужно, обратитесь к онлайн-документации компании Oracle и проведите поиск в интернете. Этот подход позволяет выполнять практически любую задачу, которую можно себе представить.Некоторые важные моменты, не затронутые в данном разделе:
+ Дополнительные специальные компоненты, такие как `JColorChooser`, `JFileChooser`, `JPasswordField`, `JEditorPane` (для простого форматирования и отображения HTML) и `JTextPane` (текстовый редактор с поддержкой форматирования, текстового процессинга и изображений). Все они очень удобны в использовании.
+ Новые типы событий в Swing. В некоторых методах они выглядят как исключения: типы данных имеют большое значение, а названия могут использоваться для представления чего угодно помимо самих себя.
+ Новые менеджеры раскладки: Springs & Struts и `BoxLayout`.
+ Разделение управления: разделочная полоса, которая позволяет нам динамически управлять положением других компонентов.
+ `JLayeredPane` и `JInternalFrame` используются вместе для создания подоконников внутри текущего окна, что создаёт приложения с многооконной средой (MDI).
+ Поддержка встраиваемых внешних видов и эффектов, позволяющая писать программы, которые могут динамически адаптироваться к различным платформам и операционным системам так, как это требуется.
+ Поддержка пользовательских курсоров.
+ `JToolBar` API предоставляет возможность перемещаемых плавающих панелей инструментов.
+ Двойное буферизирование и автоматическое перерисование для гладкого экрана.
+ Встроенная поддержка отмены.
+ Поддержка перетаскивания.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )