I'm trying to make a list of boxes which a user can select through with their mouse and when one box is selected, it highlights it with a color, and all the rest of the boxes turn white. Is there an equivalent to the css tag :target in javafx like there is an equivalent to :focus(:focused) or do I have to handle selecting items in a list my own way?
There is no built-in target pseudoclass, but there is an API for creating your own CSS PseudoClass objects.
Here is a simple example:
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class SelectableBoxes extends Application {
private static final PseudoClass SELECTED_PSEUDOCLASS = PseudoClass.getPseudoClass("selected");
private ObjectProperty<Pane> selectedBox = new SimpleObjectProperty<>();
#Override
public void start(Stage primaryStage) {
VBox container = new VBox(5);
container.setPadding(new Insets(20));
int numBoxes = 5 ;
for (int i = 0 ; i < numBoxes; i++) {
container.getChildren().add(createBox());
}
ScrollPane scroller = new ScrollPane(container);
Scene scene = new Scene(scroller, 400, 400);
scene.getStylesheets().add("selectable-boxes.css");
primaryStage.setScene(scene);
primaryStage.show();
}
private Pane createBox() {
Pane pane = new Pane();
pane.setMinSize(50, 50);
pane.getStyleClass().add("box");
pane.setOnMouseClicked(e -> selectedBox.set(pane));
selectedBox.addListener((obs, oldSelection, newSelection) ->
pane.pseudoClassStateChanged(SELECTED_PSEUDOCLASS, newSelection == pane));
return pane ;
}
public static void main(String[] args) {
launch(args);
}
}
with the corresponding CSS file selectable-boxes.css:
.box {
-fx-border-width: 1 ;
-fx-border-color: black ;
}
.box:selected {
-fx-background-color: blue ;
}
This "answer" is to provide an alternate solution for the task you wish to accomplish (rather than directly answering your question regarding CSS tags).
For your task, you may wish to use ToggleButtons in a ToggleGroup, or a ListView.
Oracle provide a ToggleButton demo. By default a ToggleButton behaves a bit different from a radio button (e.g. it is possible to have nothing selected). If you want radio button style behavior, to ensure something is always selected, you can use the bit of code at: JDK-8090668
Need TogglePolicy for ToggleButton in ToggleGroup
toggleGroup.selectedToggleProperty().addListener(new ChangeListener<Toggle>() {
public void changed(ObservableValue<? extends Toggle> ov, Toggle toggle, Toggle new_toggle) {
if (toggle != null && new_toggle == null) {
toggle.setSelected(true);
}
}
});
The in-built controls can be styled quite extensively using CSS to get custom looks. Refer to the modena.css file for default css styles for the controls which can be overriden.
Related
Using JavaFX 8, I would like to have a responsive layout with three radio buttons that changes from a horizontal layout to a vertical layout as soon as there is not enough horizontal space to put all radio buttons next to each other.
This means the default layout should be like this:
(x) Radio button 1 ( ) Radio button 2 ( ) Radio button 3
And as soon as there is not enough horizontal space to put all three buttons on a single line, I want the layout to change to this:
(x) Radio button 1
( ) Radio button 2
( ) Radio button 3
Note that I want to avoid intermediate states like:
(x) Radio button 1 ( ) Radio button 2
( ) Radio button 3
I have tried to achieve the desired behaviour with a flow pane that changes its orientation dynamically but I encountered small layout glitches when there was another control (e.g. a text area) positioned underneath the flow pane. Here's the code I have used:
import javafx.application.Application;
import javafx.geometry.Orientation;
import javafx.scene.Scene;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextArea;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ResponsiveLayout extends Application {
RadioButton radioButton1 = new RadioButton("Radio button 1");
RadioButton radioButton2 = new RadioButton("Radio button 2");
RadioButton radioButton3 = new RadioButton("Radio button 3");
FlowPane flowPane = new FlowPane(radioButton1, radioButton2, radioButton3);
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
flowPane.setHgap(10);
flowPane.setPrefWrapLength(60);
flowPane.widthProperty().addListener((obs, oldWidth, newWidth) -> updateOrientation(newWidth));
VBox container = new VBox(flowPane, new TextArea());
Scene scene = new Scene(container, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private void updateOrientation(Number paneWidth) {
double childrenPadding = flowPane.getHgap() * (flowPane.getChildren().size() - 1);
int extraPadding = 5;
double childrenWidth = radioButton1.getWidth() +
radioButton2.getWidth() +
radioButton3.getWidth() +
childrenPadding +
extraPadding;
if (paneWidth.doubleValue() < childrenWidth) {
flowPane.setOrientation(Orientation.VERTICAL);
} else {
flowPane.setOrientation(Orientation.HORIZONTAL);
}
}
}
When I run this application and carefully change the width of the scene, I can see the following layout glitch right after the orientation of the flow pane changed from vertical to horizontal:
As you can see, there is an unwanted empty space between the radio buttons and the text area. It vanishes when I increase the scene width further or if the window loses focus. In a bigger application with more controls and containers around the flow pane, I can see even more layout glitches so I'm wondering if there is a better approach to achieving the responsiveness I need.
What could I do to improve the flow pane behavior? Or is there a more suitable layout pane that I could use for this?
I'd appreciate any support. Thanks.
You could toggle between VBox and HBox. See if this meets your requirements.
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Control;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextArea;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ResponsiveLayout extends Application
{
RadioButton radioButton1 = new RadioButton("Radio button 1");
RadioButton radioButton2 = new RadioButton("Radio button 2");
RadioButton radioButton3 = new RadioButton("Radio button 3");
HBox hBox = new HBox(radioButton1, radioButton2, radioButton3);
StackPane stackPane = new StackPane(hBox);
VBox vBox = new VBox();
boolean isHBox = true;
double controlWidth = -1;
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage primaryStage)
{
hBox.setMaxSize(Control.USE_PREF_SIZE, Control.USE_PREF_SIZE);
stackPane.setAlignment(Pos.CENTER_LEFT);
VBox container = new VBox(stackPane, new TextArea());
Scene scene = new Scene(container, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
container.widthProperty().addListener((obs, oldWidth, newWidth) -> updateOrientation(newWidth));
}
private void updateOrientation(Number paneWidth)
{
if (isHBox) {
if (hBox.getWidth() == stackPane.getWidth()) {
controlWidth = hBox.getWidth();
vBox.getChildren().addAll(hBox.getChildren());
stackPane.getChildren().clear();
stackPane.getChildren().add(vBox);
isHBox = false;
}
}
else {
if (controlWidth <= stackPane.getWidth()) {
hBox.getChildren().addAll(vBox.getChildren());
stackPane.getChildren().clear();
stackPane.getChildren().add(hBox);
isHBox = true;
}
}
}
}
I would like to dynamically change the color of the text in the tab label of a TabPane.
In SceneBuilder I've given the tab a name of randomTab.
In my code, I have the following:
if (randomEnabled)
randomTab.setStyle("-fx-color:Green;");
else
randomTab.setStyle("-fx-color:Black;");
However, this doesn't change the color of the text, it changes the color of the background of the tab's label.
I've tried "-fx-text-fill:Green" instead, as well as "-fx-foreground-color:Green", but neither of those have any effect.
Labels inside the tab header, by default, use the "looked-up color" -fx-text-base-color defined in the default stylesheet modena.css. So a quick-and-dirty approach is just to override that color definition:
randomTab.setStyle("-fx-text-base-color: green;");
The problem with this approach is that anything else in the tab (i.e. not in the tab header) that uses -fx-text-base-color as its text fill will also change text color. Most controls actually use -fx-text-background-color as the text (foreground!) color, so you might get away with this simple approach. (Thanks to jewelsea, whose comments led me to the correct version of this approach.)
Probably a more robust approach is to define your own "looked-up color" for the text fill in the tab, and then change its value for a specific tab in code.
In your external CSS file, add
.root {
-tab-text-color: -fx-text-base-color ;
}
.tab-label {
-fx-text-fill: -tab-text-color ;
}
Now to change the value of -tab-text-color for a specific tab, do
randomTab.setStyle("-tab-text-color: green;");
Here's a SSCCE:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class TabStyleTest extends Application {
#Override
public void start(Stage primaryStage) {
TabPane tabPane = new TabPane();
Tab tab1 = new Tab("Tab 1");
tab1.setContent(new StackPane(new Label("Tab 1")));
tab1.setStyle("-tab-text-color: green;");
Tab tab2 = new Tab("Tab 2");
tab2.setContent(new StackPane(new Label("Tab 2")));
tabPane.getTabs().addAll(tab1, tab2);
Scene scene = new Scene(tabPane, 600, 600) ;
scene.getStylesheets().add("style.css");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
where style.css is
.root {
-tab-text-color: -fx-text-base-color ;
}
.tab-label {
-fx-text-fill: -tab-text-color ;
}
Giving
Look, You can add this to your CSS file
.tab-label {
-fx-text-fill: #ffffff;
}
I have a JavaFX application that has various TextField widgets in the main frame. I have a MenuBar that includes the MenuItem objects "Copy" and "Paste" like a standard production application would have. Since any or none of the various TextField objects could be selected at any given time, it seems easier to just hardcode a "Ctrl+C" or "Ctrl+V" key press in the setOnAction events of the "Copy" and "Paste" MenuItem objects rather than use a Clipboard object and loop iterating through all TextFields to find the highlighted text (if any).
Is there a way to hardcode this key press action in Java? I looked into the KeyCombination class but it does not actually trigger the action described by the given key combination.
I think by "Since any or none of the various TextField objects could be selected at any given time" you are referring to which (if any) text field has the keyboard focus.
You can easily get this information from the scene: just do
Node focusOwner = scene.getFocusOwner();
if (focusOwner instanceof TextField) {
TextField textField = (TextField) focusOwner ;
String selectedText = textField.getSelectedText();
// ...
}
Note also that TextInputControl defines a copy() method that copies the selected text to the system clipboard. (Similarly, there's a paste() method too.) So you can leverage those to make the functionality easy.
Here's a SSCCE:
import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.control.TextInputControl;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class FocusMenuTest extends Application {
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
VBox textFields = new VBox(5, new TextField("One"), new TextField("Two"), new TextField("Three"));
MenuBar menuBar = new MenuBar();
Menu edit = new Menu("Edit");
MenuItem copy = new MenuItem("Copy");
copy.setOnAction(e -> {
Node focusOwner = menuBar.getScene().getFocusOwner();
if (focusOwner instanceof TextInputControl) {
((TextInputControl)focusOwner).copy();
}
});
MenuItem paste = new MenuItem("Paste");
paste.setOnAction(e -> {
Node focusOwner = menuBar.getScene().getFocusOwner();
if (focusOwner instanceof TextInputControl) {
((TextInputControl)focusOwner).paste();
}
});
menuBar.getMenus().add(edit);
edit.getItems().addAll(copy, paste);
root.setCenter(textFields);
root.setTop(menuBar);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I have a TextField and a ListView. As the user types in the TextField, suggestions come up in the ListView:
When the TextField is empty, the ListView disappears, by setting the visible and managed properties to false.
However, when the user starts to type, the ListView takes up space and pushes everything down. Using .setManaged(false) allows it not to take up any space, but it doesn't display anymore, as I haven't defined a position for it. I have tried setting the layoutX and layoutY of the search list, but it still doesn't display.
Ideally I'd like the ListView's position to be affected by the layout but not to take up any space.
Any ideas?
Wrap the container that holds the text field(s) in an AnchorPane. Add the ListView to the AnchorPane after the text field container (so it stays on top). Then you need to position the ListView appropriately relative to the text field when you make it visible; I think the best way to do this is to first convert the bounds of the text field from local coordinates to Scene coordinates, then convert those bounds to the coordinates relative to the AnchorPane.
Here's an SSCCE:
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class SuggestionList extends Application {
#Override
public void start(Stage primaryStage) {
AnchorPane root = new AnchorPane();
ListView<String> suggestionBox = new ListView<>();
suggestionBox.getItems().addAll("Here", "Are", "Some", "Suggestions");
suggestionBox.setMaxHeight(100);
suggestionBox.setVisible(false);
// Grid pane to hold a bunch of text fields:
GridPane form = new GridPane();
for (int i=0; i<10; i++) {
form.addRow(i, new Label("Enter Text:"), createTextField(suggestionBox));
}
// just move the grid pane a little to test suggestion box positioning:
AnchorPane.setLeftAnchor(form, 20.0);
AnchorPane.setRightAnchor(form, 20.0);
AnchorPane.setTopAnchor(form, 20.0);
AnchorPane.setBottomAnchor(form, 20.0);
// allows focus on grid pane, so user can click on it to remove focus from text field.
form.setFocusTraversable(true);
root.setPadding(new Insets(20));
root.getChildren().addAll(form, suggestionBox);
Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
private TextField createTextField(ListView<String> suggestionBox) {
TextField textField = new TextField();
ChangeListener<String> selectionListener = (obs, oldItem, newItem) -> {
if (newItem != null) {
textField.setText(newItem);
}
};
textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (isNowFocused) {
suggestionBox.setVisible(true);
// compute bounds of text field relative to suggestion box's parent:
Parent parent = suggestionBox.getParent(); // (actually the anchor pane)
Bounds tfBounds = textField.getBoundsInLocal();
Bounds tfBoundsInScene = textField.localToScene(tfBounds);
Bounds tfBoundsInParent = parent.sceneToLocal(tfBoundsInScene);
// position suggestion box:
suggestionBox.setLayoutX(tfBoundsInParent.getMinX());
suggestionBox.setLayoutY(tfBoundsInParent.getMaxY());
suggestionBox.setPrefWidth(tfBoundsInParent.getWidth());
suggestionBox.getSelectionModel().selectedItemProperty().addListener(selectionListener);
} else {
suggestionBox.setVisible(false);
suggestionBox.getSelectionModel().selectedItemProperty().removeListener(selectionListener);
}
});
textField.setOnAction(event -> {
suggestionBox.setVisible(false);
suggestionBox.getSelectionModel().selectedItemProperty().removeListener(selectionListener);
});
return textField ;
}
public static void main(String[] args) {
launch(args);
}
}
You might be able to use similar positional tricks and just add it to the same scene, with managed set to false.
Is there any way to add a changelistener to group of nodes for following changes?
For example, we can add a changelistener to a tabpane for getting tabselectedproperty.
I want to add changelistener a to a group of buttons for getting buttonActionedProperty! I want to get old button and new button....
Is there any way to do this?
When you compare the tabs in a tab pane to a collection of buttons, you're not really comparing like to like. A tab pane naturally has a sense of which tab is currently selected; buttons just generate events when they are pressed.
If you want your buttons to have a "selected" state, and want a collection of those grouped together so that only one is selected, then consider using ToggleButtons instead. You can put the toggle buttons into a ToggleGroup and register a listener with the toggle group's selectedToggle property:
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class ToggleButtonDemo extends Application {
#Override
public void start(Stage primaryStage) {
ToggleButton apples = new ToggleButton("Apples");
ToggleButton oranges = new ToggleButton("Oranges");
ToggleButton pears = new ToggleButton("Pears");
ToggleGroup fruitToggleGroup = new ToggleGroup();
fruitToggleGroup.getToggles().addAll(apples, oranges, pears);
fruitToggleGroup.selectedToggleProperty().addListener((obs, oldToggle, newToggle) ->
System.out.println("Selected toggle changed from "+oldToggle+" to "+newToggle));
HBox root = new HBox(5, apples, oranges, pears);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 350, 75);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If you really just want buttons, and don't have the notion of one of them being selected (I find it hard to see a use case for this), you can just create an ObjectProperty<Button> to store the last button on which an action occurred. Register an event listener with each button to update the property:
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class LastActionTrackingDemo extends Application {
#Override
public void start(Stage primaryStage) {
Button apples = new Button("Apples");
Button oranges = new Button("Oranges");
Button pears = new Button("Pears");
ObjectProperty<Button> lastActionedButton = new SimpleObjectProperty<>();
EventHandler<ActionEvent> buttonActionHandler = event ->
lastActionedButton.set((Button) event.getSource());
apples.addEventHandler(ActionEvent.ACTION, buttonActionHandler);
oranges.addEventHandler(ActionEvent.ACTION, buttonActionHandler);
pears.addEventHandler(ActionEvent.ACTION, buttonActionHandler);
lastActionedButton.addListener((obs, oldButton, newButton) ->
System.out.println("Button changed from "+oldButton+" to "+newButton));
HBox root = new HBox(5, apples, oranges, pears);
root.setAlignment(Pos.CENTER);
Scene scene = new Scene(root, 350, 75);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Note there is a subtle different between the appearance of the two demos. The first (with toggle buttons) has a visual representation of which button is selected. The second does not. In both cases you can still set action listeners on the buttons if you need that functionality. There is also a (less subtle) difference in behavior: the toggle buttons can be "unselected"; so if you press the same toggle button twice, the selection goes back to null. This doesn't happen with the buttons.