See mailinglist post titled 'Little worried about the ListView/TreeView/TableView classes'.
This occurs very rarely with the default ScrollBarSkin, but happens much more often with the provided Skin (that isn't 100% according to spec) -- it may help to debug the issue for the default ScrollBarSkin.
What happens is that when scrolling through the ListView with cursor down and holding it at some point causes every Xth cell to be highlighted instead of the currently active cell. Play with the provided example and you should be able to reproduce it hopefully.
package hs.mediasystem;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class ListTest extends Application {
public static void main(String[] args) {
Application.launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
Scene scene = new Scene(new VBox() {{
getChildren().add(
new HBox() {{
ListView<String> listView = new ListView<>();
listView.setOnKeyPressed(new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
if(event.getCode() == KeyCode.LEFT) {
System.out.println("Consuming cursor left!");
event.consume();
}
if(event.getCode() == KeyCode.RIGHT) {
System.out.println("Consuming cursor right!");
event.consume();
}
}
});
listView.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
private int cellCount;
@Override
public ListCell<String> call(ListView<String> param) {
System.out.println("New cell created " + cellCount++);
return new ListCell<String>() {
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if(item != null) {
setText(item);
}
}
};
}
});
for(int i = 0; i < 1000; i++) {
listView.getItems().add("" + i);
if((i - 90) % 100 == 0) {
listView.getItems().add("Extra Long Item to get a ScrollBar at the Bottom of the ListView");
}
}
getChildren().add(new Button("1"));
getChildren().add(new Button("2"));
getChildren().add(new Button("3"));
getChildren().add(listView);
getChildren().add(new Button("4"));
getChildren().add(new Button("5"));
getChildren().add(new Button("6"));
}}
);
getChildren().add(new Label("Tab to ListView, hold down cursor down until the bug occurs (may take a while and take multiple tries, usually at item 600)") {{
setWrapText(true);
}});
}});
scene.getStylesheets().add("bug.css");
primaryStage.setScene(scene);
primaryStage.show();
}
}
bug.css
.root {
-c-base: rgb(173, 216, 230);
-c-text: -c-base;
-c-text-highlight: derive(-c-base, +75%);
-c-text-focused: derive(-c-base, +75%);
-c-text-unobtrusive: derive(-c-base, -30%);
-c-text-watermark: rgba(173, 216, 230, 0.1);
-c-shadow-highlight: derive(-c-base, -50%);
-c-border-highlight: -c-text-highlight;
-c-border-dialog: -c-text-highlight;
-c-fill-dark: derive(-c-base, -75%);
-c-star: yellow;
-c-star-disabled: gray;
-c-bg-transparent: rgba(0, 0, 0, 0.66);
color-main: -c-base;
color-focused-text: derive(color-main, 30%);
color-content-background: derive(color-blue-80, -90%);
color-blue: -c-base;
color-blue-80: rgba(173, 216, 230, 0.8);
color-blue-70: rgba(173, 216, 230, 0.7);
color-blue-50: rgba(173, 216, 230, 0.5);
color-blue-40: rgba(173, 216, 230, 0.4);
color-blue-20: rgba(173, 216, 230, 0.2);
color-blue-10: rgba(173, 216, 230, 0.1);
color-white: rgb(255, 255, 255);
color-black-50: rgba(0, 0, 0, 0.50);
color-black-75: rgba(0, 0, 0, 0.75);
-fx-font-family: "Arial";
-fx-font-size: 16pt;
-fx-font-weight: normal;
}
.scroll-pane {
-fx-background-color: transparent;
}
/*
* List & Tree styles
*/
.list-cell:even, .tree-cell:even, .table-cell:even {
-fx-background-color: transparent;
}
.list-cell:odd, .tree-cell:odd, .table-cell:odd {
-fx-background-color: transparent;
}
.list-cell:focused, .tree-cell:focused {
-fx-background-color: rgba(173, 216, 230, 0.3);
}
.list-cell, .tree-cell, .option-cell {
-fx-background-color: transparent;
-fx-border-color: linear-gradient(to right, transparent, color-blue-20 15%, color-blue-20 85%, transparent);
-fx-border-width: 1;
-fx-border-insets: 1;
}
.list-cell:empty, .tree-cell:empty {
-fx-border-color: transparent;
-fx-border-width: 1;
-fx-border-insets: 1;
}
.list-cell:focused, .tree-cell:focused, .option-cell.focused {
-fx-background-insets: 1, 1, 1;
-fx-background-color: radial-gradient(center 25% 0%, radius 25%, color-blue 0%, transparent),
radial-gradient(center 75% 100%, radius 25%, color-blue 0%, transparent),
linear-gradient(to right, transparent, color-blue-20 15%, color-blue-20 85%, transparent);
-fx-border-width: 1, 1, 1;
-fx-border-color: radial-gradient(center 25% 0%, radius 25%, color-blue 0%, transparent),
radial-gradient(center 75% 100%, radius 25%, color-blue 0%, transparent),
linear-gradient(to right, transparent, color-blue-50 15%, color-blue-50 85%, transparent);
}
.label {
-fx-text-fill: black;
}
.list .list-view {
-fx-border-width: 2;
-fx-border-color: color-blue-20;
-fx-border-radius: 10;
}
.scroll-bar {
-fx-skin: "hs.mediasystem.ScrollBarSkinBad";
}
.scroll-bar .track {
-fx-fill: derive(color-blue-50, -80%);
}
.scroll-bar .thumb {
-fx-stroke-width: 2;
-fx-stroke: derive(color-blue-50, -30%);
-fx-stroke-type: inside;
-fx-fill: derive(color-blue-50, -60%);
}
ScrollBarSkinBad:
package hs.mediasystem;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.NumberBinding;
import javafx.geometry.Orientation;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.Skin;
import javafx.scene.shape.Rectangle;
public class ScrollBarSkinBad implements Skin<ScrollBar> {
private final ScrollBar scrollBar;
public ScrollBarSkinBad(ScrollBar scrollBar) {
this.scrollBar = scrollBar;
}
@Override
public void dispose() {
}
@Override
public Node getNode() {
try {
return new Group() {{
getChildren().add(new Rectangle() {{
getStyleClass().add("track");
if(scrollBar.getOrientation() == Orientation.HORIZONTAL) {
widthProperty().bind(scrollBar.widthProperty());
setHeight(16);
}
else {
setWidth(16);
heightProperty().bind(scrollBar.heightProperty());
}
}});
getChildren().add(new Rectangle() {{
getStyleClass().add("thumb");
NumberBinding range = Bindings.subtract(scrollBar.maxProperty(), scrollBar.minProperty());
NumberBinding position = Bindings.divide(Bindings.subtract(scrollBar.valueProperty(), scrollBar.minProperty()), range);
if(scrollBar.getOrientation() == Orientation.HORIZONTAL) {
setHeight(16);
widthProperty().bind(scrollBar.visibleAmountProperty().divide(range).multiply(scrollBar.widthProperty()));
xProperty().bind(Bindings.subtract(scrollBar.widthProperty(), widthProperty()).multiply(position));
}
else {
setWidth(16);
heightProperty().bind(scrollBar.visibleAmountProperty().divide(range).multiply(scrollBar.heightProperty()));
yProperty().bind(Bindings.subtract(scrollBar.heightProperty(), heightProperty()).multiply(position));
}
}});
}};
}
catch(Exception e) {
e.printStackTrace();
throw e;
}
}
@Override
public ScrollBar getSkinnable() {
return scrollBar;
}
}
This occurs very rarely with the default ScrollBarSkin, but happens much more often with the provided Skin (that isn't 100% according to spec) -- it may help to debug the issue for the default ScrollBarSkin.
What happens is that when scrolling through the ListView with cursor down and holding it at some point causes every Xth cell to be highlighted instead of the currently active cell. Play with the provided example and you should be able to reproduce it hopefully.
package hs.mediasystem;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class ListTest extends Application {
public static void main(String[] args) {
Application.launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
Scene scene = new Scene(new VBox() {{
getChildren().add(
new HBox() {{
ListView<String> listView = new ListView<>();
listView.setOnKeyPressed(new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
if(event.getCode() == KeyCode.LEFT) {
System.out.println("Consuming cursor left!");
event.consume();
}
if(event.getCode() == KeyCode.RIGHT) {
System.out.println("Consuming cursor right!");
event.consume();
}
}
});
listView.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
private int cellCount;
@Override
public ListCell<String> call(ListView<String> param) {
System.out.println("New cell created " + cellCount++);
return new ListCell<String>() {
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if(item != null) {
setText(item);
}
}
};
}
});
for(int i = 0; i < 1000; i++) {
listView.getItems().add("" + i);
if((i - 90) % 100 == 0) {
listView.getItems().add("Extra Long Item to get a ScrollBar at the Bottom of the ListView");
}
}
getChildren().add(new Button("1"));
getChildren().add(new Button("2"));
getChildren().add(new Button("3"));
getChildren().add(listView);
getChildren().add(new Button("4"));
getChildren().add(new Button("5"));
getChildren().add(new Button("6"));
}}
);
getChildren().add(new Label("Tab to ListView, hold down cursor down until the bug occurs (may take a while and take multiple tries, usually at item 600)") {{
setWrapText(true);
}});
}});
scene.getStylesheets().add("bug.css");
primaryStage.setScene(scene);
primaryStage.show();
}
}
bug.css
.root {
-c-base: rgb(173, 216, 230);
-c-text: -c-base;
-c-text-highlight: derive(-c-base, +75%);
-c-text-focused: derive(-c-base, +75%);
-c-text-unobtrusive: derive(-c-base, -30%);
-c-text-watermark: rgba(173, 216, 230, 0.1);
-c-shadow-highlight: derive(-c-base, -50%);
-c-border-highlight: -c-text-highlight;
-c-border-dialog: -c-text-highlight;
-c-fill-dark: derive(-c-base, -75%);
-c-star: yellow;
-c-star-disabled: gray;
-c-bg-transparent: rgba(0, 0, 0, 0.66);
color-main: -c-base;
color-focused-text: derive(color-main, 30%);
color-content-background: derive(color-blue-80, -90%);
color-blue: -c-base;
color-blue-80: rgba(173, 216, 230, 0.8);
color-blue-70: rgba(173, 216, 230, 0.7);
color-blue-50: rgba(173, 216, 230, 0.5);
color-blue-40: rgba(173, 216, 230, 0.4);
color-blue-20: rgba(173, 216, 230, 0.2);
color-blue-10: rgba(173, 216, 230, 0.1);
color-white: rgb(255, 255, 255);
color-black-50: rgba(0, 0, 0, 0.50);
color-black-75: rgba(0, 0, 0, 0.75);
-fx-font-family: "Arial";
-fx-font-size: 16pt;
-fx-font-weight: normal;
}
.scroll-pane {
-fx-background-color: transparent;
}
/*
* List & Tree styles
*/
.list-cell:even, .tree-cell:even, .table-cell:even {
-fx-background-color: transparent;
}
.list-cell:odd, .tree-cell:odd, .table-cell:odd {
-fx-background-color: transparent;
}
.list-cell:focused, .tree-cell:focused {
-fx-background-color: rgba(173, 216, 230, 0.3);
}
.list-cell, .tree-cell, .option-cell {
-fx-background-color: transparent;
-fx-border-color: linear-gradient(to right, transparent, color-blue-20 15%, color-blue-20 85%, transparent);
-fx-border-width: 1;
-fx-border-insets: 1;
}
.list-cell:empty, .tree-cell:empty {
-fx-border-color: transparent;
-fx-border-width: 1;
-fx-border-insets: 1;
}
.list-cell:focused, .tree-cell:focused, .option-cell.focused {
-fx-background-insets: 1, 1, 1;
-fx-background-color: radial-gradient(center 25% 0%, radius 25%, color-blue 0%, transparent),
radial-gradient(center 75% 100%, radius 25%, color-blue 0%, transparent),
linear-gradient(to right, transparent, color-blue-20 15%, color-blue-20 85%, transparent);
-fx-border-width: 1, 1, 1;
-fx-border-color: radial-gradient(center 25% 0%, radius 25%, color-blue 0%, transparent),
radial-gradient(center 75% 100%, radius 25%, color-blue 0%, transparent),
linear-gradient(to right, transparent, color-blue-50 15%, color-blue-50 85%, transparent);
}
.label {
-fx-text-fill: black;
}
.list .list-view {
-fx-border-width: 2;
-fx-border-color: color-blue-20;
-fx-border-radius: 10;
}
.scroll-bar {
-fx-skin: "hs.mediasystem.ScrollBarSkinBad";
}
.scroll-bar .track {
-fx-fill: derive(color-blue-50, -80%);
}
.scroll-bar .thumb {
-fx-stroke-width: 2;
-fx-stroke: derive(color-blue-50, -30%);
-fx-stroke-type: inside;
-fx-fill: derive(color-blue-50, -60%);
}
ScrollBarSkinBad:
package hs.mediasystem;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.NumberBinding;
import javafx.geometry.Orientation;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.Skin;
import javafx.scene.shape.Rectangle;
public class ScrollBarSkinBad implements Skin<ScrollBar> {
private final ScrollBar scrollBar;
public ScrollBarSkinBad(ScrollBar scrollBar) {
this.scrollBar = scrollBar;
}
@Override
public void dispose() {
}
@Override
public Node getNode() {
try {
return new Group() {{
getChildren().add(new Rectangle() {{
getStyleClass().add("track");
if(scrollBar.getOrientation() == Orientation.HORIZONTAL) {
widthProperty().bind(scrollBar.widthProperty());
setHeight(16);
}
else {
setWidth(16);
heightProperty().bind(scrollBar.heightProperty());
}
}});
getChildren().add(new Rectangle() {{
getStyleClass().add("thumb");
NumberBinding range = Bindings.subtract(scrollBar.maxProperty(), scrollBar.minProperty());
NumberBinding position = Bindings.divide(Bindings.subtract(scrollBar.valueProperty(), scrollBar.minProperty()), range);
if(scrollBar.getOrientation() == Orientation.HORIZONTAL) {
setHeight(16);
widthProperty().bind(scrollBar.visibleAmountProperty().divide(range).multiply(scrollBar.widthProperty()));
xProperty().bind(Bindings.subtract(scrollBar.widthProperty(), widthProperty()).multiply(position));
}
else {
setWidth(16);
heightProperty().bind(scrollBar.visibleAmountProperty().divide(range).multiply(scrollBar.heightProperty()));
yProperty().bind(Bindings.subtract(scrollBar.heightProperty(), heightProperty()).multiply(position));
}
}});
}};
}
catch(Exception e) {
e.printStackTrace();
throw e;
}
}
@Override
public ScrollBar getSkinnable() {
return scrollBar;
}
}