Портянкин И. Swing
.pdf
Диапазоны значений |
345 |
|
Таблица 12.1. Настройка отметок ползунков |
||
|
|
|
Свойство |
Значение |
|
(и методы get/set) |
|
|
majorTickSpacing |
Позволяет задать расстояние, через которое будут рисоваться основ- |
|
|
ные (большие) деления или метки (если они должны выводиться) |
|
minorTickSpacing |
Задает расстояние, через которое рисуются промежуточные (малень- |
|
|
кие) деления. Логично делать это расстояние меньше, чем предыду- |
|
|
щее, и так, чтобы между большими делениями было кратное количе- |
|
|
ство маленьких делений |
|
paintTicks |
Включает или отключает прорисовку делений. Если вы включаете про- |
|
|
рисовку, не забудьте настроить два предыдущих свойства (расстояния |
|
|
между большими и/или маленькими делениями) |
|
paintLabels |
Позволяет задать прорисовку меток под большими делениями. По |
|
|
умолчанию выводится значение, соответствующее делению, но как мы |
|
|
увидим далее, выводимые метки можно гибко настраивать |
|
paintTrack |
Управляет выводом на экран полосы (шкалы), по которой перемеща- |
|
|
ется регулятор, меняющий выбранное значение. По умолчанию шкала |
|
|
выводится, и отключать ее не стоит — становится непонятно, к чему |
|
|
относится регулятор. Это свойство может быть полезным если, напри- |
|
|
мер, вы решите создать собственную шкалу, но не захотите писать UI- |
|
|
представителя ползунка «с нуля» |
|
snapToTicks |
Мы не использовали данное свойство в примере, но часто оно бывает |
|
|
весьма полезным. Если данное свойство равно true, регулятор ползунка |
|
|
всегда будет находиться только на метках (метки должны быть включе- |
|
ны), «отскакивая» к ним, даже если пользователь остановит регулятор
в произвольном месте шкалы. Задействовать это свойство можно там,
где вы хотите применить ползунок в качестве своеобразного элемента для выбора одного из нескольких доступных вариантов (отмеченных метками)
Используя конструкторы, описанные в примере, и свойства, перечисленные в таблице, можно получить большое количество разнообразных ползунков, вполне достаточное для большинства приложений. Впрочем, как и всегда, Swing не останавливается на полпути, и мы увидим, что ползунки способны на большее.
Модель BoundedRangeModel
Мы уже упомянули о том, что отображаемые ползунком данные хранит модель, описываемая интерфейсом BoundedRangeModel. Она состоит из четырех целых чисел: минимального, максимального и текущего значений, а также внутреннего диапазона (extent). Первые три числа вряд ли нужно объяснять подробно, назначение их понятно, а вот зачем в модели внутренний диапазон? Внутренний диапазон начинается от текущего значения и занимает какой-то числовой интервал, хотя и не может быть больше максимального значения. Он используется не только в ползунках, но и в полосах прокрутки JScrollBar (они также хранят свои данные в модели BoundedRangeModel), где именно внутренний диапазон отвечает за длину полосы прокрутки. Грубо говоря, внутренний диапазон определяет, какая часть от целого в данный момент полностью доступна пользователю (это может быть видимая часть компонента в панели прокрутки JScrollPane). Подробнее мы обсудим полосы прокрутки JScrollBar в главе 13. Ползунки же используют внутренний диапазон, чтобы определить, на сколько должен «прыгнуть» регулятор, если пользователь щелкает на шкале ползунка, не трогая регулятор. Все это иллюстрирует следующая схема:
346 |
ГЛАВА 12 |
extent
|
|
|
|
|
|
|
|
|
|
|
|
|
|
min |
value |
max |
||||
Если в вашем случае диапазон данных велик и подразумевает резкое изменение, вы можете сделать внутренний диапазон большим, так что пользователь сможет быстро менять его, щелкая на полосе. К сожалению, поведение ползунков с внутренним диапазоном отличается в зависимости от того, какой внешний вид вы используете, и в большинстве из реализаций вы не сможете выбрать максимальное значение, если установлен внутренний диапазон. Так что как правило при настройке ползунков внутренний диапазон лучше оставить нулевым.
Помимо хранения и при необходимости изменения (все четыре значения можно изменять, в модели описаны соответствующие методы get/set) четырех целых чисел модель BoundedRangeModel, как и можно было бы ожидать, обязана оповещать присоединенных к ней слушателей об изменениях в этих числах. Для этого она генерирует событие ChangeEvent. Модель BoundedRangeModel также поддерживает специальный режим valueIsAdjusting, в котором она перестает оповещать слушателей об изменениях
вданных. Этот режим включается UI-представителем при перетаскивании пользователем регулятора, чтобы не замедлять программу запуском событий на каждое изменение положения указателя мыши. При завершении перетаскивания модель возвращается
вобычное состояние и сообщает слушателям об изменении в данных, запуская событие ChangeEvent.
Вы видите, что модель BoundedRangeModel для ограниченных данных очень проста, и реализовывать ее самому вряд ли потребуется. Лучше задействовать класс DefaultBoundedRangeModel — стандартную реализацию, поставляемую вместе с библиотекой Swing. Стандартная модель позволяет задавать и изменять все четыре значения и прекрасно справляется со слушателями. Именно этот класс незаметно для вас создают конструкторы ползунка JSlider, передавая в него те же значения, которые вы передаете им. При этом остаются доступными все достоинства моделей: возможность присоединения модели к нескольким видам, отделение механизмов обработки данных от пользовательского интерфейса, и т. д. Рассмотрим простой пример с двумя ползунками:
//SliderDefaultModel.java
//Использование в ползунках стандартной модели import javax.swing.*;
import java.awt.*;
public class SliderDefaultModel extends JFrame { // наша модель
private BoundedRangeModel model; public SliderDefaultModel() {
super("SliderDefaultModel"); setDefaultCloseOperation(EXIT_ON_CLOSE); // создадем модель и пару ползунков
model = new DefaultBoundedRangeModel(10, 0, 0, 100);
Диапазоны значений |
347 |
|
JSlider |
slider1 = new JSlider(model); |
|
JSlider |
slider2 = new JSlider(JSlider.VERTICAL); |
|
slider2.setModel(model);
//добавляем ползунки в окно setLayout(new FlowLayout()); add(slider1);
add(slider2);
//выводим окно на экран setSize(300, 300); setVisible(true);
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new SliderDefaultModel(); } });
}
}
Мы создаем стандартную модель DefaultBoundedRangeModel, используя удобный конструктор: в нем сразу задаются текущее значение, внутренний диапазон (он нам не понадобится, а вернее сказать, даже помешает, так что мы задали его равным нулю), а также минимальное и максимальное значения. Далее мы присоединяем модель к двум ползункам. Первый ползунок создается с помощью удобного конструктора, сразу же позволяющего задать модель. Впрочем, это не обязательно: второй ползунок мы создаем конструктором, в котором указываем, что ползунок будет вертикальным, а модель присоединяем позже методом setModel(). После этого ползунки помещаются в окно и появляются на экране.
Запустив программу с примером, вы увидите, как одна модель поставляет данные для двух разных компонентов, так что они меняются синхронно. Достоинства моделей налицо: вы храните в них данные и при необходимости меняете эти данные (или их меняет пользователь), не беспокоясь о правильном отображении или получении измененной информации — это задача вида, то есть компонента JSlider.
События ползунков
Ползунки — очень простые компоненты, поэтому событий у них немного, а точнее одно. Это событие ChangeEvent, запускаемое моделью BoundedRangeModel при изменении пользователем текущего значения ползунка1. С его помощью вы сможете сразу узнавать о перемещении пользователем регулятора и соответствующим образом изменить состояние программы и ее интерфейса согласно новому текущему значению ползунка. Надо сказать, что для ползунков свойственен «мгновенный эффект»: любое перемещение регулятора по шкале ползунка заставляет пользователя смотреть (или слушать, например при перемещении регулятора громкости), что при этом изменилось, так что всегда следует присоединять к ползунку слушателя событий ChangeEvent и сразу же реагировать на изменения, иначе ваша программа рискует стать неудобной и нелогичной.
1 Событие ChangeEvent генерируется также при изменении любого из четырех значений, хранимых в модели (вы легко можете изменить, к примеру, максимальное значение), но это происходит редко, чаще всего пользователю достаточно знать только об изменении текущего значения.
348 |
ГЛАВА 12 |
Присоединить слушателя событий ChangeEvent можно либо к модели BoundedRangeModel, используемой в ползунке, либо к самому ползунку JSlider. Разница будет лишь в источнике события, получаемого методом getSource() класса ChangeEvent: в первом случае источником будет модель, а во втором — ползунок. И модель, и ползунок позволяют получить все четыре значения и при необходимости изменить их, так что выбор того или другого — дело вкуса. Рассмотрим небольшой пример:
//SliderEvents.java
//События ползунков import javax.swing.*; import javax.swing.event.*; import java.awt.*;
public class SliderEvents extends JFrame { public SliderEvents() {
super("SliderEvents"); setDefaultCloseOperation( EXIT_ON_CLOSE ); // создаем ползунок и надписи
JSlider slider = new JSlider(0, 900, 0); slider.setMajorTickSpacing(100); slider.setPaintTicks(true);
boost = new JLabel("Ускорение: "); // присоединяем слушателя
slider.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) {
// меняем надпись int value =
((JSlider)e.getSource()).getValue(); int percent = value/15;
boost.setText("Ускорение: " + percent + " %");
}
});
//добавляем компоненты в панель
JPanel contents = new JPanel(); contents.add(new JLabel("Размер буфера:")); contents.add(slider);
add(contents); add(boost, "South");
//выводим окно на экран
setSize(360, 100); setVisible(true);
}
private JLabel boost;
Диапазоны значений |
349 |
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new SliderEvents(); } });
}
}
Мы создаем ползунок, позволяющий регулировать размер некоторого буфера (возможно, для сетевого соединения или для воспроизведения звука). При этом с помощью динамически меняющейся надписи вы сможете видеть, какое ускорение можно получить при увеличении размера буфера. Для ползунка мы включаем прорисовку больших делений и присоединяем к нему слушателя события ChangeEvent. Событие ChangeEvent очень «легкое»: в нем не хранится никакой информации за исключением источника события (этому есть свои причины — ChangeEvent запускается так часто, что его пришлось сделать как можно более простым во избежание лишних затрат ресурсов), так что нужно знать, к чему вы присоединяете слушателя ChangeListener. Мы присоединили слушателя к ползунку JSlider, и можем смело преобразовать источник события к объекту JSlider, и с его помощью получить текущее значение. Полученное текущее значение обновляет надпись, так что пользователь мгновенно может оценить, как размер буфера влияет на ускорение, и выбрать оптимальный для него вариант. В заключение ползунок и надписи добавляются в окно (надпись boost на юг окна), и окно выводится на экран.
Это все, что можно сказать о событиях ползунка, который и впрямь очень простой компонент. Нужно лишь запомнить, что с точки зрения пользователя ползунок (вспомните, что он имитирует «ручки» приборов, которые дают мгновенный эффект) является динамическим элементом, поэтому при изменении текущего значения что-то сразу же должно меняться в программе и ее интерфейсе, а это значит, что к ползунку или его модели всегда должен быть присоединен слушатель событий
ChangeEvent.
Дополнительная настройка внешнего вида
Нам уже известны основные параметры внешнего вида ползунков: мы знаем, какие свойства отвечают за прорисовку основных и промежуточных делений и надписей под основными делениями, а также за отключение прорисовки шкалы ползунка (хотя это требуется редко). Оказывается, это не все, на что способен ползунок JSlider. Его внешний вид можно настроить еще точнее.
Наиболее полезна возможность создания собственных надписей для определенных значений, отображаемых ползунком. Вы передаете их в метод setLabelTable() в виде подкласса таблицы Dictionary, сопоставляя целому числу («обернутому» в класс Integer) соответствующую надпись, причем в качестве метки (надписи) могут выступать любые компоненты, унаследованные от базового класса JComponent. Это «развязывает вам руки»,
350 |
ГЛАВА 12 |
позволяя создавать ползунки самых причудливых форм и раскрасок. Далее у вас есть возможность включить для ползунка особое клиентского свойство, делающее его «наполняемым» по мере перемещения регулятора, то есть похожим на индикатор процесса (см. следующий раздел). Иногда такой визуальный эффект помогает лучше определить роль ползунка в приложении. Рассмотрим пример:
//SliderAdditionalFeatures.java
//Дополнительные возможности ползунков import javax.swing.*;
import java.util.*; import java.awt.*;
public class SliderAdditionalFeatures extends JFrame { public SliderAdditionalFeatures() {
super("SliderAdditionalFeatures"); setDefaultCloseOperation(EXIT_ON_CLOSE);
//используем стандартную модель
BoundedRangeModel model =
new DefaultBoundedRangeModel(10, 0, 0, 60);
//таблица с надписями
Dictionary labels = new Hashtable(); labels.put(0, new JLabel(
"<html><font color=red size=4>Ноль")); labels.put(10, new JLabel(
"<html><font color=green size=3>Двадцать")); labels.put(50, new JLabel(
"<html><font color=yellow size=5>Много")); labels.put(60, new JLabel(
new ImageIcon("caution.gif"))); // настройка первого ползунка
JSlider slider1 = new JSlider(JSlider.VERTICAL); slider1.setModel(model); slider1.setLabelTable(labels); slider1.setPaintLabels(true);
//"наполняемый" инвертированный ползунок
JSlider slider2 = new JSlider(model); slider2.putClientProperty(
"JSlider.isFilled", Boolean.TRUE); slider2.setInverted(true); slider2.setPaintTicks(true); slider2.setMajorTickSpacing(10);
//добавляем компоненты в окно setLayout(new FlowLayout());
Диапазоны значений |
351 |
add(slider1);
add(slider2);
// выводим окно на экран setSize(300, 300); setVisible(true);
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new SliderAdditionalFeatures(); } });
}
}
Пример демонстрирует все дополнительные возможности ползунков JSlider. Данные мы будем хранить в стандартной модели DefaultBoundedRangeModel. Минимальное значение равно нулю, максимальное — шестидесяти, а текущее значение при запуске программы — десяти. Созданную модель мы используем для двух ползунков, которые и продемонстрируют вам дополнительные возможности. У первого ползунка мы создаем собственные надписи для наиболее важных значений, их необходимо поместить в любой подходящий подкласс абстрактной таблицы Dictionary2, для примера была выбрана таблица Hashtable. Целым значениям, автоматически преобразованным в объекты Integer, можно сопоставлять любые компоненты, унаследованные от базового класса JComponent (в примере использованы прекрасно знакомые нам надписи JLabel, которые способны не только выводить текст и значки, но и поддерживают язык HTML). В первых трех метках мы применили надписи с красочным HTML-текстом, а последнее (максимальное) деление шкалы пометили особым значком. Заметьте, что создавать надписи можно для любых значений, и это позволяет получать весьма эффектные ползунки. После подготовки таблицы с надписями ее нужно передать в метод setLabelTable(). Первый ползунок является вертикальным, обратите внимание, несмотря на наличие нестандартных надписей их все равно приходится включать методом setPaintLabels().
Второй ползунок демонстрирует еще кое-что из арсенала класса JSlider. Он является «наполняемым»: по мере передвижения регулятора полоса (шкала), по которой он движется, заполняется специальным цветом. «Наполняемые» ползунки довольно эффектны — пользователь воочию видит, как выбирается все большее значение; особенно удобны такие ползунки, когда нужно показать объем или размер каких-либо объектов. Чтобы сделать ползунок «наполняемым», нужно изменить клиентское свойство с названием JSlider.isFilled, сопоставив ему объект Boolean со значением true. Правда, имейте в виду, что на данный момент «наполняемые» ползунки поддерживаются только внешним видом Metal, остальные поставляемые вместе с JDK внешние виды (Nimbus, Windows, Motif) это свойство игнорируют3. Помимо того что второй ползунок является «наполняемым», он еще и инвертированный, то есть значения в нем отсчитываются справа налево. Такое поведение чаще всего требуется, чтобы локализовать программу
2 Начиная еще с покрытого древностью пакета JDK 1.2, класс Dictionary объявлен устаревшим, вместо него рекомендуется использовать карты Map. Но ползунки были разработаны раньше, и менять уже устоявшийся программный интерфейс (что привело бы к неработоспособности уже написанного кода) создатели Swing не стали, а нам остается лишь смириться с этим и использовать устаревший класс. Дополнительных методов за все это время также не добавилось.
3 С другой стороны, почти все внешние виды от сторонних производителей свойство «наполняемости» поддерживают.
352 |
ГЛАВА 12 |
для языков с письмом справа налево, но можно использовать инвертированные ползунки и в обычных программах, в некоторых ситуациях это может внести в интерфейс разнообразие.
Оба ползунка добавляются в панель содержимого окна, которое после этого выводится на экран. Запустив программу с примером, вы сможете оценить описанные в этом разделе возможности ползунков JSlider и при желании применить их в своих пользовательских интерфейсах.
Индикаторы процесса JProgressBar
Индикаторы процесса JProgressBar наглядно демонстрируют пользователю, на каком этапе выполнения находится некий процесс, так что пользователь сможет понять, как долго остается ждать его завершения. Такие индикаторы выводятся на экран в виде непрерывно меняющейся полосы, чаще всего с текстом, который показывает (обычно в процентах), насколько выполнен процесс, или каким-либо другим образом поясняет, что в данный момент происходит. Индикаторы процесса — незаменимы для создания качественного пользовательского интерфейса. Время пользователя следует ценить, а благодаря индикаторам процесса он увидит, сколько этого времени ему понадобится для завершения какого-либо действия (и решит, стоит ли ждать завершения этого действия вообще).
Индикаторы процесса JProgressBar — чрезвычайно незатейливые компоненты, одни из самых простых в Swing. Данные они черпают из модели BoundedRangeModel, предназначенной для хранения ограниченного диапазона чисел. Модель BoundedRangeModel уже знакома нам по ползункам JSlider. Как вы помните, модель BoundedRangeModel хранит четыре целых (int) числа: максимальное, минимальное, текущее, а также внутренний диапазон. Индикаторам процесса JProgressBar требуются лишь три из этих значений: максимальное, минимальное и текущее. Именно на основе трех чисел и прорисовывается индикатор JProgressBar. Как правило, минимальное и максимальное значения задаются сразу же (вы знаете либо промежуток времени, который займет процесс, либо диапазон данных, которыми он оперирует), а текущее значение постоянно обновляется по мере выполнения процесса. Писать свою реализацию модели BoundedRangeModel вряд ли имеет смысл, она слишком проста: со всеми возможными случаями ее использования прекрасно справится стандартная реализация, описанная в знакомом нам по тем же ползункам классе DefaultBoundedRangeModel.
Диапазоны значений |
353 |
Процесс, выполнение которого наглядно выводит на экран индикатор JProgressBar, необходимо поместить в отдельный поток выполнения (Thread) или использовать инструмент SwingWorker, чтобы остальной интерфейс программы не страдал, если процесс окажется требовательным к загрузке процессора и других ресурсов, а значение индикатора (и при необходимости его текст) можно было непрерывно обновлять. Все это мы подробно обсуждали в главе 3. Рассмотрим теперь несложный пример, в котором попробуем применить самые полезные возможности индикатора процесса JProgressBar:
//UsingProgressBars.java
//Использование основных возможностей
//компонента JProgressBar
import javax.swing.*; import java.awt.*;
public class UsingProgressBars extends JFrame {
//максимальное значение (100%) private int MAX = 100;
//будем использовать общую модель private BoundedRangeModel model; public UsingProgressBars() {
super("UsingProgressBars"); setDefaultCloseOperation(EXIT_ON_CLOSE);
//создаем стандартную модель
model = new DefaultBoundedRangeModel(5, 0, 0, MAX); // горизонтальный индикатор
JProgressBar progress1 = new JProgressBar(model); progress1.setStringPainted(true);
//вертикальный индикатор
JProgressBar progress2 =
new JProgressBar(JProgressBar.VERTICAL); progress2.setModel(model); progress2.setStringPainted(true); progress2.setString("Немного терпения...");
//добавляем индикаторы в окно setLayout(new FlowLayout()); add(progress1); add(progress2);
//выводим окно на экран
setSize(400, 300); setVisible(true);
// запускаем "процесс"
new LongProcess().execute();
}
354 |
ГЛАВА 12 |
// эмуляция |
долгого процесса |
class LongProcess extends SwingWorker<String,Integer> { |
|
// работаем до завершения процесса |
|
protected |
String doInBackground() throws Exception { |
int current = 5; |
|
while ( |
current <= MAX ) { |
//обновляем внешний вид publish(current++);
//случайная задержка
Thread.sleep((int)(Math.random()*1000));
}
return "Готово";
}
// выполняется в потоке рассылки событий
public void process(java.util.List<Integer> chunks) { // увеличиваем текущее значение model.setValue(chunks.get(0));
}
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new UsingProgressBars(); } });
}
}
Итак, мы создаем небольшое окно, в котором разместятся два индикатора процесса. Прежде всего необходимо создать поставщика данных для индикаторов — модель BoundedRangeModel. Мы задействуем стандартную модель, задав в конструкторе текущее значение равным пяти, внутренний диапазон — нулю (он нам не понадобится), минимальное значение — также равным нулю, а максимальное значение — ста. Полученная модель потребуется для обоих индикаторов, так что они будут синхронно показывать ход процесса. С другой стороны, как это принято в Swing, вы можете делать вид, что модели нет, задавая минимальное и максимальное значения в особом конструкторе и при необходимости меняя их или текущее значение методами класса JProgressBar. Но это не так элегантно.
Далее мы создаем сами индикаторы процесса JProgressBar. Индикаторы могут быть вертикальными и горизонтальными, управляет этим свойство orientation, но задать ориентацию индикатора можно и в конструкторе. Если вы не указываете ориентацию индикатора процесса явно, он считается горизонтальным. Первый индикатор создается конструктором, принимающим в качестве параметра нашу модель BoundedRangeModel. По умолчанию никакого текста индикатор прорисовывать не будет, чтобы он начал это делать, следует вызвать метод setStringPainted(true). Стандартно прорисовывается строка, показывающая, сколько
впроцентах от максимального занимает текущее значение, то есть какая часть процесса уже позади (значения эти хранятся в модели). Второй индикатор создается конструктором,
вкотором мы указываем его ориентацию: он будет вертикальным. Далее мы присоединяем к нему нашу модель методом setModel(), включаем прорисовку текста уже известным мето-
