Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Портянкин И. Swing

.pdf
Скачиваний:
210
Добавлен:
07.10.2020
Размер:
5 Мб
Скачать
☆

Списки

315

Очень часто слушателя событий от модели выделения достаточно для ваших нужд: вы знаете, как и когда меняются выделенные элементы списка, и можете оперативно изменить поведение программы в зависимости от выбора пользователя. Иногда никаких событий от списка вообще не обрабатывается: вы ждете нажатия кнопки или выбора пункта меню и только после этого смотрите, какие элементы списка выделены, считая их окончательным выбором пользователя.

Можно вспомнить, что в современных приложениях очень часто выбор (в том числе и в списках) легко сделать двойным щелчком мыши. Пользователь может быстро показать, что ему нужно, дважды щелкнув на некотором элементе списка. Список JList по умолчанию не поддерживает двойные щелчки мыши и не предоставляет средства по их определению, но поддержать такое поведение самостоятельно не составляет труда. Нам помогут два полезных метода списка JList: locationToIndex() и indexToLocation(). Они позволяют узнать, какому элементу списка принадлежит некоторая точка на экране, или наоборот выяснить, в каком месте экрана находится тот или иной элемент списка. С их помощью (для нашего примера понадобится первый метод) нам будет просто обеспечить поддержку двойных щелчков мыши:

//DoubleClickList.java

//Получение элемента списка по точке экрана import javax.swing.*;

import java.awt.event.*; import java.awt.*;

public class DoubleClickList extends JFrame { // данные списка

private String[] data = { "Стейк", "Лобстер", "Борщ", "Севрюга"};

private JList list;

public DoubleClickList() { super("DoubleClickList"); setDefaultCloseOperation(EXIT_ON_CLOSE); // создаем список

list = new JList(data); list.setSelectionMode(

ListSelectionModel.SINGLE_SELECTION); // добавим cлушателя событий от мыши list.addMouseListener(new MouseAdapter() {

public void mousePressed(MouseEvent e) { if ( e.getClickCount() == 2 ) {

// получаем элемент и покажем его int pos =

list.locationToIndex(e.getPoint());

JOptionPane.showMessageDialog( list, "Уже готовится: " +

list.getModel().getElementAt(pos));

}

316

ГЛАВА 11

}

}); // выведем окно на экран

add(new JScrollPane(list)); setSize(300, 200); setVisible(true);

}

public static void main(String[] args) { SwingUtilities.invokeLater(

new Runnable() {

public void run() { new DoubleClickList(); } });

}

}

Мы создаем небольшой «кулинарный» список (пусть это будет меню небольшого ресторана фьюжн-кухни) и добавляем его в центр нашего окна. К списку присоединяется слушатель событий от мыши (конечно, в виде адаптера: к чему нам остальные методы слушателя), в котором мы следим за щелчками мыши на списке. Когда пользователь щелкает на списке дважды, мы получаем позицию элемента, на котором произошло это событие (как раз здесь нам и пригодится метод locationToIndex()), и сообщаем пользователю, что его выбор был получен и обработан. Поддержка двойных щелчков мыши интуитивно понятна пользователю («сильный», то есть двойной, щелчок равносилен выбору) и особенно полезна там, где выбирать можно только один элемент списка. В таких ситуациях всегда добавляйте поддержку двойных щелчков мыши: это уже становится нормой. Напоследок стоит сказать о том, что метод locationToIndex() работает в координатах списка JList. В нашем примере это не важно, потому что мы следили за щелчками мыши непосредственно в списке, но если вы присоединили слушатель мыши к другому компоненту, в котором находится список, не забудьте преобразовать позицию указателя мыши в координаты списка с помощью метода convertMouseEvent() класса SwingUtilities.

Список JList делает все, чтобы с ним можно было без проблем работать на самом низком уровне: при обработке событий от мыши и клавиатуры или в других подобных ситуациях. Он позволяет определить, какие элементы списка в данный момент видны на экране, прокрутить свое содержимое так, чтобы на экране появился определенный элемент, узнать размеры любого элемента и выяснить, на каких позициях экрана он находится. Вы можете смело манипулировать списком на всех уровнях: и посредством моделей, и посредством низкоуровневых событий. Вся необходимая информация у вас в руках.

Список из флажков

