Мир ПК, #10/1998
Постоянный адрес статьи: http://www.osp.ru/pcworld/1998/10/134.htm

Библиотека Swing: кнопки

Дмитрий Рамодин

17.10.1998

В современных графических интерфейсах кнопкам отводится большая роль, поэтому библиотека Swing содержит несколько кнопочных классов. Общее для всех кнопок поведение задает абстрактный класс AbstractButton. Важные данные компонента и интерфейс к ним хранятся в классе кнопочной модели DefaultButtonModel, реализующем методы интерфейса ButtonModel. Это еще один неявный "кнопочный родственник".

От AbstractButton наследуются два основных кнопочных класса: JButton и JToggleButton. Первый служит для создания обычных кнопок с разнообразными возможностями, а второй - для создания радиокнопок (класс JRadioButton) и отмечаемых кнопок (класс JCheckBox). Помимо названных, от AbstractButton наследуется пара классов JCheckBoxMenuItem и JRadioButtonMenuItem, используемых для организации тех меню, пункты которых должны быть оснащены отмечаемыми и радиокнопками.

Обычная кнопка

Класс JButton библиотеки Swing для создания обычных кнопок предлагает несколько различных конструкторов:

JButton()

JButton(String)

JButton(Icon)

JButton(String, Icon)

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

Класс JButton содержит несколько десятков методов, основные из которых приводятся в табл. 1, а для того чтобы была более понятна техника их использования, просмотрите листинг 1. Эта маленькая программа базируется на шаблоне, показанном в ╧ 8/98 "Мира ПК" (с. 183, листинг 1).

N.B. Обратите внимание на использование пустой рамки шириной в 15 пикселов, выполненной на основе класса EmptyBorder. Она придает программе эстетичный вид.

В примере, показанном в листинге 1, для размещения компонентов используется менеджер GridBagLayout. Если вы регулярно программируете на Java, то наверняка знакомы с ним, в противном случае обратитесь к документации по JDK.

N.B. Мы не задаем жестко размер окна, а позволяем виртуальной машине Java сделать его оптимальным. А для этого внутри метода main вызывается метод pack, после чего размер окна фиксируется вызовом метода setResizable.

При вызове конструктора класса JButton инициализируется ссылка jbtn. В качестве параметра ему передается строка в кодировке UNICODE, задающая надпись "Образцово-показательная кнопка". Для наглядности инициализация главной кнопки jbtn вынесена в отдельный метод setupButton. Внутри этого метода кнопке присваиваются пиктограммы для нормального, выключенного и нажатого состояний. Затем определяется пиктограмма, отображаемая в том случае, если указатель мыши находится непосредственно над кнопкой. И наконец, с помощью метода setToolTipText кнопке присваивается текст "всплывающей" подсказки.

Отмечаемая кнопка

Класс JCheckBox, как и класс обычной кнопки JButton, наследует методы абстрактной кнопки AbstractButton, поэтому большую часть методов JCheckBox вы найдете в табл. 1. Однако в силу функциональных особенностей у отмечаемой кнопки имеются методы, управляющие флагом включения/выключения (табл. 2).

С помощью отмечаемых кнопок осуществляется управление главной кнопкой jbtn. Поэтому в программу добавлен специальный внутренний класс-адаптер IL, реализующий интерфейс слушателя ItemListener, вызываемый при изменении состояния компонента. Обработка переключения отмечаемых кнопок происходит внутри метода itemStateChanged класса IL. При создании отмечаемых кнопок к каждой из них вызовом метода addItemListener подключается экземпляр класса-адаптера. Вслед за этим кнопка переводится в состояние "отмечено" с помощью метода setSelected.

Заглянем внутрь метода itemStateChanged. Когда пользователь включил или выключил отмечаемую кнопку, производится обработка события, для чего сначала получается ссылка на только что переключенную пользователем кнопку:
JCheckBox cb = (JCheckBox) e.getItem();

Затем мы получаем ссылку на модель главной кнопки jbtn:
ButtonModel bm = jbtn.getModel();

Теперь необходимо выяснить, в какое состояние перешла отмечаемая кнопка:
if(e.getStateChange() == ItemEvent.SELECTED) state = true;

Дальше все просто. Нужно определить, какая из кнопок сгенерировала событие: методом getText() считывается текст с отмечаемой кнопки, затем вызываются нужные методы, изменяющие состояние главной кнопки. Следует особо отметить, как обрабатывается переключение кнопки Released. Если ее выключить, то главная кнопка jbtn должна перейти в нажатое состояние. Казалось бы, для программного нажатия достаточно вызвать метод setPressed класса JButton. Однако это не так.

