310 ГЛАВА 11
private String text; // удобный конструктор
public ImageListElement(Icon icon, String text) { this.icon = icon;
this.text = text;
}
// методы для доступа к значку и тексту public Icon getIcon() { return icon; } public String getText() { return text; }
public void setIcon(Icon icon) { this.icon = icon; } public void setText(String text) { this.text = text; }
}
Это просто композиция из значка Icon и строки String с текстом. Чтобы было проще создавать этот объект, мы добавили удобный конструктор, позволяющий быстро задать нужные значения. Теперь нам понадобится рисующий объект, который будет прорисовывать для списка элементы ImageListElement:
//com/porty/swing/ImageListCellRenderer.java
//Класс для прорисовки в списке одновременно
//значка и текста
package com.porty.swing;
import javax.swing.*; import java.awt.*;
public class ImageListCellRenderer extends DefaultListCellRenderer {
// метод, возвращающий для элемента рисующий компонент public Component getListCellRendererComponent(
JList list, Object data, int idx, boolean isSelected, boolean hasFocus) {
// проверяем, нужного ли элемент типа
if ( data instanceof ImageListElement ) { ImageListElement lie =
(ImageListElement)data;
//получаем текст и значок
Icon icon = lie.getIcon(); String text = lie.getText();
//используем возможности базового класса
JLabel label = (JLabel) super.getListCellRendererComponent(
list, text, idx, isSelected, hasFocus); label.setIcon(icon);
Списки |
311 |
return label;
}else
return super.getListCellRendererComponent( list, data, idx, isSelected, hasFocus);
}
}
МынаследуемклассImageListCellRendererотстандартногообъектаDefaultListCellRenderer, и это дает нам возможность не беспокоиться о правильной прорисовке текста надписи с помощью установленных для списка цветов. Вместо этого мы можем сосредоточиться на обработке элементов нужного нам типа (а именно ImageListElement). Для начала мы выясняем, относится ли элемент списка к нужному типу, и если это так, начинаем его обработку. Прежде всего вызывается метод getListCellRendererComponent() базового класса DefaultListCellRenderer, но не для всего объекта ImageListElement, а только для хранящегося в нем текста (text). В результате мы получаем надпись JLabel (а мы знаем, что всегда получаем надпись от базового класса, поэтому преобразование типов безопасно) с установленным текстом. Нам остается лишь добавить к этому тексту наш значок и вернуть списку готовую надпись. Если же в списке окажется элемент незнакомого нам типа, мы просто перепоручим его обработку своему базовому классу.
Наконец, мы можем проверить работу нашего нового объекта для отображения элементов списка. Давайте попробуем:
//ImageList.java
//Список, используюший новый рисующий объект import javax.swing.*;
import com.porty.swing.*;
import java.awt.*;
public class ImageList { // данные списка
private |
static Icon bullet = new ImageIcon("bullet.gif"); |
private |
static Object[] data = { |
new |
ImageListElement(bullet, "Первый"), |
new |
ImageListElement(bullet, "Второй"), |
new |
ImageListElement(bullet, |
|
"<html><h4><font color=green>И третий!") |
};
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() { public void run() {
//создаем список и настраиваем его
JList list = new JList(data); list.setCellRenderer(new ImageListCellRenderer());
//добавляем в окно
312 |
ГЛАВА 11 |
JFrame frame = new JFrame("ImageList"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(200, 200);
frame.add(new JScrollPane(list)); frame.setVisible(true);
} });
}
}
Мы создаем небольшое окно со списком в панели содержимого. Список создается на основе массива данных, и на этот раз мы записываем наши данные в виде объектов ImageListElement,одновременноуказываязначокитекст.Заметьте,чтоблагодаряиспользованию в качестве базового класса стандартного рисующего объекта DefaultListCellRenderer, возможности HTML для списка по-прежнему доступны. Остается не забыть установить в качестве рисующего объекта списка наш новый объект ImageListCellRenderer (c помощью метода setCellRenderer()) и вывести окно на экран. Вы убедитесь в том, что теперь список показывает значок и текст одновременно, а при необходимости может выводить и HTML-текст. Не забывайте только, что созданные нами классы ImageListElement и ImageListCellRenderer находятся в пакете com.porty.swing. Они могут сослужить вам хорошую службу — списки с элементами, состоящими из значка и текста, очень популярны в приложениях.
Аналогичным образом вы можете действовать, если вам понадобятся дальнейшие изменения во внешнем виде элементов списка. Унаследуйте от стандартного рисующего объекта и добавьте к надписи то, что вам необходимо, благо ее возможности нам хорошо известны и без сомнения велики. Если же вам понадобится выводить элементы списка в совершенно особом виде, вы можете написать рисующий объект ListCellRenderer «с нуля». Пример такого объекта (для создания списка с элементами-флажками) мы вскоре увидим.
Список JList также использует интерфейс ListCellRenderer, чтобы определить, где и какие следует выводить всплывающие подсказки. Для этого он переопределяет метод базового класса JComponent getToolTipText() и в момент вывода подсказки узнает, на каком элементе списка находится указатель мыши. Для этого элемента списка он получает с помощью объекта ListCellRenderer рисующий его компонент и пытается выяснить текст его подсказки. Если у компонента подсказка есть, она выводится, а если текст подсказки получить не удалось, она не появляется вовсе. Созданный нами класс ImageListCellRenderer легко доработать для поддержки всеми элементами списка подсказок: надо лишь добавить в класс ImageListElement строку подсказки, а в классе ImageListCellRenderer дополнительно вызывать для надписи метод setToolTipText(). После этого каждый элемент списка сможет не только показывать текст и значок, но и при необходимости выводить подсказку. Учитывая, что и в элементах списка, и в самих подсказках может присутствовать HTML-текст, возможности для создания любого интерфейса открываются просто захватывающие.
Списки |
313 |
События списка
У списка JList нет собственных событий (за исключением стандартных событий, подобных событиям от мыши и клавиатуры, унаследованных им от базового компонента JComponent), все особенные события происходят в его моделях. Присоединив слушателя к модели ListModel, поставляющей данные для отображения в виде элементов списка, вы сможете узнать, когда и как меняются элементы списка и какие это элементы. Чаще всего эти события от модели обрабатывает как раз список, что позволяет ему вывести верные данные вовремя и в надлежащем виде, реализую концепцию модели и вида. Впрочем, вы можете и сами использовать события модели списка, сделав из нее некий сообщающий об изменениях в себе контейнер данных, если задача того требует.
Гораздо более интересные события происходят в модели выделения списка ListSelectionModel; они сообщают об изменениях в выделении пользователем элементов списка. Конечно, никто не запрещает вам бесцеремонно вмешаться в работу модели выделения (мы уже так делали) и следить за выделением прямо из нее, но это сложнее и не так элегантно. Присоединив слушателя к модели выделения списка, вы сможете прямо на месте узнавать обо всех изменениях в выделенных элементах, которые делает пользователь, и в зависимости от его выбора предлагать ему дополнительную информацию или новые альтернативы. Давайте рассмотрим небольшой пример, в котором мы будем узнавать о выделенных элементах списка прямо «на лету»:
//ListSelectionEvents.java
//Слежение за выделением списка import javax.swing.*;
import javax.swing.event.*; import java.awt.*;
public class ListSelectionEvents extends JFrame { // данные списка
private String[] data =
{ "Красный", "Желтый", "Зеленый"}; private JTextArea jta;
public ListSelectionEvents() { super("ListSelectionEvents"); setDefaultCloseOperation(EXIT_ON_CLOSE);
//создаем список и текстовое поле setLayout(new FlowLayout());
JList list = new JList(data); list.setSelectionMode(
ListSelectionModel.SINGLE_SELECTION); jta = new JTextArea(5, 20);
//добавим слушателя событий выделения list.addListSelectionListener(new SelectionL());
//добавляем компоненты
add(new JScrollPane(list)); add(new JScrollPane(jta)); // выводим окно на экран
314 ГЛАВА 11
setSize(300, 200); setVisible(true);
}
// слушатель событий от модели выделения
class SelectionL implements ListSelectionListener { public void valueChanged(ListSelectionEvent lse) {
int selected = ((JList)lse.getSource()).getSelectedIndex();
switch ( selected ) {
case 0: jta.setText("Переходить нельзя"); break; case 1: jta.setText("Будьте готовы"); break; case 2: jta.setText("Переходите улицу");
}
}
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new ListSelectionEvents(); } });
}
}
В примере мы добавляем в окно список на основе небольшого массива строк и текстовое поле JTextArea, которые мы будем использовать для вывода дополнительной информации. Затем к списку мы добавляем слушателя событий от модели выделения, вызывая метод addListSelectionListener(). Заметьте, что этот метод мы вызываем для самого списка JList, хотя точно такой же метод есть и в модели выделения ListSelectionModel. Разница лишь в источнике события, который будет возвращать нашему слушателю метод getSource(). При присоединении слушателя к списку (как в нашем примере) данный метод будет возвращать ссылку на него, и чаще всего это удобнее. В качестве слушателя событий будет использоваться внутренний класс SelectionL. В нем при получении извещения от модели выделения мы определяем позицию выделенного элемента списка (с помощью источника события — нашего списка) и в зависимости от этой позиции снабжаем пользователя дополнительной информацией. Запустите программу с примером и удостоверьтесь в том, что переходите улицу на нужный цвет светофора.