我从2年前开始使用
JavaFX.现在我正在使用
JavaFX创建像控件一样的电子表格.为了创建控件,我使用TableView和ScrollPane以及用于Spreadsheet Row标题的ListView控件,因为JavaFX不提供对行标题的支持.除了一件事我滚动表,最初表时,一切正常和scrollpane行同步滚动,但在一些滚动后它的开始不匹配.我已经附加了相同的屏幕截图.
1)没有滚动的初始屏幕
2)滚动后的屏幕
以下是我在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); } }
解决方法
试试这个
对于ListView,获取VBar并将其与TableView VBar绑定,您的对齐将完好无损.
为了让你的ListView VBar为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
既然你有两个VBar将ListView绑定到TableView就像这样
list_vsb.valueProperty().bind(table_vsb.valueProperty());
请注意,您需要在完整布局后或Stage.show()时调用这些代码;被称为 – 安全
希望它清醒并有所帮助
编辑
[] [] 3
它对我有用:)