JavaFX Tableview 和 ScrollPane 滚动问题

标签 java javafx

我从 2 年前就开始使用 JavaFX。现在我正在使用 JavaFX 创建类似控件的电子表格。为了创建控件,我正在使用 TableView 和 ScrollPane 以及用于电子表格行标题的 ListView 控件,因为 JavaFX 不提供对行标题的支持。除了我滚动表时的一件事,最初是表和滚动 Pane 行同步滚动,但滚动后开始不匹配。我附上了相同的屏幕截图。

1)没有滚动的初始屏幕

enter image description here

2)滚动后的屏幕

enter image description here

以下是我已粘贴到 pastebin 中的此控件的代码片段。

1)细胞.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)电子表格.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)模型.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)解析器.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)公式.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);
    }

}

最佳答案

试试这个

为您的ListView得到你的VBar并将其与您的 TableView 绑定(bind)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

既然你有两个 VBar的绑定(bind) ListViewTableView像这样

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

请注意,您需要在完整布局后或在 Stage.show(); 时调用这些代码;被称为-为了安全

希望它清晰并有所帮助

编辑

enter image description here [ enter image description here ][ enter image description here ] 3

它对我有用:)

关于JavaFX Tableview 和 ScrollPane 滚动问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39422792/

相关文章:

java - 如何在线程迭代之前和之后打印一些消息?

java - 如何防止用户关闭我的应用程序? (Linux 触摸屏应用程序)

java - 如何使用 Java 获取和设置 JScrollPane 中 ScrollBar 的位置

javaFX 在表格 View 中显示日历值

java - 比较POST请求的参数

java - 如何在 javax.xml.soap.SOAPBody 中添加 XML 序言

java - ScrollPane javafx自动滚动(将vvalue设置为1.0)仅滚动到最后一项之前的项目

java - 在 JfxPane 中创建上下文菜单

java - 如何在 macOS 上使用 JavaFX 打开应用程序的新实例

java - 检查函数别名是否存在