javafx : 支持使用微调(spinner)控制的数字的文本框(NemberTextField)

最近花了一些时间学习javaFX, 要更深入地理解新GUI包, 自定义控制器可能是一个比较好的方法.
javaFX中也有类似JFormattedTextField和JSpinner的控件. 这对我来说确实是个不错的选择.

这是我的控制器:
[list]
[*]数字文本框(NumberTextField): 可以配置任意格式的数字;
[*]微调控制器( NumberSpinner ): 可以使用键盘方向键或箭头按钮来控制数值;它也是控制器的一部分;
[/list]
控制器及其示例可以在这里下载(可直接导入到netbeans,见附件). 示例中还包含一个css样式文件, 它用于控制Spinner的风格是直角或圆角.
[img]http://dl2.iteye.com/upload/attachment/0130/8654/a00a5f48-34f0-3d59-9ff4-6fa339831efa.png[/img]
[img]http://dl2.iteye.com/upload/attachment/0130/8656/a39a02d5-8ffd-3cdd-9ba0-bfecc1dba49d.png[/img]

[b]NumberTextField[/b]

NumberTextField 的实现很容易,以致我认为这算不上自定义控制器, 而仅仅是改变一个已存在的控制器的一些行为而已. NumberTextField 扩展自JFX中的文本框(TextField), 添加一个使用BigDecimal的NumberProperty作为模型, 并做一些格式化和解析处理. 就这样, 不复杂.

import java.math.BigDecimal;
import java.text.NumberFormat;
import java.text.ParseException;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.TextField;

/**
* Textfield implementation that accepts formatted number and stores them in a
* BigDecimal property The user input is formatted when the focus is lost or the
* user hits RETURN.
*
* @author Thomas Bolz
*/
public class NumberTextField extends TextField {

private final NumberFormat nf;
private ObjectProperty<BigDecimal> number = new SimpleObjectProperty<>();

public final BigDecimal getNumber() {
return number.get();
}

public final void setNumber(BigDecimal value) {
number.set(value);
}

public ObjectProperty<BigDecimal> numberProperty() {
return number;
}

public NumberTextField() {
this(BigDecimal.ZERO);
}

public NumberTextField(BigDecimal value) {
this(value, NumberFormat.getInstance());
}

public NumberTextField(BigDecimal value, NumberFormat nf) {
super();
this.nf = nf;
initHandlers();
setNumber(value);
}

private void initHandlers() {

// try to parse when focus is lost or RETURN is hit
setOnAction(new EventHandler<ActionEvent>() {

@Override
public void handle(ActionEvent arg0) {
parseAndFormatInput();
}
});

focusedProperty().addListener(new ChangeListener<Boolean>() {

@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (!newValue.booleanValue()) {
parseAndFormatInput();
}
}
});

// Set text in field if BigDecimal property is changed from outside.
numberProperty().addListener(new ChangeListener<BigDecimal>() {

@Override
public void changed(ObservableValue<? extends BigDecimal> obserable, BigDecimal oldValue, BigDecimal newValue) {
setText(nf.format(newValue));
}
});
}

/**
* Tries to parse the user input to a number according to the provided
* NumberFormat
*/
private void parseAndFormatInput() {
try {
String input = getText();
if (input == null || input.length() == 0) {
return;
}
Number parsedNumber = nf.parse(input);
BigDecimal newValue = new BigDecimal(parsedNumber.toString());
setNumber(newValue);
selectAll();
} catch (ParseException ex) {
// If parsing fails keep old number
setText(nf.format(number.get()));
}
}
}


[b]NumberSpinner[/b]

NumberSpinner好像复杂一点. 它构建在NumberTextField 上, 并使用递增和递减按钮来改变文本框中数字的值, 每次改变步长为stepwidth.

stepwidth和NumberFormat的初始值在构造器中指定. 文本框和按钮的大小取决于文本的大小. 文本的大小可在.css文件中设置.

import java.math.BigDecimal;
import java.text.NumberFormat;
import javafx.beans.binding.NumberBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javax.swing.JSpinner;

