简体   繁体   中英

JavaFX Tableview and ScrollPane scroll issue

I am using JavaFX from 2 years back. Now i am creating spreadsheet like control using JavaFX.For creating control i am using TableView and ScrollPane with ListView control for Spreadsheet Row header as JavaFX do not provide support for row headers.Everything is working fine except one thing when i scroll table , initially table and scrollpane rows scroll in sync but after some scroll its starting mismatch.I have attached the screen shots of the same.

1)Initial screen without scroll

在此输入图像描述

2)Screen after some scroll

在此输入图像描述

Following are the code snippet for this control which i have pasted in pastebin.

1)Cells.java

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Cells extends Application {

    public void start(Stage stage) {
        stage.setScene(new Scene(new SpreadSheet(100, 26)));
        stage.setTitle("Cells");
        stage.setWidth(400);
        stage.setHeight(400);
        stage.show();
    }

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

}

2)SpreadSheet.java

import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.HBox;


public class SpreadSheet extends HBox {

    public SpreadSheet(int height, int width) {
        super();
        Model model = new Model(height, width);

        TableView<ObservableList<Model.Cell>> table = new TableView<>();
        table.setEditable(true);
        table.setItems(model.getCellsAsObservableList());

        for (char w = 'A'; w < 'A'+width; w++) {
            TableColumn<ObservableList<Model.Cell>, String> column = new TableColumn<>(w+"");
            column.setSortable(false);
            column.setMinWidth(50);
            column.setCellFactory(TextFieldTableCell.forTableColumn());
            final char w0 = w;
            column.setCellValueFactory(param -> param.getValue().get(w0-'A').text);
            column.setOnEditStart(event -> {
                int row = event.getTablePosition().getRow();
                int col = event.getTablePosition().getColumn();
                Model.Cell c = model.getCells()[row][col];
                c.setShowUserData(true);
            });

            column.setOnEditCommit(event -> {
                int row = event.getTablePosition().getRow();
                int col = event.getTablePosition().getColumn();
                Model.Cell c = model.getCells()[row][col];
                System.out.println("Hello");
                c.userData.set(event.getNewValue());
                c.setShowUserData(false);
            });
            table.getColumns().add(column);
        }

        ListView<String> rowHeaders = new ListView<>();
        rowHeaders.getItems().add("");
        for (int i = 0; i < height; i++) {
            rowHeaders.getItems().add(i+"");
            }
        ScrollPane scrolledRowHeaders = new ScrollPane(rowHeaders);
        scrolledRowHeaders.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
        scrolledRowHeaders.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);


        table.getChildrenUnmodifiable().addListener((ListChangeListener<Node>) c -> {
            ScrollBar vbarTable = (ScrollBar) table.lookup(".scroll-bar:vertical");
            ScrollBar vbarRowHeaders = (ScrollBar) scrolledRowHeaders.lookup(".scroll-bar:vertical");

            if (vbarRowHeaders != null && vbarTable != null)
                vbarTable.valueProperty().bindBidirectional(vbarRowHeaders.valueProperty());


        });

        getChildren().addAll(scrolledRowHeaders, table);

    }
}

3)Model.java

import java.util.List;

import javafx.beans.binding.Binding;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

class Model {

    private Cell[][] cells;

