(My) expected behaviour:
When you call setFocusTraversable(false) on a Node with children (i.e. the Node has a method getChildren() that returns a List of Nodes) the focusTraversable properties of that node and all its children get the value false.
Actual behaviour:
However, when I call setFocusTraversable(false) on for example a TextFlow, its children are still being able to receive focus. This is illustrated in the code below.
Why does the setFocusTraversable(boolean) method work this way, and how should one work around this limitation?
// File: App.java
package nl.aronhoogeveen.bugreports;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Hyperlink;
import javafx.scene.layout.BorderPane;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.stage.Stage;
/**
* JavaFX App
*/
public class App extends Application {
#Override
public void start(Stage stage) {
// TextFlow 1
Text text1 = new Text("This is text1");
Hyperlink hyperlink1 = new Hyperlink("This is hyperlink1");
Text text2 = new Text("This is text2");
Hyperlink hyperlink2 = new Hyperlink("This is hyperlink2");
TextFlow textFlow1 = new TextFlow(text1, hyperlink1, text2, hyperlink2);
textFlow1.setFocusTraversable(false);
// TextFlow 2 (CustomTextFlow)
Text text3 = new Text("This is text3");
Hyperlink hyperlink3 = new Hyperlink("This is hyperlink3");
Text text4 = new Text("This is text4");
Hyperlink hyperlink4 = new Hyperlink("This is SPARTAAAAA");
CustomTextFlow textFlow2 = new CustomTextFlow(text3, hyperlink3, text4, hyperlink4);
textFlow2.customSetFocusTraversable(false);
BorderPane rootBorderPane = new BorderPane();
rootBorderPane.setTop(textFlow1);
rootBorderPane.setBottom(textFlow2);
stage.setScene(new Scene(rootBorderPane));
stage.sizeToScene();
stage.show();
}
public static void main(String[] args) {
launch();
}
}
// File: CustomTextFlow.java
package nl.aronhoogeveen.bugreports;
import javafx.scene.Node;
import javafx.scene.text.TextFlow;
public class CustomTextFlow extends TextFlow {
public CustomTextFlow(Node... children) {
super(children);
}
/**
* Sets the property focusTraversable of all children's children to {#code b}.
*
* #param b the value to set for the properties
*/
public void customSetFocusTraversable(boolean b) {
for (Node child : getChildren()) {
// Assume for simplicity that the children are not Parents or other types that have children
child.setFocusTraversable(b);
}
}
}
I filed a bug on the ControlsFX component HyperlinkLabel regarding this behaviour, and after investigating I discovered that this behaviour was already present in the JavaFX TextFlow component that is used by the HyperlinkLabel and therefore I am posting this question.
Related
I am trying to build a Combobox in JavaFX that should work as followed.
The user should only type numbers in, but the typed numbers have to be formattet.
ex. 111-111-1111.
So if the user types in three numbers a - should be added automatically.
I figured out how to do that.
I blocked everything but numbers with a TexFormatter.
Now the main Part of the problem comes the part after this.
I added a keyevent.key_released place the caret at the end when a key is released.
But if the user is typing too fast it won't work.
Most of the time it works just fine like that, but when is the user every doing something only the way I expect?
I could not find another way to get the actual value of the combobox, because it seems to refresh after hitting enter or so.
Adding the - in the Textformatter resultet in the programm listening to itself and I wasn't able to place the caret at the end position.
import java.util.function.UnaryOperator;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextFormatter;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
/**
*
* #author
*/
public class tester extends Application{
ComboBox<String> combo = new ComboBox<String>();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
System.out.println("test");
HBox root = new HBox();
//
UnaryOperator<TextFormatter.Change> filter = new UnaryOperator<TextFormatter.Change>() {
#Override
public TextFormatter.Change apply(TextFormatter.Change t) {
System.out.println(t.getText());
if (t.isReplaced())
if(t.getText().matches("[^0-9]")) {
t.setText(t.getControlText().substring(t.getRangeStart(), t.getRangeEnd()));}
if (t.isAdded()) {
// Add in Formatter //
if (t.getControlText().length() == 2 || t.getControlText().length() == 6 && t.getText().matches("[0-9]")){
t.setText(t.getText() + "-");
combo.getEditor().end();
}
//
if (t.getText().matches("[^0-9]")) {
t.setText("");}
}
return t;
}
};
combo.setEditable(true);
combo.getEditor().setTextFormatter(new TextFormatter<>(filter));
combo.getEditor().addEventFilter(KeyEvent.KEY_RELEASED, e -> {
combo.getEditor().end();
});
root.getChildren().add(combo);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
I have a JavaFX application with several Stages open to provide floating windows. I want to iterate through these Stages from front to back. I have a list of all the Stages which I'd like to sort and am looking for a method that will let me compare two of them and determine which is in front. Is there a way to do this?
This is just one possibility.
Store each open Stage in a list that can be observed for changes.
Add a listener on each Stage's focusedProperty. When it changes to true,
Remove the stage from our List and readd it at index 0
Now, create a listener on the List and your "focused" Stage will always be at index 0.
You now have an ArrayList that stores the open stages, in order.
Here is a simple MCVE to demonstrate. There are certainly areas to be improved upon and I welcome suggestions, but this does provide some basic functionality.
OpenStages.java:
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
/**
* Implementation of a SimpleListProperty that will store our ObservableArrayList
*/
public class OpenStages<Stage> extends SimpleListProperty<Stage> {
/**
* Constructor that creates an ObservableArrayList
*/
public OpenStages() {
super(FXCollections.observableArrayList());
}
/**
* Removes this Stage from the list and re-adds it at index 0
*/
public void focusStage(Stage stage) {
this.remove(stage);
this.add(0, stage);
}
}
Main.java:
import javafx.application.Application;
import javafx.collections.ListChangeListener;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Custom SimpleListProperty that holds our ObservableArrayList of open stages
OpenStages<Stage> openStages = new OpenStages<>();
// Simple interface
VBox root = new VBox(5);
root.setPadding(new Insets(10));
root.setAlignment(Pos.CENTER);
Button btnCreateStage = new Button("Create New Stage");
btnCreateStage.setOnAction(event -> {
// Create a new Stage
Stage newStage = new Stage();
// Add a listener to the focusedProperty of the Stage. When focus changes to true, we
// need to update our openStages list
newStage.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
openStages.focusStage(newStage);
}
});
// Add the stage to our OpenStages list.
openStages.add(newStage);
// Simple layout for the new Stage
VBox stageRoot = new VBox();
stageRoot.setPrefSize(300, 100);
stageRoot.setPadding(new Insets(10));
stageRoot.setAlignment(Pos.CENTER);
// Let's add a label and title to indicate which Stage this is
stageRoot.getChildren().add(new Label("Stage #" + openStages.size()));
newStage.setTitle("Stage #" + openStages.size());
newStage.setScene(new Scene(stageRoot));
// Finally, let's show the stage
newStage.show();
});
// Now, let's create a simple listener for our openStages list to print out the focused Stage
openStages.addListener(new ListChangeListener<Stage>() {
#Override
public void onChanged(Change<? extends Stage> c) {
// Only interested in a stage being added
if (c.next() && c.wasAdded()) {
System.out.println("Focused stage: " + openStages.get(0).getTitle());
}
}
});
// Add the Button to our main layout
root.getChildren().add(btnCreateStage);
// Show the Stage
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
I would like to apply two or three different styles to text in a single cell in a TableView.
For example, I'd like the single cell to have text formatted like this:
Edge of the Sun [EP] (Disc 2)
I'd really like to do it with colors, as well.
I know how to apply styling to the entire cell, but I don't even know where to start for applying style to part of the text.
Putting the data in different columns isn't a viable option.
Below's a quick example that
uses TextFlow to style parts of a text
implements a custom TableCell that has a TextFlow as its graphics and updates the text parts as appropriate
Note that there is a slight visual glitch: the prefHeight of the flow seems to return the accumulated height of the lines as if they were wrapped even if they aren't, thus making the row height oversized. As a quick hack, the computePrefHeight is overridden to force a single line - with the drawback that the other line/s simply disappear if the column width is decreased. Pretty sure there's something better but too lazy to further dig ;)
import java.util.Locale;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.scene.text.TextFlow;
import javafx.stage.Stage;
/**
*
*/
public class TableFormattedCell extends Application {
public static class MyCell extends TableCell<Locale, Locale> {
private TextFlow flow;
private Label displayName;
private Label displayLanguage;
public MyCell() {
displayName = new Label();
displayName.setStyle("-fx-font-weight: bold");
displayLanguage = new Label();
displayLanguage.setStyle("-fx-font-style: italic; -fx-text-fill: darkviolet");
flow = new TextFlow(displayName, displayLanguage) {
#Override
protected double computePrefHeight(double width) {
// quick hack to force into single line ...
// there must be something better ..
return super.computePrefHeight(-1);
}
};
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setGraphic(flow);
}
#Override
protected void updateItem(Locale item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
displayName.setText("");
displayLanguage.setText("");
} else {
displayName.setText(item.getDisplayName() + " ");
displayLanguage.setText(item.getDisplayLanguage());
}
}
}
private Parent getContent() {
TableView<Locale> table = new TableView<>(FXCollections.observableArrayList(
Locale.getAvailableLocales()));
TableColumn<Locale, String> countryCode = new TableColumn<>("CountryCode");
countryCode.setCellValueFactory(new PropertyValueFactory<>("country"));
TableColumn<Locale, String> language = new TableColumn<>("Language");
language.setCellValueFactory(new PropertyValueFactory<>("language"));
table.getColumns().addAll(countryCode, language);
TableColumn<Locale, Locale> local = new TableColumn<>("Locale");
local.setCellValueFactory(c -> new SimpleObjectProperty<>(c.getValue()));
local.setCellFactory(e -> new MyCell());
table.getColumns().addAll(local);
BorderPane pane = new BorderPane(table);
return pane;
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(getContent(), 800, 400));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
#SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(TableFormattedCell.class.getName());
}
I want to set up a model for my project so my controllers can communicate with each other. I want it to have a setter and getter, to allow easy access to styling certain nodes from either class.
My question: is it possible to bind a style property (ex. "-fx-background-color: blue") to a node?
From my research, I see that this is definitely possible with text values for labels (explained by James_D here: JavaFX - How to use a method in a controller from another controller?), but I am having a hard time figuring out what the syntax for doing a similar thing with "setStyle" would be.
The model I have so far:
public class Model {
private final StringProperty shadow = new SimpleStringProperty("-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.24), 10,0,0,0)");
public StringProperty shadowProperty() {
return shadow;
}
public final String getShadow() {
return shadowProperty().get();
}
public final void setShadow(String shadow) {
shadowProperty().set(shadow);
}
}
I understand how I would set the "shadow" value from a controller, but what I don't understand is how I can bind a node from another controller to listen to that change.
Let's say the node is something like:
#FXML AnchorPane appBar
I want "appBar" to take on any changes made to "shadow" in the model. What would that look like?
You need to add a listener to the shadowProperty to listen to its changes.
something.shadowProperty() .addListener( (observable, oldValue, newValue) -> {
//do something with appBar
}) ;
I'm not entirely sure what you want to achieve, but this should answer your question about how to listen to property changes.
PS: im on mobile, so no guarantees regarding typos
Edit: you can also bind the property of one object to the property of another. Use bind() for that.
EDIT: Here is an example:
import javafx.application.Application;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Main extends Application {
Property<Background> backgroundProperty;
StringProperty styleProperty;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
VBox root = new VBox(10);
backgroundProperty = new SimpleObjectProperty<>();
styleProperty = new SimpleStringProperty();
// Pane that changes background by listener
Pane pane1 = new Pane();
pane1.setMinHeight(40);
backgroundProperty.addListener( (observable, oldValue, newValue) -> {
pane1.setBackground(backgroundProperty.getValue());
});
// Pane that changes background by property binding
Pane pane2 = new Pane();
pane2.setMinHeight(40);
pane2.backgroundProperty().bind(backgroundProperty);
// Pane that binds the style property
Pane pane3 = new Pane();
pane3.setMinHeight(40);
pane3.styleProperty().bind(styleProperty);
backgroundProperty.setValue(new Background(new BackgroundFill(Color.RED, null, null)));
styleProperty.setValue("-fx-background-color: black");
root.getChildren().add(pane1);
root.getChildren().add(pane2);
root.getChildren().add(pane3);
Scene scene = new Scene(root, 200, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
}
I have a TreeItem<String> object.
I would like to put a button in it.
Button button = new Button("Hello!");
root.setGraphic(button); // root is the TreeItem object
Not bad, but I would like to freely position this button. I would like to the right of "Root".
Changing the button's layout X doesn't seem to do anything. How can I do this then?
You can create custom class extending HBox class and containing constructor declarations with Label and Button initialization. Next, you can use this class to specify TreeView<T> and TreeItem<T> generic types. Sample of such simple HBox custom class can look like this:
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
public class CustomItem extends HBox {
private Label boxText;
private Button boxButton;
CustomItem(Label txt) {
super();
this.boxText = txt;
this.getChildren().add(boxText);
this.setAlignment(Pos.CENTER_LEFT);
}
CustomItem(Label txt, Button bt) {
super(5);
this.boxText = txt;
this.boxButton = bt;
this.getChildren().addAll(boxText, boxButton);
this.setAlignment(Pos.CENTER_LEFT);
}
}
And the use of it in practice can look like shown below:
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class TreeViewDemo extends Application {
public Parent createContent() {
/* layout */
BorderPane layout = new BorderPane();
/* layout -> center */
/* initialize TreeView<CustomItem> object */
TreeView<CustomItem> tree = new TreeView<CustomItem>();
/* initialize tree root item */
TreeItem<CustomItem> root = new TreeItem<CustomItem>(new CustomItem(new Label("Root")));
/* initialize TreeItem<CustomItem> as container for CustomItem object */
TreeItem<CustomItem> node = new TreeItem<CustomItem>(new CustomItem(new Label("Node 1"), new Button("Button 1")));
/* add node to root */
root.getChildren().add(node);
/* set tree root */
tree.setRoot(root);
/* add items to the layout */
layout.setCenter(tree);
return layout;
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setWidth(200);
stage.setHeight(200);
stage.show();
}
public static void main(String args[]) {
launch(args);
}
}
Effect will look like this:
The quick way will be not to set the valueProperty of tree item and add the desired value as text (or label) to the graphicProperty.
root.setValue("");
Text text = new Text("Root");
Button button = new Button("Hello!");
HBox hbox = new HBox(5);
hbox.getChildren().addAll(text, button);
root.setGraphic(vbox);
The other long way, will be looking up the sub node of tree item and change its properties accordingly.
Edit note: Well it would more appropriate to use HBox instead of VBox. I was in hurry :)