Добавил:
СПбГУТ * ИКСС * Программная инженерия Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

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

.pdf
Скачиваний:
154
Добавлен:
07.10.2020
Размер:
4.63 Mб
Скачать

Списки

335

public void removeActionListener( ActionListener e) { }

}

Новый редактор для раскрывающегося списка с поддержкой HTML мы разместили в пакете com.porty.swing, так что при случае вам будет несложно использовать его в собственных приложениях. Для того чтобы некий класс смог действовать как редактор для списка JComboBox, ему необходимо реализовать интерфейс ComboBoxEditor. Интерфейс этот несложен, в нем четыре основных метода и еще пара методов служит для присоединения слушателей ActionListener. Наш новый объект для редактирования реализует данный интерфейс и работает следующим образом: метод getEditorComponent() возвращает компонент, который раскрывающийся список выведет на экран рядом с кнопкой, открывающей выпадающее меню, именно этот компонент и должен выполнять редактирование. В нашем классе это текстовый компонент JEditorPane, мы создаем и настраиваем его в конструкторе (указываем, что редактироваться будет HTML-текст и применяем специальную рамку, чтобы он не выглядел чересчур бледно). Метод setItem() вызывается раскрывающимся списком при смене элемента для редактирования, получаемый элемент мы преобразуем в строку и сразу же передаем компоненту JEditorPane, подразумевая, что все элементы списка записаны с помощью HTML-кода. Метод selectAll() является сигналом редактору выделить всю область редактирования и приступить к работе. В нем мы выделяем весь текст и на всякий случай запрашиваем в редактор фокус ввода, чтобы пользователь видел, что поле готово к вводу данных. Наконец, метод getItem() предназначен для возвращения набранного пользователем значения, мы возвращаем текст, находящийся в данный момент в редакторе.

Интерфейс ComboBoxEditor обязывает нас реализовать еще два метода, служащие для присоединения и отсоединения слушателей ActionListener. Данные слушатели должны оповещаться об окончании ввода в редакторе, так что программист-клиент раскрывающегося списка сможет узнать, когда пользователь заканчивает ввод нового значения. Стандартный объект для редактирования использует текстовое поле JTextField, а оно, как мы узнаем из главы 16, позволяет присоединять к себе слушателей ActionListener, которые оповещаются об окончании ввода. Эта возможность поля JTextField и применяется стандартным объектом для поддержки слушателей ActionListener. Увы, но текстовый редактор JEditorPane не позволяет легко узнать об окончании ввода, потому что он рассчитан на ввод неограниченного количества текста, так что мы оставляем наш редактор без поддержки слушателей. Это не слишком страшно — слушатели ActionListener применяются при работе с JComboBox не так уж и часто, к тому же вы будете знать о том, что на них не следует полагаться при работе с нашим новым редактором. Более того, при потере выпадающим списком фокуса ввода он сам генерирует искусственное событие ActionEvent, говорящее о новом значении в списке, что тоже неплохо. Событие не будет возникать лишь от самого редактора.

Ну а теперь посмотрим, как будет работать созданный своими руками объект для редактирования:

//HTMLComboEditorTest.java

//Пример использование специального объекта для

//редактирования

import javax.swing.*;

import com.porty.swing.HTMLComboBoxEditor;

import java.awt.*;

import java.awt.event.ActionListener;

336

ГЛАВА 11

import java.awt.event.ActionEvent;

 

public class HTMLComboEditorTest

 

extends JFrame {

 

// данные для раскрывающегося списка

 

private String[] data = {

 

"<html><font color=yellow>Желтый",

 

"<html><strike>Зачеркнутый",

 

"<html><font color=green>Зеленый",

 

"<html><em>С наклоном" };

 

public HTMLComboEditorTest() {

 

super("HTMLComboEditorTest");

 

setDefaultCloseOperation(EXIT_ON_CLOSE);

 

// создаем список

 

final JComboBox combo = new JComboBox(data);

 

combo.setPrototypeDisplayValue("11223344556677");

 

combo.setEditable(true);

 

combo.setEditor(new HTMLComboBoxEditor());

 

//добавляем список в окно setLayout(new FlowLayout()); add(combo);

//кнопка для добавления нового элемента в список

JButton addButton = new JButton("Добавить"); addButton.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent e) { combo.addItem(combo.getSelectedItem());

}

});