/**
* JavaFX Control that behaves like a {@link JSpinner} known in Swing. The
* number in the textfield can be incremented or decremented by a configurable
* stepWidth using the arrow buttons in the control or the up and down arrow
* keys.
*
* @author Thomas Bolz
*/
public class NumberSpinner extends HBox {

public static final String ARROW = "NumberSpinnerArrow";
public static final String NUMBER_FIELD = "NumberField";
public static final String NUMBER_SPINNER = "NumberSpinner";
public static final String SPINNER_BUTTON_UP = "SpinnerButtonUp";
public static final String SPINNER_BUTTON_DOWN = "SpinnerButtonDown";
private final String BUTTONS_BOX = "ButtonsBox";
private NumberTextField numberField;
private ObjectProperty<BigDecimal> stepWitdhProperty = new SimpleObjectProperty<>();
private final double ARROW_SIZE = 4;
private final Button incrementButton;
private final Button decrementButton;
private final NumberBinding buttonHeight;
private final NumberBinding spacing;

public NumberSpinner() {
this(BigDecimal.ZERO, BigDecimal.ONE);
}

public NumberSpinner(BigDecimal value, BigDecimal stepWidth) {
this(value, stepWidth, NumberFormat.getInstance());
}

public NumberSpinner(BigDecimal value, BigDecimal stepWidth, NumberFormat nf) {
super();
this.setId(NUMBER_SPINNER);
this.stepWitdhProperty.set(stepWidth);

// TextField
numberField = new NumberTextField(value, nf);
numberField.setId(NUMBER_FIELD);

// Enable arrow keys for dec/inc
numberField.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {

@Override
public void handle(KeyEvent keyEvent) {
if (keyEvent.getCode() == KeyCode.DOWN) {
decrement();
keyEvent.consume();
}
if (keyEvent.getCode() == KeyCode.UP) {
increment();
keyEvent.consume();
}
}
});

// Painting the up and down arrows
Path arrowUp = new Path();
arrowUp.setId(ARROW);
arrowUp.getElements().addAll(new MoveTo(-ARROW_SIZE, 0), new LineTo(ARROW_SIZE, 0),
new LineTo(0, -ARROW_SIZE), new LineTo(-ARROW_SIZE, 0));
// mouse clicks should be forwarded to the underlying button
arrowUp.setMouseTransparent(true);

Path arrowDown = new Path();
arrowDown.setId(ARROW);
arrowDown.getElements().addAll(new MoveTo(-ARROW_SIZE, 0), new LineTo(ARROW_SIZE, 0),
new LineTo(0, ARROW_SIZE), new LineTo(-ARROW_SIZE, 0));
arrowDown.setMouseTransparent(true);

// the spinner buttons scale with the textfield size
// TODO: the following approach leads to the desired result, but it is
// not fully understood why and obviously it is not quite elegant
buttonHeight = numberField.heightProperty().subtract(3).divide(2);
// give unused space in the buttons VBox to the incrementBUtton
spacing = numberField.heightProperty().subtract(2).subtract(buttonHeight.multiply(2));

// inc/dec buttons
VBox buttons = new VBox();
buttons.setId(BUTTONS_BOX);
incrementButton = new Button();
incrementButton.setId(SPINNER_BUTTON_UP);
incrementButton.prefWidthProperty().bind(numberField.heightProperty());
incrementButton.minWidthProperty().bind(numberField.heightProperty());
incrementButton.maxHeightProperty().bind(buttonHeight.add(spacing));
incrementButton.prefHeightProperty().bind(buttonHeight.add(spacing));
incrementButton.minHeightProperty().bind(buttonHeight.add(spacing));
incrementButton.setFocusTraversable(false);
incrementButton.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent ae) {
increment();
ae.consume();
}
});

// Paint arrow path on button using a StackPane
StackPane incPane = new StackPane();
incPane.getChildren().addAll(incrementButton, arrowUp);
incPane.setAlignment(Pos.CENTER);

decrementButton = new Button();
decrementButton.setId(SPINNER_BUTTON_DOWN);
decrementButton.prefWidthProperty().bind(numberField.heightProperty());
decrementButton.minWidthProperty().bind(numberField.heightProperty());
decrementButton.maxHeightProperty().bind(buttonHeight);
decrementButton.prefHeightProperty().bind(buttonHeight);
decrementButton.minHeightProperty().bind(buttonHeight);

decrementButton.setFocusTraversable(false);
decrementButton.setOnAction(new EventHandler<ActionEvent>() {

@Override
public void handle(ActionEvent ae) {
decrement();
ae.consume();
}
});

