问题
To highlight parts of text in a Table i'm tagging it and tell a column renderer to split the text and render it in Labels summed up in a HBox. Unfortunately the performance is poor. When displaying > 400 lines its becoming very slow. How can i speed it up?
Im using the following code to highlight text in a Table with HBoxes containing multiple Labels. But some Labels display ellipses instead of text. How can i force the Labels to appear in full width?
ObservableList<TraceLine> items = FXCollections.observableArrayList();
for (int i = 0; i < lines.size(); i++) {
items.add(new TraceLine(i + startLineNumber + 1, lines.get(i)));
}
final TableColumn col1 = new TableColumn("#");
col1.setCellValueFactory(new PropertyValueFactory<TraceLine, Integer>("lineNumber"));
final TableColumn col2 = new TableColumn("Trace");
col2.setCellValueFactory(new PropertyValueFactory<RegexMatch, String>("trace"));
col2.setCellFactory(new Callback<TableColumn, TableCell>() {
@Override
public TableCell call(TableColumn param) {
TableCell cell = new TableCell() {
@Override
protected void updateItem(Object text, boolean empty) {
if (text != null && text instanceof String) {
HBox hbox = new HBox();
hbox.setAlignment(Pos.CENTER_LEFT);
String str = (String) text;
if (txtHighlight.getText().length() > 3 && str.contains(MainController.HIGHLIGHT_START)) {
// Something to highlight
hbox.getChildren().clear();
while (str.contains(MainController.HIGHLIGHT_START)) {
// First part
Label label = new Label(str.substring(0, str.indexOf(MainController.HIGHLIGHT_START)));
hbox.getChildren().add(label);
str = str.substring(str.indexOf(MainController.HIGHLIGHT_START) + MainController.HIGHLIGHT_START.length(), str.length());
// Part to highlight
Label label2 = new Label(str.substring(0, str.indexOf(MainController.HIGHLIGHT_END)));
label2.setStyle("-fx-background-color: " + MainController.HIGHLIGHT_COLOR + ";");
hbox.getChildren().add(label2);
// Last part
str = str.substring(str.indexOf(MainController.HIGHLIGHT_END) + MainController.HIGHLIGHT_END.length(), str.length());
if (!str.contains(MainController.HIGHLIGHT_START)) {
Label label3 = new Label(str);
hbox.getChildren().add(label3);
}
}
} else if (txtHighlight.getText().length() < 1) {
// Remove former highlightings and show simple text
str = str.replaceAll(MainController.HIGHLIGHT_START, "");
str = str.replaceAll(MainController.HIGHLIGHT_END, "");
hbox.getChildren().clear();
Label label = new Label(str);
hbox.getChildren().add(label);
} else {
// show simple text
hbox.getChildren().clear();
Label label = new Label(str);
hbox.getChildren().add(label);
}
setGraphic(hbox);
}
}
};
return cell;
}
});
tableLines.getColumns().clear();
tableLines.getColumns().addAll(col1, col2);
tableLines.setItems(items);
回答1:
For my solution to work you need to move some parts around. Especially the logic of searching and splitting should be moved out of the Renderer as it is not really GUI related work..
So lets assume the following data class for your TableView (where dataNumber and dataString) are just random properties of other TableColumns:
public class MyTableData {
private final IntegerProperty dataNumber = new SimpleIntegerProperty(this, "dataNumber");
private final StringProperty dataString = new SimpleStringProperty(this, "dataString");
private String temporarySearch = null;
private ObjectProperty<List<String>> searchMatches = null;
public ObjectProperty<List<String>> searchMatchesProperty() {
if(searchMatches == null) {
searchMatches = new SimpleObjectProperty<>(this, "searchMatches");
doSearch();
}
return searchMatches;
}
public List<String> getSearchMatches() {
if(searchMatches == null) {
return Collections.emptyList(); // or return null
}
return searchMatchesProperty().get();
}
public void setSearchMatches(List<String> pSearchMatches) {
searchMatchesProperty().set(pSearchMatches);
}
public void search(String pSearchTerm) {
temporarySearch = pSearchTerm;
doSearch();
}
private void doSearch() {
if(temporarySearch != null && searchMatches != null) {
//TODO perform the search and split here, putting the search match fragments as
// List<String> to setSearchMatches(List)
}
}
}
The interesting part is the searchMatches property which only gets initialized if searchMatchesProperty() is called.
Any calls to search(String) only store the search term but won't do any actual logic until the property is intialized.
In your Cell Renderer you will call List<String> fragments = MyTableData.getSearchMatches();.
If the list is empty or null (in other words: the property is not intialized) nothing to display, leaving your cell for the search results empty. Either because nothing matched the search term or because the property is not initialized.
Now to the ValueFactory:
searchResultColumn.setCellValueFactory(p -> p.getValue().firstNameProperty());
The TableView is intelligent enough to not soak every value into memory, but only those which are currently being displayed. So your search result property gets initialized when its cell becomes visible. As a result your CellRenderer should have much less work, rendering only the necessary parts.
来源:https://stackoverflow.com/questions/26991719/javafx-table-with-highlighted-text-labels-with-poor-performance