N.B. Программное нажатие произойдет лишь в том случае, если заранее был вызван метод setArmed.

Запустите пример и потестируйте его, включая и отключая различные опции, задаваемые отмечаемыми кнопками.

Радиокнопки

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

JRadioButton()
JRadioButton(Icon)
JRadioButton(Icon, boolean)
JRadioButton(String)
JRadioButton(String, boolean)
JRadioButton(String, Icon)
JRadioButton(String, Icon, boolean)

Параметры типа String задают надпись на кнопке, а параметры типа Icon определяют рисунок, который будет отображен вместо стандартного, рисуемого по умолчанию. Начальное состояние (включено/выключено) задается параметром конструктора, который имеет тип boolean. При значении true кнопка включается, а при false - выключается.

Методы класса JRadioButton практически совпадают с методами отмечаемых кнопок класса JCheckBox. Однако использовать радиокнопки несколько сложнее, поскольку они должны быть объединены в группы. А перед этим группа должна быть создана. Библиотека Swing содержит специальный класс ButtonGroup, от которого нам требуется всего три метода:

  • конструктор ButtonGroup();
  • add(AbstractButton) - метод добавления кнопки в группу;
  • remove(AbstractButton) - метод удаления кнопки из группы.

Создав экземпляр класса ButtonGroup, надо последовательно вызывать метод add для каждой из радиокнопок, сгруппировать их.

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

В качестве элемента пользовательского интерфейса, отвечающего за показ рисунков, используется класс JLabel. Вызывая метод setIcon этого класса, мы выводим заданное изображение в окно программы. Расположен метод setIcon непосредственно в обработчике события itemStateChanged, возникающего при переключении группы радиокнопок. Когда обработчик срабатывает, метод getItem класса ItemEvent получает ссылку на включенную кнопку, а уже по этой ссылке запрашивается присвоенный данной кнопке текст. Остается загрузить файл изображения в память и передать его экземпляру класса JLabel для отображения:

jlbl.setIcon(new ImageIcon
            ".\\Icons\\" + ((JRadioButton)e.
getItem()).get
Text()));

Кнопки JCheckBoxMenuItem и JRadioButtonMenuItem

Библиотека Swing предлагает воспользоваться новыми возможностями. Так, теперь вы сможете добавить к пунктам меню отмечаемые и радиокнопки. Это очень удобно, особенно когда вы создаете пункты меню для настройки опций приложения.

Меню с отмечаемыми кнопками можно сделать, воспользовавшись классом JCheckBoxMenuItem, для которого определены следующие конструкторы:

JCheckBoxMenuItem()
JCheckBoxMenuItem(Icon)
JCheckBoxMenuItem(String)
JCheckBoxMenuItem(String, Icon)
JCheckBoxMenuItem(String, boolean)
JCheckBoxMenuItem(String, Icon, boolean)

Как и в случае с другими кнопками, описанными ранее, аргументы типа String задают надпись для пункта меню, Icon - отображаемый в меню рисунок, boolean - включение/выключение кнопки меню.

Меню с радиокнопками создается конструкторами на базе класса JCheckBoxMenuItem:

JRadioButtonMenuItem()
JRadioButtonMenuItem(Icon)
JRadioButtonMenuItem(String)
JRadioButtonMenuItem(String, Icon)

Назначение параметров конструкторов такое же, как и у всех кнопок библиотеки Swing.

Как реализовать меню с кнопками, видно из листинга 3. В данном примере сначала изготавливается полоса меню. Для этого оператором new нужно создать экземпляр класса JMenuBar:
private JMenuBar bar = new JMenuBar();

После этого на основе класса JMenu формируются раскрывающиеся меню:

private JMenu cbmenu = new JMenu("\u041c\u0435\u043d\u044e");
...
private JMenu rbmenu = new JMenu("\u041a\u0440\u0430\u043d");

Затем с помощью классов JCheckBoxMenuItem и JRadioButtonMenuItem создаются пункты выбора:

private JCheckBoxMenuItem cbitem1
   = new JCheckBoxMenuItem("1 \u0411\u043b\u044e\u0434\u043e");
private JCheckBoxMenuItem cbitem2
   = new JCheckBoxMenuItem("2 \u0411\u043b\u044e\u0434\u043e");
private JCheckBoxMenuItem cbitem3
   = new JCheckBoxMenuItem("3 \u0411\u043b\u044e\u0434\u043e");
...
private JRadioButtonMenuItem rbitem1
     = new JRadioButtonMenuItem("\u041e\u0442\u043a\u0440\u044b\u0442\u044c");
