我使用的是2年前的JavaFX。现在我正在使用JavaFX创建像控件一样的电子表格。为了创建控件,我使用TableView和ScrollPane以及用于Spreadsheet Row标题的ListView控件,因为JavaFX不提供对行标题的支持。除了一件事我滚动表,最初表时,一切正常和滚动窗格行同步滚动但在一些滚动后它的开始不匹配。我已经附加了相同的屏幕截图。
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);
}
}
答案 0 :(得分:0)
试试这个
为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
它对我有用:)