    Model(int height, int width) {
        cells = new Cell[height][width];
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < width; j++) {
                cells[i][j] = new Cell();
            }
        }
    }

    public Cell[][] getCells() {
        return cells;
    }

    public ObservableList<ObservableList<Cell>> getCellsAsObservableList() {
        ObservableList<ObservableList<Cell>> cs = FXCollections.observableArrayList();
        for (int i = 0; i < cells.length; i++) {
            cs.add(FXCollections.observableArrayList());
            for (int j = 0; j < cells[i].length; j++) {
                cs.get(i).add(cells[i][j]);
            }
        }
        return cs;
    }

    class Cell {

        public final StringProperty userData = new SimpleStringProperty("");
        public final StringProperty text = new SimpleStringProperty("");
        ObservableValue<Double>[] toArray(List<ObservableValue<Double>> l) {
              return l.toArray(new ObservableValue[l.size()]);
        }
        // Has same problem
//        public ObservableValue<Double> value = EasyBind.map(userData, Parser::parse)
//                .flatMap(f -> Bindings.createObjectBinding(() -> f.eval(Model.this), toArray(f.getReferences(Model.this))));
        // Has same problem
        public ObservableValue<Double> value =
                Bindings.createObjectBinding(() -> {
                    System.out.println(System.currentTimeMillis());
                    Formula f = Parser.parse(userData.get());
                    ObservableValue<Double>[] fs = toArray(f.getReferences(Model.this));
                    Binding<Double> d = Bindings.createObjectBinding(() -> {
                        double v = f.eval(Model.this);
//                        text.set(String.valueOf(v));
                        return v;
                    }, fs);
                    d.addListener((v, o, n) -> {
                        // ???
                    });
                    return d.getValue();
                }, userData);


        public void setShowUserData(Boolean b) {
            if (b)  text.setValue(userData.get());
            else text.setValue(String.valueOf(value.getValue()));
        }

    }
}

4)Parser.java

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

class Parser {

    private static Parser instance = new Parser();
    private static Tokenizer tokenizer;
    static {
        tokenizer = new Tokenizer();
        tokenizer.add("[a-zA-Z_]\\d+", Token.CELL);
        tokenizer.add("[a-zA-Z_]\\w*", Token.IDENT);
        tokenizer.add("-?\\d+(\\.\\d*)?", Token.DECIMAL);
        tokenizer.add("=", Token.EQUALS);
        tokenizer.add(",", Token.COMMA);
        tokenizer.add(":", Token.COLON);
        tokenizer.add("\\(", Token.OPEN_BRACKET);
        tokenizer.add("\\)", Token.CLOSE_BRACKET);
    }

    public static Formula parse(String formulaString) {
        return instance.parseFormula(formulaString);
    }


    String formulaString;
    LinkedList<Token> tokens;
    Token lookahead;

    private Parser() {}

    private Formula parseFormula(String formulaString) {
        this.formulaString = formulaString;

        try {
            tokenizer.tokenize(formulaString.replaceAll("\\s+",""));
        } catch (ParseError e) {
            System.out.println(e.getMessage());
        }
        this.tokens = tokenizer.getTokens();
        if (tokens.isEmpty()) return Formula.Empty;
        lookahead = this.tokens.getFirst();

        return formula();
    }

    private Formula formula() {
        switch(lookahead.token) {
            case Token.DECIMAL:
                String n = lookahead.sequence;
                nextToken();
                return new Number(Double.parseDouble(n));
            case Token.EQUALS:
                nextToken();
                return expression();
            case Token.EPSILON:
                return Formula.Empty;
            default:
                return new Textual(formulaString);
        }
    }

    private Formula expression() {
        switch(lookahead.token) {
            case Token.CELL:
                int c = lookahead.sequence.charAt(0) - 'A';
                int r = Integer.parseInt(lookahead.sequence.substring(1));
                nextToken();
                if (lookahead.token == Token.COLON) { // Range
                    nextToken();
                    if (lookahead.token == Token.CELL) {
                        int c2 = lookahead.sequence.charAt(0) - 'A';
                        int r2 = Integer.parseInt(lookahead.sequence.substring(1));
                        nextToken();
                        return new Range(new Coord(r, c), new Coord(r2, c2));
                    } else {
                        throw new ParseError("Incorrect Range: " + lookahead.sequence);
                    }
                } else {
                    return new Coord(r, c);
                }
            case Token.DECIMAL:
                Double d = Double.parseDouble(lookahead.sequence);
                nextToken();
                return new Number(d);
            case Token.IDENT:
                return application();
            default:
                throw new ParseError("Incorrect Expression: " + lookahead.sequence);
        }
    }