private JRadioButtonMenuItem rbitem2
   = new JRadioButtonMenuItem("\u0417\u0430\u043a\u0440\u044b\u0442\u044c");

Таблица 1. Основные методы кнопочных классов
Метод Назначение
void setText(String) Установить надпись на кнопке
String getText() Считать надпись на кнопке
void setIcon(Icon) Установить пиктограмму, отображаемую в нормальном состоянии
Icon getIcon() Считать пиктограмму, отображаемую в нормальном состоянии
void setDisabledIcon(Icon) Установить пиктограмму, отображаемую, когда кнопка отключена
Icon getDisabledIcon() Считать пиктограмму, отображаемую, когда кнопка отключена
void setPressedIcon(Icon) Установить пиктограмму для нажатой кнопки
Icon getPressedIcon() Считать пиктограмму для нажатой кнопки
void setRolloverIcon(Icon) Установить пиктограмму, отображаемую в тот момент, когда указатель мыши находится над кнопкой
Icon getRolloverIcon() Считать пиктограмму, отображаемую в тот момент, когда указатель мыши находится над кнопкой
void setHorizontalAlignment(int) Установить горизонтальное выравнивание кнопки (константами LEFT, CENTER и RIGHT)
void setVerticalAlignment(int) Установить вертикальное выравнивание кнопки (константами TOP, CENTER и BOTTOM)
int getHorizontalAlignment() Считать горизонтальное выравнивание кнопки
int getVerticalAlignment() Считать вертикальное выравнивание кнопки
void setHorizontalTextPosition(int) Установить горизонтальное выравнивание текста относительно отображаемой на кнопке пиктограммы (константы LEFT, CENTER и RIGHT)
void setVerticalTextPosition(int) Установить вертикальное выравнивание текста относительно отображаемой на кнопке пиктограммы (константы TOP, CENTER и BOTTOM)
int getHorizontalTextPosition() Считать горизонтальное выравнивание текста относительно отображаемой на кнопке пиктограммы
int getVerticalTextPosition() Считать вертикальное выравнивание текста относительно отображаемой на кнопке пиктограммы
void setMargin(Insets) Установить зазор между краями кнопки и ее содержимым (в пикселах)
Insets getMargin() Считать зазор между краями кнопки и ее содержимым (в пикселах)
void setFocusPainted(boolean) Установить состояние флага, определяющего необходимость отображения графической рамки, если кнопка получила фокус ввода (значение true включает отображение)
boolean isFocusPainted() Считать состояние флага, определяющего необходимость отображения графической рамки в том случае, если кнопка получила фокус ввода
void setBorderPainted(boolean) Установить состояние флага, определяющего необходимость отрисовки краев кнопки (значение true включает отрисовку)
boolean isBorderPainted() Считать состояние флага, определяющего необходимость отрисовки краев кнопки
void setActionCommand(String) Установить имя команды, выполняемой при нажатии на кнопку
String getActionCommand(void) Считать имя команды, выполняемой при нажатии на кнопку
void setMnemonic(char) Установить клавишную комбинацию для нажатия кнопки
char getMnemonic() Считать клавишную комбинацию для нажатия кнопки
void doClick() Программно эмулировать нажатие кнопки

Затем в меню добавляются пункты выбора, а само меню добавляется в полосу меню программы:

cbmenu.add(cbitem1);
cbmenu.add(cbitem2);
cbmenu.add(cbitem3);
cbitem1.setSelected(true);
bar.add(cbmenu);
...
group.add(rbitem1);
group.add(rbitem2);
rbmenu.add(rbitem1);
rbmenu.add(rbitem2);
rbitem1.setSelected(true);
bar.add(rbmenu);
Таблица 2. Дополнительные методы класса JCheckBox
Метод Назначение
void setSelectedIcon(Icon) Установить пиктограмму, отображаемую на отмеченной кнопке
Icon getSelectedIcon() Считать пиктограмму, отображаемую на отмеченной кнопке
void setDisabledSelectedIcon(Icon) Установить пиктограмму, отображаемую на отмеченной кнопке, когда она отключена
Icon getDisabledSelectedIcon() Считать пиктограмму, отображаемую на отмеченной кнопке, когда она отключена
void setSelected(boolean) Отметить кнопку или снять отметку
boolean isSelected() Прочитать текущее состояние отмечаемой кнопки

При этом пункты меню с радиокнопками добавляются в кнопочную группу group точно так же, как обычные радиокнопки на основе класса JRadioButton.