В современных приложениях довольно часто встречаются списки, в качестве своих элементов показывающие флажки. Флажки позволяют упорядочить набор альтернатив и более наглядно представить глазам пользователя сделанный выбор. В качестве примера можно вспомнить разнообразные программы установки, в которых вы выбираете, какие компоненты устанавливаемой системы будут копироваться на вашу машину и сколько пространства на жестком диске в итоге потребуется, или всяческие «корзины покупателя», в которых аналогичным образом можно увидеть общую стоимость выбранных товаров и при необходимости от некоторых товаров тут же отказаться. Одним словом, наличие списка с флажками в вашем арсенале лишним не будет, и попробовать создать его стоит, заодно закрепив все полученные нами знания о списках JList.

Списки

317

Сразу ясно, что нам понадобится особый класс для хранения данных об элементе списка (включен или выключен элемент, а также строка с надписью) и специальный элемент ListCellRenderer, который будет отображать элементы списка в надлежащем виде, а именно как флажки JCheckBox. Но на этом работа не заканчивается: мы хорошо знаем, что список не помещает создаваемые объектом ListCellRenderer компоненты на экран, он всего лишь использует их как кисти для прорисовки, так что автоматически флажки устанавливаться или сбрасываться не будут (они не будут получать событий). Придется присоединить к списку слушателя событий от мыши и при щелчке сменять состояние элемента (включенное на выключенное и наоборот), чтобы флажок правильно отображал его. Давайте приступим. Сначала самое простое — класс с данными элементов нашего будущего списка:

//com/porty/swing/CheckBoxListElement.java

//Данные элемента списка с флажками package com.porty.swing;

public class CheckBoxListElement {

//данные элемента private boolean selected; private String text;

//удобный конструктор

public CheckBoxListElement(boolean selected, String text){ this.selected = selected;

this.text = text;

}

// методы для доступа к значку и тексту

public boolean isSelected() { return selected; } public String getText() { return text; }

public void setSelected(boolean selected) { this.selected = selected;

}

public void setText(String text) { this.text = text; }

}

В классе CheckBoxListElement (мы поместили его в библиотечный пакет com.porty. swing) находятся данные для списка с флажками: признак выделения элемента и строка с текстом, которая этот элемент будет описывать. Для удобства был добавлен конструктор, который позволяет быстро задать нужные значения элемента. Теперь нужно написать отображающий объект ListCellRenderer, который на основе этого класса должен создавать подходящий флажок. Мы не станем писать его в виде отдельного класса (вряд ли он будет использоваться где-то еще помимо списка с флажками, потому что ему требуется только определенный тип данных), а сделаем частью класса нашего нового списка с флажками (опишем его в виде статического внутреннего класса). Вот что получится:

//CheckBoxList.java

//Список с флажками package com.porty.swing;

318

ГЛАВА 11

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

public class CheckBoxList extends JList { // сохраняем все конструкторы

public CheckBoxList(ListModel model) { super(model);

initList();

}

public CheckBoxList(Object[] data) { super(data);

initList();

}

public CheckBoxList(Vector data) { super(data);

initList();

}

// специальная настройка списка private void initList() {

setCellRenderer(new CheckBoxCellRenderer()); setSelectionMode(

ListSelectionModel.SINGLE_SELECTION); addMouseListener(new MouseAdapter() {

public void mousePressed(MouseEvent e) { // следим за щелчками

if ( e.getClickCount() == 1 && SwingUtilities.isLeftMouseButton(e) ) {

//нужный нам щелчок int pos =

locationToIndex(e.getPoint()); CheckBoxListElement cbel =

(CheckBoxListElement)getModel(). getElementAt(pos);

cbel.setSelected(! cbel.isSelected());

//заново рисуем только эту ячейку списка repaint(getCellBounds(pos, pos));

}

}

});

Списки

319

}

// отображающий флажки объект

public static class CheckBoxCellRenderer

extends JCheckBox implements ListCellRenderer { public Component getListCellRendererComponent(

JList list, Object data, int idx, boolean isSelected, boolean hasFocus) {

//полагаем, что данные всегда нужного типа

CheckBoxListElement

cbel = (CheckBoxListElement)data;

//настраиваем флажок

if ( isSelected ) { setBackground(list.getSelectionBackground()); setForeground(list.getSelectionForeground());

} else { setBackground(list.getBackground()); setForeground(list.getForeground());

}

setSelected(cbel.isSelected());

setText(cbel.getText()); return this;

}

}

}

