Привет всем, мне нужна помощь со следующим:
Я пытаюсь добавить «При действии» к своему пользовательскому элементу управления, который я создаю в Scene Builder 2.0.
В моей сцене будет несколько таких, поэтому я хочу иметь только один обработчик для всех этих кнопок переключения. Проблема в том, что у моего пользовательского элемента управления нет раздела «При действии» в разделе «Код:», как у других элементов управления?
Большинство встроенных элементов управления в разделе «Код:» выглядят следующим образом:
Как добавить эту функцию в свой пользовательский элемент управления?
Мой код кнопки переключения:
public final ObjectProperty<EventHandler<ActionEvent>> onActionProperty() { return onAction; }
public final void setOnAction(EventHandler<ActionEvent> value) { onActionProperty().set(value); }
public final EventHandler<ActionEvent> getOnAction() { return onActionProperty().get(); }
private ObjectProperty<EventHandler<ActionEvent>> onAction = new ObjectPropertyBase<EventHandler<ActionEvent>>() {
@Override protected void invalidated() {
setEventHandler(ActionEvent.ACTION, get());
}
@Override
public Object getBean() {
return SliderSwitch.this;
}
@Override
public String getName() {
return "onAction";
}
};
Загружая его в Scene Builder 2.0, я все еще не вижу никаких опций действия на вкладке «Код».
Пользовательские компоненты не получают автоматически свойство «при действии». Вам действительно нужно реализовать свойство onAction
в коде. В качестве примера взгляните на реализации встроенных элементов управления, которые предоставляют такое свойство. Обычно реализация свойства выглядит примерно так:
// assumes 'this' is some subtype of 'javafx.scene.Node'
private final ObjectProperty<EventHandler<ActionEvent>> onAction =
new SimpleObjectProperty<>(this, "onAction") {
@Override
protected void invalidated() {
setEventHandler(ActionEvent.ACTION, get());
}
};
public final void setOnAction(EventHandler<ActionEvent> onAction) { this.onAction.set(onAction); }
public final EventHandler<ActionEvent> getOnAction() { return onAction.get(); }
public final ObjectProperty<EventHandler<ActionEvent>> onActionProperty() { return onAction; }
Но учтите, этого недостаточно. Пользовательский компонент также должен активировать ActionEvent
всякий раз, когда это необходимо. Когда это уместно? Ну, это зависит от пользовательского компонента.
И, наконец, Scene Builder, к сожалению, не помещает свойство onAction
пользовательского компонента в аккордеон «Код». Он расположен в аккордеоне «Свойства» в разделе «Пользовательский» вверху (см. снимок экрана в конце примера ниже). Я не знаю, как это изменить.
Пара примечаний:
На самом деле вы можете добавлять прослушиватели изменений в свойства через FXML. Хотя я не знаю, как это сделать с помощью Scene Builder.
Scene Builder 2.0 — очень устаревшая версия. Рассмотрите возможность использования последней версии от Gluon, то есть версии 22.0.0 на момент написания этого ответа.
Вот пример пользовательского элемента управления «переключатель», который предоставляет свойство onAction
. В этом примере пользовательский элемент управления фактически расширяется Control
, что означает, что есть также класс «скин» и класс «поведение», чтобы разделить вещи.
В конце ответа есть скриншот Scene Builder.
Скомпилировано и протестировано с использованием Java 22.0.2 и JavaFX 22.0.2.
Switch.java
package com.example.control;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.css.PseudoClass;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
public class Switch extends Control {
public Switch() {
getStyleClass().add(DEFAULT_STYLE_CLASS);
}
public Switch(boolean selected) {
this();
setSelected(selected);
}
public void toggle() {
if (!isDisabled() && !selected.isBound()) {
setSelected(!isSelected());
}
}
@Override
protected Skin<?> createDefaultSkin() {
return new SwitchSkin(this);
}
/* **************************************************************************
* *
* Properties *
* *
****************************************************************************/
// -- selected property
private final BooleanProperty selected = new SimpleBooleanProperty(this, "selected") {
private boolean wasSelected;
@Override
protected void invalidated() {
boolean isSelected = get();
if (wasSelected != isSelected) {
pseudoClassStateChanged(SELECTED, isSelected);
fireEvent(new ActionEvent());
wasSelected = isSelected;
}
}
};
public final void setSelected(boolean selected) {
this.selected.set(selected);
}
public final boolean isSelected() {
return selected.get();
}
public final BooleanProperty selectedProperty() {
return selected;
}
// -- onAction property
private ObjectProperty<EventHandler<? super ActionEvent>> onAction;
public final void setOnAction(EventHandler<? super ActionEvent> onAction) {
if (this.onAction != null || onAction != null) {
onActionProperty().set(onAction);
}
}
public final EventHandler<? super ActionEvent> getOnAction() {
return onAction == null ? null : onAction.get();
}
public final ObjectProperty<EventHandler<? super ActionEvent>> onActionProperty() {
if (onAction == null) {
onAction = new SimpleObjectProperty<>(this, "onAction") {
@Override
protected void invalidated() {
setEventHandler(ActionEvent.ACTION, get());
}
};
}
return onAction;
}
/* **************************************************************************
* *
* CSS *
* *
****************************************************************************/
private static final String DEFAULT_STYLE_CLASS = "switch";
private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected");
}
SwitchSkin.java
package com.example.control;
import javafx.animation.Animation;
import javafx.animation.FillTransition;
import javafx.animation.ParallelTransition;
import javafx.animation.TranslateTransition;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.control.SkinBase;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.util.Duration;
class SwitchSkin extends SkinBase<Switch> {
private static final Duration ANIMATION_DURATION = Duration.millis(100);
private final Circle thumb = new Circle(10);
private final ParallelTransition animation;
private final TranslateTransition translateAnimation;
private SwitchBehavior behavior;
SwitchSkin(Switch control) {
super(control);
var fillAnimation = new FillTransition(ANIMATION_DURATION);
fillAnimation.setFromValue(Color.FIREBRICK);
fillAnimation.setToValue(Color.FORESTGREEN);
thumb.setFill(fillAnimation.getFromValue());
translateAnimation = new TranslateTransition(ANIMATION_DURATION);
translateAnimation.setFromX(0);
animation = new ParallelTransition(thumb, fillAnimation, translateAnimation);
}
@Override
public void install() {
var control = getSkinnable();
var bgFill = new BackgroundFill(Color.GRAY, new CornerRadii(10), new Insets(2));
control.setBackground(new Background(bgFill));
control.setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
control.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
getChildren().add(thumb);
registerChangeListener(control.selectedProperty(), _ -> selectedChanged());
behavior = new SwitchBehavior(control);
}
@Override
public void dispose() {
super.dispose();
if (behavior != null) {
behavior.dispose();
behavior = null;
}
}
private void selectedChanged() {
animation.setRate(isSelected() ? 1 : -1);
animation.play();
}
private boolean isSelected() {
return getSkinnable().isSelected();
}
private boolean animationNotRunning() {
return animation.getStatus() != Animation.Status.RUNNING;
}
@Override
protected void layoutChildren(
double contentX, double contentY, double contentWidth, double contentHeight) {
positionInArea(
thumb, contentX, contentY, contentWidth, contentHeight, -1, HPos.LEFT, VPos.CENTER);
double toX = contentX + contentWidth - thumb.getLayoutBounds().getWidth();
translateAnimation.setToX(toX);
if (isSelected() && animationNotRunning() && thumb.getTranslateX() != toX) {
animation.setRate(1);
animation.playFromStart();
} else if (!isSelected() && animationNotRunning() && thumb.getTranslateX() != 0) {
animation.setRate(-1);
animation.playFrom(ANIMATION_DURATION);
}
}
@Override
protected double computePrefWidth(
double height, double topInset, double rightInset, double bottomInset, double leftInset) {
return leftInset + rightInset + (thumb.getRadius() * 4);
}
@Override
protected double computePrefHeight(
double width, double topInset, double rightInset, double bottomInset, double leftInset) {
return topInset + bottomInset + (thumb.getRadius() * 2);
}
}
SwitchBehavior.java
package com.example.control;
import java.util.Objects;
import javafx.event.EventHandler;
import javafx.event.WeakEventHandler;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
class SwitchBehavior {
private final EventHandler<MouseEvent> onClick = this::handleMouseClicked;
private final WeakEventHandler<MouseEvent> weakOnClick = new WeakEventHandler<>(onClick);
private final Switch node;
SwitchBehavior(Switch node) {
this.node = Objects.requireNonNull(node);
node.addEventHandler(MouseEvent.MOUSE_CLICKED, weakOnClick);
}
private void handleMouseClicked(MouseEvent event) {
if (event.getButton() == MouseButton.PRIMARY) {
node.toggle();
}
}
void dispose() {
node.removeEventHandler(MouseEvent.MOUSE_CLICKED, weakOnClick);
}
}
Использование Конструктора сцен 22.0.0.
Ах, я вижу проблему. В версии 2.0 он не отображается, но я вижу, что он отображается в версии 22.