JavaFX Alert with multiple colors - css

I have a program that at some point (may) displays two warnings - one about errors - those are in red, and one about warnings - those are in orange.
I wonder however if there is a way - using css - to have just one warning with some text red and some text orange.
Here is an example of what I want to achieve (the two can be separated into "sections"):
RED ERROR1
RED ERROR2
RED ERROR3
ORANGE WARNING1
ORANGE WARNING2
I've seen some answers pointing to RichTextFX like this one, however I don't see (or don't know) how that could apply to generic Alerts. Is that even possible, without writing some custom ExpandedAlert class?

The Alert class inherits from Dialog, which provides a pretty rich API and allows arbitrarily complex scene graphs to be set via the content property.
If you just want static text with different colors, the simplest approach is probably to add labels to a VBox; though you could also use more complex structures such as TextFlow or the third-party RichTextFX mentioned in the question if you need.
A simple example is:
import java.util.Random;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class App extends Application {
private final Random rng = new Random();
private void showErrorAlert(Stage stage) {
Alert alert = new Alert(Alert.AlertType.ERROR);
int numErrors = 2 + rng.nextInt(3);
int numWarnings = 2 + rng.nextInt(3);
VBox errorList = new VBox();
for (int i = 1 ; i <= numErrors ; i++) {
Label label = new Label("Error "+i);
label.setStyle("-fx-text-fill: red; ");
errorList.getChildren().add(label);
}
for (int i = 1 ; i <= numWarnings ; i++) {
Label label = new Label("Warning "+i);
label.setStyle("-fx-text-fill: orange; ");
errorList.getChildren().add(label);
}
alert.getDialogPane().setContent(errorList);
alert.initOwner(stage);
alert.show();
}
#Override
public void start(Stage stage) {
Button showErrors = new Button("Show Errors");
showErrors.setOnAction(e -> showErrorAlert(stage));
BorderPane root = new BorderPane(showErrors);
Scene scene = new Scene(root, 400, 400);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
which gives this result:

Related

Updating the Width of TextField and VBox when Full screened JavaFX

whenever I try to full screen my application, it doesn't scale. I've made multiple copies of this application trying different methods but none seem to work right.
First attempt: Application was a Parent, it would scale the background but the elements inside wouldn't scale to screen size.
As an update: here is the actual Parent that was made. The layout is the original one I wrote and has no issues when it's windowed. It has a preset WIDTH and HEIGHT but when full screened, The first example picture is what it looks like where the WIDTH of the the TextField doesn't update (since it's preset and not updating to the highest WIDTH of the screen it's running on). There are two parts to this that CAN be fixed when only one is fixed. The displayed Text has a set wrapping length of the console, though it is set by using WIDTH.
Here's what the console looks like when it's windowed:
If I could find a way to change the WIDTH, I'm thinking this can be fixed for both the TextField and the setWrappingWidth().
package application.console;
import application.areas.startingArea.SA;
import application.areas.vanguardForest.VFCmds;
import application.areas.vanguardForest.VFNavi;
import application.areas.vanguardForest.VFPkups;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Parent;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.control.TextField;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
public class Ce extends Region {
public static boolean fullscreen = false;
public static double WIDTH = 990;
// 990;
// Screen.getPrimary().getBounds().getMaxX();
public static double HEIGHT = 525;
// 525;
// Screen.getPrimary().getBounds().getMaxY();
public static Font Cinzel = (Font.loadFont("file:fonts/static/Cinzel-Medium.ttf", 16));
public static VBox console = new VBox(2);
public static TextField input = new TextField();
public static ScrollPane scroll = new ScrollPane();
public static BorderPane root = new BorderPane();
public static String s;
public static Parent Window() {
root.setMinSize(WIDTH, (HEIGHT - input.getHeight()));
root.setStyle("-fx-background-color: #232323;");
scroll.setContent(console);
root.setCenter(scroll);
scroll.setStyle("-fx-background: #232323;"
+ "-fx-background-color: transparent;"
+ "-fx-border-color: #232323;"
+ "-fx-focus-color: #232323;"
);
scroll.setHbarPolicy(ScrollBarPolicy.NEVER);
scroll.setVbarPolicy(ScrollBarPolicy.NEVER);
scroll.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, null, null)));
console.setStyle("-fx-background-color: #232323;"
+ "-fx-focus-color: #232323;");
console.heightProperty().addListener(new ChangeListener<Object>() {
#Override
public void changed(ObservableValue<?> observable, Object oldValue, Object newValue) {
scroll.setVvalue((Double)newValue);
}
});
HBox hbox = new HBox();
hbox.setPrefSize(WIDTH, 16);
root.setBottom(hbox);
Text carrot = new Text(" >");
carrot.setFont(Font.loadFont("file:fonts/static/Cinzel-Medium.ttf", 26));
carrot.setFill(Color.WHITE);
input.setStyle("-fx-background-color: transparent;"
+ "-fx-text-fill: #FFFFFF;"
+ "-fx-highlight-fill: #FFFFFF;"
+ "-fx-highlight-text-fill: #232323;"
// + "-fx-border-color: #FFFFFF;"
// + "-fx-border-width: .5;"
);
input.setFont(Cinzel);
input.setMinWidth(console.getWidth());
input.setOnAction(e -> {
String s = (input.getText()).stripTrailing();
input.clear();
});
Pane pane = new Pane();
root.getChildren().add(pane);
hbox.getChildren().addAll(carrot, input);
return root;
}
This isn't the main issue as I've stated, once getting the scaled width for the TextField the process of for setWrappingWidth() for displaying the text should be the if a solution is found, here's how it goes:
#SuppressWarnings("static-access")
public void print(String s, Color c) {
Ce Ce = new Ce();
HBox text1 = new HBox();
text1.setMinWidth(Ce.WIDTH);
text1.setMaxWidth(Ce.WIDTH);
Text tCarrot = new Text(" > ");
tCarrot.setFont(Ce.Cinzel);
tCarrot.setFill(c);
Text text2 = new Text();
final IntegerProperty i = new SimpleIntegerProperty(0);
Timeline tl = new Timeline();
KeyFrame kf = new KeyFrame(
Duration.seconds(textSpeed(fastText)),
e1 -> {
if(i.get() > s.length()) {
tl.stop();
} else {
text2.setText(s.substring(0, i.get()));
i.set(i.get() + 1);
}
});
tl.getKeyFrames().add(kf);
tl.setCycleCount(Animation.INDEFINITE);
tl.play();
text2.setFill(c);
text2.setFont(Ce.Cinzel);
text2.setWrappingWidth(Ce.WIDTH - 40);
text1.getChildren().addAll(tCarrot, text2);
Ce.console.getChildren().add(text1);
Ce.console.setMargin(text1, new Insets(5, 0, 0, 3));
}
Lastly, the HEIGHT of the VBox for the displayed Text works just as intended, it's just the setting/updating the WIDTH to set it to the size of the window whether Windowed of Full screened that is the main issue here.
Try this app. It will not be exactly what you want but may provide some useful help for you if you study it, if not just ignore it, tears can keep you blind, and sometimes, that is ok.
The implementation follows the suggestions you have received in the comments on your questions which together explain what is being done and why, so I won't provide much commentary on the solution here.
Type text in the input bar, press enter and it will appear in the listview for the console log. Use the Toggle full-screen button to toggle full-screen mode on or off.
Console.java
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class Console extends VBox {
private final ObservableList<String> consoleLog = FXCollections.observableArrayList();
private final ListView<String> logView = new ListView<>(consoleLog);
public Console(Stage stage) {
VBox.setVgrow(logView, Priority.ALWAYS);
HBox ribbon = createRibbon(
createFullScreenToggle(stage)
);
ribbon.setMinHeight(HBox.USE_PREF_SIZE);
getChildren().addAll(
ribbon,
logView
);
}
private ToggleButton createFullScreenToggle(Stage stage) {
ToggleButton fullScreenToggle = new ToggleButton("Toggle full screen");
fullScreenToggle.setOnAction(e ->
stage.setFullScreen(
fullScreenToggle.isSelected()
)
);
return fullScreenToggle;
}
private HBox createRibbon(ToggleButton fullscreenToggle) {
Text prompt = new Text(">");
TextField input = new TextField();
input.setOnAction(e -> {
consoleLog.add(0, input.getText());
logView.scrollTo(0);
input.clear();
});
HBox.setHgrow(input, Priority.ALWAYS);
HBox ribbon = new HBox(10,
prompt,
input,
fullscreenToggle
);
ribbon.setAlignment(Pos.BASELINE_LEFT);
return ribbon;
}
public ObservableList<String> getConsoleLog() {
return consoleLog;
}
}
ConsoleApplication.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class ConsoleApplication extends Application {
#Override
public void start(Stage stage) {
Console console = new Console(stage);
console.getConsoleLog().addAll(
TEXT.lines().toList()
);
stage.setScene(
new Scene(
console
)
);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
private static final String TEXT = """
W. Shakespeare - Sonnet 148
O me, what eyes hath Love put in my head,
Which have no correspondence with true sight!
Or, if the have, where is my judgement fled,
That censures falsely what they see aright?
If that be fair whereon my false eyes dote,
What means the world to say it is not so?
If it be not, then love doth well denote
Love’s eye is not so true as all men’s ‘No.’
How can it? O, how can Love’s eye be true,
That is so vex’d with watching and with tears?
No marvel then, though I mistake my view;
The sun itself sees not till heaven clears.
O cunning Love! with tears thou keep’st me blind.
Lest eyes well-seeing thy foul faults should find.
""";
}
If you want to increase the nodes height/width according to the viewport, then this's not the best practice, because every user will have the same font size at the end. What you can do is to make the font resizable by either GUI buttons or keyboard/mouse keys.
Here is a modification on your code, that will allow users to use ctrl + mouse wheel to increase/decrease the font (like any browser or terminal):
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class ConsoleTest extends Application {
#Override
public void start(Stage stage) {
Scene scene = new Scene(new GameWindow().Console(), 600, 600);
stage.setTitle("Console");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
class GameWindow {
public static Console c = new Console();
public Parent Console() {
for (int i = 0; i < 100; i++) c.addText(new Text("Test" + i));
return c;
}
}
class Console extends BorderPane {
private final SimpleDoubleProperty fontSize = new SimpleDoubleProperty(20);
private final ObjectBinding<Font> fontBinding = Bindings.createObjectBinding(() -> Font.font(fontSize.get()), fontSize);
private final VBox console;
public Console() {
console = new VBox();
console.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
ScrollPane scroll = new ScrollPane(console);
scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scroll.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scroll.setFitToHeight(true);
scroll.setFitToWidth(true);
scroll.setPadding(Insets.EMPTY);
Text caret = new Text(" >");
caret.fontProperty().bind(fontBinding);
caret.setFill(Color.WHITE);
TextField input = new TextField();
input.setStyle("-fx-background-color: transparent;" + "-fx-text-fill: #FFFFFF;" + "-fx-highlight-fill: #FFFFFF;" + "-fx-highlight-text-fill: #232323;");
input.fontProperty().bind(fontBinding);
HBox inputBar = new HBox(2, caret, input);
inputBar.setStyle("-fx-background-color: #232323;");
inputBar.setAlignment(Pos.CENTER_LEFT);
setCenter(scroll);
setBottom(inputBar);
EventHandler<ScrollEvent> scrollEvent = e -> {
if (e.isControlDown()) {
if (e.getDeltaY() > 0) {
fontSize.set(fontSize.doubleValue() + 2);
} else {
double old;
fontSize.set((old = fontSize.doubleValue()) < 10 ? old : old - 2);
}
e.consume();
}
};
inputBar.setOnScroll(scrollEvent);
console.setOnScroll(scrollEvent);
}
public void addText(Text text) {
text.fontProperty().bind(fontBinding);
text.setFill(Color.WHITE);
console.getChildren().add(text);
}
}

Can't get images to show up, trying to display 5 random cards from directory file

I'm not sure what I'm doing wrong. My pane opens, but it is blank. I have the file (card) as a directory (image) in the src folder. I have tried writing this so many different way but always get the same result. I just need to make the images actually show up. I don't know how to get the card images to show up. Any help is greatly appreciated.
package Assignment;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.Collections;
public class Assignment extends Application {
#Override // Override the start method in the Application class
public void start(Stage primaryStage) {
// Initialize card deck
ArrayList<Integer> cards = getCards();
// Create a FlowPane
FlowPane pane = new FlowPane();
pane.setVgap(10);
pane.setHgap(10);
pane.setPadding(new Insets(15, 15, 15, 15));
for (int i = 0; i < 5; i++) {
pane.getChildren().add(new ImageView(new Image("file:image/card/" + cards.get(i) + ".png")));
}
// Create scene and place it in the stage
Scene scene = new Scene(pane,500, 300);
primaryStage.setTitle("Poker 1");
primaryStage.setScene(scene);
primaryStage.show();
}
private ArrayList<Integer> getCards() {
ArrayList<Integer> cards = new ArrayList<>();
for (int i = 0; i < 52; i++) {
cards.add(i + 1);
}
Collections.shuffle(cards);
return cards;
}
public static void main(String[] args) {
launch(args);
}
}
What directory are your cards images in?
Click 'edit configuration' on the run button drop down menu, then look at the "Working directory" entry. Ex. Mine was C:\Users\NAME\IdeaProjects\Test
Placing the cards in C:\Users\NAME\IdeaProjects\Test\image\cards[1-52].png made your code work for me as written.

Java FX TextField blur

Can anyone tell me why sometimes JavaFX displays the content of a TextField with a blur effect on it? It seems to be random and occurs in any of my TextFields. Please see the image attached.
Focusing on the intermittent rendering artifact mentioned here, the 2 glyph looks like it's been rendered twice, with one copy shifted horizontally relative to the other. Such apparently random anomalies are notoriously difficult to identify. Myriad causes may include incorrect synchronization, improper layout, defects in the host platform's rendering pipeline, etc. For reference, the example below may allow you to test on disparate platforms.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* #see https://stackoverflow.com/a/53989899/230513
*/
public class TextFieldTest extends Application {
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("TextFieldTest");
BorderPane root = new BorderPane();
root.setCenter(createContent());
root.setBottom(createVersion());
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
private Node createContent() {
HBox row1 = new HBox(4);
Label channelsLabel = new Label("Channels:");
TextField channelsText = new TextField("2");
channelsText.setPrefWidth(32);
Label separatorLabel = new Label("Separator:");
TextField separatorText = new TextField("!");
separatorText.setPrefWidth(32);
row1.setPadding(new Insets(8));
row1.getChildren().addAll(
channelsLabel, channelsText, separatorLabel, separatorText);
HBox row2 = new HBox(4, new Label("Label:"), new TextField());
row2.setPadding(new Insets(8));
return new VBox(row1, row2);
}
private Label createVersion() {
Label label = new Label(
System.getProperty("os.name") + " v"
+ System.getProperty("os.version") + "; Java v"
+ System.getProperty("java.version"));
label.setPadding(new Insets(8));
return label;
}
public static void main(String[] args) {
launch(args);
}
}
As shown in the Modena example, an intentional blur effect indicates that the text field is focused:
The detail that gives rise to the blurred effect in your image is a compound border, seen below at 2x:
Comparable effects are seen here for buttons (top row) and default buttons (bottom row):

Adding value from textfield

I a m creating a java application and I have a problem.
Here is the code.
package javastackoverflow;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class Javastackoverflow extends Application {
TextField deduct2;
Label text;
double ammount = 0.0;
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Apply");
text = new Label(Double.toString(ammount));
btn.setOnAction((e->{
double getamount = Double.parseDouble(deduct2.getText());
text.setText(Double.toString(getamount)+ ammount);
//this is where the program is suppose to get the amount and add it to amount, notice the + sign.
}))
;
deduct2 = new TextField();
FlowPane root = new FlowPane();
root.setAlignment(Pos.CENTER);
root.getChildren().addAll(btn,deduct2,text);
Scene scene = new Scene(root, 400, 450);
primaryStage.setTitle("Yo Stack");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
What it is suppose to do
When the user enters a number into the textfield the program is suppose to
to take that number and add it to ammount = 0;
My problem
But when ever the using enters a new number the text changes to That number. Remember I would like it to add to the currant number like (currant number = 23; user enters new Number) new number = 3; I would like results to equal = 26 but right now the program shows the result 3;
What I think
I think the problem is in the onAction() method.
I think that the text.setText() method is displaying the currant text entered into the textfield, rather then adding it to ammount.
I alse don't think I am using the correct operator from this line of code. That may be part of the problem.
text.setText(Double.toString(getamount)+ ammount);
notice how I use the + sign, that + adds the getamount to ammount..or its supposed to. But when I change that plus sign to - or * I get this error
===============================
bad operand types for binary operator '*'
first type: String
second type: TextField
===============================
As you can probably tell I really would like this code to be correct so if you don't understand something please comment before you report me. Then I can change it fast. Thank you!
You are adding the amount to the getAmount variable when amount = 0.0
Try adding the new value to getAmount
package javastackoverflow;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class Javastackoverflow extends Application {
TextField deduct2;
Label text;
double getamount = 0.0; //Edit 1
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Apply");
text = new Label(Double.toString(ammount));
btn.setOnAction((e->{
getamount += Double.parseDouble(deduct2.getText()); //Edit 2
text.setText(Double.toString(getamount));
//this is where the program is suppose to get the amount and add it to amount, notice the + sign.
}))
;
deduct2 = new TextField();
FlowPane root = new FlowPane();
root.setAlignment(Pos.CENTER);
root.getChildren().addAll(btn,deduct2,text);
Scene scene = new Scene(root, 400, 450);
primaryStage.setTitle("Yo Stack");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}

javafx 8 listview first and last row

Is there a way to determine the first and last visible row of a listview? In other words I'm looking for two indexes into an array that populates a listview which represent the top and the bottom row of the 'display window'.
You could get the VirtualFlow of the ListView which has methods for getting the first and last rows.
Example:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.scene.Scene;
import javafx.scene.control.IndexedCell;
import javafx.scene.control.ListView;
import javafx.scene.control.ScrollBar;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import com.sun.javafx.scene.control.skin.VirtualFlow;
public class ListViewSample extends Application {
#Override
public void start(Stage stage) {
VBox box = new VBox();
ListView<Integer> list = new ListView<>();
ObservableList<Integer> items = FXCollections.observableArrayList();
for( int i=0; i < 100; i++) {
items.add(i);
}
list.setItems(items);
box.getChildren().add(list);
VBox.setVgrow(list, Priority.ALWAYS);
Scene scene = new Scene(box, 200, 200);
stage.setScene(scene);
stage.show();
VirtualFlow flow = (VirtualFlow) list.lookup( ".virtual-flow");
flow.addEventFilter(Event.ANY, event -> {
IndexedCell first = flow.getFirstVisibleCellWithinViewPort();
IndexedCell last = flow.getLastVisibleCellWithinViewPort();
System.out.println( list.getItems().get( first.getIndex()) + " - " + list.getItems().get( last.getIndex()) );
});
}
public static void main(String[] args) {
launch(args);
}
}
You see the fully visible first and last items in the console.
ps: I leave the no data check and event handling to you
Alternate version without css lookup:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.IndexedCell;
import javafx.scene.control.ListView;
import javafx.scene.control.ScrollBar;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import com.sun.javafx.scene.control.skin.VirtualFlow;
public class ListViewSample extends Application {
ListView<String> list = new ListView<String>();
#Override
public void start(Stage stage) {
VBox box = new VBox();
ListView<Integer> list = new ListView<>();
ObservableList<Integer> items = FXCollections.observableArrayList();
for( int i=0; i < 100; i++) {
items.add(i);
}
list.setItems(items);
box.getChildren().add(list);
VBox.setVgrow(list, Priority.ALWAYS);
Scene scene = new Scene(box, 200, 200);
stage.setScene(scene);
stage.show();
VirtualFlow virtualFlow = null;
for( Node node: list.getChildrenUnmodifiable()) {
if( node instanceof VirtualFlow) {
virtualFlow = (VirtualFlow) node;
}
}
final VirtualFlow flow = virtualFlow;
flow.addEventFilter(Event.ANY, event -> {
IndexedCell first = flow.getFirstVisibleCellWithinViewPort();
IndexedCell last = flow.getLastVisibleCellWithinViewPort();
System.out.println( list.getItems().get( first.getIndex()) + " - " + list.getItems().get( last.getIndex()) );
});
}
public static void main(String[] args) {
launch(args);
}
}
UPDATE
VirtualFlow is available only after the ListView has been rendered, because it uses Layout parameters which are not available until after the ListView is visible on the stage. So I had to make sure that I got the VirtualFlow when it was certain that the ListView had been rendered. Since I was manipulating the list with various methods I call this method at the end of each method:
private VirtualFlow flow;
private void updateListView(int centreIndex) {
if (flow == null)
flow = (VirtualFlow) myListView.lookup(".virtual-flow");
if (flow != null){
IndexedCell first = flow.getFirstVisibleCellWithinViewPort();
IndexedCell last = flow.getLastVisibleCellWithinViewPort();
System.out.println(first.getIndex() + " - " + last.getIndex());
}
// Now the list can be selectively 'redrawn' using the scollTo() method,
// and using the .getSelectionModel().select(centreIndex) to set the
// desired cell
}
It's bit of a hack, but it works. Using layout parameters does have a drawback though that needs to be considered. If the height of the ListView is only 1 pixel less than the total height of all rows, n number of rows will be visible, but the flow will report n-1 rows which will appear to be a discrepancy at first. Hence keeping a fixed layout height is imperative. At least now by using scrollTo(..) I have control over the position of the selected item in the list (I want to keep it centred in the list display when an item is dragged through the list). This solution leaves me feeling uneasy, but it seems to be the only 'simple' way.
Just a note on the odd-looking logic. It seems that getting the flow takes time, while the program keeps executing. The second (flow != null) is necessary to avoid a NullPointerException.
UPDATE 2
My hack turns out not to work. The whole hack is dependent on timing. Rendering is done on a different thread and as soon as I changed the order of instantiation of classes in my app, I got a NullPointerException again. I turned to the Java doc:
"JavaFX is not thread safe and all JavaFX manipulation should be run on the JavaFX processing thread. If you allow a JavaFX application to interact with a thread other than the main processing thread, unpredictable errors will occur"
And they do! So forget the above - it does not work and will make you scratch your head (and more!) trying to debug it ;-)

Resources