首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

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

2012-09-09 
javafx2 : 支持使用微调(spinner)控制的数字的文本框(NemberTextField)译自:?http://java.dzone.com/artic

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

译自:?http://java.dzone.com/articles/javafx-numbertextfield-and

作者:?Thomas Bolz

?

?

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

?

?javafx2 : 支持施用微调(spinner)控制的数字的文本框(NemberTextField)

?NumberTextField
NumberTextField?的实现很容易,以致我认为这算不上自定义控制器, 而仅仅是改变一个已存在的控制器的一些行为而已.??NumberTextField??扩展自JFX中的文本框(TextField), 添加一个使用BigDecimal(由于财务软件需要精确的类型)的NumberProperty作为模型, 并做一些格式化和解析处理. 就这样, 不复杂.
package de.thomasbolz.javafx;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()));        }    }}
?

?NumberSpinner

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

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

?

package de.thomasbolz.javafx;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());    }}
?

?number_spinner.css

?最后, 控制器的样式可在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中自定义控制器并不困难. JavaFX自2.0版本以后作为一个纯粹的java API, 其比以前的任何版本更好地整合诸如groovy(BigDecimal的乐土)的语言. 这将是财务桌面应用的黄金组合.


热点排行