И завершается инициализация программы вызовом специального метода, подключающего полосу меню к окну программы:

setJMenuBar(bar);

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

Таким образом средства библиотеки Swing позволяют внести элемент творчества в такую рутинную работу, как создание пользовательского интерфейса.


Листинг 1. Пример использования кнопок JButton и JCheckBox

import com.sun.java.swing.*;
import com.sun.java.swing.border.*;
import java.awt.*;
import java.awt.event.*;

public class Sample extends JFrame
{
  String[] labels = {
        "Enabled",
        "Rollover",
        "Released",
        "Draw focus",
        "Draw border"
  };

  private GridBagLayout gbl = new GridBagLayout();
  private GridBagConstraints lc = new GridBagConstraints();
  private JPanel jp = new JPanel(gbl);
  private JButton jbtn = new JButton(
       "\u041e\u0431\u0440\u0430\u0437\u0446" +
       "\u043e\u0432\u043e-\u043f\u043e\u043a\u0430\u0437" +
       "\u0430\u0442\u0435\u043b\u044c\u043d\u0430\u044f " +
       "\u043a\u043d\u043e\u043f\u043a\u0430");
  
  public Sample(String caption)
  {
    super(caption);
    try
    {
      UIManager.setLookAndFeel(
         UIManager.getCrossPlatformLookAndFeelClassName());
    }
    catch(Exception e){e.printStackTrace();};
    jp.setBorder(BorderFactory.createEmptyBorder(15,15,15,15));

    lc.gridheight = 5;
    lc.weightx = 1.0;
    lc.weighty = 1.0;
    lc.fill = GridBagConstraints.BOTH;
    setupButton();
    jp.add(jbtn, lc);

    lc.gridx = 1;
    lc.weightx = 0;
    lc.weighty = 0;
    lc.insets = new Insets(0, 15, 0, 0);
    lc.gridheight = 1;
    lc.anchor = GridBagConstraints.NORTH;
    for(int i = 0; i < labels.length; i++)
    {
      JCheckBox cb = new JCheckBox(labels[i]);
      cb.addItemListener(new IL());
      cb.setSelected(true);
      jp.add(cb, lc);
    }
    setContentPane(jp);
  }

  void setupButton()
  {
    jbtn.setIcon(new ImageIcon(".\\Icons\\normal.gif"));
    jbtn.setDisabledIcon(new
ImageIcon(".\\Icons\\disabled.gif"));
    jbtn.setPressedIcon(new
ImageIcon(".\\Icons\\pressed.gif"));
    jbtn.setRolloverIcon(new
ImageIcon(".\\Icons\\rollover.gif"));
    jbtn.setToolTipText(
      "\u0412\u0441\u043f\u043b\u044b\u0432\u0430\u044e" +
      "\u0449\u0430\u044f \u043f\u043e\u0434\u0441\u043a" +
      "\u0430\u0437\u043a\u0430 - \u043e\u0431\u044b" +
      "\u0447\u043d\u043e\u0435 \u0434\u0435\u043b\u043e " +
      "\u0434\u043b\u044f Swing");
  }

  private class IL implements ItemListener
  {
    public void itemStateChanged(ItemEvent e)
    {
      JCheckBox cb = (JCheckBox) e.getItem();
      ButtonModel bm = jbtn.getModel();
      boolean state;
      if(e.getStateChange() == ItemEvent.SELECTED)
state = true;
      else state = false;

      if(cb.getText().equals("Enabled"))
        jbtn.setEnabled(state);
      if(cb.getText().equals("Rollover"))
        jbtn.setRolloverEnabled(state);
      if(cb.getText().equals("Released"))
        {
          bm.setArmed(true);
          bm.setPressed(!state);
        }
      if(cb.getText().equals("Draw focus"))
        jbtn.setFocusPainted(state);
      if(cb.getText().equals("Draw border"))
        jbtn.setBorderPainted(state);
    }
  }

  public static void main(String[] args)
  {
    Sample s = new Sample(
"\u0411\u0438\u0431\u043b\u0438\u043e
\u0442\u0435\u043a\u0430Swing");
    s.addWindowListener(new WindowAdapter()
      {
        public void windowClosing(WindowEvent e)
        {
          System.exit(0);
        }
      });
    s.pack();
    s.setResizable(false);
    s.setVisible(true);
  }
}

Листинг 2. Пример использования кнопок JRadioButton

import com.sun.java.swing.*;
import com.sun.java.swing.border.*;
import java.awt.*;
import java.awt.event.*;

public class Sample extends JFrame
{
  String[] labels = {
        "normal.gif",
        "disabled.gif",
        "pressed.gif",
        "rollover.gif"
  };

