Наиболее простым является использование мастера создания компонента. О нём будет сказано в следующем параграфе.
Другой способ – создать для компонента класс BeanInfo, обеспечивающий поставку необходимой информации о компоненте. Этот класс должен реализовывать интерфейс BeanInfo, и его имя должно состоять из имени компонента и названия интерфейса. Например, для компонента MyComponent это будет MyComponentBeanInfo. Существует класс-адаптер SimpleBeanInfo для интерфейса BeanInfo. В его наследнике достаточно переписать методы, подлежащие модификации. Среда NetBeans позволяет с помощью мастера создать заготовку такого класса.
Для редактирования некоторых свойств в среде визуального проектирования требуется специальный объект – редактор свойств (Property Editor). Среда NetBeans позволяет с помощью мастера создать заготовку и для такого класса.
Мастер создания компонента в NetBeans
Рассмотрим подробнее процесс создания собственного компонента. В NetBeans для этого необходимо выбрать в меню File/New File…/JavaBeans Objects/JavaBeans Component и нажать кнопку Next>.
Создание компонента JavaBeans. Шаг 1
Далее в поле Class Name надо ввести имя компонента. В качестве примера мы введём MyBean. Затем обязательно следует выбрать пакет, в котором мы будем создавать компонент – мы выберем пакет нашего приложения. После чего следует нажать на кнопку Finish.
Создание компонента JavaBean. Шаг 2
Приведём код получившейся заготовки:
/*
* MyBean.java
*
* Created on 30 Октябрь 2006 г., 23:16
*/
package java_gui_example;
import java.beans.*;
import java.io.Serializable;
/**
* @author В.Монахов
*/
public class MyBean extends Object implements Serializable {
public static final String PROP_SAMPLE_PROPERTY = "sampleProperty";
private String sampleProperty;
private PropertyChangeSupport propertySupport;
public MyBean() {
propertySupport = new PropertyChangeSupport(this);
}
public String getSampleProperty() {
return sampleProperty;
}
public void setSampleProperty(String value) {
String oldValue = sampleProperty;
sampleProperty = value;
propertySupport.firePropertyChange(PROP_SAMPLE_PROPERTY,
oldValue, sampleProperty);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertySupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener
listener) {
propertySupport.removePropertyChangeListener(listener);
}
}
В данном компоненте создана заготовка для строкового свойства sampleProperty. Геттер public String getSampleProperty() обеспечивает чтение значения свойства, а сеттер public void setSampleProperty(String value) обеспечивает установку нового значения.
Служебный объект private PropertyChangeSupport propertySupport обеспечивает поддержку работы с обработчиком события PropertyChange. Отметим, что “property change” означает “изменение свойства”. Это событие должно возникать при каждом изменении свойств нашего компонента.
Как уже говорилось, в каждом объекте, поддерживающем работу с неким событием (в нашем случае это событие PropertyChange), имеется список объектов-слушателей событий (listeners). Иногда их называют зарегистрированными слушателями. Методы с названием fireИмяСобытия (“fire” – “стрелять”, в данном случае – “выстрелить событием”) осуществляют поочерёдный вызов зарегистрированных слушателей из списка для данного события, передавая им событие на обработку. В нашем случае это метод propertySupport.firePropertyChange. Сначала он обеспечивает создание объекта-события, если значение свойства действительно изменилось, а потом поочерёдно вызывает слушателей этого события для его обработки.
Методы
public void addPropertyChangeListener(PropertyChangeListener listener)
и
public void removePropertyChangeListener(PropertyChangeListener listener)
обеспечивают для компонента возможность добавления и удаления объекта слушателя - обработчика события Property Change.
Если требуется создать другие свойства или обеспечить добавление и удаление обработчиков других событий, можно воспользоваться соответствующим мастером. В узле Bean Patterns (“Pattern” означает “образец” ) следует правой кнопкой мыши вызвать всплывающее меню, и выбрать Add. А затем в зависимости от того, что необходимо, выбрать один из видов свойств (Property) или событий (Event). Об этом более подробно будет говориться далее.
Таким же образом удаляются свойства и события компонента.
Пример создания компонента в NetBeans – панель с заголовком
Задание новых свойств и событий
В качестве простейшего примера визуального компонента создаем панель, у которой имеется заголовок (title). Унаследуем наш компонент от класса javax.swing.JPanel – для этого в импорте запишем
import javax.swing.*;
а в качестве родительского класса вместо Object напишем JPanel .
С помощью рефакторинга заменим имя myBean на JTitledPanel, в узле полей Fields (а не в Bean Patterns!) поле sampleProperty на title, а константу PROP_SAMPLE_PROPERTY уберём, написав в явном виде имя свойства “title” в методе firePropertyChange.
После чего в области Bean Patterns правой клавишей мыши вызовем всплывающее меню, и там вызовем пункт Rename… (“Переименовать”) для свойства sampleProperty – заменим имя на title. Это приведёт к тому, что методы getSampleProperty и setSampleProperty будут переименованы в getTitle и setTitle.
Обязательно следует присвоить начальное значение полю title – в заготовке, полученной из Bean Pattern, это не делается. Мы установим
private String title=”Заголовок”;
Для показа заголовка необходимо импортировать классы java.awt.Graphics, java.awt.geom.Rectangle2D и переопределить в JTitledPanel.java метод paint:
public void paint(Graphics g){
super.paint(g);
FontMetrics fontMetrics=g.getFontMetrics();
Rectangle2D rect = fontMetrics.getStringBounds(title, g);
g.drawString(title,(int)Math.round((this.getWidth()-rect.getWidth())/2),
10);
}
Для того, чтобы можно было пользоваться классами Graphics, FontMetrics и Rectangle2D, нам следует добавить импорт
import java.awt.*;
import java.awt.geom.Rectangle2D;
Отметим, что можно было бы не вводить переменные fontMetrics и rect, а сразу писать в методе drawString соответствующие функции в следующем виде:
g.drawString(title,
(int)Math.round( ( this.getWidth() -
g.getFontMetrics().getStringBounds(title,g).getWidth()
)/2 ),
10);
Но от этого текст программы стал бы гораздо менее читаемым. Даже несмотря на попытки отформатировать текст так, чтобы было хоть что-то понятно.
Ещё одно необходимое изменение – добавление repaint() в операторе setTitle. Если этого не сделать, после изменения свойства компонент не перерисуется с вновь установленным заголовком.
В результате получим следующий код компонента:
/*
* JTitledPanel.java
*
* Created on 30 Октябрь 2006 г., 23:16
*/
package java_gui_example;
import java.beans.*;
import java.io.Serializable;
import javax.swing.*; //добавлено вручную
import java.awt.*; //добавлено вручную
import java.awt.geom.Rectangle2D; //добавлено вручную
/**
* @author В.Монахов
*/
public class JTitledPanel extends JPanel implements Serializable {
private String title="Заголовок"; //добавлено вручную
private PropertyChangeSupport propertySupport;
public JTitledPanel() {
super();
propertySupport = new PropertyChangeSupport(this);
}
public String getTitle() {
return title;
}
public void setTitle(String value) {
String oldValue = title;
title = value;
propertySupport.firePropertyChange(”title”, oldValue, title);
repaint(); //добавлено вручную
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertySupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener){
propertySupport.removePropertyChangeListener(listener);
}
public void paint(Graphics g){ //метод добавлен вручную
super.paint(g);
FontMetrics fontMetrics=g.getFontMetrics();
Rectangle2D rect = fontMetrics.getStringBounds(title, g);
g.drawString(title,(int)Math.round((this.getWidth() -
rect.getWidth())/2), 10);
}
}
Для того, чтобы добавить наш компонент в палитру, следует открыть файл JTitledPanel.java в окне редактора исходного кода, и в меню Tools выбрать пункт Add to Palette. После чего в появившемся диалоге выбрать палитру, на которую будет добавлен компонент.
Выбор палитры, на которую будет добавлен компонент
Желательно выбрать Beans (чтобы не путать наши компоненты со стандартными) и нажать OK. Теперь компонент можно использовать наравне с другими.
Использование созданного компонента
Теперь мы можем менять текст заголовка как в редакторе свойств на этапе визуального проектирования, так и программно во время работы приложения. Мы также можем рисовать по нашей панели, и заголовок при этом будет виден, как и отрисовываемые примитивы. Например, мы можем вывести по нажатию на какую-нибудь кнопку строку “Тест”:
Graphics g=jTitledPanel1.getGraphics();
FontMetrics fontMetrics=g.getFontMetrics();
Rectangle2D rect = fontMetrics.getStringBounds("Тест", g);
g.drawString("Тест",10,30 );
Если мы будем усовершенствовать код нашего компонента, нет необходимости каждый раз удалять его из палитры компонентов и заново устанавливать – достаточно после внесения изменений заново скомпилировать проект (Build main project – F11).
Добавление в компонент новых свойств
В компонент можно добавить новое свойство. Пусть мы хотим задать свойство titleShift типа int – оно будет задавать высоту нашего заголовка., выбираем в окне Projects… для соответствующего компонента узел Bean Patterns и щелкаем по ним правой клавишей мыши. В появившемся всплывающем меню выбираем Add/Property, после чего в появившемся диалоге вводим имя и тип свойства.
Добавление в компонент свойства, слева – обычного, справа - массива
Пункты Bound (“связанное свойство”) и Constrained (“стеснённое свойство”) позволяют использовать опцию “Generate Property Change Support” – без её выбора они ни на что не влияют.
Свойства вида Bound – обычные свойства. При отмеченных опциях “Bound” и “Generate Property Change Support” автоматически добавляется код, генерирующий в компоненте событие PropertyChange при изменении свойства компонента. Именно таким образом была ранее создан средой NetBeans код для работы с событием PropertyChange.
Например, если мы добавим целочисленное свойство titleShift (“shift”- сдвиг) вида Bound, задающее сдвиг заголовка по вертикали, в исходный код компонента добавится следующий текст: