1. 程式人生 > >J3001.JavaFX組件擴展(一)——IntegerField、DecimalField和CurrencyField

J3001.JavaFX組件擴展(一)——IntegerField、DecimalField和CurrencyField

rep copyright 返回 edit ldr size 兩個 lse 如果

我們在處理界面展現時,對於整型、浮點型、金額類型的數據時,希望界面組件至少已經處理了以下事項:

1、不接受非法輸入。如對於整型來說,只能輸入數字、負號,並且不允許超過當前平臺上整形數值的最大值。

2、使用千分位對輸入的數據進行格式化。

3、如果是貨幣型,則獲取當前所在區域的貨幣符號等信息,並據此進行數據格式化。

對於界面處理人員來說,這是對開發組件庫最基本的要求。但是實際上,JavaFX沒有提供這些或相似的組件。開源組件中也沒有找到類似的組件。著名的ControlsFX組件庫的開發者額外提供了一個MoneyField,但功能比較弱,並且支持也不算好。

使用Java做界面,你得有這樣一種覺悟:JDK提供的界面功能,有時候是比較弱的,甚至是比較弱智的,得自行不斷豐富、完善自己的組件庫。有時可以采用一些第三方的類庫,有時一些特別基礎的組件,也得自己寫。可能Oracle認為每一個Java界面開發者首先都是合格的界面組件開發人員。

想寫這三個組件很久了,一直沒有時間。昨天我下班後,坐在新電腦前,發了一會兒呆後,花了幾個小時實現了這三個組件。今天上班前,測試完善了一下,感覺可以放出來,供大家參考了。

先看一下效果:

技術分享圖片

有三個輸入框,分別只接受Integer、Decimal(4位小數)、Currency類型的數據。並提供兩個按鈕,一個是打印數值,一個是通過setText()的方式給Decimal賦值。

直接放代碼。

NumberTypeEnum枚舉類,用於區分不同類型的輸入數據,以便於基類分別處理。

package com.lirong.foundation.ui.javafx.control;

/**
 * <p>Title: LiRong Java Application Platform</p>
 * Description: AbstractNumberField及其子類支持的數據類型 <br>
 * Copyright: CorpRights lrjap.com<br>
 * Company: lrjap.com<br>
 *
 * @author yujj
 * @version 1.1.1
 * @date 2017-12-27
 */
public enum NumberTypeEnum {
    INTEGER,
    CURRENCY,
    DECIMAL;
}

AbstractNumberField基類,封裝了大多數行為,這是最重要的一個類。

package com.lirong.foundation.ui.javafx.control;

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import javafx.geometry.Pos;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import org.apache.commons.lang3.StringUtils;

/**
 * <p>Title: LiRong Java Application Platform</p>
 * Description: 整數、高精度浮點數、貨幣 數值輸入框的虛擬基類,自動校驗輸入合法性,自動增加貨幣符號、千分位 <br>
 * Copyright: CorpRights lrjap.com<br>
 * Company: lrjap.com<br>
 *
 * @author yujj
 * @version 1.1.1
 * @date 2017-12-27
 */
public abstract class AbstractNumberField extends TextField {

    private NumberTypeEnum numberType;

    private static final String DEFAULT_NUMBER_SEPARTOR_FORMAT = ",###";

    private final static DecimalFormatSymbols symbols = new DecimalFormatSymbols();

    public AbstractNumberField(NumberTypeEnum numberType) {

        super();
        this.numberType = numberType;
        setAlignment(Pos.CENTER_RIGHT);
        // 輸入時的有效性檢查
        addEventFilter(KeyEvent.KEY_TYPED, event -> {

            if (!isValid(getText())) {
                event.consume();
            }
        });
        // 格式化
        textProperty().addListener((observableValue, oldValue, newValue) -> {

            if (!isValid(newValue)) {
                setText(oldValue);
            }
            setText(formatValue(getFormatter()));
        });
    }

    /**
     * 格式化數值
     *
     * @param valueFormatter 格式
     * @return
     */
    private String formatValue(final String valueFormatter) {

        if ("-".equals(getText())) {
            return getText();
        }
        String currString = null;
        if (StringUtils.isNotBlank(getText())) {
            if (getText().endsWith(".") || getText().endsWith(getCurrencySymbols())) {
                return getText();
            }
            DecimalFormat numberFormatter = new DecimalFormat(valueFormatter);
            if (NumberTypeEnum.INTEGER == this.numberType) {
                Integer currValue = getIntegerValue();
                currString = numberFormatter.format(currValue);
            } else {
                BigDecimal currValue = getDecimalValue();
                currString = numberFormatter.format(currValue);
            }
        }
        return currString;
    }

