JSlider, JProgressbar, JSpinner

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

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

Ползунки JSlider

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

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

Модель BoundedRangeModel

Модель BoundedRangeModel очень проста и нет смысла реализовывать ее. Лучше использовать стандартную модель DefaultBoundedRangeModel, поставляемую вместе с библиотекой Swing. Стандартная модель позволяет определять и изменять все четыре значения и взаимодействует со слушателями. Именно DefaultBoundedRangeModel используют конструкторы ползунка JSlider по умолчанию.

Конструктор модели DefaultBoundedRangeModel

public DefaultBoundedRangeModel(int value, int extent, int min, int max)

Конструктор DefaultBoundedRangeModel принимает параметры для инициализации значений и вызывает исключение IllegalArgumentException, если не выполняется следующее условие :
min <= value <= value+extent <= max

Модель BoundedRangeModel поддерживает специальный режим valueIsAdjusting, в котором она перестает оповещать слушателей об изменениях в данных. Этот режим включается UI-представителем при перетаскивании пользователем регулятора, чтобы не замедлять программу запуском событий на каждое изменение положения указателя мыши. При завершении перетаскивания модель возвращается в обычное состояние и сообщает слушателям об изменении в данных, вызывая событие ChangeEvent.

Конструкторы ползунков JSlider

public JSlider(int min, int max);
public JSlider(int min, int max, int value);
public JSlider(int orientation, int min, int max, int value);
public JSlider(BoundedRangeModel brm);

Первый конструктор принимает два параметра: минимальное и максимальное значения. Текущим значением такого ползунка будет среднее между предельными значениями. Для второго конструктора текущее значение передается третьим параметром. Третий конструктор определяет ориентацию и значения. Четвертый конструктор создается с использованием модели.

Конструкторы JSlider создают по умолчанию горизонтальный ползунок. Для создания вертикального ползунка необходимо использовать третий конструктор с соответствующим параметром orientation или применить метод setOrientation. Если ползунок создается конструктором без модели, то будет использоваться стандартная модель DefaultBoundedRangeModel.

Настройка внешнего вида ползунков

Свойство (методы get/set)Описание
majorTickSpacing Определение расстояния для прорисовки больших делений или меток, если они выводятся.
minorTickSpacing Определение расстояния для прорисовки промежуточных делений. Желательно выбирать значение так, чтобы между большими делениями было кратное количество маленьких делений.
paintTicks Включение или отключение прорисовки делений.
paintLabels Прорисовка меток под большими делениями.
paintTrack Управление отображением на экране полосы (шкалы), по которой перемещается регулятор. По умолчанию шкала отображается. Это свойство полезно, если потребуется создать собственную шкалу без разработки UI-представителя ползунка «с нуля».
snapToTicks Определение привязки положения ползунка к меткам, если значение true (метки должны быть включены).

В следующем примере создаётся 4 разнообразных ползунка. Для трех ползунков используется модель BoundedRangeModel. Третий ползунок создается полнофункциональным конструктором напрямую с определением типа ползунка. Ползунки JSlider могут быть горизонтальными (JSlider.HORIZONTAL) и вертикальными (JSlider.VERTICAL).

Для создания модели ползунков используется конструктор класса DefaultBoundedRangeModel. Внутренний диапазон extent не используется и в конструктор передается нулевое значение.

Листинг примера использования ползунков JSlider

// Пример использования ползунков

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import java.awt.BorderLayout;
import java.util.Dictionary;
import java.util.Hashtable;

public class SliderTest extends JFrame 
{
    private JLabel label;

    public SliderTest()
    {
        super("Пример использования ползунков JSlider");
        setDefaultCloseOperation(EXIT_ON_CLOSE);

        // Таблица с надписями ползунка
        Dictionary <Integer, JLabel> labels = new Hashtable<Integer, JLabel>();
        labels.put(new Integer(0), new JLabel("<html><font color=red size=3>0"));
        labels.put(new Integer(60), new JLabel("<html><font color=gray size=3>30"));
        labels.put(new Integer(120), new JLabel("<html><font color=blue size=4>60"));
        labels.put(new Integer(180), new JLabel(new ImageIcon("images/star.png")));

        // Создание модели ползунков
        BoundedRangeModel model = new DefaultBoundedRangeModel(80, 0, 0, 200);

        // Создание ползунков
        JSlider slider1 = new JSlider(model);
        JSlider slider2 = new JSlider(model);
        JSlider slider3 = new JSlider(0, 80, 20);
        JSlider slider4 = new JSlider(model);

        // Настройка внешнего вида ползунков
        slider2.setOrientation(JSlider.VERTICAL);
        slider2.setMajorTickSpacing(50);
        slider2.setMinorTickSpacing(10);
        slider2.setPaintTicks(true);

        slider3.setPaintLabels(true);
        slider3.setMajorTickSpacing(10);

        slider4.setLabelTable(labels);
        slider4.setPaintLabels(true);

        label = new JLabel(getPercent(slider1.getValue()));
        // присоединяем слушателя
        slider1.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                // меняем надпись
                int value = ((JSlider)e.getSource()).getValue();
                label.setText(getPercent(value));
            }
        });
        // Размещение ползунков в интерфейсе
        JPanel contents = new JPanel();
        contents.add(slider1);
        contents.add(slider2);
        contents.add(slider3);
        contents.add(slider4);
        getContentPane().add(contents);
        getContentPane().add(label, BorderLayout.SOUTH);
        // Вывод окна на экран
        setSize(500, 300);
        setVisible(true);
    }
    private String getPercent(int value)
    {
        return "Размер : " + (int)value/2 + "%";
    }
    public static void main(String[] args) {
        new SliderTest();
    }
}

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

