Alternative for removed impl_isTreeVisible() - javafx

We are reliant on Node.impl_isTreeVisible() because isVisible does not work properly (or at least the way we want it to).
/**
* #treatAsPrivate implementation detail
* #deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
#Deprecated
public final boolean impl_isTreeVisible() {
return impl_treeVisibleProperty().get();
}
We have a custom Node which contains a Plot. This gets continuous data. We want to avoid to update the plot if it is not visible (still managed/rendered, but hidden).
If the node is placed on a tab which is not selected, hence it is not visible in the window, then using isVisible still returns true. This causes the Node on the selected tab to be rendred every time the plot is updated.
This will evaluate to true even though the node is not visible in the application window.
if (isVisible()) {
updatePlot()
}
So we have been using the following which works as we want it.
if (impl_isTreeVisible()) {
updatePlot()
}
However this will no longer work in Java 9 as such methods are removed. Is there a new approach to this in Java 9?
Update:
Looking at Java 9 source code for javafx.scene.Node I have found the method isTreeVisible(), which looks like a replacement for impl_isTreeVisible. However looking at the Javadoc I cannot find this isTreeVisible().
http://download.java.net/java/jdk9/docs/api/javafx/scene/Node.html
Trying with an example using isTreeVisible() will not compile with Java 9
Java9AppTest.java:50: error: cannot find symbol
if (text1.isTreeVisible()) {
^
symbol: method isTreeVisible()
location: variable text1 of type Text
Update2: Failed to see at first that isTreeVisible() is package private.
Update3: Taken another look at Node source code, I started to check out NodeHelper if could use it to get isTreeVisible(), however the package NodeHelper is not visible. Though using --add-exports for com.sun.javafx.scene to get access to NodeHelper works.
--add-exports javafx.graphics/com.sun.javafx.scene=ALL-UNNAMED
Then I can read the state of isTreeVisible() of a Node.
final boolean isTreeVisible = NodeHelper.isTreeVisible(node);
Code Example
Contains two Tab, each with its own Text.
Has a Task that updates each Text.
Using isVisible() will update each text on both tabs.
Using impl_isTreeVisible() will only update the text that is truely visible.
It makes sense that Text should be updated, even if it is not visible. This is just to illustrate the problem. Replace Text with background process that does alot more CPU heavy work.
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class Java9AppTest extends Application {
private Text text1, text2;
public static void main(String[] args) {
Java9AppTest.launch(args);
}
public void start(Stage stage) throws Exception {
TabPane root = new TabPane();
VBox box1 = new VBox();
text1 = new Text();
text1.setText("Hello World!");
text1.textProperty().addListener((observable, oldValue, newValue) -> {
System.out.println("text1 changed from " + oldValue + " to " + newValue);
});
box1.getChildren().addAll(text1);
Tab tab1 = new Tab("Tab 1");
tab1.setContent(box1);
VBox box2 = new VBox();
text2 = new Text();
text2.setText("Another Hello World!");
text2.textProperty().addListener((observable, oldValue, newValue) -> {
System.out.println("text2 changed from " + oldValue + " to " + newValue);
});
box2.getChildren().add(text2);
Tab tab2 = new Tab("Tab 2");
tab2.setContent(box2);
root.getTabs().addAll(tab1, tab2);
Task<Void> task = new Task<Void>() {
/* (non-Javadoc)
* #see javafx.concurrent.Task#call()
*/
#Override
protected Void call() throws Exception {
final String oldText = "Hello World!";
final String newText = "New Hello World!";
while (true) {
if (text1.isVisible()) {
if (text1.getText().equals(oldText)) {
text1.setText(newText);
} else {
text1.setText(oldText);
}
}
if (text2.isVisible()) {
if (text2.getText().equals(oldText)) {
text2.setText(newText);
} else {
text2.setText(oldText);
}
}
Thread.sleep(2000);
}
}
};
stage.setScene(new Scene(root));
stage.setWidth(200);
stage.setHeight(200);
stage.setTitle("JavaFX 9 Application");
stage.show();
Thread thread = new Thread(task, "Task");
thread.start();
}
}