    /**
     * 數值有效性檢查
     *
     * @param value 帶格式的字符串
     * @return
     */
    private boolean isValid(final String value) {

        if (StringUtils.isBlank(value) || value.equals("-")) {
            return true;
        }

        try {
            if (NumberTypeEnum.INTEGER == this.numberType) {
                getIntegerValue();
            } else if (NumberTypeEnum.CURRENCY == this.numberType) {
                getDecimalValue();
            } else {
                getCurrencyValue();
            }
            return true;
        } catch (NumberFormatException ex) {
            return false;
        }
    }

    /**
     * 轉為整型
     *
     * @return
     */
    protected Integer getIntegerValue() {

        if (StringUtils.isBlank(getText()) || "-".equals(getText())) {
            return null;
        }
        return Integer.valueOf(getText().replace(",", ""));
    }

    /**
     * 轉為BigDecimal
     *
     * @return
     */
    protected BigDecimal getDecimalValue() {

        return getDecimalValue(‘.‘);
    }

    /**
     * 轉為貨幣
     *
     * @return
     */
    protected BigDecimal getCurrencyValue() {

        return getDecimalValue(getCurrencySeparator());
    }

    private BigDecimal getDecimalValue(final char separator) {

        if (StringUtils.isBlank(getText()) || "-".equals(getText())) {
            return null;
        }
        int pos = getText().indexOf(separator);
        if (pos > -1) {
            final String subStr = getText().substring(pos + 1, getText().length());
            if (subStr.length() > decimalScale()) {
                throw new NumberFormatException("Scale error.");
            }
        }
        return new BigDecimal(getText().replace(",", "").replace(getCurrencySymbols(), ""));
    }

    /**
     * 生成用於格式化數據的字符串
     *
     * @return
     */
    protected String getFormatter() {

        if (this.numberType == null) {
            throw new RuntimeException("Type error.");
        }
        if (NumberTypeEnum.INTEGER == this.numberType) {
            return getIntegerFormatter();
        } else if (NumberTypeEnum.CURRENCY == this.numberType) {
            return getCurrencyFormatter();
        } else {
            return getDecimalFormatter();
        }
    }

    protected String getIntegerFormatter() {

        return DEFAULT_NUMBER_SEPARTOR_FORMAT;
    }

    protected String getCurrencyFormatter() {

        return String.format("%s%s%s", getCurrencySymbols(), DEFAULT_NUMBER_SEPARTOR_FORMAT, getScaleFormatter());
    }

    protected String getDecimalFormatter() {

        return String.format("%s%s", DEFAULT_NUMBER_SEPARTOR_FORMAT, getScaleFormatter());
    }

    public abstract Integer decimalScale();

    /**
     * 為BigDecimal和貨幣型數據生成小數占位信息,有多少有效小數位就生成多少個占位符
     *
     * @return
     */
    protected String getScaleFormatter() {

        String currFormatter = "";
        if (decimalScale() == 0) {
            return currFormatter;
        } else {
            if (NumberTypeEnum.CURRENCY == this.numberType) {
                currFormatter += getCurrencySeparator();
            } else {
                currFormatter += ".";
            }
            Integer tempScale = decimalScale();
            while (tempScale > 0) {
                currFormatter += "#";
                tempScale--;
            }
            return currFormatter;
        }
    }

    /**
     * 獲取貨幣符號
     *
     * @return
     */
    protected static String getCurrencySymbols() {

        return symbols.getCurrencySymbol();
    }

    /**
     * 獲取貨幣分隔符
     *
     * @return
     */
    protected static char getCurrencySeparator() {

        return symbols.getMonetaryDecimalSeparator();
    }

    /**
     * 虛擬方法。用於子類返回指定類型的數值
     *
     * @return
     */
    public abstract Object getValue();
}

以下分別是三個實現類,主要是對小數位數、返回值的類型進行處理。

CurrencyField:

package com.lirong.foundation.ui.javafx.control;

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Locale;

/**
 * <p>Title: LiRong Java Application Platform</p>
 * Description: <br>
 * Copyright: CorpRights lrjap.com<br>
 * Company: lrjap.com<br>
 *
 * @author yujj
 * @version 1.1.1
 * @date 2017-12-27
 */
public class CurrencyField extends AbstractNumberField {

    public CurrencyField() {

        super(NumberTypeEnum.CURRENCY);
    }

    @Override
    public Integer decimalScale() {

        Locale locale = Locale.getDefault();
        DecimalFormat formatter = (DecimalFormat) NumberFormat.getCurrencyInstance(locale);
        return formatter.getCurrency().getDefaultFractionDigits();
    }

    @Override
    public BigDecimal getValue() {

        return getCurrencyValue();
    }
}

DecimalField:

package com.lirong.foundation.ui.javafx.control;

import java.math.BigDecimal;

/**
 * <p>Title: LiRong Java Application Platform</p>
 * Description: <br>
 * Copyright: CorpRights lrjap.com<br>
 * Company: lrjap.com<br>
 *
 * @author yujj
 * @version 1.1.1
 * @date 2017-12-27
 */
public class DecimalField extends AbstractNumberField {

    private Integer scale = 2;

