简体   繁体   English

javafx:如何让TableCell Edit返回double而不是string,字体根据条件改变颜色?

[英]javafx: How can I make TableCell Edit return double instead of string and the font changes color based on a condition?

I have the Trade object class with a 我有一个带有的Trade对象类

public class Trade {
    private DoubleProperty price;
    private ReadOnlyBooleanWrapper caution;

    public Trade(double price){
        this.price = new SimpleDoubleProperty(price);
        this.caution = new ReadOnlyBooleanWrapper();
        this.caution.bind(this.volume.greaterThan(0));
    }   

    public double getPrice(){
        return this.price.get();
    }   

    public DoubleProperty priceProperty(){
        return this.price;
    }

    public void setPrice(double price){
        this.price.set(price);
    }
}

In my Controller class, I have the following TableView and TableColumn 在我的Controller类中,我有以下TableViewTableColumn

Problem is two-fold: 问题是双重的:

  1. The price property and price column only accepts double . 价格属性和价格列只接受double But the EditingDoubleCell code below only return String. 但是下面的EditingDoubleCell代码只返回String。 How can I make it return double and all the String s the user typed in will be ignored? 如何让它返回double并且用户输入的所有String都将被忽略?
  2. The second function I would like to have is that: the font within the cell of Price column ( talking about the same price cell ) will change its color to blue when the caution property is true and to red when the caution property is false? 我想要的第二个功能是: Price栏的单元格内的字体( 谈论相同的价格单元格 )将在caution属性为真时将其颜色更改为蓝色,在caution属性为假时将其颜色更改为红色?

public class EditingDoubleCell extends TableCell<Trade,String>{

    private TextField textField;

    public EditingDoubleCell() {
    }

    @Override
    public void startEdit() {
        if (!isEmpty()) {
            super.startEdit();
            createTextField();
            setText(null);
            setGraphic(textField);
            textField.requestFocus();
            //textField.selectAll();
        }
    }

    @Override
    public void cancelEdit() {
        super.cancelEdit();
        setText((String) getItem());
        setGraphic(null);
    }


    @Override
    public void updateItem(String item, boolean empty) {
        super.updateItem(item, empty);

        if (empty) {
            setText(null);
            setGraphic(null);
        } else {
            if (isEditing()) {
                if (textField != null) {
                    textField.setText(getString());

                }
                setText(null);
                setGraphic(textField);
            } else {
                setText(getString());
                setGraphic(null);
            }
        }
    }

    private String getString() {
        return getItem() == null ? "" : getItem().toString();
    }

    private void createTextField(){

        Locale locale  = new Locale("en", "UK");
        String pattern = "###,###.###";
        DecimalFormat df = (DecimalFormat) NumberFormat.getNumberInstance(locale);
        df.applyPattern(pattern);
        //String format = df.format(123456789.123);
        //System.out.println(format);

        //NumberFormat nf = NumberFormat.getIntegerInstance();        
        textField = new TextField();

        // add filter to allow for typing only integer
        textField.setTextFormatter( new TextFormatter<>( c ->
        {
            if (c.getControlNewText().isEmpty()) {
                return c;
            }
            ParsePosition parsePosition = new ParsePosition( 0 );
            Object object = df.parse( c.getControlNewText(), parsePosition );

            if ( object == null || parsePosition.getIndex() < c.getControlNewText().length() )
            {
                return null;
            }
            else
            {
                return c;
            }
        } ) );

        textField.setText( getString() );

        textField.setMinWidth( this.getWidth() - this.getGraphicTextGap() * 2 );

        // commit on Enter
        textField.setOnAction( new EventHandler<ActionEvent>()
        {
            @Override
            public void handle( ActionEvent event )
            {
                commitEdit( textField.getText() );
            }
        } );

        textField.focusedProperty().addListener( new ChangeListener<Boolean>()
        {
            @Override
            public void changed( ObservableValue<? extends Boolean> arg0,
                    Boolean arg1, Boolean arg2 )
            {
                if ( !arg2 )
                {
                    commitEdit( textField.getText() );
                }
            }
        } );

    }
}

the first part of the question : You can try the following class (It worked for me): 问题的第一部分 :你可以尝试以下课程(它对我有用):

     public class EditingDoubleCell extends TableCell<Trade, Double> {

        private TextField textField;

        public EditingDoubleCell() {
            textField = new TextField();
            textField.setOnAction(e -> commitEdit(Double.valueOf(textField.getText())));
        }

        @Override
        public void startEdit() {
            if (!isEmpty()) {
                super.startEdit();
                setText(null);
                setGraphic(textField);
                textField.requestFocus();

            }
        }

        @Override
        public void cancelEdit() {
            super.cancelEdit();
            setText(getString());
            setGraphic(null);
        }

        @Override
        public void commitEdit(Double newValue) {
            super.commitEdit(newValue);
        }

        @Override
        public void updateItem(Double item, boolean empty) {
            super.updateItem(item, empty);

            if (empty) {
                setText(null);
                setGraphic(null);
            } else {

                Locale locale = new Locale("en", "UK");
                String pattern = "###,###.###";
                DecimalFormat df = (DecimalFormat) NumberFormat.getNumberInstance(locale);
                df.applyPattern(pattern);
                String s = df.format(getItem());
                setText(s);
                setGraphic(null);
              // set font of Price cell to a color
            TableRow<Trade> row = getTableRow();
            if (row.getItem().getCaution()) {
                setStyle("-fx-background-color:blue;");
            } else {
                setStyle("-fx-background-color: red;");
                    }
            }
        }

        private String getString() {
            return getItem() == null ? "" : getItem().toString();
        }

    }

the second part of the question : Just call setcellfactory(...) for caution column and you have to override the method updateItem(...) : 问题的第二部分 :只需调用setcellfactory(...)以获取警告列,您必须覆盖方法updateItem(...)

 cautionCol.setCellFactory(column -> new TableCell<Trade, Boolean>() {

        @Override
        protected void updateItem(Boolean item, boolean empty) {
            super.updateItem(item, empty);
            if (item == null || empty) {
                setText(null);
            } else {
                setText(String.valueOf(item));
                //TableRow<Trade> row = getTableRow();
                if (item) {
                    setStyle("-fx-background-color:blue;");
                } else {
                    setStyle("-fx-background-color: red;");
                }

            }
        }

    });

For the first part of the problem, you should create your TextFormatter as a TextFormatter<Double> . 对于问题的第一部分,您应该将TextFormatter创建为TextFormatter<Double> This makes the valueProperty of the TextFormatter into a Property<Double> , so you can commit your edits by calling getValue() on the formatter. 这使TextFormattervalueProperty变为Property<Double> ,因此您可以通过在格式化程序上调用getValue()来提交编辑。 You need to specify a StringConverter<Double> so that it knows how to go from text to a Double , and vice-versa. 您需要指定StringConverter<Double>以便它知道如何从文本转到Double ,反之亦然。 So this looks like: 所以这看起来像:

        StringConverter<Double> converter = new StringConverter<Double>() {

            @Override
            public String toString(Double number) {
                return df.format(number);
            }

            @Override
            public Double fromString(String string) {
                try {
                    double value = df.parse(string).doubleValue() ;
                    return value;
                } catch (ParseException e) {
                    e.printStackTrace();
                    return 0.0 ;
                }
            }

        };

        textFormatter = new TextFormatter<>(converter,  0.0, c -> {
            if (partialInputPattern.matcher(c.getControlNewText()).matches()) {
                return c ;
            } else {
                return null ;
            }
        }) ;

I changed the filter here, because your filter was only matching a "complete" input. 我在这里更改了过滤器,因为您的过滤器只匹配“完整”输入。 Since the filter is applied to every individual edit, you must allow "partial" input, such as "100," . 由于过滤器应用于每个单独的编辑,因此必须允许“部分”输入,例如"100," The filter you had would not allow this (for example). 您拥有的过滤器不允许这样(例如)。 The filter in the version here uses a regular expression: you can tinker with this to get it right but I use 这里版本中的过滤器使用正则表达式:您可以修改它以使其正确但我使用

Pattern partialInputPattern = Pattern.compile(""[-+]?[,0-9]*(\\.[0-9]*)?");

which is pretty lenient with what it allows. 它允许的东西相当宽松。

Now, instead of committing the edit directly when the user hits enter, just commit the edit when the value of the text formatter changes: 现在,当用户点击进入时,不是直接提交编辑,只需在文本格式化程序的值更改时提交编辑:

    // commit on Enter
    textFormatter.valueProperty().addListener((obs, oldValue, newValue) -> {
        commitEdit(newValue);
    });

The whole cell class now looks like 整个细胞类现在看起来像

public static class EditingDoubleCell extends TableCell<Trade,Double>{

    private TextField textField;
    private TextFormatter<Double> textFormatter ;

    private DecimalFormat df ;

    public EditingDoubleCell(String...styleClasses) {
        Locale locale  = new Locale("en", "UK");
        String pattern = "###,###.###";
        df = (DecimalFormat) NumberFormat.getNumberInstance(locale);
        df.applyPattern(pattern);

        getStyleClass().addAll(styleClasses);
    }

    @Override
    public void startEdit() {
        if (!isEmpty()) {
            super.startEdit();
            createTextField();
            setText(null);
            setGraphic(textField);
            textField.requestFocus();
        }
    }

    @Override
    public void cancelEdit() {
        super.cancelEdit();
        setText(df.format(getItem()));
        setGraphic(null);
    }


    @Override
    public void updateItem(Double item, boolean empty) {
        super.updateItem(item, empty);

        if (empty) {
            setText(null);
            setGraphic(null);
        } else {
            if (isEditing()) {
                if (textField != null) {
                    textField.setText(getString());

                }
                setText(null);
                setGraphic(textField);
            } else {
                setText(getString());
                setGraphic(null);
            }
        }
    }

    private String getString() {
        return getItem() == null ? "" : df.format(getItem());
    }

    private void createTextField(){

        textField = new TextField();

        StringConverter<Double> converter = new StringConverter<Double>() {

            @Override
            public String toString(Double number) {
                return df.format(number);
            }

            @Override
            public Double fromString(String string) {
                try {
                    double value = df.parse(string).doubleValue() ;
                    return value;
                } catch (ParseException e) {
                    e.printStackTrace();
                    return 0.0 ;
                }
            }

        };

        textFormatter = new TextFormatter<>(converter,  0.0, c ->
        {
            if (c.getControlNewText().isEmpty()) {
                return c;
            }
            ParsePosition parsePosition = new ParsePosition( 0 );
            Object object = df.parse( c.getControlNewText(), parsePosition );

            if ( object == null || parsePosition.getIndex() < c.getControlNewText().length() )
            {
                return null;
            }
            else
            {
                return c;
            }
        } ) ;

        // add filter to allow for typing only integer
        textField.setTextFormatter( textFormatter);

        textField.setText( getString() );

        textField.setMinWidth( this.getWidth() - this.getGraphicTextGap() * 2 );

        // commit on Enter
        textFormatter.valueProperty().addListener((obs, oldValue, newValue) -> {
            commitEdit(newValue);
        });
    }
}

(I added the constructor parameter so it will work with the solution to your second question.) (我添加了构造函数参数,因此它将与第二个问题的解决方案一起使用。)

The second part is answered elsewhere, but I would just create a rowFactory for your table that sets a CSS pseudoclass based on the state of the caution property: 第二部分在别处回答,但我只是为你的表创建一个rowFactory ,它根据caution属性的状态设置一个CSS伪类:

PseudoClass caution = PseudoClass.getPseudoClass("caution");

table.setRowFactory(tv -> {
    TableRow<Trade> row = new TableRow<>();

    ChangeListener<Boolean> cautionListener = (obs, wasCaution, isNowCaution) -> 
        row.pseudoClassStateChanged(caution, isNowCaution);

    row.itemProperty().addListener((obs, oldTrade, newTrade) -> {
        if (oldTrade != null) {
            oldTrade.cautionProperty().removeListener(cautionListener);
        }
        if (newTrade == null) {
            row.pseudoClassStateChanged(caution, false);
        } else {
            row.pseudoClassStateChanged(caution, newTrade.isCaution());
            newTrade.cautionProperty().addListener(cautionListener);
        }
    });

    return row ;
});

Then just set a style class on the cell you want the style to change on (eg add the style class "price-cell" to the EditingDoubleCell you defined). 然后在要更改样式的单元格上设置样式类(例如,将样式类"price-cell"到您定义的EditingDoubleCell )。 Then you can just use a CSS stylesheet to change the style as you need, eg 然后你可以根据需要使用CSS样式表来改变样式,例如

.table-row-cell .price-cell {
    -fx-text-fill: red ;
}

.table-row-cell:caution .price-cell {
    -fx-text-fill: blue ;
}

will make the text red for price cells in rows that do not have caution set, and make it blue in rows that do. 将为没有caution设置的行中的价格单元格设置红色文本,并将其设置为蓝色。

Here is the complete SSCCE: 这是完整的SSCCE:

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import java.util.function.Function;
import java.util.regex.Pattern;

import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.StringConverter;

public class TradeTable extends Application {

    private final Random rng = new Random();

    @Override
    public void start(Stage primaryStage) {
        TableView<Trade> table = new TableView<>();
        table.setEditable(true);
        TableColumn<Trade, Integer> volumeCol = column("Volume", trade -> trade.volumeProperty().asObject());
        TableColumn<Trade, Double> priceCol = column("Price", trade -> trade.priceProperty().asObject());

        priceCol.setCellFactory(col -> new EditingDoubleCell("price-cell"));

        table.getColumns().add(volumeCol);
        table.getColumns().add(priceCol);

        PseudoClass caution = PseudoClass.getPseudoClass("caution");

        table.setRowFactory(tv -> {
            TableRow<Trade> row = new TableRow<>();

            ChangeListener<Boolean> cautionListener = (obs, wasCaution, isNowCaution) -> 
                row.pseudoClassStateChanged(caution, isNowCaution);

            row.itemProperty().addListener((obs, oldTrade, newTrade) -> {
                if (oldTrade != null) {
                    oldTrade.cautionProperty().removeListener(cautionListener);
                }
                if (newTrade == null) {
                    row.pseudoClassStateChanged(caution, false);
                } else {
                    row.pseudoClassStateChanged(caution, newTrade.isCaution());
                    newTrade.cautionProperty().addListener(cautionListener);
                }
            });

            return row ;
        });

        table.getItems().addAll(createRandomData());

        Button button = new Button("Change Data");
        button.setOnAction(e -> table.getItems().forEach(trade -> {
            if (rng.nextDouble() < 0.5) {
                trade.setVolume(0);
            } else {
                trade.setVolume(rng.nextInt(10000));
            }
            trade.setPrice(rng.nextDouble() * 1000);
        }));
        BorderPane.setAlignment(button, Pos.CENTER);
        BorderPane.setMargin(button, new Insets(10));

        BorderPane root = new BorderPane(table, null, null, button, null);
        Scene scene = new Scene(root, 600, 600);
        scene.getStylesheets().add("trade-table.css");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private  List<Trade> createRandomData() {
        List<Trade> trades = new ArrayList<>(50);
        for (int i = 0 ; i < 50; i++) {
            int volume = rng.nextDouble() < 0.5 ? 0 : rng.nextInt(10000) ;
            double price = rng.nextDouble() * 10000 ;
            trades.add(new Trade(price, volume));
        }
        return trades ;
    }

    private static <S,T> TableColumn<S,T> column(String title, Function<S, ObservableValue<T>> property) {
        TableColumn<S,T> col = new TableColumn<>(title);
        col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
        return col ;
    }

    public static class Trade {
        private DoubleProperty price;
        private IntegerProperty volume ;
        private ReadOnlyBooleanWrapper caution;

        public Trade(double price, int volume){
            this.price = new SimpleDoubleProperty(price);
            this.volume = new SimpleIntegerProperty(volume);
            this.caution = new ReadOnlyBooleanWrapper();
            this.caution.bind(this.volume.greaterThan(0));
        }   

        public double getPrice(){
            return this.price.get();
        }   

        public DoubleProperty priceProperty(){
            return this.price;
        }

        public void setPrice(double price){
            this.price.set(price);
        }

        public final IntegerProperty volumeProperty() {
            return this.volume;
        }

        public final int getVolume() {
            return this.volumeProperty().get();
        }

        public final void setVolume(final int volume) {
            this.volumeProperty().set(volume);
        }

        public final ReadOnlyBooleanProperty cautionProperty() {
            return this.caution.getReadOnlyProperty();
        }

        public final boolean isCaution() {
            return this.cautionProperty().get();
        }


    }

    public static class EditingDoubleCell extends TableCell<Trade,Double>{

        private TextField textField;
        private TextFormatter<Double> textFormatter ;

        private Pattern partialInputPattern = Pattern.compile(
                "[-+]?[,0-9]*(\\.[0-9]*)?");

        private DecimalFormat df ;

        public EditingDoubleCell(String...styleClasses) {
            Locale locale  = new Locale("en", "UK");
            String pattern = "###,###.###";
            df = (DecimalFormat) NumberFormat.getNumberInstance(locale);
            df.applyPattern(pattern);

            getStyleClass().addAll(styleClasses);
        }

        @Override
        public void startEdit() {
            if (!isEmpty()) {
                super.startEdit();
                createTextField();
                setText(null);
                setGraphic(textField);
                textField.requestFocus();
            }
        }

        @Override
        public void cancelEdit() {
            super.cancelEdit();
            setText(df.format(getItem()));
            setGraphic(null);
        }


        @Override
        public void updateItem(Double item, boolean empty) {
            super.updateItem(item, empty);

            if (empty) {
                setText(null);
                setGraphic(null);
            } else {
                if (isEditing()) {
                    if (textField != null) {
                        textField.setText(getString());

                    }
                    setText(null);
                    setGraphic(textField);
                } else {
                    setText(getString());
                    setGraphic(null);
                }
            }
        }

        private String getString() {
            return getItem() == null ? "" : df.format(getItem());
        }

        private void createTextField(){

            textField = new TextField();

            StringConverter<Double> converter = new StringConverter<Double>() {

                @Override
                public String toString(Double number) {
                    return df.format(number);
                }

                @Override
                public Double fromString(String string) {
                    try {
                        double value = df.parse(string).doubleValue() ;
                        return value;
                    } catch (ParseException e) {
                        e.printStackTrace();
                        return 0.0 ;
                    }
                }

            };

            textFormatter = new TextFormatter<>(converter,  0.0, c -> {
                if (partialInputPattern.matcher(c.getControlNewText()).matches()) {
                    return c ;
                } else {
                    return null ;
                }
            }) ;

            // add filter to allow for typing only integer
            textField.setTextFormatter( textFormatter);

            textField.setText( getString() );

            textField.setMinWidth( this.getWidth() - this.getGraphicTextGap() * 2 );

            // commit on Enter
            textFormatter.valueProperty().addListener((obs, oldValue, newValue) -> {
                commitEdit(newValue);
            });
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

With the CSS code above in trade-table.css. 使用trade-table.css中的CSS代码。

I had similar problem, I did as follows: 我有类似的问题,我做了如下:

SimpleDoubleProperty price = new SimpleDoubleProperty();
price.setValue(Double.parseDouble(EditingDoubleCell().getString()));
ObservableValue<Double> g = price.asObject();
return g;

This method anticipates that you can parse your String into double. 此方法预计您可以将String解析为double。 Works for me, tell me if it helped :) 适合我,告诉我它是否有帮助:)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM