JavaFX 自定义控件未显示“操作”选项

问题描述 投票:0回答:1

嘿,我需要以下帮助:

我正在尝试将“On Action”添加到我在 Scene Builder 2.0 中创建的自定义控件中。

enter image description here

我的场景中会有几个这样的按钮,所以我希望能够为所有这些切换按钮只有 1 个处理程序。问题是我的自定义控件在“代码:”部分中没有像其他控件那样有“操作时”部分?

enter image description here

大多数内置控件的代码如下:部分:

enter image description here

如何将此功能添加到我的自定义控件中?

我的开关按钮代码:

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 中加载它时,我仍然在“代码”选项卡下看不到任何操作选项。

java javafx custom-controls fxml
1个回答
0
投票

自定义组件不会自动带有“on action”属性。您必须在代码中实际实现

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
放入“代码”手风琴中。自定义属性位于顶部名为“自定义”的部分下的“属性”折叠面板中。我不知道有什么方法可以改变这个。


示例

这是一个提供

onAction
属性的自定义“开关”控件的示例。此示例的自定义控件实际上扩展了
Control
,这意味着还有一个“皮肤”类和一个“行为”类来将事物分开。

答案末尾有Scene Builder的截图。

源代码

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(), obs -> onChanged());

    behavior = new SwitchBehavior(control);
  }

  @Override
  public void dispose() {
    super.dispose();
    if (behavior != null) {
      behavior.dispose();
      behavior = null;
    }
  }

  private void onChanged() {
    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.

Screenshot of Scene Builder showing

© www.soinside.com 2019 - 2024. All rights reserved.