add(addButton);

//выводим окно на экран

setSize(330, 200); setVisible(true);

}

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

new Runnable() {

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

}

}}

Мы создаем раскрывающийся список, используя для записи элементов HTML-код, и устанавливаем для списка новый объект для редактирования (не забывая включить редактирование свойством editable). Обратите внимание на маленький нюанс: длина

Списки

337

элемента раскрывающегося списка с помощью свойства prototypeCellValue делается довольно большой. Этому есть простое объяснение: HTML-текст, выводимый в выпадающем меню, выглядит гораздо компактнее, чем в редакторе JEditorPane, и если не сделать размер списка побольше (с помощью подходящего менеджера расположения или, как в примере, с помощью свойства prototypeCellValue), некоторые элементы при редактировании рискуют оказаться на нескольких строках, а это может быть неожиданностью для пользователя. Запустив программу с примером, вы сможете оценить новый объект для редактирования. Без сомнения, он прибавляет раскрывающемуся списку красок, к тому же с ним вы можете использовать для элементов все возможности HTML, не опасаясь неприятностей при редактировании.

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

Аналогичным образом вы сможете применять для редактирования и другие компоненты, хотя чаще всего все сводится к использованию тех или иных текстовых компонентов Swing. Так вы сможете хранить, отображать и редактировать данные любых типов, и сделаете свои раскрывающиеся списки по-настоящему приятными в работе, а комфортная работа с пользовательским интерфейсом — одна из основных задач при его создании.

События раскрывающегося списка

Раскрывающийся список JComboBox обладает куда более интересным набором событий в сравнении с обычным списком JList, где они происходят в основном в моделях. Прежде всего стоит обратить внимание на событие ItemEvent, которое происходит при смене выбранного элемента списка. С помощью этого события можно узнать не только о том, что выбранный элемент сменился, но и быстро получить данный элемент, а также узнать, какой элемент был выбран перед ним. Далее следует уже знакомое нам событие ActionEvent, которое должно сообщать о новом выбранном элементе или конце редактирования (если список допускает редактирование). Правда, после написания собственных редакторов для раскрывающегося списка мы видели, что правильная работа этого

338

ГЛАВА 11

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

Применение событий раскрывающегося списка проиллюстрирует небольшой пример:

//ComboBoxEvents.java

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

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

public class ComboBoxEvents extends JFrame { // данные для списков

private String[] data = { "США", "Италия", "Швейцария", "Таиланд" };

public ComboBoxEvents() { super("ComboBoxEvents"); setDefaultCloseOperation(EXIT_ON_CLOSE); // первый список

JComboBox combo1 = new JComboBox(data); // слушатель смены выбранного элемента

combo1.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) {

// выясняем, что случилось if ( e.getStateChange() == ItemEvent.SELECTED ) {

// покажем выбранный элемент

Object item = e.getItem(); JOptionPane.showMessageDialog(

ComboBoxEvents.this, item);

}

}

}); // список, позволяющий редактирование

final JComboBox combo2 = new JComboBox(data); combo2.setEditable(true);

// слушатель окончания редактирования combo2.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent e) {

Списки

339

// покажем выбор пользователя

Object item = combo2.getModel(). getSelectedItem();

JOptionPane.showMessageDialog( ComboBoxEvents.this, item);

}

});

//добавим списки в окно setLayout(new FlowLayout()); add(combo1);

add(combo2);

//выведем окно на экран setSize(350, 250); setVisible(true);

}

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

new Runnable() {

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

}

}

В примере мы создаем пару раскрывающихся списков, используя в качестве данных массив строк String. Один из списков будет поддерживать редактирование, именно к нему мы присоединяем слушателя событий ActionListener, чтобы узнавать об окончании редактирования (здесь задействован стандартный объект для редактирования, так что это событие будет обрабатываться). К первому списку мы присоединяем слушателя событий ItemListener, метод itemStateChanged() этого слушателя будет вызываться при смене выбранного элемента списка, причем происходить такой вызов будет два раза: первый раз событие сообщит о том, что прежде выбранный элемент больше таковым не является, а второй раз оно позволит узнать все о новом выбранном элементе. Различить два типа события позволяет метод getStateChange(), возвращающий целое число, которое и указывает на тип события: первому типу соответствует константа DESELECTED из класса ItemEvent, второму типу соответствует константа SELECTED. Нас интересует выбранный элемент, так что мы выбираем событие типа SELECTED. Далее остается получить выбранный элемент методом getItem() и вывести его на экран (используя стандартное диалоговое окно JOptionPane, подробнее о нем рассказывается в главе 14). Экспериментируя с примером, попробуйте выбрать уже выбранный элемент — событие ItemEvent при этом не рассылается, хотя вы заново выбираете его в списке. Это некоторого рода оптимизация.