Мы создаем список с флажками CheckBoxList на основе обычного списка JList, наследуя от него, так что все возможности обычных списков остаются с вами. Прежде всего обратите внимание на реализацию специального рисующего объекта CheckBoxCellRenderer. Он унаследован от флажка JCheckBox и реализует необходимый для всех рисующих объектов списка интерфейс ListCellRenderer. Для отображения элементов списка требуется только один флажок — собственно сам рисующий объект. Он приводит нуждающийся в прорисовке элемент списка к нужному типу CheckBoxListElement (мы полагаем, что в списке с флажками будут элементы только такого типа, в противном случае возникнет исключение при приведении типов) и производит настройку флажка. Так как в этот раз нам приходится создавать рисующий объект «с нуля», нужно самостоятельно позаботится о соответствующих цветах элемента списка (которые могут меняться пользователем или текущим менеджером внешнего вида и поведения). Далее, используя специальные данные класса CheckBoxListElement, мы устанавливаем состояние флажка и текст для него.

Теперь остается только сам список с флажками. Он почти не отличается от обычного списка JList, за исключением того, что отдельно следит за нажатиями мыши, сразу же устанавливает специальный рисующий объект CheckBoxCellRenderer и по умолчанию задействует режим одиночного выделения элементов (это удобно для пользователя — он может сосредоточиться на смене состояния нужных ему элементов, а смена состояния возможна только для одного флажка одновременно). Мы сохранили все три конструктора обычного списка JList, так что вы не заметите разницы между функционированием обычного и нового списков CheckBoxList. Все что необходимо присоединяется в методе

320

ГЛАВА 11

initList(). Отдельного внимания заслуживает только специальный слушатель событий от мыши, реализованный прямо на месте. При подходящем событии (одно нажатие левой кнопкой мыши6) слушатель с помощью модели получает элемент, на котором было произведено нажатие, и меняет его состояние. Также он посылает запрос на перерисовку списка (repaint()), чтобы тот обновил свои элементы, заметьте, как мы эффективно это делаем, перерисовывая только один, изменившийся, элемент списка, определить его прямоугольник на экране помогает метод списка getCellBounds(). Это очень важно для списков больших размеров, со сложными рисующими объектами, иначе перерисовка будет слишком медленной. И здесь мы подразумеваем, что все элементы в списке имеют одинаковый тип CheckBoxListElement, так что других элементов лучше в него не помещать.

Мы сделали основу для правильной работы списка с флажками, и теперь нам остается только проверить его в деле. Проиллюстрирует все небольшой пример:

//CheckBoxListTest.java

//Проверка работы списка с флажками import javax.swing.*;

import com.porty.swing.*; import java.awt.*;