    private Formula application() {
        String opName = lookahead.sequence;
        nextToken();
        if (lookahead.token != Token.OPEN_BRACKET)
            throw new ParseError("No opening bracket: " + opName);
        nextToken();
        List<Formula> args = new ArrayList<Formula>();
        while (true) {
            if (lookahead.token == Token.EPSILON)
                throw new ParseError("No closing bracket");
            args.add(expression());
            if (lookahead.token == Token.COMMA) nextToken();
            if (lookahead.token == Token.CLOSE_BRACKET)
                return new Application(opName, args);
        }
    }

    private void nextToken() {
        tokens.pop();
        if (tokens.isEmpty()) lookahead = new Token(Token.EPSILON, "");
        else lookahead = tokens.getFirst();
    }

}

class ParseError extends RuntimeException {

    ParseError(String message) {
        super(message);
    }

}

class Token {

    public static final int EPSILON = 0;
    public static final int EQUALS = 1;
    public static final int IDENT = 2;
    public static final int DECIMAL = 3;
    public static final int OPEN_BRACKET = 4;
    public static final int CLOSE_BRACKET = 5;
    public static final int COMMA = 6;
    public static final int COLON = 7;
    public static final int CELL = 8;

    public final int token;
    public final String sequence;

    public Token(int token, String sequence) {
        this.token = token;
        this.sequence = sequence;
    }

}

class Tokenizer {

    private LinkedList<TokenInfo> tokenInfos;
    private LinkedList<Token> tokens;

    public Tokenizer() {
        tokenInfos = new LinkedList<TokenInfo>();
        tokens = new LinkedList<Token>();
    }

    public void add(String regex, int token) {
        tokenInfos.add(new TokenInfo(Pattern.compile("^("+regex+")"), token));
    }

    public void tokenize(String s) {
        tokens.clear();
        while (!s.equals("")) {
            boolean match = false;
            for (TokenInfo info : tokenInfos) {
                Matcher m = info.regex.matcher(s);
                if (m.find()) {
                    match = true;
                    String tok = m.group().trim();
                    tokens.add(new Token(info.token, tok));
                    s = m.replaceFirst("");
                    break;
                }
            }
            if (!match) throw new ParseError("Unexpected char in input: " + s);
        }
    }

    public LinkedList<Token> getTokens() {
        return tokens;
    }

    private static class TokenInfo {

        public final Pattern regex;
        public final int token;

        public TokenInfo(Pattern regex, int token) {
            super();
            this.regex = regex;
            this.token = token;
        }

    }

}

5)Formula.java

import javafx.beans.value.ObservableValue;
import javafx.scene.control.Cell;

import java.util.*;

abstract class Formula {
    public static final Formula Empty = new Textual("");
    public double eval(Model env) { return 0.0; }
    public List<ObservableValue<Double>> getReferences(Model env) { return Collections.emptyList(); }
}

class Textual extends Formula {
    String value;

    public Textual(String value) {
        this.value = value;
    }

    public String toString() {
        return value;
    }
}

class Number extends Formula {
    double value;

    public Number(double value) {
        this.value = value;
    }

    public String toString() {
        return String.valueOf(value);
    }

    public double eval(Model env) {
        return value;
    }
}

class Coord extends Formula {
    int row, column;

    public Coord(int row, int column) {
        this.row = row;
        this.column = column;
    }

    public String toString() {
        return ((char)('A'+column))+""+row;
    }

    public double eval(Model env) {
        return env.getCells()[row][column].value.getValue();
    }

    public List<ObservableValue<Double>> getReferences(Model env) {
        List<ObservableValue<Double>> result = new ArrayList<>(1);
        result.add(env.getCells()[row][column].value);
        return result;
    }
}

class Range extends Formula {
    Coord coord1, coord2;

    public Range(Coord coord1, Coord coord2) {
        this.coord1 = coord1; this.coord2 = coord2;
    }

    public String toString() {
        return String.valueOf(coord1)+":"+String.valueOf(coord2);
    }

    public double eval(Model env) {
        throw new RuntimeException("Range cannot be evaluated!");
    }