Ко второму списку (тому, что допускает редактирование) мы присоединяем слушателя ActionListener, он дает возможность узнать, когда пользователь заканчивает ввод нового значения, или когда он выбирает его из списка. После работы со вторым раскрывающимся списком вы убедитесь в том, что слушатель ActionListener оповещается не только об окончании ввода (окончанием ввода считается нажатие клавиши Enter в поле для редактирования), но и о смене выбранного элемента. В слушателе мы, как и для события ItemEvent, показываем новый выбранный элемент в стандартном диалоговом окне

340

ГЛАВА 11

JOptionPane. Если вы введете в поле для редактирования новое значение, то стандартное окно с сообщением увидите два раза — как сигнал о конце редактирования, и как сигнал о том, что в списке в качестве текущего значения используется новый элемент. Как видно, слушатель ActionListener более универсален, особенно при использовании его в списках с поддержкой редактирования, у слушателя ItemEvent перед ним лишь одно преимущество: он позволяет узнать о том, какой элемент был выбран перед новым выделенным элементом.

Чтобы подвести итог, составим маленькое резюме по не всегда очевидному действию событий выпадающих списков:

Таблица 11.4. Основные события выпадающего списка

Свойство

Описание

(и методы get/set)

 

ItemEvent

Возникает только при смене элемента. Не позволяет узнать о выборе

 

того же самого элемента. Помогает узнать не только о выбранном, но

 

и о предыдущем значении (выбор с которого перешел к новому эле-

 

менту)

ActionEvent

Возникает при выборе элемента, даже если выбранный элемент не

 

изменился (повторный выбор). Это позволяет применять данное со-

 

бытие для немедленного анализа выбранного значения, даже если оно

 

не поменялось, и предпринять какие-то действия, например, обновить

 

интерфейс, но следует помнить, что событие возникнет и от редакто-

 

ра списка, если будет введено новое значение и нажата клавиша (как

 

правило Enter) или список потеряет фокус ввода. Таким образом, для

 

редактируемых выпадающих списков событие стоит применять крайне

 

осторожно.

У раскрывающегося списка есть еще одно событие — PopupMenuEvent. Если говорить точнее, это событие выпадающего меню списка, с его помощью вы сможете узнать, в какой момент времени меню появляется на экране и исчезает с него. Во всех стандартных внешних видах, поставляемых вместе с JDK, UI-представитель JComboBox применяет в качестве всплывающего меню хорошо известный нам компонент JPopupMenu, к нему и присоединяется слушатель событий PopupMenuListener.

Управление всплывающим меню

Класс JComboBox предоставляет нам некоторые методы, позволяющие ограниченно управлять своим всплывающим меню. Метод hidePopup() скрывает меню с экрана даже если пользователь не собирался его закрывать. У метода hidePopup() есть «брат-близнец» showPopup(); как нетрудно догадаться, он выводит всплывающее меню на экран. Кстати, функции обоих методов с успехом может выполнить и метод setPopupVisible() — передавая ему соответствующие булевы значения, вы сможете вывести меню на экран или скрыть его.

Кроме того, вы сможете рекомендовать раскрывающемуся списку JComboBox использовать в качестве контейнера для всплывающего меню легковесный компонент (один из тех, что затем разместятся в слое POPUP_LAYER многослойной панели) или его тяжеловесного собрата (окно без рамки JWindow или тяжеловесной панели Panel). По умолчанию всегда используется легковесный компонент (тяжеловесный компонент применяется, только если легковесному компоненту не хватает места в многослойной панели), однако вызвав метод setLightweightPopupEnabled(false), вы сможете рекомендовать списку применение тяжеловесных компонентов. В обычных ситуациях

