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); } @Overrideprotected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) { updateStar(contentWidth, contentHeight); layoutInArea(star, contentX, contentY, contentWidth, contentHeight, -1, HPos.CENTER, VPos.CENTER); } @Overrideprotected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { return 150 + topInset + bottomInset; } @Overrideprotected 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