StackPane decPane = new StackPane();
decPane.getChildren().addAll(decrementButton, arrowDown);
decPane.setAlignment(Pos.CENTER);

buttons.getChildren().addAll(incPane, decPane);
this.getChildren().addAll(numberField, buttons);
}

/**
* increment number value by stepWidth
*/
private void increment() {
BigDecimal value = numberField.getNumber();
value = value.add(stepWitdhProperty.get());
numberField.setNumber(value);
}

/**
* decrement number value by stepWidth
*/
private void decrement() {
BigDecimal value = numberField.getNumber();
value = value.subtract(stepWitdhProperty.get());
numberField.setNumber(value);
}

public final void setNumber(BigDecimal value) {
numberField.setNumber(value);
}

public ObjectProperty<BigDecimal> numberProperty() {
return numberField.numberProperty();
}

public final BigDecimal getNumber() {
return numberField.getNumber();
}

// debugging layout bounds
public void dumpSizes() {
System.out.println("numberField (layout)=" + numberField.getLayoutBounds());
System.out.println("buttonInc (layout)=" + incrementButton.getLayoutBounds());
System.out.println("buttonDec (layout)=" + decrementButton.getLayoutBounds());
System.out.println("binding=" + buttonHeight.toString());
System.out.println("spacing=" + spacing.toString());
}
}


[b]number_spinner.css[/b]

最后, 控制器的样式可在css文件中设置. 我实现了圆角和直角两种风格(见上文截图). 你可以通过修改 #NumberField, #ButtonBox, #SpinnerButtonUp 和#SpinnerButtonDown 中的border/background-radiuses来切换不同的风格.

.root{
-fx-font-size: 24pt;
/* -fx-base: rgb(255,0,0);*/
/* -fx-background: rgb(50,50,50);*/
}
#NumberField {
-fx-border-width: 1;
-fx-border-color: lightgray;
-fx-background-insets:1;
-fx-border-radius:3 0 0 3;
/* -fx-border-radius:0 0 0 0;*/
}
#NumberSpinnerArrow {
-fx-fill: gray;
-fx-stroke: gray;
/* -fx-effect: innershadow( gaussian , black , 2 , 0.6 , 1 , 1 )*/
}
#ButtonsBox {
-fx-border-color:lightgray;
-fx-border-width: 1 1 1 0;
-fx-border-radius: 0 3 3 0;
/* -fx-border-radius: 0 0 0 0;*/
}
#SpinnerButtonUp {
-fx-background-insets: 0;
-fx-background-radius:0 3 0 0;
/* -fx-background-radius:0;*/
}
#SpinnerButtonDown {
-fx-background-insets: 0;
-fx-background-radius:0 0 3 0;
/* -fx-background-radius:0;*/
}


结论

从上面的例子可以看出在javaFx中自定义控制器并不困难.
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
哦,我明白了。如果你想在JavaFX中播放视频并弹出文本框,可以使用MediaPlayer类和MediaView类来实现。以下是一个示例代码: ```java import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.control.TextArea; import javafx.scene.layout.StackPane; import javafx.scene.media.Media; import javafx.scene.media.MediaPlayer; import javafx.scene.media.MediaView; import javafx.stage.Stage; import java.io.File; public class VideoPlayer extends Application { private MediaPlayer mediaPlayer; private MediaView mediaView; @Override public void start(Stage primaryStage) { // 创建媒体对象 String path = "path/to/video/file.mp4"; // 视频文件路径 Media media = new Media(new File(path).toURI().toString()); // 创建媒体播放器 mediaPlayer = new MediaPlayer(media); // 创建媒体视图 mediaView = new MediaView(mediaPlayer); // 创建文本框 TextArea textArea = new TextArea(); // 创建根容器 StackPane root = new StackPane(); root.getChildren().addAll(mediaView, textArea); // 创建场景 Scene scene = new Scene(root, 800, 600); // 设置场景 primaryStage.setScene(scene); primaryStage.show(); // 播放媒体 mediaPlayer.play(); } @Override public void stop() { // 停止媒体播放 mediaPlayer.stop(); } public static void main(String[] args) { launch(args); } } ``` 这个示例程序会在窗口中播放视频,并在视频上方弹出一个文本框。你可以根据自己的需要来调整窗口大小、视频文件路径等参数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值