  private ButtonGroup group =  new ButtonGroup();
  private GridBagLayout gbl = new GridBagLayout();
  private GridBagConstraints lc = new GridBagConstraints();
  private JPanel jp = new JPanel(gbl);
  private JLabel jlbl = new JLabel();
  
  public Sample(String caption)
  {
    super(caption);
    try
    {
      UIManager.setLookAndFeel(
         UIManager.getCrossPlatformLookAndFeelClassName());
    }
    catch(Exception e) {e.printStackTrace();};
    jp.setBorder(BorderFactory.createEmptyBorder(15,15,15,15));

    lc.gridheight = 4;
    lc.weightx = 1.0;
    lc.weighty = 1.0;
    lc.fill = GridBagConstraints.BOTH;
    jp.add(jlbl, lc);

    lc.gridx = 1;
    lc.weightx = 0;
    lc.weighty = 0;
    lc.insets = new Insets(0, 15, 0, 0);
    lc.gridheight = 1;
    lc.anchor = GridBagConstraints.NORTH;
    for(int i = 0; i < labels.length; i++)
    {
      JRadioButton rb = new JRadioButton(labels[i]);
      rb.addItemListener(new IL());
      group.add(rb);
      jp.add(rb, lc);
      if(i == 0) rb.setSelected(true);
    }
    jlbl.setPreferredSize(new Dimension(100, 100));
    setContentPane(jp);
  }

  private class IL implements ItemListener
  {
    public void itemStateChanged(ItemEvent e)
    {
      if(e.getStateChange() == ItemEvent.SELECTED)
         jlbl.setIcon(new ImageIcon(
            ".\\Icons\\" +((JRadioButton)e.getItem()).
getText()));
    }
  }

  public static void main(String[] args)
  {
    Sample s = new Sample(
"\u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430
Swing");
    s.addWindowListener(new WindowAdapter()
      {
        public void windowClosing(WindowEvent e)
        {
          System.exit(0);
        }
      });
    s.pack();
    s.setResizable(false);
    s.setVisible(true);
  }
}


Листинг 3. Пример использования кнопок меню

import com.sun.java.swing.*;
import com.sun.java.swing.border.*;
import java.awt.*;
import java.awt.event.*;

public class Sample extends JFrame
{
  private ButtonGroup group =  new ButtonGroup();
  private JMenuBar bar = new JMenuBar();
  private JMenu cbmenu = new JMenu("\u041c\u0435\u043d
\u044e");
  private JCheckBoxMenuItem cbitem1
     = new JCheckBoxMenuItem("1 \u0411\u043b\u044e\u0434
\u043e");
  private JCheckBoxMenuItem cbitem2
     = new JCheckBoxMenuItem("2 \u0411\u043b\u044e\u0434
\u043e");
  private JCheckBoxMenuItem cbitem3
     = new JCheckBoxMenuItem("3 \u0411\u043b\u044e\u0434
\u043e");
  private JMenu rbmenu = new JMenu("\u041a\u0440\u0430
\u043d");
  private JRadioButtonMenuItem rbitem1
     = new JRadioButtonMenuItem(
     "\u041e\u0442\u043a\u0440\u044b\u0442\u044c");
  private JRadioButtonMenuItem rbitem2
     = new JRadioButtonMenuItem(
     "\u0417\u0430\u043a\u0440\u044b\u0442\u044c");
  
  public Sample(String caption)
  {
    super(caption);
    try
    {
      UIManager.setLookAndFeel(
         UIManager.getCrossPlatformLookAndFeelClassName());
    }
    catch(Exception e){e.printStackTrace();};

    cbmenu.add(cbitem1);
    cbmenu.add(cbitem2);
    cbmenu.add(cbitem3);
    cbitem1.setSelected(true);
    bar.add(cbmenu);

    group.add(rbitem1);
    group.add(rbitem2);
    rbmenu.add(rbitem1);
    rbmenu.add(rbitem2);
    rbitem1.setSelected(true);
    bar.add(rbmenu);

    setJMenuBar(bar);
  }

  public static void main(String[] args)
  {
    Sample s = new Sample(
"\u0411\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430
Swing");
    s.addWindowListener(new WindowAdapter()
      {
        public void windowClosing(WindowEvent e)
        {
          System.exit(0);
        }
      });
    s.setSize(300,200);
    s.setResizable(false);
    s.setVisible(true);
  }
}
Мир ПК, #10/1998
Постоянный адрес статьи: http://www.osp.ru/pcworld/1998/10/134.htm