Внешний вид JSlider можно настроить с использованием собственных надписей для определенных значений. Для этого следует в метод setLabelTable() передать список в виде подкласса таблицы Dictionary, сопоставляя целому числу («обернутому» в Integer) соответствующую надпись. В качестве метки (надписи) могут выступать любые компоненты, унаследованные от JComponent. В примере данная функция реализована для четвертой таблицы.

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

На следующем скриншоте представлен интерфейс примера использования JSlider.

Событие ползунков ChangeEvent

Ползунки JSlider являются несложными компонентами и имеют всего одно событие ChangeEvent, которое вызывается моделью BoundedRangeModel при изменении текущего значения ползунка. В примере показано как подключить к ползунку данное событие, узнавать о перемещении регулятора и отображать в интерфейсе значение.

Присоединить слушателя событий ChangeEvent можно либо к используемой в ползунке модели BoundedRangeModel, либо к самому ползунку JSlider. Отличие связано с источником события, получаемого методом getSource() класса ChangeEvent. В первом случае источником будет модель, а во втором — ползунок. И модель, и ползунок позволяют управлять значениями.

Индикаторы процесса JProgressBar

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

Данные JProgressBar черпает из модели BoundedRangeModel. В отличие от ползунков JSlider индикаторы процесса JProgressBar используют лишь три значения : минимальное и максимальное значения задаются сразу же, а текущее значение должно обновляться по мере выполнения процесса.

При использовании индикатора JProgressBar необходимо создать отдельный поток Thread, в который помещается обработка процесса, чтобы значение индикатора можно было непрерывно обновлять и интерфейс программы не «страдал», если процесс окажется требовательным к загрузке процессора и других ресурсов.

Конструкторы JProgressBar

// Создание индикатора процесса определенной ориентации
public JProgressBar(int orient);

// Создание горизонтального индикатора процесса с определенной моделью
public JProgressBar(BoundedRangeModel newModel);

// Создание горизонтального индикатора процесса с определенными 
// минимальным и максимальным значениями
public JProgressBar(int min, int max);

Ориентация индикатора процесса может быть горизонтальной SwingConstants.VERTICAL и вертикальной SwingConstants.VERTICAL. По умолчанию используется горизонтальное расположение. Если значение ориентации окажется неопределенным, то будет вызвано исключение IllegalArgumentException. Конструктор использует следующие значения индикатора процесса по умолчанию - минимальное 0, максимальное 100; текстовое значение в полосе не отображается.

Листинг примера использования JProgressBar

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

Для первого индикатора используется конструктор с определением вертикальной ориентации. Если не указывать ориентацию индикатора процесса явно, то устанавливается горизонтальная ориентация. Модель к первому индикатору подключается с ипользованием метода setModel(). Второй и третий индикаторы создаются с использованием конструктора с моделью.

// Пример использования JProgressBar

import javax.swing.*;