public class CheckBoxListTest { // данные списка

private

static Object[] data = {

new

CheckBoxListElement(false, "Корпус"),

new

CheckBoxListElement(false,

 

"<html><h3><font color=red>Колонки"),

new

CheckBoxListElement(false, "Набор шлейфов"),

new

CheckBoxListElement(true, "Системная плата")

};

public static void main(String[] args) { SwingUtilities.invokeLater(

new Runnable() { public void run() {

//создаем список и настраиваем его

JList list = new CheckBoxList(data);

//добавляем в окно

JFrame frame = new JFrame("CheckBoxListTest"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(200, 200);

6 Обратите внимание, что в слушателе мы переопределяем метод mousePressed(), вызываемый при нажатиях кнопок мыши. Если бы мы выбрали метод mouseClicked (то есть щелчок), то у нас могли бы возникнуть небольшие затруднения. Щелчком мыши считается событие нажатия и отпускания кнопки мыши на одном и том же месте экрана. Если пользователь при этом хотя бы немного шевелит мышью, щелчка не получится. Все зависит от того, как конкретный пользователь работает с мышью, но иногда непредсказуемость щелчков мыши сильно раздражает, так что мы выбрали нажатия.

Списки

321

frame.add(new JScrollPane(list)); frame.setVisible(true);

} });

}

}

Мы создаем небольшое окно JFrame, в панель содержимого которого добавляем наш новый список CheckBoxList, вложенный в панель прокрутки. Для списка CheckBoxList годятся только данные типа CheckBoxListElement, их мы и подготавливаем в массиве data: задаем начальное состояние элемента (флажок может быть установленным или сброшенным) и надпись к нему. Если вспомнить все, что мы узнали о флажках JCheckBox в главе 9, то не останется сомнений, что флажки в Swing тоже поддерживают HTML. Вы видите, как мы используем HTML-текст для второго элемента списка. Запустив программу с примером, вы увидите хорошо знакомый по многим приложениям список с флажками, который к тому же способен задействовать всю мощь HTML.

Чаще всего к такому списку присоединяется слушатель событий от модели выделения, чтобы при выборе пользователем нового флажка что-то изменялось в выводимой программой информации, например, заново подсчитывалось количество свободного места на диске, требуемого для установки. Но мы и так уже хорошо знаем, как работать с событиями от модели выделения, так что для простоты в примере мы этого не делали.

Конечно, это лишь начальная заготовка для полнофункционального списка со флажками, который будет легко вписываться в любой отшлифованный интерфейс. К примеру, у нас не поддерживается выделение флажков с клавиатуры, нет эффекта «нажимания» флажка, а стоило бы его добавить, чтобы флажок не отличался от своих существующих на самом деле на экране собратьев, на элементе не прорисовываются фокус ввода. Но это все легко добавить всего несколькими мазками богатой палитры Swing.

Действительно, список JList позволяет тонко настраивать любые аспекты своей работы. Вы можете начать с модели данных, по своему вкусу выбирая источник и тип данных, которые должен будет отображать список. Совсем нетрудно изменить внешний вид списка, начиная с используемых им цветов и заканчивая любым нужным вам видом его элементов. Он позволяет работать с собой посредством низкоуровневых событий и полностью контролировать процесс выделения элементов. Список с флажками хорошо иллюстрирует всю гибкость списка JList — несколькими строками кода вы фактически создаете новый элемент пользовательского интерфейса, не прикладывая для этого титанических усилий.

322

ГЛАВА 11

Списки JList в Java 7

Новая версия платформы Java 7 (или JDK 1.7) привнесла в списки JList некоторые изменения. Функциональность списков и все то, что мы рассмотрели в данной главе, осталось неизменным, а вот способ работы с элементами списка в модели ListModel стал несколько иным. Начиная с Java 7, списки JList являются типизированными компонентами, то есть при их создании вы можете указать точный тип, который они хранят. То же самое относится и к модели списков ListModel. Без сомнения это удобно, вспоминая наши специальные объекты для списков со значками и списков с флажками, где нам приходилось постоянно заниматься ручным приведением типов. В Java 7, задав тип хранимого в списке объекта, мы не только упрощаем реализацию, но и запрещаем хранение в списке объектов неподходящих типов еще на этапе компиляции. Типизированным стал и объект для отображения элементов списка ListCellRenderer, что также позволит избавиться от проверки конкретных типов в его коде.

Раскрывающиеся списки JComboBox

Раскрывающиеся списки JComboBox служат для выбора одного из множества доступных вариантов, примерно так же, как это происходит в группе переключателей JRadioButton или выключателей JToggleButton. В отличие от групп кнопок, которые располагаются на экране все вместе, в раскрывающемся списке JComboBox виден только сам выбранный элемент, а список возможных альтернатив появляется, только когда этого захочет пользователь (щелкнув на специальной кнопке раскрытия списка). Список возможных альтернатив выводится в специальном всплывающем меню, которое после выбора скрывается. Таким образом раскрывающиеся списки (в отличие от групп элементов управления и обычных списков) позволяют экономить место в контейнере, даже если список альтернатив велик. Кроме того, раскрывающиеся списки допускают редактирование текущего элемента, и пользователь может не только выбрать что-то среди имеющихся альтернатив, но и ввести свое собственное значение. К тому же Swing автоматически делает работу с раскрывающимся списком весьма комфортной: список JComboBox обладает встроенной возможностью поиска элементов с клавиатуры, и как мы вскоре увидим, это значительно упрощает работу с большими списками.

В отличие от обычного списка JList, раскрывающийся список JComboBox вполне обходится услугами одной модели, поставляющей ему информацию об элементах списка; обязанности этой модели описаны в интерфейсе ComboBoxModel. На самом деле, вряд ли имело бы смысл использовать для раскрывающегося списка отдельную модель выделения — в любом случае в нем может быть только один выделенный элемент. Создатели Swing учли это и максимально упростили нам знакомство с моделью раскрывающегося списка ComboBoxModel: во-первых, эта модель унаследована от уже знакомой нам очень простой модели списка ListModel, во-вторых, в ней появилась лишь пара новых методов, предназначенных для смены выделенного элемента или его получения. Помимо интерфейса модели ComboBoxModel вы можете использовать унаследованный от нее интерфейс MutableComboBoxModel, который поддерживает вставку и удаление произвольных элементов списка.

Но прежде чем обратиться к моделям раскрывающегося списка, давайте посмотрим, как его проще всего создать и какие свойства чаще всего настраиваются при его создании. Вот простой пример:

//SimpleComboBoxes.java

//Создание простых раскрывающихся списков import javax.swing.*;

import java.util.*;

Списки

323

import java.awt.*;

public class SimpleComboBoxes extends JFrame { // массив с элементами списка

public String[] elements = new String[] { "Обаятельный", "Умный", "Нежный", "Сильный"

};

public SimpleComboBoxes() { super("SimpleComboBoxes"); setDefaultCloseOperation(EXIT_ON_CLOSE);

//создадим пару раскрывающихся списков

JComboBox combo1 = new JComboBox(elements);

//задаем прототип элемента списка combo1.setPrototypeDisplayValue("Длинный элемент");

//второй раскрывающийся список

Vector<String> data = new Vector<String>(); for (int i=0; i<10; i++)

data.add("Элемент №: " + i); JComboBox combo2 = new JComboBox(data);

//сделаем его редактируемым combo2.setEditable(true); combo2.setMaximumRowCount(6);

//добавим списки и выведем окно на экран setLayout(new FlowLayout());

add(combo1);

add(combo2); setSize(300, 200); setVisible(true);

}

public static void main(String[] args) { SwingUtilities.invokeLater(

new Runnable() {

public void run() { new SimpleComboBoxes(); } });

}

}

В примере мы создаем небольшое окно и размещаем в его панели содержимого пару раскрывающихся списков. Нетрудно заметить очевидное сходство раскрывающегося списка JComboBox с обычным списком JList: точно так же, как это было для него, вы можете задавать элементы раскрывающегося списка в конструкторе JComboBox в виде массива или динамического массива (вектора) Vector. Первый список мы создаем на основе массива строк elements. Его нужно просто передать в конструктор, который незаметно для нас создаст на его основе стандартную модель. Заметьте, как мы изменяем для первого списка свойство prototypeDisplayValue. С его помощью список получает возможность

324

ГЛАВА 11

определить, какой длины должны быть все элементы списка независимо от того, находятся они в списке сразу же или добавляются потом. Данное свойство особенно полезно в том случае, если вы не знаете заранее, какой длины элементы появятся в списке со временем. Задав прототип элемента достаточно большим, можно не опасаться, что добавляемые впоследствии длинные элементы будут отображаться неверно. Более того, наличие рядом с элементами списка небольшого свободного пространства эстетически воспринимается лучше, чем плотно упакованный набор таких элементов.

Второй раскрывающийся список демонстрирует работу с вектором Vector. Мы динамически наполняем вектор простыми строками, а затем передаем его в конструктор JComboBox. Во многих ситуациях, если вам не нравится модель, использование класса Vector — намного более гибкий способ быстро создать раскрывающийся список (впоследствии содержимое вектора гораздо проще изменить). Второй список мы делаем редактируемым, а также изменяем для него свойство maximumRowCount. Это свойство контролирует, сколько элементов будет одновременно выводиться во всплывающем меню списка. Если это значение меньше количества элементов в списке (а у нас так и есть: в списке десять элементов, а свойство мы устанавливаем равным шести), то список элементов во всплывающем меню автоматически включается в панель прокрутки.

Оба созданных нами раскрывающихся списка помещаются окно, после чего оно выводится на экран. Запустив программу с примером, вы сможете увидеть внешний вид раскрывающихся списков и оценить, как отразились на них сделанные нами изменения в свойствах.

Продемонстрированные в примере конструкторы и свойства раскрывающихся списков часто позволяют создать нужный вам список буквально за мгновение, так что стоит иметь их в виду. Обратите также внимание, что раскрывающийся список JComboBox обладает встроенной поддержкой клавиатуры для поиска элементов: раскрыв первый список и нажимая соответствующие элементам списка символьные клавиши, вы убедитесь в том, что соответствующие элементы выделяются. Встроенный поиск элементов с клавиатуры незаменим в больших раскрывающихся списках с множеством элементов — поиск элементов в них только с помощью мыши и кнопок прокрутки легко превращается в настоящую головную боль. Поддержка поиска с клавиатуры осуществляется внутренним классом JComboBox.KeySelectionManager, в случае необходимости вы можете заменить используемый списком экземпляр этого класса собственным.

Модель ComboBoxModel

Для нас уже не секрет, что как бы быстро ни позволяли создавать раскрывающиеся списки их конструкторы, по настоящему гибкой и удобной в работе программу делают модели. Мы вскользь упоминали, что данные раскрывающихся списков хранит модель, описываемая несложным интерфейсом ComboBoxModel. Давайте познакомимся с этой моделью поближе.