Списки

341

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

Резюме

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

Глава 12. Диапазоны значений

В этой главе мы познакомимся с компонентами, которые позволяют пользователю выбрать данные из некоторого диапазона или видеть, какая часть данных из некоторого диапазона обработана. Начнем мы с ползунка JSlider, который в наглядной форме представляет пользователю возможность плавного изменения выбранного значения, от минимального до максимального. Передвигая по шкале регулятор (специальный элемент интерфейса), пользователь, как правило, в реальном времени видит результат своих действий и может быстро оценить, какое именно значение из заданного диапазона подходит ему более всего.

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

В завершение мы остановимся на счетчике JSpinner. Счетчик представляет пользователю набор из нескольких значений, который он в поиске нужного может «прокручивать» в обе стороны. Это своеобразный симбиоз раскрывающегося (JComboBox) и простого (JList) списков — вы сразу же можете прокручивать доступные альтернативы, как в обычном списке, не вызывая на экран всплывающее окно, и у вас остаются возможности раскрывающегося списка, как-то редактирование элемента и единственно возможный выбор. Есть у счетчика и свое уникальное свойство — в отличие от списков количество элементов в нем может быть неограниченным. Особенно полезен счетчик там, где по каким-то причинам нет места для списков или они нежелательны.

Ползунки JSlider

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

Для работы ползунка требуется специальная модель BoundedRangeModel, хранящая информацию об ограниченном наборе данных. Данные в ней хранятся в числовом виде в четырех целых (int) числах. Это минимальное значение (свойство с названием minimum), максимальное значение (maximum), текущее значение (value) и особое значение — внутренний диапазон (extent). При изменении любого из четырех значений модель запускает событие ChangeEvent, сообщающее о необходимости обновить вид (или провести другие действия). Чуть подробнее мы рассмотрим модель ограниченных данных позже, а сейчас давайте посмотрим, какие конструкторы предоставляет нам ползунок JSlider, и какие компоненты мы можем с их помощью создать. Рассмотрим простой пример:

Диапазоны значений

343

//SimpleSliders.java

//Простые ползунки import javax.swing.*; import java.awt.*;

public class SimpleSliders extends JFrame { public SimpleSliders() {

super("SimpleSliders"); setDefaultCloseOperation(EXIT_ON_CLOSE);

//создаем несколько ползунков

JSlider s1 = new JSlider(0, 100); JSlider s2 = new JSlider(

JSlider.VERTICAL, 0, 200, 50);

//настройка внешнего вида s2.setPaintTicks(true); s2.setMajorTickSpacing(50); s2.setMinorTickSpacing(10);

JSlider s3 = new JSlider(0, 50, 40); s3.setPaintLabels(true); s3.setMajorTickSpacing(10);

//добавим их в панель содержимого setLayout(new FlowLayout()); add(s1);

add(s2);

add(s3);

//выводим окно на экран setSize(300, 300); setVisible(true);

}

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

new Runnable() {

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

}

}

В примере мы создаем небольшое окно, в котором размещаем разнообразные ползунки. Пока модель BoundedRangeModel напрямую не используется — для демонстрации основных возможностей ползунков достаточно удобных конструкторов класса JSlider (впрочем, нетрудно догадаться, что эти конструкторы неявно задействуют стандартную модель DefaultBoundedRangeModel). Первый ползунок создается с помощью конструктора, принимающего два параметра: минимальное и максимальное значения. Текущим значением такого ползунка будет среднее (минимальное плюс максимальное разделить на два). Второй ползунок создается самым пол-

344

ГЛАВА 12

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

Все три ползунка помещаются в окно (с последовательным расположением FlowLayout), и последнее выводится на экран. Запустив программу с примером, вы сможете увидеть, как работают и выглядят самые простые ползунки.

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

Почти всегда приходится дополнительно настраивать внешний вид ползунков, включая для них основные (большие) и промежуточные (маленькие) деления и/или метки, помечающие основные значения. Запуская программу с примером, вы могли убедиться в том, что первый ползунок (без делений и меток) выглядит весьма безлико и легко может ввести пользователя в заблуждение. Составим теперь перечень наиболее часто используемых свойств, изменяющих (путем вывода делений и меток) внешний вид ползунков (табл. 12.1).