    public DecimalField() {

        super(NumberTypeEnum.DECIMAL);
    }

    public DecimalField(final Integer scale) {

        this();
        if (scale < 0) {
            throw new NumberFormatException("Scale must great than equals to 0.");
        }
        this.scale = scale;
    }

    @Override
    public Integer decimalScale() {

        return this.scale;
    }

    @Override
    public BigDecimal getValue() {

        return getDecimalValue();
    }
}

IntegerField:

package com.lirong.foundation.ui.javafx.control;

/**
 * <p>Title: LiRong Java Application Platform</p>
 * Description: <br>
 * Copyright: CorpRights lrjap.com<br>
 * Company: lrjap.com<br>
 *
 * @author yujj
 * @version 1.1.1
 * @date 2017-12-27
 */
public class IntegerField extends AbstractNumberField {

    public IntegerField() {

        super(NumberTypeEnum.INTEGER);
    }

    @Override
    public Integer decimalScale() {

        return Integer.valueOf(0);
    }

    @Override
    public Integer getValue() {

        return getIntegerValue();
    }
}

測試類:

package com.lirong.test.ui.javafx.decimalfiled;

import com.lirong.foundation.ui.javafx.control.CurrencyField;
import com.lirong.foundation.ui.javafx.control.DecimalField;
import com.lirong.foundation.ui.javafx.control.IntegerField;
import java.math.BigDecimal;
import javafx.application.Application;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.layout.*;
import javafx.stage.Stage;

/**
 * <p>Title: LiRong Java Application Platform</p>
 * Description: <br>
 * Copyright: CorpRights lrjap.com<br>
 * Company: lrjap.com<br>
 *
 * @author yujj
 * @version 1.1.1
 * @date 2017-12-27
 */
public class TestNumberField extends Application {

    public static void main(String[] args) {

        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {

        ColumnConstraints columnLabel = new ColumnConstraints();
        columnLabel.setPrefWidth(120);
        columnLabel.setHalignment(HPos.RIGHT);

        ColumnConstraints columnControll = new ColumnConstraints();
        columnControll.setHgrow(Priority.ALWAYS);

        GridPane gridPane = new GridPane();
        gridPane.setPadding(new Insets(10));
        gridPane.setHgap(10);
        gridPane.setVgap(10);
        gridPane.getColumnConstraints().addAll(columnLabel, columnControll);

        Label lblInteger = new Label("Integer:");
        IntegerField integerTextField = new IntegerField();
        integerTextField.setPromptText("Please input a integer value");
        gridPane.add(lblInteger, 0, 0);
        gridPane.add(integerTextField, 1, 0);

        Label lblDecimal = new Label("Decimal:");
        DecimalField decimalField = new DecimalField(4);
        decimalField.setPromptText("Please input a decimal value");
        gridPane.add(lblDecimal, 0, 1);
        gridPane.add(decimalField, 1, 1);

        Label lblCurrency = new Label("Currency:");
        CurrencyField currencyField = new CurrencyField();
        currencyField.setPromptText("Please input a currency value");
        gridPane.add(lblCurrency, 0, 2);
        gridPane.add(currencyField, 1, 2);

        TextArea textConsole = new TextArea();
        textConsole.setEditable(Boolean.FALSE);
        gridPane.add(textConsole, 0, 6, 2, 6);

        HBox toolBar = new HBox();
        toolBar.setPadding(new Insets(10));
        toolBar.setSpacing(10);

        Button buttonPrintValue = new Button("PrintValue");
        buttonPrintValue.setMinSize(75, 30);
        buttonPrintValue.setOnAction(action -> {

            final String LINE_SEP = System.getProperty("line.separator");

            StringBuilder sbInfo = new StringBuilder();
            sbInfo.append(String.format("%s=%s", "IntegerField Value", integerTextField.getValue())).append(LINE_SEP).append(LINE_SEP);
            sbInfo.append(String.format("%s=%s", "DecimalField Value", decimalField.getValue())).append(LINE_SEP).append(LINE_SEP);
            sbInfo.append(String.format("%s=%s", "CurrencyField Value", currencyField.getValue())).append(LINE_SEP);
            textConsole.setText(sbInfo.toString());
        });

        Button buttonSetValue = new Button("SetValue");
        buttonSetValue.setMinSize(75, 30);
        buttonSetValue.setOnAction(action -> decimalField.setText(new BigDecimal("2080280808.2223").toString()));

        toolBar.getChildren().addAll(buttonPrintValue, buttonSetValue);
        gridPane.add(toolBar, 0, 4, 2, 2);

        BorderPane container = new BorderPane();
        container.setCenter(gridPane);

        Scene scene = new Scene(container, 600, 300);
        primaryStage.setTitle("IntegerField、DecimalField、CurrencyField 測試");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

當然,估計使用正則可能會有更簡單的實現方式。有時間我再研究一下。

J3001.JavaFX組件擴展(一)——IntegerField、DecimalField和CurrencyField