I suggest adding a property to your node, that controls if you want to update the plot. So instead of if (impl_isTreeVisible()) { just have if (shouldUpdate) {. Upon tab selection changes, just toggle the property. So in essence your TabPane would control if the plot is updated.
Alternatively you could pass the TabPane to your node and query the selected tab: tabPane.getSelectionModel().getSelectedIndex(). This, however means that your node must know on which tab it resides.

A Tab has a property selected, bind that property to an update property of your plot, which determines if you redraw your plot.
In your control (or its skin) add a listener to the update property of the plot, where you pause or resume listening to your input source, or pause or resume the timer that gets the data.
This solution does not add additional dependencies to the object graph, the type of container it should be in and enables you to create more complex bindings if necessary (like a pause button), and eases testing as this property is controllable in a standalone manner.
Depending on the data source implementation this solution can also pause your data source if it determines that there are no listeners processing your data actively.

Related

JavaFX: ComboBox with custom cell factory - buggy rendering

I have a ComboBox with a custom cell factory, but the rendering is buggy and I don't know what I'm doing wrong.
The ComboBox's data is based on an enum, let's call it MyEnum. So my ComboBox is defined as:
#FXML
private ComboBox<MyEnum> comboBox;
I am setting it up like this:
comboBox.getItems().addAll(MyEnum.values());
comboBox.setButtonCell(new CustomRenderer());
comboBox.setCellFactory(cell -> new CustomRenderer());
comboBox.getSelectionModel().select(0);
My CustomRenderer looks like this:
public class CustomRenderer extends ListCell<MyEnum> {
#Override
protected void updateItem(MyEnum enumValue, boolean empty) {
super.updateItem(enumValue, empty);
if (enumValue== null || empty) {
setGraphic(null);
setText(null);
} else {
setGraphic(enumValue.getGraphic());
}
}
}
The getGraphic() method of MyEnum returns a HBox which can contain any number of elements, but in my specific case it's just an ImageView:
public enum MyEnum{
ONE(new ImageView(new Image(MyApp.class.getResourceAsStream("image1.png"),
15,
15,
true,
true))),
TWO(new ImageView(new Image(MyApp.class.getResourceAsStream("image2.png"),
15,
15,
true,
true)));
private final HBox graphic;
MyEnum(Node... graphicContents) {
graphic = new HBox(graphicContents);
graphic.setSpacing(5);
}
public HBox getGraphic() {
return graphic;
}
}
Now, when I start the app, the first bug is that the ComboBox doesn't show anything selected, even though I have the comboBox.getSelectionModel().select(0) in the initialization:
When I click on it, the dropdown is correct and shows my two entries with their images:
When I select one of the entries, everything still seems fine:
But when I open the dropdown again, then it looks like this:
So suddenly the selected image is gone from the dropdown.
After I select the other entry where the icon is still displayed, and reopen the dropdown, then both images are gone. The images are still shown in the ButtonCell though, just not in the dropdown.
I first thought maybe it has something to do specifically with ImageViews, but when I replaced them with other nodes, like Labels, it was still the same 2 bugs:
Nothing shown as selected on app start
Everything that I click in the dropdown box is then gone from the dropdown
If a runnable sample is needed, let me know. But maybe someone can already spot my mistake from the given code.
Thx
Your issue is that a node cannot appear in the scene graph more than once.
From the node documentation:
A node may occur at most once anywhere in the scene graph. Specifically, a node must appear no more than once in all of the following: as the root node of a Scene, the children ObservableList of a Parent, or as the clip of a Node.
If a program adds a child node to a Parent (including Group, Region, etc) and that node is already a child of a different Parent or the root of a Scene, the node is automatically (and silently) removed from its former parent.
You are trying to reuse the same node in both the button for the list selection and in the selection list itself, which is not allowed.
Additionally, as noted in comments by kleopatra: "it's wrong to use nodes as data". One reason for that (among others), is that if you want to have more than one view of the same data visible at the same time, you won't be able to because the node related to the data can only be attached to the scene at a single place at any given time.
I'd especially recommend not placing nodes in enums. In my opinion, that it is really not what enums are for.
What you need to do is separate the model from the view. The cell factory is creating a view of the changing value of the enum which is the model. This view only needs to be created once for the cell and it needs to be updated (by providing the view with the appropriate image) whenever the cell value changes.
Example Code
You can see in the example that, because we have created two separate views of the same Monster (the dragon), the example is able to render both views at the same time. This is because the views are completely different nodes, that provide a view to the same underlying data (an enum value and its associated image).
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class ChooseYourDoomApp extends Application {
public static final String CSS = "data:text/css," + // language=CSS
"""
.root {
-fx-background-color: lightblue;
-fx-base: palegreen;
}
""";
#Override
public void start(Stage stage) throws Exception {
ComboBox<Monster> choiceOfDoom = new ComboBox<>(
FXCollections.observableArrayList(
Monster.values()
)
);
choiceOfDoom.setButtonCell(new MonsterCell());
choiceOfDoom.setCellFactory(listView -> new MonsterCell());
choiceOfDoom.getSelectionModel().select(Monster.Dragon);
StackPane layout = new StackPane(choiceOfDoom);
layout.setPadding(new Insets(20));
Scene scene = new Scene(layout);
scene.getStylesheets().add(CSS);
stage.setScene(scene);
stage.show();
}
public class MonsterCell extends ListCell<Monster> {
private ImageView imageView = new ImageView();
#Override
protected void updateItem(Monster item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
imageView.setImage(null);
setGraphic(null);
} else {
imageView.setImage(monsterImages.get(item));
setGraphic(imageView);
}
}
}
public enum Monster {
Medusa,
Dragon,
Treant,
Unicorn
}
private Map<Monster, Image> monsterImages = createMonsterImages();
private Map<Monster, Image> createMonsterImages() {
Map<Monster, Image> monsterImages = new HashMap<>();
for (Monster monster : Monster.values()) {
monsterImages.put(
monster,
new Image(
Objects.requireNonNull(
ChooseYourDoomApp.class.getResource(
monster + "-icon.png"
)
).toExternalForm()
)
);
}
return monsterImages;
}
public static void main(String[] args) {
launch(args);
}
}
Icons are placed in the resource directory under the same hierarchy as the package containing the main application code.
https://iconarchive.com/show/role-playing-icons-by-chanut.html
Dragon-icon.png
Medusa-icon.png
Treant-icon.png
Unicorn-icon.png

JavaFX: How can I detect end of rendering in TitledPane?

This is the background for my question:
I have a GUI with an accordion with many TitledPanes, and each Titledpane contains a spreadsheetView from the controlsFX package.
There is a search-function in the code, where a Titledpane is opened and a specific cell in the spreadsheetView is opened for text input using the edit method of the spreadsheetcell type.
If the TitledPane is already open, this works fine, but if it must open first then the call of the edit-method fails. (The program is actually written in scalafx, but I don't think that matters here because scalafx is just a wrapper of javaFX and calls all the javaFX methods.)
Someone from the scalafx user group found out, that when I put in a wait time of 350ms (The animation time of the TitledPane is 300ms) then the call of 'edit' on the cell succeeds. He thought that the call fails, when the rendering of the content of the TitledPane is not complete.
This is also true when I turn the animation for the TitledPane off. In this case, it is sufficient to wait for 50ms, which does not work when animation is on.
Anyway - I am concerned about just waiting 350ms and hoping that this will always work. Which brings me back to the question: How can I tell that the rendering inside the TitledPane (or the spreadsheetView?) is complete so that I can safely call my edit method on the spreadsheetView?
Astonishingly, that doesn't seem to be supported.
The property that changes during the expand/collapse phase is the content's height: so a hack around might be to listen to it and start editing when fully expanded (which is a bit hacky in itself, could change due to layout constraints as well).
The example below simply initializes the fully expanded height after showing, listens to content's height property and starts editing when it reaches the fully expanded height.
The code:
public class TitledPaneEndOfExpansion extends Application {
private DoubleProperty expandedHeight = new SimpleDoubleProperty();
private TitledPane titled = new TitledPane();
private Parent createContent() {
titled.setText("Titled");
ListView<String> list = new ListView<>(FXCollections.observableArrayList("some", "content"));
list.setEditable(true);
list.setCellFactory(TextFieldListCell.forListView());
titled.setContent(list);
list.heightProperty().addListener((src, ov, nv) -> {
if (nv.doubleValue() == expandedHeight.get()) {
list.edit(0);
}
});
BorderPane content = new BorderPane(titled);
return content;
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setTitle(FXUtils.version());
stage.show();
expandedHeight.set(((Region) titled.getContent()).getHeight());
}
public static void main(String[] args) {
launch(args);
}
}
Basically I like kleopatras idea, but unfortunately I can't figure out if this works for me or not.
At first I had some problems reading the code - only because my java knowledge is very limited. So I transferred it to scala. When I run it there, the call to edit works only sometimes (after startup it does not, when i clicked into a cell to edit it does). So I added a button that also calls edit - and it had the same behavior. So calling edit in general seems to have a problem in scalafx. But I learned something interesting here. I will now wait a few more days to see if anyone can think of anything else. If not then I will accept kleopatras solution.
For my own reference I add my not working scala-code here:
import scalafx.Includes._
import scalafx.application.JFXApp
import scalafx.beans.property.DoubleProperty
import scalafx.beans.value.ObservableValue
import scalafx.collections.ObservableBuffer
import scalafx.event.ActionEvent
import scalafx.scene.Scene
import scalafx.scene.control.cell.TextFieldListCell
import scalafx.scene.control.{Button, ListView, TitledPane}
import scalafx.scene.layout.BorderPane
object TitledPaneEndOfExpansion extends JFXApp {
val expandedHeight = new DoubleProperty()
val data: ObservableBuffer[String] = new ObservableBuffer[String]() ++= List("some", "content", "for", "testing")
stage = new JFXApp.PrimaryStage {
title = "JavaFX: edit after rendering test"
val list: ListView[String] = new ListView[String](data) {
editable = true
cellFactory = TextFieldListCell.forListView()
height.onChange { (source: ObservableValue[Double, Number], oldValue: Number, newValue: Number) =>
println("old height is: " + oldValue.doubleValue() + " new height is: " + newValue.doubleValue())
if (newValue.doubleValue() == expandedHeight.value) {
edit(1)
}
}
}
val titled: TitledPane = new TitledPane {
text = "titled"
content = list
}
scene = new Scene {
root = new BorderPane {
center = titled
bottom = new Button() {
text = "edit cell 1"
onAction = { _: ActionEvent => list.edit(1) }
}
}
}
expandedHeight.value = 400
list.edit(1)
}
}

First option in the context menu is highlighted without hovering the mouse from javafx 9

when we right click for context menu, the first option in the list is being highlighted without hovering the mouse. This happens only for the first time right click after the application is opened. This behavior is observed from javafx-9. Till javafx-8 its working fine.
Tried with the sample code:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.TilePane;
import javafx.stage.Stage;
public class SampleContextMenu extends Application {
// labels
Label l;
public static void main(String args[]) {
// launch the application
launch(args);
}
// launch the application
public void start(Stage stage) {
// set title for the stage
stage.setTitle("creating contextMenu ");
// create a label
Label label1 = new Label("This is a ContextMenu example ");
// create a menu
ContextMenu contextMenu = new ContextMenu();
// create menuitems
MenuItem menuItem1 = new MenuItem("menu item 1");
MenuItem menuItem2 = new MenuItem("menu item 2");
MenuItem menuItem3 = new MenuItem("menu item 3");
// add menu items to menu
contextMenu.getItems().add(menuItem1);
contextMenu.getItems().add(menuItem2);
contextMenu.getItems().add(menuItem3);
// create a tilepane
TilePane tilePane = new TilePane(label1);
// setContextMenu to label
label1.setContextMenu(contextMenu);
// create a scene
Scene sc = new Scene(tilePane, 200, 200);
// set the scene
stage.setScene(sc);
stage.show();
}
}
After a bit of digging, turns out that the culprit (so to say) is the default focus traversal on initially showing of a scene - which is to focus the first focusable node, which in the case of a contextMenu is the first item.
First try of a hack-around: request the focus back onto the scene's root when the item is focused. The steps:
register a onShown handler on the contextMenu
in the handler, grab the scene that contains the contextMenu: at this time, its focusOwner is still null, so we need to register a changeListener on its focusOwner property
in the listener, on first change of the focusOwner (the old value is null), request focus on the root and cleanup the listeners
Beware: this is not good enough, turned out to be a cosmetic hack only, there are several glitches as noted in the comments
requesting the focus to the scene root disables keyboard navigation
the first item is still active: pressing enter activates its action
Next try (now going really dirty, requiring access to hidden implementation details of non-public classes!): replace the last step of the first try by
grab the containing ContextMenuContent (internal class in com.sun.xx), it's the grandparent of the focused item
request focus on that content to make the highlight disappear
update that content to be aware of no-item-focused (reflective access to private field)
In code:
contextMenu.setOnShown(e -> {
Scene scene = contextMenu.getScene();
scene.focusOwnerProperty().addListener((src, ov, nv) -> {
// focusOwner set after first showing
if (ov == null) {
// transfer focus to root
// old hack (see the beware section) on why it doesn't work
// scene.getRoot().requestFocus();
// next try:
// grab the containing ContextMenuContainer and force the internal
// book-keeping into no-item-focused state
Parent parent = nv.getParent().getParent();
parent.requestFocus();
// reflective setting of private field, this is my utility method, use your own ;)
invokeSetFieldValue(ContextMenuContent.class, parent, "currentFocusedIndex", -1);
// cleanup
contextMenu.setOnShown(null);
}
});
});
For convenience, here's the utility method for reflective access of internal fields (no rocket sciene, just plain java ;)
public static void invokeSetFieldValue(Class<?> declaringClass, Object target, String name, Object value) {
try {
Field field = declaringClass.getDeclaredField(name);
field.setAccessible(true);
field.set(target, value);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
}
}

How to control specific text selection in a TextArea (JavaFX)?

I'm using JavaFX and have a TextArea. In the textArea I need some way to ensure the user may only select one line at a time. So for my delete button what I have so far is:
deleteButton.setOnAction(e -> {
String s = "DELETE: ";
s += receipt.getSelectedText();
receipt.replaceSelection(s);
});
How can I enforce that user can only select one full line at a time? Each line will have \n as a breaker, so I was thinking I can some how use it as a key. An issue is the users ability to select more than one line at a time or a partial line. And yes, I must use textArea. I have right now where the delete button is reading what got deleted and displays it. The readt of my code is working great with this one issue. I have about 15 classes that all take in textAreas as a parameter where, when a button is clicked, it appends it to the TextArea and then it saves it to a specified object as a certain attribute. I just need highlight control, or a way to added a checkbox or a way to read where the user clicks that highlights the entire row (but if the click somewhere else, it highlights/selects that line, or try to highlight themselves, it doesn't let them).
You can use the filter in a TextFormatter to intercept and modify changes to the selection.
Here is a quick example. You could also modify the change to actually effect the functionality you are looking for on delete as well.
import java.util.function.UnaryOperator;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextFormatter.Change;
import javafx.stage.Stage;
public class TextAreaSelectByLine extends Application {
#Override
public void start(Stage primaryStage) {
TextArea textArea = new TextArea();
UnaryOperator<Change> filter = c -> {
int caret = c.getCaretPosition();
int anchor = c.getAnchor() ;
if (caret != anchor) {
int start = caret ;
int end = caret ;
String text = c.getControlNewText();
while (start > 0 && text.charAt(start) != '\n') start-- ;
while (end < text.length() && text.charAt(end) != '\n') end++ ;
c.setAnchor(start);
c.setCaretPosition(end);
}
return c ;
};
TextFormatter<String> formatter = new TextFormatter<>(filter);
textArea.setTextFormatter(formatter);
Scene scene = new Scene(textArea, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Using JavaFX 2.2 Mnemonic (and accelerators)

I'm trying to make JavaFX Mnemonic work. I have some button on scene and what I want to achieve is to fire this button event by pressing Ctrl+S.
Here is a code sceleton:
#FXML
public Button btnFirst;
btnFirst.getScene().addMnemonic(new Mnemonic(btnFirst,
new KeyCodeCombination(KeyCode.S, KeyCombination.CONTROL_DOWN)));
Button's mnemonicParsing is false. (Well, while trying to make this work I've tried to set it to true, but no result). JavaFX documentation states that when a Mnemonic is registered on a Scene, and the KeyCombination reaches the Scene unconsumed, then the target Node will be sent an ActionEvent. But this doesn't work, probably, I'm doing wrong...
I can use the standard button's mnemonic (by setting mnemonicParsing to true and prefix 'F' letter by underscore character). But this way user have to use Alt key, that brings some strange behaviour on browsers with menu bar (if application is embedded into web page than browser's menu activated after firing button event by pressing Alt+S).
Besides, standard way makes it impossible to make shortcuts like Ctrl+Shift+F3 and so on.
So, if there some way to make this work?
For your use case, I think you actually want to use an accelerator rather than a mnemonic.
button.getScene().getAccelerators().put(
new KeyCodeCombination(KeyCode.S, KeyCombination.SHORTCUT_DOWN),
new Runnable() {
#Override public void run() {
button.fire();
}
}
);
In most cases it is recommended that you use KeyCombination.SHORTCUT_DOWN as the modifier specifier, as in the code above. A good explanation of this is in the KeyCombination documentation:
The shortcut modifier is used to represent the modifier key which is
used commonly in keyboard shortcuts on the host platform. This is for
example control on Windows and meta (command key) on Mac. By using
shortcut key modifier developers can create platform independent
shortcuts. So the "Shortcut+C" key combination is handled internally
as "Ctrl+C" on Windows and "Meta+C" on Mac.
If you wanted to specifically code to only handle a Ctrl+S key combination, they you could use:
new KeyCodeCombination(KeyCode.S, KeyCombination.CONTROL_DOWN)
Here is an executable example:
import javafx.animation.*;
import javafx.application.Application;
import javafx.event.*;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.input.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class SaveMe extends Application {
#Override public void start(final Stage stage) throws Exception {
final Label response = new Label();
final ImageView imageView = new ImageView(
new Image("http://icons.iconarchive.com/icons/gianni-polito/colobrush/128/software-emule-icon.png")
);
final Button button = new Button("Save Me", imageView);
button.setStyle("-fx-base: burlywood;");
button.setContentDisplay(ContentDisplay.TOP);
displayFlashMessageOnAction(button, response, "You have been saved!");
layoutScene(button, response, stage);
stage.show();
setSaveAccelerator(button);
}
// sets the save accelerator for a button to the Ctrl+S key combination.
private void setSaveAccelerator(final Button button) {
Scene scene = button.getScene();
if (scene == null) {
throw new IllegalArgumentException("setSaveAccelerator must be called when a button is attached to a scene");
}
scene.getAccelerators().put(
new KeyCodeCombination(KeyCode.S, KeyCombination.SHORTCUT_DOWN),
new Runnable() {
#Override public void run() {
fireButton(button);
}
}
);
}
// fires a button from code, providing visual feedback that the button is firing.
private void fireButton(final Button button) {
button.arm();
PauseTransition pt = new PauseTransition(Duration.millis(300));
pt.setOnFinished(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent event) {
button.fire();
button.disarm();
}
});
pt.play();
}
// displays a temporary message in a label when a button is pressed,
// and gradually fades the label away after the message has been displayed.
private void displayFlashMessageOnAction(final Button button, final Label label, final String message) {
final FadeTransition ft = new FadeTransition(Duration.seconds(3), label);
ft.setInterpolator(Interpolator.EASE_BOTH);
ft.setFromValue(1);
ft.setToValue(0);
button.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent event) {
label.setText(message);
label.setStyle("-fx-text-fill: forestgreen;");
ft.playFromStart();
}
});
}
private void layoutScene(final Button button, final Label response, final Stage stage) {
final VBox layout = new VBox(10);
layout.setPrefWidth(300);
layout.setAlignment(Pos.CENTER);
layout.getChildren().addAll(button, response);
layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 20; -fx-font-size: 20;");
stage.setScene(new Scene(layout));
}
public static void main(String[] args) { launch(args); }
}
// icon license: (creative commons with attribution) http://creativecommons.org/licenses/by-nc-nd/3.0/
// icon artist attribution page: (eponas-deeway) http://eponas-deeway.deviantart.com/gallery/#/d1s7uih
Sample output:
Update Jan 2020, using the same accelerator for multiple controls
One caveat for accelerators in current and previous implementations (JavaFX 13 and prior), is that you cannot, out of the box, define the same accelerator key combination for use on multiple menus or controls within a single application.
For more information see:
JavaFX ContextMenu accelerator firing from wrong tab
and the related JDK-8088068 issue report.
The linked issue report includes a work-around you can use to allow you define and use the same accelerator within multiple places within an application (for example on two different menu items in different context menus).
Note that this only applies to trying to use the same accelerator in multiple places within an application, if you don't need try to do that, then you can ignore this information.

Resources