简体   繁体   English

JavaFX Tableview和ScrollPane滚动问题

[英]JavaFX Tableview and ScrollPane scroll issue

I am using JavaFX from 2 years back. 我从2年前开始使用JavaFX。 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. 现在我正在使用JavaFX创建像控件一样的电子表格。为了创建控件,我使用TableView和ScrollPane以及用于Spreadsheet Row标题的ListView控件,因为JavaFX不提供对行标题的支持。除了一件事我滚动表,最初表时,一切正常和scrollpane行同步滚动,但在一些滚动后它的开始不匹配。我已经附加了相同的屏幕截图。

1)Initial screen without scroll 1)没有滚动的初始屏幕

在此输入图像描述

2)Screen after some scroll 2)滚动后的屏幕

在此输入图像描述

Following are the code snippet for this control which i have pasted in pastebin. 以下是我在pastebin中粘贴的此控件的代码段。

1)Cells.java 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 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 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 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 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. 对于ListView获取VBar并将其与TableView VBar绑定,您的对齐将完好无损。

To get your ListView VBar run this code same for TableView VBar 为了让你的ListView VBarTableView 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 既然你有两个VBarListView绑定到TableView就像这样

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

Please note that you need to call these codes after a full layout, or when Stage.show(); 请注意,您需要在完整布局后或Stage.show()时调用这些代码; is called - to be safe 被称为 - 安全

Hope its lucid and helps 希望它清醒并有所帮助

EDIT 编辑

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

it does work for me :) 它对我有用:)

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

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