public class ProgressBarTest extends JFrame 
{
    // Общая модель 
    private BoundedRangeModel model;
    public ProgressBarTest() {
        super("Пример использования JProgressBar");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        // Создание стандартной модели
        model = new DefaultBoundedRangeModel(5, 0, 0, 100);
        // Вертикальный индикатор
        JProgressBar pbVertical = new JProgressBar(JProgressBar.VERTICAL);
        pbVertical.setModel(model);
        pbVertical.setStringPainted(true);
        pbVertical.setString("Процесс ...");

        // Горизонтальный индикатор
        JProgressBar pbHorizontal = new JProgressBar(model);
        pbHorizontal.setStringPainted(true);

        // Настройка параметрой UI-представителя
        UIManager.put("ProgressBar.cellSpacing", new Integer(2));
        UIManager.put("ProgressBar.cellLength", new Integer(6));
        // Создание индикатора
        JProgressBar pbUIManager = new JProgressBar(model);        
        
        // Неопределенный индикатор
        JProgressBar pbUndefined = new JProgressBar(0, 100);
        pbUndefined.setIndeterminate(true);
        pbUndefined.setStringPainted(true);

        // Размещение индикаторов в окне
        JPanel contents = new JPanel();
        contents.add(pbVertical  );
        contents.add(pbHorizontal);
        contents.add(pbUIManager );
        contents.add(pbUndefined );

        // Вывод окна на экран
        setContentPane(contents);
        setSize(360, 220);
        setVisible(true);
        // Старт "процесса"
        new ThreadProcess().start();
    }
    // Поток эмуляции некоторого процесса
    class ThreadProcess extends Thread {
        public void run() {
            // Проверка завершения процесса
            while ( model.getValue() < model.getMaximum() ) {
                try {
                    // Увеличение текущего значение
                    model.setValue(model.getValue() + 1);
                    // Случайная временная задержка
                    sleep((int)(Math.random() * 1000));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public static void main(String[] args) {
        new ProgressBarTest();
    }
}

По умолчанию индикатор процесса текст не прорисовывает. Чтобы в индикаторе отображать текст необходимо вызвать метод setStringPainted(true). В этом случае будет прорисовываться строка, показывающая текущее значение в процентах. Чтобы прорисовывать нестандартный текст необходимо применить метод setString(String text). Текст прорисовывается в той же оирентации, как располагается сам индикатор. Для 1-го индикатора текст отображается вертикально.

На следующем скриншоте представлен интерфейс примера использования JProgressBar.

Внешний вид индикатора JProgressBar можно изменить с помощью некоторых незамысловатых свойств UI-представителя, не попавших в класс самого компонента. UI-представители индикаторов процесса выполняют фактическую прорисовку компонента на экране. Свойства UI-представителей всех компонентов Swing для любого внешнего вида хранятся в специальной таблице класса UIManager, и их можно менять с использованием метода put().

С помощью внутренних свойств UI-представителей индикаторов процесса можно тонко настроить внешний вид перемещающейся в них полосы. Свойство cellLength управляет размером полосы индикатора. Полоса индикатора не обязательно прорисовывается сплошной, она может состоять из набора ячеек одинакового размера, а свойство cellSpacing задает расстояние между ячейками. Если cellSpacing приравнять нулю, то полоса будет сплошной. 3-ий индикатор создан с использованием «тонкой настройки внешнего вида».

Иногда возникают ситуации, когда нельзя сказать, сколько потребуется времени или ресурсов до завершения процесса и на каком этапе процесс находится. В этом случае можно показать, что программа не зависла и процесс продолжается. Для таких ситуаций индикаторы процесса JProgressBar поддерживают состояние «неопределенности», при котором происходит периодическое движение регулятора на полосе вперед и назад. Для этого необходимо вызвать метод setIndeterminate(true), как это представлено в 4-ом индикаторе. Данный индикатор создается с использованием конструктора, которому передается минимальное и максимальное значения, а стандартная модель создается автоматически. Дополнительно включена прорисовка текста методом setStringPainted(true), в результате чего в полосе отобразится текущее значение процесса «0%».

Счетчик JSpinner

Счетчики JSpinner позволяют выбирать значение из набора данных путем «прокручивания» списка вперед и назад. Внешне счетчик похож на раскрывающийся список JComboBox с выбранным одним элементом списка. Но раскрывающийся список позволяет просматривать набор данных в выпадающем окне, а счетчик вообще не показывает набора своих элементов. Данное свойство счетчика позволяет использовать неограниченное количество элементов.

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

Счетчики JSpinner могут использовать одну из трех стандартных моделей :

  • SpinnerListModel - модель содержит определенный набор элементов;
  • SpinnerNumberModel - модель определяет набор числовых значений;
  • SpinnerDateModel - модель хранения/выбора дат.

Модель можно передать конструктору счетчика или подключить отдельно с использованием метода setModel().

Модель SpinnerListModel в качестве данных может принимать список типа List (к примеру ArrayList). Список элементов можно сменить после того, как модель создана или даже подключена. Для этого следует использовать метод модели setList().

Модель для выбора чисел SpinnerNumberModel в качестве параметров принимает начальное значение, минимальное значение, максимальное значения и приращение, на которое будет увеличиваться или уменьшаться текущее значение. SpinnerNumberModel имеет несколько перегруженных конструкторов для выбора целых чисел (int), чисел с плавающей запятой (double), произвольных чисел, представленных объектами Number. Сравнение этих объектов выполняется с помощью задаваемых в конструкторе объектов Comparable.

Модель SpinnerDateModel позволяет организовать гибкий выбор дат из определенного диапазона. Принимая во внимание, что в Swing отсутствует стандартный компонент «календаря», данная модель находит в некоторых случаях применение. Для использования модели выбора дат необходимо понимать, как можно манипулировать датами. Модель SpinnerDateModel использует класс Date, который описывает момент во времени с точностью до миллисекунды, и класс Calendar, позволяющий менять даты по любому из параметров (день недели, день месяца, год, месяц или даже эра). Конструктору SpinnerDateModel необходимо передать текущее значение даты и минимальную и максимальную даты, реализующие интерфейс Comparable. Если минимальное и максимальное значения задать нулевыми (null), то диапазон будет неограниченным.

Листинг примера использования JSpinner

// Использование счетчиков JSpinner и моделей SpinnerModel 

import javax.swing.*;

import java.util.Date;
import java.util.Calendar;

public class JSpinnerTest extends JFrame
{
    private  String[]  data     = {"Понедельник", "Вторник", "Среда", "Четверг", 
                                   "Пятница", "Суббота", "Воскресенье"};
    private  Calendar  calendar = Calendar.getInstance();
    
    public JSpinnerTest()
    {
        super("Пример использования JSpinner");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        // Создание модели списка из массива строк
        SpinnerModel week = new SpinnerListModel(data);
        // Создание модели списка целых чисел
        SpinnerModel numbers = new SpinnerNumberModel(8, 0, 16, 2);

        // Модель выбора дня месяца
        SpinnerModel dayModel = new SpinnerDateModel(new Date(), null, null, 
                                                     Calendar.DAY_OF_MONTH);
        // Модель выбора дня текущего месяца
        SpinnerModel monthModel = new SpinnerDateModel(new Date(), 
                                                       new MinDate(), 
                                                       new MaxDate(),
                                                       Calendar.MONTH);

        // Создание счетчиков
        JSpinner spinInt   = new JSpinner(numbers   );
        JSpinner spinWeek  = new JSpinner(week      );
        JSpinner spinDay   = new JSpinner(dayModel  );
        JSpinner spinMonth = new JSpinner(monthModel);

        // Размещение счетчиков в интерфейсе
        JPanel contents = new JPanel();
        contents.add(spinInt  );
        contents.add(spinWeek );
        contents.add(spinDay  );
        contents.add(spinMonth);

        setContentPane(contents);

        // Вывод окна на экран
        setSize(500, 90);
        setVisible(true);
    }
    // Проверка минимальной даты
    class MinDate extends Date implements Comparable<Date>
    {
        private static final long serialVersionUID = 1L;

        public int compareTo(Date o) {
            Date d = (Date)o;
            calendar.setTime(d);

            int year  = calendar.get(Calendar.YEAR );
            int month = calendar.get(Calendar.MONTH);

            Calendar prev = Calendar.getInstance();
            prev.add(Calendar.MONTH, -1);
            int year_pred  = prev.get(Calendar.YEAR );
            int month_pred = prev.get(Calendar.MONTH);
            // Только текущий месяц
            return (((year == year_pred) && (month > month_pred)) ||
                    ((year > year_pred) && (month < month_pred))) ? -1 : 1;
        }
    }
    // Проверка максимальной даты
    class MaxDate extends Date implements Comparable<Date>
    {
        private static final long serialVersionUID = 1L;
        public int compareTo(Date o) {
            Date d = (Date)o;
            calendar.setTime(d);

            int year = calendar.get(Calendar.YEAR);
            int month = calendar.get(Calendar.MONTH);

            Calendar next = Calendar.getInstance();
            next.add(Calendar.MONTH, 1);
            int year_next  = next.get(Calendar.YEAR );
            int month_next = next.get(Calendar.MONTH);
            // Только текущий месяц
            return (((year == year_next) && (month < month_next)) ||
                    ((year < year_next) && (month > month_next))) ? 1 : -1;
        }
    }
    public static void main(String[] args) {
        new JSpinnerTest();
    }
}

В примере первый счетчик позволяет выбрать только целочисленное значение в диапазоне от 0 до 16. Второй счетчик определяет диапазон значений из дней недели. Для третьего счетчика используется модель dayModel, которая не имеет ограничений по выбору даты. При использовании модели monthModel для четвертого счетчика можно выбрать только дни текущего месяца. Модели dayModel и monthModel реализуют интерфейс Comparable.

Прокручивая счетчик можно столкнуться с не слишком приятной ситуацией : при смене элементов разной длины поле счетчика может динамически менять свой предпочтительный размер, что оказывает влияние на интерфейс. К сожалению JSpinner не имеет метод типа setPrototypeValue() списка JComboBox. Так что в отдельных случаях лучше использовать раскрывающийся список JComboBox.

На следующем скриншоте представлен интерфейс примера использования различных JSpinner.

Скачать примеры

Исходные коды примеров, рассмотренных в тексте страницы, можно скачать здесь (4.14 Кб).

  Рейтинг@Mail.ru