Custom Control JavaFx: part 1
Пример создания собственного контролла в JavaFx. Например такой кнопки:
Часть 1: основные классы и обработчик событий
За функционирование контролла ответственны два класса - сам класс контролла с обработчиками событий и класс скина - наследники Control и BaseSkin соответственно:
Они соотносятся один-к-одному.
Рисует фигуру:
2. Возможность сменить цвет. Пусть например будет менять цвет при нажатии.
2.1. Сначала добавить поле проперти хранящее цвет в контроле:
2.2. В скине: добавить листенер на изменение добавленного выше поля backgrondFill. В листенере вызывается setFill() с новым цветом для существующей фигуры.
А метод getSkinnable() из родительского SkinBase - возвращает сам контрол к которому привязан скин (сейчас это StarButtonControl). Оттуда и берется для заливки измененный цвет.
2.3. Проверка что получилось. Добавить сам контрол и экшен на нажатие в контейнер:
Теперь нужно доделать обработку нажатий - что бы только клик по самой фигуре срабатывал, а не по области в которую она вписана. Для этого нужно добавить поле хранящее EventHandler и вызывать ивент только по клику на самой фигуре, Shape .
2. В скине - вызов экшена по событию mouseClick:
3. Проверка. При создании - добавить новый обработчик событий - onAction:
Книга Mastering JavaFx Controls
svn
![]() |
|
Основные классы
Они соотносятся один-к-одному.
public class StarButtonControl extends Control {
@Override
protected Skin<?> createDefaultSkin() {
return new StarButtonSkin(this);
}
}
В скин передается класс контролл к которому он относится:public class StarButtonSkin extends SkinBase<StarButtonControl> {
public StarButtonSkin(StarButtonControl control) {
super(control);
}
}
Эти классы и определяют контролл.Добаление внешнего вида
1. В скине добавлю метод рисующий звезду через набот точек в Path. А так же перегрузку некоторых методов. computePrefHeight, computePrefWidth - размеры, layoutChildren - перерисовка самого элемента.public class StarButtonSkin extends SkinBase<StarButtonControl> {
private Shape star;
// ...........
public void updateStar(double w, double h) {
if (star != null) {
getChildren().remove(star);
}
double x = w / 2.0;
double y = 0;
Color color = Color.ORANGE;
Path starPath = new Path();
starPath.setFill(color);
starPath.setFillRule(FillRule.NON_ZERO);
starPath.getElements().add(new MoveTo(x, y));
starPath.getElements().add(new LineTo((5.0 / 6.0) * w, w));
starPath.getElements().addAll(new LineTo(0, w / 3),
new LineTo(w, w / 3),
new LineTo(w / 6.0, w),
new LineTo(w / 2, 0),
new LineTo(x, y));
star = Path.intersect(starPath, starPath);
star.setFill(color);
getChildren().add(star);
}
@Override
protected void layoutChildren(double contentX, double contentY,
double contentWidth, double contentHeight) {
updateStar(contentWidth, contentHeight);
layoutInArea(star, contentX, contentY, contentWidth, contentHeight, -1,
HPos.CENTER, VPos.CENTER);
}
@Override
protected double computePrefHeight(double width, double topInset,
double rightInset, double bottomInset, double leftInset) {
return 150 + topInset + bottomInset;
}
@Override
protected double computePrefWidth(double height, double topInset,
double rightInset, double bottomInset, double leftInset) {
return 150 + topInset + bottomInset;
}
}
Рисует фигуру:
2. Возможность сменить цвет. Пусть например будет менять цвет при нажатии.
2.1. Сначала добавить поле проперти хранящее цвет в контроле:
public class StarButtonControl extends Control {
private final ObjectProperty<Color> backgroundFill;
public StarButtonControl() {
backgroundFill = new SimpleObjectProperty<>();
}
public ObjectProperty<Color> backgroundFillProperty() {
return backgroundFill;
}
public Color getBackgroundFill() {
return backgroundFill.get();
}
public void setBackgroundFill(Color color) {
backgroundFill.set(color);
}
@Override
protected Skin<?> createDefaultSkin() {
return new StarButtonSkin(this);
}
}
2.2. В скине: добавить листенер на изменение добавленного выше поля backgrondFill. В листенере вызывается setFill() с новым цветом для существующей фигуры.
А метод getSkinnable() из родительского SkinBase - возвращает сам контрол к которому привязан скин (сейчас это StarButtonControl). Оттуда и берется для заливки измененный цвет.
public class StarButtonSkin extends SkinBaseТак же добавлен флаг invalidStar для предотвращения лишних перерисовок.{ private Shape star; private boolean invalidStar = true; public StarButtonSkin(StarButtonControl control) { super(control);control.backgroundFillProperty().addListener(observable -> updateStarColor()); }private void updateStarColor() { if (star != null) { star.setFill(getSkinnable().getBackgroundFill()); getSkinnable().requestLayout(); } } // updateStarMethod.... @Override protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) {if (invalidStar) { updateStar(contentWidth, contentHeight);invalidStar = false; } layoutInArea(star, contentX, contentY, contentWidth, contentHeight, -1, HPos.CENTER, VPos.CENTER); } // compute pref width/height methods... }
2.3. Проверка что получилось. Добавить сам контрол и экшен на нажатие в контейнер:
public class FXMLController implements Initializable {
@FXML
private AnchorPane container;
@FXML
private Label messageLabel;
@Override
public void initialize(URL url, ResourceBundle rb) {
StarButtonControl sb = new StarButtonControl();
sb.setOnMouseClicked((MouseEvent event) -> {
sb.setBackgroundFill(Color.BROWN);
messageLabel.setText("Clicked");
});
container.getChildren().add(sb);
}
}
В результате нажатия кнопка меняет цвет:Обработчик событий
1. В контроле добавить поле-проперти для хранения EventHandler:public class StarButtonControl extends Control {
// ....
private final ObjectProperty<EventHandler<ActionEvent>> onAction =
new ObjectPropertyBase<EventHandler<ActionEvent>>() {
@Override
protected void invalidated() {
setEventHandler(ActionEvent.ACTION, get());
}
@Override
public Object getBean() {
return StarButtonControl.this;
}
@Override
public String getName() {
return "onAction";
}
};
public ObjectProperty<EventHandler<ActionEvent>> onActionProperty() {
return onAction;
}
public void setOnAction(EventHandler<ActionEvent> value) {
onAction.set(value);
}
public EventHandler<ActionEvent> getOnAction() {
return onAction.get();
}
}
2. В скине - вызов экшена по событию mouseClick:
public class StarButtonSkin extends SkinBase{ private Shape star; //.... public void updateStar(double w, double h) { // ... star.setOnMouseClicked(e -> getSkinnable().fireEvent(new ActionEvent())); getChildren().add(star); } }
3. Проверка. При создании - добавить новый обработчик событий - onAction:
public class FXMLController implements Initializable {
//........
@Override
public void initialize(URL url, ResourceBundle rb) {
StarButtonControl sb = new StarButtonControl();
sb.setOnAction(e -> {
// .....
});
container.getChildren().add(sb);
}
}
В результате клик работает только на самом контролле, а не вокруг него:Книга Mastering JavaFx Controls
svn



