When I select element from combobox, it must be remove from list of elements. And selected element mustn't be shown it must be deleted immediately.
When I select element from combobox, it must be remove from list of elements. And selected element mustn't be shown it must be deleted immediately.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.TilePane;
import javafx.stage.Stage;
public class Test extends Application {
// Launch the application
public void start(Stage stage)
{
// Set title for the stage
stage.setTitle("creating combo box ");
// Create a tile pane
TilePane r = new TilePane();
// Create a label
Label description_label =
new Label("This is a combo box example ");
// Weekdays
String week_days[] =
{ "Monday", "Tuesday", "Wednesday",
"Thrusday", "Friday" };
// Create a combo box
ComboBox combo_box =
new ComboBox(FXCollections
.observableArrayList(week_days));
// Label to display the selected menuitem
Label selected = new Label("default item selected");
// Create action event
EventHandler<ActionEvent> event =
new EventHandler<ActionEvent>() {
public void handle(ActionEvent e)
{
selected.setText(combo_box.getValue() + " selected");
}
};
// Set on action
combo_box.setOnAction(event);
// Create a tile pane
TilePane tile_pane = new TilePane(combo_box, selected);
// Create a scene
Scene scene = new Scene(tile_pane, 200, 200);
// Set the scene
stage.setScene(scene);
stage.show();
}
public static void main(String args[])
{
// Launch the application
launch(args);
}
}
Output:
Since onAction is invoked before ComboBox has properly dealt with updating itself, you need to delay the removal using Platform.runLater:
EventHandler<ActionEvent> event = new EventHandler<ActionEvent>() {
public void handle(ActionEvent e) {
String value = combo_box.getValue();
if (value != null) {
selected.setText(value + " selected");
Platform.runLater(() -> {
combo_box.setValue(null);
combo_box.getItems().remove(value);
});
}
}
};
Related
My setup
I have added an EventFilter to an Hbox node which also contains two VBoxes.
In one of the VBoxes I have a Button, a Label, and a TextArea class instance.
The EventHandler is:
public static EventHandler<MouseEvent> pageEventFilter = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
EventTarget target = e.getTarget();
String name = target.getClass().getName();
System.out.println("NAME: " + name);
}
};
In the event filter, I want to get the id string for buttons or labels that the user clicks on.
I cannot determine the button or label clicked
When I click on a part of the button or label, I get the following class names reported:
click on button text ->
com.sun.javafx.scene.control.LabeledText
click outside button text ->
javafx.scene.control.Button or javafx.scene.control.Label
Trying to cast the event target of LabeledText to a Button or a Label gives me a ClassCastException.
If I try to create a variable:
LabeledText lbt = target
I get a compilation error that the LabeledText class does not exist.
Similarly for TextArea
When I click on the TextArea, I get javafx.scene.control.skin.TextAreaSkin$ContentView as the name of the target class. I am unable to click anywhere to get TextArea reported as the event target class. When I click the text in the TextArea, I get javafx.scene.text.Text, I cannot get the id of the TextArea node from either of these.
My Question
How do I find the parent of the target node, for the types of nodes I am interested in (buttons, labels and perhaps some other types), for which I can then get the node ID string using:
String nodeId = targetParentNode.getId();
In the screenshot, the user has clicked on a descendent node of a TextArea which is of type ContentView.
On intercepting the click event in a filter, the application has walked up the scene graph branch via repeated getParent() calls until it has found a higher level node of one of the specified types. In this case it has found a TextArea. It has then queried that text area for its "id" value and reported it in the appropriate label.
IdReporterApp.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class IdReporterApp extends Application {
private static final String CSS =
"""
data:text/css,
.label {
-fx-padding: 3px;
-fx-background-color: lightblue;
}
VBox {
-fx-spacing: 10px;
-fx-padding: 10px;
-fx-background-color: lemonchiffon;
}
HBox {
-fx-padding: 10px;
-fx-background-color: palegreen;
}
""";
#Override
public void start(Stage stage) {
Scene scene = new Scene(new IdController().getUI());
scene.getStylesheets().add(CSS);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
IdController.java
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import java.util.List;
class IdController {
private final Label clickedTargetTypeLabel = new Label();
private final Label reportedTargetTypeLabel = new Label();
private final Label reportedTargetIdLabel = new Label();
private final EventTargetFinder eventTargetFinder = new EventTargetFinder(
List.of(
Label.class, Button.class, TextArea.class
)
);
public Parent getUI() {
final VBox layout = new VBox(
new VBox(
id("box1", new Label("Box 1")),
id("button1", new Button("Button"))
),
new VBox(
id("box2", new Label("Box 2")),
id("textArea1", new TextArea("Text Area"))
),
new HBox(
id("clickTargetLabel", new Label("Clicked target Type")),
id( "clickTargetValueLabel", clickedTargetTypeLabel)
),
new HBox(
id("reportedTargetLabel", new Label("Reported target Type")),
id( "reportedTargetTypeLabel", reportedTargetTypeLabel)
),
new HBox(
id("reportedTargetIdLabel", new Label("Reported target ID")),
id("reportedTargetIdValueLabel", reportedTargetIdLabel)
)
);
layout.addEventFilter(
MouseEvent.MOUSE_CLICKED,
this::updateTargetLabels
);
return layout;
}
private Node id(String id, Node node) {
node.setId(id);
return node;
}
private void updateTargetLabels(MouseEvent e) {
EventTargetFinder.TargetSearchResult searchResult =
eventTargetFinder.findTargetsForMouseEvent(e);
clickedTargetTypeLabel.setText(
searchResult
.clickedTarget()
.getClass()
.getSimpleName()
);
reportedTargetTypeLabel.setText(
searchResult.reportedTarget() == null
? "null"
: searchResult
.reportedTarget()
.getClass()
.getSimpleName()
);
reportedTargetIdLabel.setText(
searchResult.reportedTarget() == null
? "none"
: searchResult.reportedTarget().getId() == null
? "null"
: searchResult.reportedTarget().getId()
);
}
}
EventTargetFinder.java
import javafx.scene.Node;
import javafx.scene.input.MouseEvent;
import java.util.Collections;
import java.util.List;
record EventTargetFinder(List<Class<? extends Node>> filterTargetTypes) {
EventTargetFinder(List<Class<? extends Node>> filterTargetTypes) {
this.filterTargetTypes = Collections.unmodifiableList(filterTargetTypes);
}
record TargetSearchResult(
Node clickedTarget,
Node reportedTarget
) {}
public TargetSearchResult findTargetsForMouseEvent(MouseEvent event) {
Node target = (Node) event.getTarget();
Node clickedTarget = target;
while (target != null && !isTargetFiltered(target)) {
target = target.getParent();
}
Node reportedTarget = target;
return new TargetSearchResult(clickedTarget, reportedTarget);
}
private boolean isTargetFiltered(Node target) {
return filterTargetTypes.stream()
.anyMatch(
t -> t.isInstance(target)
);
}
}
So, using JAVAfx, which I painfully downloaded, I need to be able to move this ball, but not let it leave the bounds. Right now, I have it all set up, it just leaves the area. What am i doing wrong? Any tutors don't know anything about JavaFX, so I'm having trouble getting past this. Right now I tried to work it out, but keep getting this error Cannot Make a Static Reference to Bound
'''
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
import javafx.geometry.Bounds;
public class MoveBall extends Application {
// create buttons
Button left = new Button();
Button right = new Button();
Button up = new Button();
Button down = new Button();
Circle ball = new Circle(30, 30, 30);
// action event class
EventHandler<ActionEvent> event = new EventHandler<ActionEvent>() {
// handle them
#Override
public void handle(ActionEvent event) {
// movement of ball
if (event.getSource().equals(left) && ball.getLayoutX() >= (Bounds.getMaxX() + ball.getRadius())) {
ball.setCenterX(ball.getCenterX() - 5);
}
if (event.getSource().equals(right)) {
ball.setCenterX(ball.getCenterX() + 5);
}
if (event.getSource().equals(up)) {
ball.setCenterY(ball.getCenterY() - 5);
}
if (event.getSource().equals(down) && ball.getCenterY() < 400)) {
ball.setCenterY(ball.getCenterY() + 5);
}
}
};
// main method
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Ball Movement");
ball.setStroke(Color.BLACK);
ball.setFill(null);
// button location
left.setLayoutX(100);
left.setLayoutY(210);
left.setText("Left");
// button event
left.setOnAction(event);
// similar for all button
right.setLayoutX(150);
right.setLayoutY(210);
right.setText("Right");
right.setOnAction(event);
// button location
up.setLayoutX(200);
up.setLayoutY(210);
up.setText("Up");
// button event
up.setOnAction(event);
// similar for all button
down.setLayoutX(250);
down.setLayoutY(210);
down.setText("Down");
down.setOnAction(event);
Group root = new Group();
// add to a group
root.getChildren().add(left);
root.getChildren().add(right);
root.getChildren().add(up);
root.getChildren().add(down);
root.getChildren().add(ball);
primaryStage.setScene(new Scene(root, 400, 250, Color.WHITE));
// show the scene
primaryStage.show();
}
}
'''
I'm new with Java, I would like to know how to prevent MenuButton popup from closing when I click on the item, I tried the following function, which I found on this site, but does not seem to do anything. I need this in order to make a clone of my C# application whit the purpose of learning Java.
#FXML
private MenuButton menuButton;
#FXML
void initialize() {
CheckMenuItem menuButtonItem1 = new CheckMenuItem("Item 1");
CheckMenuItem menuButtonItem2 = new CheckMenuItem("Item 2");
CheckMenuItem menuButtonItem3 = new CheckMenuItem("Item 3");
menuButtonItem1.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent e) {
e.consume();
}
});
menuButtonItem2.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent e) {
e.consume();
}
});
menuButtonItem3.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent e) {
e.consume();
}
});
}
The CustomMenuItem class provides a setHideOnClick() method that will handle this. In order to use it, you'll wrap a standard Node in this CustomMenuItem:
CheckBox checkBox = new CheckBox("Item 1");
CustomMenuItem customMenuItem = new CustomMenuItem(checkBox);
customMenuItem.setHideOnClick(false);
Below is a full example for you to try:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.MenuButton;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class CheckMenuKeepOpen extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Simple Interface
VBox root = new VBox(10);
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(10));
// Create the MenuButton
MenuButton menuButton = new MenuButton("Click Me");
for (int i = 0; i < 5; i++) {
// In order to keep the menu open when selecting a CheckBox, we need to wrap it in a CustomMenuItem
CustomMenuItem menuItem = new CustomMenuItem(new CheckBox("Item #" + i));
// This method is pretty obvious; it keeps the menu open when selecting this item.
menuItem.setHideOnClick(false);
menuButton.getItems().add(menuItem);
}
root.getChildren().add(menuButton);
// Show the stage
primaryStage.setScene(new Scene(root));
primaryStage.setTitle("Sample");
primaryStage.show();
}
}
We found using custom menu item would lose the styling of normal menu items. Instead we solved by using a Menu (i.e. a sub menu) with no items in which other than arrows to the right looks identical to a normal menu item but does not close when clicked. Then finally used css to remove the arrow to the right.
I have a javafx application with multiple textboxes that the user can enter information in. I also have a keyboard built into the application that when pressed adds that text to the textbox.
My issue is that since I have multiple textboxes, I don't know which one to add the buttons text to. Is there a way in javafx to check if a user has clicked on a certain textbox so I can check which one has been selected and add the text there?
You can use the Scene.focusOwner property of the active scene to get the focused node. Check, if it's a TextInputControl and call the appropriate method for the button clicked. Note that clicking a button may move the focus, if focusTraversable is true for that button. (By default this is the case.)
#Override
public void start(Stage primaryStage) {
GridPane grid = new GridPane();
final Scene scene = new Scene(grid);
for (int i = 0; i < 4; i++) {
grid.add(new TextField(), 0, i);
final String buttonValue = Character.toString((char) ('a'+i));
Button button = new Button(buttonValue);
button.setFocusTraversable(false); // prevent buttons from stealing focus
button.setOnAction(evt -> {
Node fo = scene.getFocusOwner();
if (fo instanceof TextInputControl) {
((TextInputControl) fo).replaceSelection(buttonValue);
}
});
grid.add(button, 1, i);
}
primaryStage.setScene(scene);
primaryStage.show();
}
You should create a listener for each TextField's focusProperty and set an instance variable.
Once you have a global reference to the currently focused TextField, you can do any processing on it that you choose.
Here is a quick application to demonstrate. I've included a couple extra details in the code itself:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
// Instance variable to hold the currently-selected TextField
private TextField selectedTextField;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
// Create TextFields
TextField txt1 = new TextField();
TextField txt2 = new TextField();
TextField txt3 = new TextField();
TextField txt4 = new TextField();
// This method sets the same change listener on each textfield
installListener(txt1, txt2, txt3, txt4);
VBox pane = new VBox(5);
pane.setPadding(new Insets(5));
// Add the TextFields to the layout
pane.getChildren().addAll(
new HBox(5, new Label("Txt1: "), txt1),
new HBox(5, new Label("Txt2: "), txt2),
new HBox(5, new Label("Txt3: "), txt3),
new HBox(5, new Label("Txt4: "), txt4)
);
primaryStage.setScene(new Scene(pane));
primaryStage.show();
}
// Accepts multiple TextFields
private void installListener(TextField... textFields) {
// Install the same listener on all of them
for (TextField textField : textFields) {
textField.focusedProperty().addListener((observableValue, oldValue, newValue) -> {
// Set the selectedTextField to null whenever focus is lost. This accounts for the
// TextField losing focus to another control that is NOT a TextField
selectedTextField = null;
if (newValue) {
// The new textfield is focused, so set the global reference
selectedTextField = textField;
System.out.println("Selected Text: " + selectedTextField.getText());
}
});
}
}
}
Context :
Hi !
I'm trying to create a little popup which display the value of slice when mouse hover, on my PieChart (with JavaFX).
I successed on my LineChart, AreaChart etc.. Thanks this post : JavaFX LineChart Hover Values (thank you so much Jewelsea for your help).
Problem (1/2) :
But with the PieChart, I have a problem : The popup is blinking oO
My code :
With syntactic color : https://bpaste.net/show/12838ad6b2e2
import java.util.ArrayList;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.event.EventHandler;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.chart.PieChart;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import com.alpha.client.view.nodes.stats.statsEngine.beans.ListRepere;
import com.alpha.client.view.nodes.stats.statsEngine.beans.OptionsChart;
import com.alpha.client.view.nodes.stats.statsEngine.beans.ValueStat;
/**
*
* #author Zombkey
*/
public class PieChartNode implements ChartNode {
//My personnal attributes
private ListRepere categories;
private ArrayList<ValueStat> values;
//The PieChart
private PieChart chart;
//The data of Chart, will be fill by a thread
private ObservableList<PieChart.Data> pieChartData;
//The node which contain chart and label
private Group group;
//The Label
private final Label caption;
public PieChartNode(ListRepere categories, ArrayList<ValueStat> values, OptionsChart optionsChart) {
this.categories = categories;
this.values = values;
//New Group
group = new Group();
//I must use a StackPane to place Label hover Chart
StackPane pane = new StackPane();
group.getChildren().add(pane);
//Init' PieChart
pieChartData = FXCollections.observableArrayList();
chart = new PieChart(pieChartData);
chart.setStartAngle(180.0);
//Add chart to StackPane
pane.getChildren().add(chart);
//Init Popup(Label)
caption = new Label("");
caption.setVisible(false);
caption.getStyleClass().addAll("chart-line-symbol", "chart-series-line");
caption.setStyle("-fx-font-size: 12; -fx-font-weight: bold;");
caption.setMinSize(Label.USE_PREF_SIZE, Label.USE_PREF_SIZE);
//Add Label to StackPane
pane.getChildren().add(caption);
}
#Override
public Node getNodeGraph() {
return (Node) group;
}
#Override
public Task initTaskFormat() {
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
//i and sizeOfallElements are just use for ProgressBar
int i = 0;
int sizeOfallElements = values.size();
updateProgress(i, sizeOfallElements);
//For Each ValueStat (a Personnal pojo Class), I must create a slice
for (ValueStat v : values) {
//Create the PieChart.Data and add it to ObservableList
PieChart.Data dataTemp = new PieChart.Data(v.getCategorie().getStringName(), v.getDoubleValue());
pieChartData.add(dataTemp);
//HERE, the interessante code !
//At the same way that the LineChart, I add Event when mouse entered and mouse exited.
//When mouse entered (on the slice of PieChart)
dataTemp.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED,
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
System.out.println("MOUSE_ENTERED : "+dataTemp.getName());
//I display Label
caption.setVisible(true);
//I move Label near the mouse cursor
caption.setTranslateX(e.getX());
caption.setTranslateY(e.getY());
//I hide the mouse cursor
dataTemp.getNode().setCursor(Cursor.NONE);
//I change text of Label
caption.setText(String.valueOf(dataTemp.getPieValue()) + "\n" + dataTemp.getName());
//I try to change the frame color of Label
caption.getStyleClass().add(dataTemp.getNode().getStyleClass().get(2));
}
});
//When mouse exited (the slice of PieChart)
dataTemp.getNode().addEventHandler(MouseEvent.MOUSE_EXITED,
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
System.out.println("MOUSE_EXITED : "+dataTemp.getName());
//I Hide Label
caption.setVisible(false);
//I show the mouse cursor
dataTemp.getNode().setCursor(Cursor.DEFAULT);
}
});
//Update progress
updateProgress(i++, sizeOfallElements);
}
return null;
}
};
return task;
}
}
Problem (2/2) :
The problem is that the events (MOUSE_ENTERED and MOUSE_EXITED) are emitted, too often instead of once.
Ex :
I just put in, then put off, my mouse hover a slice.
Here the result on console :
MOUSE_ENTERED : BC
MOUSE_EXITED : BC
MOUSE_ENTERED : BC
MOUSE_EXITED : BC
MOUSE_ENTERED : BC
MOUSE_EXITED : BC
MOUSE_ENTERED : BC
MOUSE_EXITED : BC
Anyone know why the event bug ?
Thanks : )
It not the blinking effect caused by label?
When you shows the label, it means that you exited the node which is listened. This causes hiding the label. When label disappears, it fires the mouse entered event on the node, it shows the label etc.
Not tested, just an idea.
EDIT:
If I am right, try to avoid putting label under the mouse pointer:
caption.setTranslateX(e.getX()+10);
caption.setTranslateY(e.getY()+10);
For example (10 is a magic number, depends on insets etc.)
Thanks all for your help.
#maskacovnik to find the problem, #James_D to find a cool solution, and #ItachiUchiha to put my image on my post : D
Now, my new code.
import java.util.ArrayList;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.event.EventHandler;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.chart.PieChart;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import com.alpha.client.view.nodes.stats.statsEngine.beans.ListRepere;
import com.alpha.client.view.nodes.stats.statsEngine.beans.OptionsChart;
import com.alpha.client.view.nodes.stats.statsEngine.beans.ValueStat;
public class PieChartNode implements ChartNode {
//My personnal attributes
private ListRepere categories;
private ArrayList<ValueStat> values;
//The PieChart
private PieChart chart;
//The data of Chart, will be fill by a thread
private ObservableList<PieChart.Data> pieChartData;
//The node which contain chart and label
private Group group;
//The Label
private final Label caption;
public PieChartNode(ListRepere categories, ArrayList<ValueStat> values, OptionsChart optionsChart) {
this.categories = categories;
this.values = values;
//New Group
group = new Group();
//I must use a StackPane to place Label hover Chart
StackPane pane = new StackPane();
group.getChildren().add(pane);
//Init' PieChart
pieChartData = FXCollections.observableArrayList();
chart = new PieChart(pieChartData);
chart.setStartAngle(180.0);
//Add chart to StackPane
pane.getChildren().add(chart);
//Init Popup(Label)
caption = new Label("");
caption.setVisible(false);
caption.getStyleClass().addAll("chart-line-symbol", "chart-series-line");
caption.setStyle("-fx-font-size: 12; -fx-font-weight: bold;");
caption.setMinSize(Label.USE_PREF_SIZE, Label.USE_PREF_SIZE);
//Add Label to StackPane
pane.getChildren().add(caption);
}
#Override
public Node getNodeGraph() {
return (Node) group;
}
#Override
public Task initTaskFormat() {
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
//i and sizeOfallElements are just use for ProgressBar
int i = 0;
int sizeOfallElements = values.size();
updateProgress(i, sizeOfallElements);
//For Each ValueStat (a Personnal pojo Class), I must create a slice
for (ValueStat v : values) {
//Create the PieChart.Data and add it to ObservableList
PieChart.Data dataTemp = new PieChart.Data(v.getCategorie().getStringName(), v.getDoubleValue());
pieChartData.add(dataTemp);
//At the same way that the LineChart, I add Event when mouse entered and mouse exited.
//When mouse entered (on the slice of PieChart)
dataTemp.getNode().addEventHandler(MouseEvent.MOUSE_ENTERED,
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
//Set Label ignores the mouse
caption.setMouseTransparent(true);
//I move Label near the mouse cursor, with a offset !
caption.setTranslateX(e.getX());
caption.setTranslateY(e.getY()+20);
//I change text of Label
caption.setText(String.valueOf(dataTemp.getPieValue()) + "\n" + dataTemp.getName());
//Change the color of popup, to adapt it to slice
if(caption.getStyleClass().size() == 4){
caption.getStyleClass().remove(3);
}
caption.getStyleClass().add(dataTemp.getNode().getStyleClass().get(2));
//I display Label
caption.setVisible(true);
}
});
//Need to add a event when the mouse move hover the slice
//If I don't the popup stay blocked on edges of the slice.
dataTemp.getNode().addEventHandler(MouseEvent.MOUSE_MOVED,
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
//Keep Label near the mouse
caption.setTranslateX(e.getX());
caption.setTranslateY(e.getY()+20);
}
});
//When mouse exited (the slice of PieChart)
dataTemp.getNode().addEventHandler(MouseEvent.MOUSE_EXITED,
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
//I Hide Label
caption.setVisible(false);
}
});
//Update progress
updateProgress(i++, sizeOfallElements);
}
return null;
}
};
return task;
}
}
Here the result :
I had the same problem but also wanted to make sure that the popup can extend beyond the chart, i.e. that it does not get cut off when the text does not fit in the chart. Here's a solution using a Tooltip instead of a Label:
public class ChartHoverUtil<T> {
public static void setupPieChartHovering(PieChart chart) {
new ChartHoverUtil<PieChart.Data>(
data -> String.format("Value = ", data.getPieValue()),
data -> data.getNode())
.setupHovering(chart.getData());
}
private final Tooltip tooltip = new Tooltip();
private final SimpleBooleanProperty adjustingTooltip = new SimpleBooleanProperty(false);
private final Function<T, String> textProvider;
private final Function<T, Node> nodeProvider;
private EventHandler<MouseEvent> moveHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
if (tooltip.isShowing()) {
setLabelPosition(e);
}
}
};
private EventHandler<MouseEvent> enterHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
adjustingTooltip.set(true);
Node chartNode = (Node) e.getSource();
tooltip.show(chartNode, e.getScreenX(), e.getScreenY());
setLabelPosition(e);
ObservableBooleanValue stillHovering = chartNode.hoverProperty().or(adjustingTooltip);
stillHovering.addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean nowHovering) {
if (!nowHovering) {
stillHovering.removeListener(this);
tooltip.hide();
}
}
});
T chartData = (T) chartNode.getUserData();
String txt = textProvider.apply(chartData);
tooltip.setText(txt);
adjustingTooltip.set(false);
}
};
public ChartHoverUtil(Function<T, String> textProvider, Function<T, Node> getNode) {
this.textProvider = textProvider;
this.nodeProvider = getNode;
tooltip.addEventFilter(MouseEvent.MOUSE_MOVED, moveHandler);
}
public void setupHovering(Collection<T> data) {
for (T chartData : data) {
Node node = nodeProvider.apply(chartData);
node.setUserData(chartData);
setupNodeHovering(node);
}
}
private void setupNodeHovering(Node node) {
node.addEventFilter(MouseEvent.MOUSE_MOVED, moveHandler);
node.addEventHandler(MouseEvent.MOUSE_ENTERED, enterHandler);
// Do not use MOUSE_EXIT handler because it is triggered immediately when showing the tooltip
}
private void setLabelPosition(MouseEvent e) {
adjustingTooltip.set(true);
tooltip.setAnchorX(e.getScreenX());
tooltip.setAnchorY(e.getScreenY() + 20);
adjustingTooltip.set(false);
}
}