    public List<ObservableValue<Double>> getReferences(Model env) {
        List<ObservableValue<Double>> result = new ArrayList<>();
        for (int r = coord1.row; r <= coord2.row; r++) {
            for (int c = coord1.column; c <= coord2.column; c++) {
                result.add(env.getCells()[r][c].value);
            }
        }
        return result;
    }
}

class Application extends Formula {
    String function;
    List<Formula> arguments;

    public Application(String function, List<Formula> arguments) {
        this.function = function;
        this.arguments = arguments;
    }

    public String toString() {
        StringBuilder t = new StringBuilder();
        t.append(function);
        t.append("(");
        for (int i = 0; i < arguments.size()-1; i ++) {
            t.append(arguments.get(i).toString());
            t.append(", ");
        }
        if (!arguments.isEmpty()) t.append(arguments.get(arguments.size()-1).toString());
        t.append(")");
        return t.toString();
    }

    public double eval(Model env) {
        try {
            List<Double> argvals = evalList(arguments, env);
            return opTable.get(function).eval(argvals);
        } catch(Exception e) {
            return Double.NaN;
        }
    }

    public List<ObservableValue<Double>> getReferences(Model env) {
        List<ObservableValue<Double>> result = new ArrayList<>();
        for (Formula argument : arguments) {
            result.addAll(argument.getReferences(env));
        }
        return result;
    }


    private static List<Double> evalList(List<Formula> args, Model env) {
        List<Double> result = new ArrayList<>();
        for (Formula f : args) {
            if (f instanceof Range) {
                for (ObservableValue<Double> c : f.getReferences(env)) {
                    result.add(c.getValue());
                }
            } else {
                result.add(f.eval(env));
            }
        }
        return result;
    }

    private static Map<String, Op> opTable = new HashMap<>();
    static {
        opTable.put("add", vals -> vals.get(0) + vals.get(1));
        opTable.put("sub", vals -> vals.get(0) - vals.get(1));
        opTable.put("div", vals -> vals.get(0) / vals.get(1));
        opTable.put("mul", vals -> vals.get(0) * vals.get(1));
        opTable.put("mod", vals -> vals.get(0) % vals.get(1));
        opTable.put("sum", vals -> {
            double accum = 0;
            for (Double i : vals) {
                accum += i;
            }
            return accum;
        });
        opTable.put("prod", vals -> {
            double accum = 1;
            for (Double i : vals) {
                accum *= i;
            }
            return accum;
        });
    }

    private static interface Op {
        public double eval(List<Double> vals);
    }

}

Try this

For your ListView get your VBar and bind it with your TableView VBar and your alignment will be intact.

To get your ListView VBar run this code same for TableView VBar

VirtualFlow tvVF = (VirtualFlow) TableView.lookup("#virtual-flow");
for (Node n : tvVF.getChildrenUnmodifiable()) {
     if (n.getClass().isAssignableFrom(VirtualScrollBar.class)) {
         VirtualScrollBar table_vsb = (VirtualScrollBar) n;
         if (tvVF.getWidth() - table_vsb.getWidth() > tvVF.getWidth() / 2) {
              //table_vsb is your Vertical bar for the TableView
   ....//close braces
VirtualFlow lvVF = (VirtualFlow) ListView.lookup("#virtual-flow");
// we do the same for listview
 for (Node c : lvVF.getChildrenUnmodifiable()) {
      if (c.getClass().isAssignableFrom(VirtualScrollBar.class)) {
            VirtualScrollBar list_vsb = (VirtualScrollBar) c;
            if (tvVF.getWidth() - vsb.getWidth() > tvVF.getWidth() / 2) {
                //list_vsb is your vbar for the listview

now since you have your two VBar 's bind the ListView 's to the TableView like this

list_vsb.valueProperty().bind(table_vsb.valueProperty());

Please note that you need to call these codes after a full layout, or when Stage.show(); is called - to be safe

Hope its lucid and helps

EDIT

在此输入图像描述 [ 在此输入图像描述 ][ 在此输入图像描述 ] 3

it does work for me :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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