JavaFX custom Fonts - java

Actually searched everywhere but any answer can't help me, sohere's the problem:
I want to add a custom font to my buttons. I already tried to write a css java class and a lot of other solutions, but nothing helped my.
/code deleted/
I'd be so glad if anyone could help me!
Update:
I tried this:
package view;
import ...
public class SceneStyle {
ImageView header = new ImageView("/view/images/header.jpg");
ImageView chatImg = new ImageView("/view/images/chat_win.jpg");
//Layout Style
//public String font1 = "Impact";
//public String font2 = "Comic Sans MS";
public static String color1 = "#00adf0";
public static String color2 = "#0076a3";
public void setLabelStyle(Label label) {
label.setStyle("-fx-font-family: Inconsolata:700; -fx-font-size: 25");
Scene scene = new Scene(label);
scene.getStylesheets().add("https://fonts.googleapis.com/css?family=Inconsolata:700");
label.setTextFill(Color.GRAY);
}
public void setLabelStyle2(Label label){
label.setStyle("-fx-font-family: Inconsolata:700; -fx-font-size: 25");
Scene scene = new Scene(label);
scene.getStylesheets().add("https://fonts.googleapis.com/css?family=Inconsolata:700");
label.setTextFill(Color.GRAY);
}
public void setLabelStyle3(Label label){
label.setStyle("-fx-font-family: Inconsolata:700; -fx-font-size: 25");
Scene scene = new Scene(label);
scene.getStylesheets().add("https://fonts.googleapis.com/css?family=Inconsolata:700");
label.setTextFill(Color.RED);
}
public void setButtonStyle(Button button){
button.setStyle("-fx-font-family: Inconsolata:700; -fx-font-size: 30; -fx-font-style: normal");
Scene scene = new Scene(button);
scene.getStylesheets().add("https://fonts.googleapis.com/css?family=Inconsolata:700");
button.setTextFill(Color.web(color2));
button.setPrefWidth(190);
}
public void setBorderPaneStyle(BorderPane pane, VBox btnBox, HBox scoreBox){
pane.setTop(header);
pane.setLeft(btnBox);
pane.setRight(chatImg);
pane.setBottom(scoreBox);
pane.getLeft().setStyle("-fx-background-image: url(\"/view/images/border_left.jpg\");");
pane.getBottom().setStyle("-fx-background-image: url(\"/view/images/bottom.jpg\");");
//pane.getCenter().setStyle("-fx-background-image: url(\"/view/images/center.jpg\");");
//TODO: why can't I implement this?
}
public static String getFontColor1(){ return color1; }
public static String getFontColor2(){ return color2; }
Thanks for the detailed answers, but actually I don't want to write a start method which is overwritten. Just want to implemtent the font in my code. And yes now I'm trying with the Google Fonts.
Thanks for answering my question.

Here is a simple example of how to use custom fonts in your JavaFX application. This example is just an edited version of the sample FontLoad application.
Output
Project Structure
The follow diagram is the project structure.
Java Class
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class FontLoad extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
Label label = new Label("JavaFX Application");
Button button = new Button("My Button");
VBox box = new VBox(15, label, button);
box.setAlignment(Pos.CENTER);
Scene scene = new Scene(box, 500, 300);
scene.getStylesheets().add(getClass().getResource("/fontstyle.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Stylesheet
#font-face {
font-family: 'Fleftex';
src: url('fonts/Fleftex_M.ttf');
}
.label {
-fx-font-family: 'Fleftex';
-fx-font-size: 20;
}
.button .text {
-fx-font-family: 'Fleftex';
}
Update >= 8u60
Starting with this version the attribute “font-family” is ignored and you have to use the real name of the TTF.
By example: to use the font “Fleftex” contained on the file Fleftex_M.ttf You have to use this CSS file :
#font-face {
src: url(“fonts/Fleftex_M.ttf”);
}
.label {
-fx-font-family: 'Fleftex';
}

Here is a little example showing how to load and set a custom font.
package createfont;
import java.io.IOException;
import java.io.InputStream;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class CustomFontTest extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
String currentFontFile = "English Gothic, 17th c..TTF";
InputStream fontStream = CustomFontTest.class.getResourceAsStream(currentFontFile);
if (fontStream != null) {
Font bgFont = Font.loadFont(fontStream, 36);
fontStream.close();
final Button button = new Button("Press me");
button.setFont(bgFont);
BorderPane root = new BorderPane();
root.setCenter(button);
Scene scene = new Scene(root, 500, 100);
primaryStage.setTitle("CustomFontTest");
primaryStage.setScene(scene);
primaryStage.show();
} else {
throw new IOException("Could not create font: " + currentFontFile);
}
}
public static void main(String[] args) {
launch(args);
}
}

I tried this:
public void setLabelStyle(Label label) {
label.setStyle("-fx-font-family: Inconsolata:700; -fx-font-size: 25");
Scene scene = new Scene(label);
scene.getStylesheets().add("https://fonts.googleapis.com/css?family=Inconsolata:700");
label.setTextFill(Color.GRAY);
in each method. Because I don't want write a new methode which overrides the start method just like above. (Actually thanks for this detailed answer, but it isn't what I am searching for).
But also my solution doesn't work well...

Related

JavaFX Slider Tick Marks Initialized via CSS Disappear

I am using CSS to configure my JavaFX Sliders, then applying the style in code with:
cssSlider.getStyleClass().add("slider-style");
When I first open my window, the tick marks are present on the CSS configured Slider(s). When I close and reopen the window, the tick marks are no longer present.
This following example demonstrates the anomaly using 2 Sliders, one configured directly, the other via CSS. Click the button to hide the window for 2 seconds. Notice that the Slider in which I directly configure the attributes works fine after hiding and re-showing, but the CSS configured Slider loses its tick marks after hiding and re-showing.
Does anyone have any ideas why showing, hiding, and re-showing the window causes the tick marks to vanish from the CSS configured Slider? Am I doing something wrong, or is this a JavaFX bug?
sample.css:
.slider-style {
-fx-show-tick-marks: true;
-fx-snap-to-ticks: true;
-fx-major-tick-unit: 5;
-fx-minor-tick-count: 5;
}
CssExample.java:
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.io.IOException;
import java.net.URL;
/**
* This simple example demonstrates that JavaFX Sliders configured with CSS only show their tick marks the first time
* they are shown. If the Slider is hidden, then shown again, the tick marks are gone forever.
*/
public class CssExample extends Application {
#Override
public void start(Stage stage) throws InterruptedException, IOException {
Group root = new Group();
Scene scene = new Scene(root, 400, 200);
stage.setScene(scene);
stage.setTitle("Slider Sample");
scene.setFill(Color.BLACK);
GridPane grid = new GridPane();
grid.setPadding(new Insets(10, 10, 10, 10));
grid.setVgap(10);
grid.setHgap(70);
scene.setRoot(grid);
int rowNumber = 1;
Label directLabel = new Label("Slider from attribute assignment");
GridPane.setConstraints(directLabel, 1, rowNumber++);
grid.getChildren().add(directLabel);
Slider directSlider = new Slider();
GridPane.setConstraints(directSlider, 1, rowNumber++);
grid.getChildren().add(directSlider);
directSlider.setShowTickMarks(true);
directSlider.setSnapToTicks(true);
directSlider.setMajorTickUnit(5);
directSlider.setMinorTickCount(5);
Label cssLabel = new Label("Slider from CSS (tick marks disappear after hidden)");
GridPane.setConstraints(cssLabel, 1, rowNumber++);
grid.getChildren().add(cssLabel);
Slider cssSlider = new Slider();
GridPane.setConstraints(cssSlider, 1, rowNumber++);
grid.getChildren().add(cssSlider);
URL url = getClass().getResource("sample.css");
String cssString = url.toExternalForm();
scene.getStylesheets().add(cssString);
cssSlider.getStyleClass().add("slider-style");
Button button = new Button("Hide for 2 Seconds");
GridPane.setConstraints(button, 1, rowNumber++);
grid.getChildren().add(button);
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
stage.hide();
stage.show();
}
});
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
is this a JavaFX bug?
Yes.
See: https://github.com/openjdk/jfx/blob/fdc88341f1df8fb9c99356ada54b25124b77ea6e/modules/javafx.controls/src/main/java/javafx/scene/control/skin/SliderSkin.java#L398
It is a bug in the internal implementation of the setShowTickMarks method of SliderSkin (verified in JavaFX 18.0.1).
Test case:
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Slider;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.io.IOException;
public class CssExample extends Application {
private static final String CSS = // language=CSS
"""
.slider-style {
-fx-show-tick-marks: true;
-fx-snap-to-ticks: true;
-fx-major-tick-unit: 5;
-fx-minor-tick-count: 5;
}
""";
private static final String CSS_INLINE = "data:text/css," + CSS;
#Override
public void start(Stage stage) throws InterruptedException, IOException {
Platform.setImplicitExit(false);
Slider cssSlider = new Slider();
cssSlider.showTickMarksProperty().addListener((observable, oldValue, newValue) ->
System.out.println(cssSlider.showTickMarksProperty())
);
cssSlider.getStyleClass().add("slider-style");
PauseTransition hideAnimation = new PauseTransition(Duration.seconds(2));
hideAnimation.setOnFinished(e -> stage.show());
Button hideWindow = new Button("Hide for 2 Seconds");
hideWindow.setOnAction(e -> {
stage.hide();
hideAnimation.play();
});
Button closeApp = new Button("Close app");
closeApp.setOnAction(e -> Platform.exit());
VBox layout = new VBox(
10,
cssSlider, hideWindow, closeApp
);
layout.setPadding(new Insets(10));
layout.setPrefSize(400, 120);
Scene scene = new Scene(layout);
scene.getStylesheets().add(CSS_INLINE);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Test output:
BooleanProperty [bean: Slider#132908b9[styleClass=slider slider-style], name: showTickMarks, value: true]
BooleanProperty [bean: Slider#132908b9[styleClass=slider slider-style], name: showTickMarks, value: false]
BooleanProperty [bean: Slider#132908b9[styleClass=slider slider-style], name: showTickMarks, value: true]
It switches showTicks from true to false, and back to true, which triggers the bug.
In the current implementation for the setShowTicks method:
private void setShowTickMarks(boolean ticksVisible, boolean labelsVisible) {
showTickMarks = (ticksVisible || labelsVisible);
Slider slider = getSkinnable();
if (showTickMarks) {
if (tickLine == null) {
tickLine = new NumberAxis();
tickLine.setAutoRanging(false);
tickLine.setSide(slider.getOrientation() == Orientation.VERTICAL ? Side.RIGHT : (slider.getOrientation() == null) ? Side.RIGHT: Side.BOTTOM);
tickLine.setUpperBound(slider.getMax());
tickLine.setLowerBound(slider.getMin());
tickLine.setTickUnit(slider.getMajorTickUnit());
tickLine.setTickMarkVisible(ticksVisible);
tickLine.setTickLabelsVisible(labelsVisible);
tickLine.setMinorTickVisible(ticksVisible);
// add 1 to the slider minor tick count since the axis draws one
// less minor ticks than the number given.
tickLine.setMinorTickCount(Math.max(slider.getMinorTickCount(),0) + 1);
if (slider.getLabelFormatter() != null) {
tickLine.setTickLabelFormatter(stringConverterWrapper);
}
getChildren().clear();
getChildren().addAll(tickLine, track, thumb);
} else {
tickLine.setTickLabelsVisible(labelsVisible);
tickLine.setTickMarkVisible(ticksVisible);
tickLine.setMinorTickVisible(ticksVisible);
}
}
else {
getChildren().clear();
getChildren().addAll(track, thumb);
// tickLine = null;
}
getSkinnable().requestLayout();
}
The first time it shows the ticks it will do this:
getChildren().clear();
getChildren().addAll(tickLine, track, thumb);
Then, when the ticks are hidden, it will do this:
getChildren().clear();
getChildren().addAll(track, thumb);
Then, when the ticks are supposed to be shown again, the tickLine is not added back to the children, so it never shows the ticks again.

How to make a ListView selectable but not editable

so I'm writing a javafx app and I need to be able to select the cells from the list view (for copy paste purposes) but I don't want to make it editable, I mean, the content cannot be changed unless I want to (allowing it through a button, for example).
So I have the following code:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.control.cell.TextFieldListCell;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
List<String> contacts = new ArrayList<>(Arrays.asList("968787522","3424234234","2343234324"));
ListView<String> contactsList = new ListView();
contactsList.setItems(FXCollections.observableArrayList(contacts));
//this gives me the ability to edit the row as text field but I want this text field to not be editable
contactsList.setCellFactory(TextFieldListCell.forListView());
StackPane pane = new StackPane();
pane.getChildren().add(contactsList);
primaryStage.setScene(new Scene(pane, 300, 275));
primaryStage.show(); }
public static void main(String[] args) {
launch(args);
}
}
and if I set 'contactsList' as not editable, I'm not able to edit, neither select.
As you can see (image bellow),I'm editing the cell, but I want to be able to select the text(not the item), but I don't want to be able to delete characters (text selectable but not editable).
so after breaking my head off, lots of research and API reading, I came up with a solution. This does EXACTLY what I wanted to do. Here is the demo if someone needs it ;)
So the idea is, each time we want to select the content of a row we need to select the row, get the textField and set the editing to true or false, (every time).
So in the demo that I made, I placed a button so you can toggle the editing to true or false to be sure that's is working, and how is working.
Cheers.
I commented some of the code for better understanding, if you have any questions about this just let me know.
package sample;
import com.sun.javafx.scene.control.skin.VirtualFlow;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.TextFieldListCell;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main extends Application {
private boolean editable = false;
public static IndexedCell getCell(final Control control, final int index) {
return getVirtualFlow(control).getCell(index);
}
public static VirtualFlow<?> getVirtualFlow(Control control) {
Group group = new Group();
Scene scene = new Scene(group);
Stage stage = new Stage();
if(control.getScene() == null) {
group.getChildren().setAll(control);
stage.setScene(scene);
stage.show();
}
VirtualFlow<?>flow = (VirtualFlow<?>) control.lookup("#virtual-flow");
return flow;
}
public void setEditable(ListView contactsList){
//this needs to be done since we need to run our code after the text field was rendered
//so we need to invoke our code after this happens, if not it will throw a null pointer...
Platform.runLater(() -> {
//this is one of the most important guys because javafx api says that
//TextFieldListCell.forListView() allows editing of the cell content when the cell is double-clicked,
// or when {#link ListView#edit(int)} is called.
int rowIndex = contactsList.getSelectionModel().getSelectedIndex();
contactsList.edit(rowIndex);
ListCell rootCell = (ListCell) getCell(contactsList, rowIndex);
TextField textField = (TextField) rootCell.getGraphic();
textField.setEditable(editable);
});
}
#Override
public void start(Stage primaryStage) throws Exception{
List<String> contacts = new ArrayList<>(Arrays.asList("968787522","3424234234","2343234324"));
ListView<String> contactsList = new ListView();
contactsList.setItems(FXCollections.observableArrayList(contacts));
contactsList.setEditable(true);
//this gives me the ability to edit the row as text field but I want this text field to not be editable
contactsList.setCellFactory(TextFieldListCell.forListView());
contactsList.setOnEditStart(e -> {
setEditable(contactsList);
});
StackPane pane = new StackPane();
Button editBtn = new Button("Toggle edit");
editBtn.setOnAction(event -> {
editable = !editable;
editBtn.setText("Editing = " + editable);
//to cancel any editing that might be occuring
contactsList.getSelectionModel().clearSelection();
});
pane.getChildren().addAll(contactsList,editBtn);
primaryStage.setScene(new Scene(pane, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If I understand you correctly, it is not necessary to set the listview to 'not editable', as the default behaviour should suffice for your purpose. Take a look at this code, for example:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class NewFXMain extends Application {
#Override
public void start(Stage primaryStage) {
ListView listView = new ListView();
listView.getItems().addAll("one","two","three","four");
listView.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.println(listView.getSelectionModel().getSelectedItem());
}
});
StackPane root = new StackPane();
root.getChildren().add(listView);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("ListView Example");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I changed nothing about the editable-property of the ListView, but I can select every item, without being able to edit it (in the sense of changing its value). You can easily add an EventHandler to the ListView to perform whatever operation you want to perform. You could also add an EventHandler to every cell of the ListView by manipulating the CellFactory, as shown in this answer: How to handle ListView item clicked action?
Here's what works for me:
TableView<DataBean> table = new TableView<>();
table.setItems(...); // list of some DataBean objects with dataBeanField proprty
table.setEditable(true);
TableColumn<DataBean, String> column = new TableColumn<>("SomeData");
column.setCellValueFactory(new PropertyValueFactory<DataBean, String>("dataBeanField"));
column.setCellFactory(new Callback<TableColumn<DataBean, String>, TableCell<DataBean, String>>() {
#Override
public TableCell<DataBean, String> call(TableColumn<DataBean, String> param) {
return new TextFieldTableCell<>(new DefaultStringConverter() {
private String defaultValue = "";
#Override
public String fromString(String newValue) {
return super.fromString(defaultValue);
}
#Override
public String toString(String value) {
return defaultValue = super.toString(value);
}
});
}
});

javafx Pagination memory leak when replacing buttons

I first noticed this by having a huge memory leak caused by resizing javafx.scene.control.Pagination.
I created a sample app to show the behavior. Every click on any button causes more memory to be consumed. I suppose that the correct way to deal with this is to remove any listeners on the ToggleButtons before clearing them but PaginationSkin isn't doing that.
Have I missed something or is this a real bug in the Pagination code?
Are there any workarounds?
The same behavior is seen it both 8u40 and 8u45. I haven't tried any older versions yet.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class ToggleGroupLeak extends Application {
private HBox hbox;
private ToggleGroup toggleGroup;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
toggleGroup = new ToggleGroup();
hbox = new HBox();
redo();
root.getChildren().add(hbox);
primaryStage.setScene(new Scene(root, 700, 100));
primaryStage.show();
}
private void redo() {
hbox.getChildren().clear();
toggleGroup.getToggles().clear();
for (int i = 0; i < 20; i++) {
addButton(i);
}
}
private void addButton(final int i) {
ToggleButton btn = new ToggleButton("" + i);
btn.setToggleGroup(toggleGroup);
btn.setOnAction(e -> redo());
hbox.getChildren().add(btn);
}
}
Concretely, usage of Pagination in an AnchorPane seems to cause the same leak by resizing the control.
FXML
<Pagination fx:id="pagination" visible="true" AnchorPane.topAnchor="3.0" AnchorPane.leftAnchor="12.0" AnchorPane.rightAnchor="130.0"/>
Java
#FXML
protected Pagination pagination;
// Init method run once
pagination.setMaxPageIndicatorCount(100);
pagination.currentPageIndexProperty().addListener((observable, oldPageIndex, newPageIndex) -> {
if (!oldPageIndex.equals(newPageIndex)) {
if (searchParams.getPage() == newPageIndex.intValue()) {
return;
}
searchParams.setPage(newPageIndex.intValue());
// Code that inits the new page
}
});
EDIT: Added pagination specific code

Live update Pie Chart

I want to create very useful and easy way to live update Pie chart. For example:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.chart.PieChart;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class MainApp extends Application {
#Override
public void start(Stage stage) {
Scene scene = new Scene(new Group());
stage.setTitle("Imported Fruits");
stage.setWidth(500);
stage.setHeight(500);
ObservableList<PieChart.Data> pieChartData =
FXCollections.observableArrayList(
new PieChart.Data("Grapefruit", 13),
new PieChart.Data("Oranges", 25),
new PieChart.Data("Plums", 10),
new PieChart.Data("Pears", 22),
new PieChart.Data("Apples", 30));
final PieChart chart = new PieChart(pieChartData);
chart.setTitle("Imported Fruits");
final Label caption = new Label("");
caption.setTextFill(Color.DARKORANGE);
caption.setStyle("-fx-font: 24 arial;");
for (final PieChart.Data data : chart.getData()) {
data.getNode().addEventHandler(MouseEvent.MOUSE_PRESSED,
new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent e) {
caption.setTranslateX(e.getSceneX());
caption.setTranslateY(e.getSceneY());
caption.setText(String.valueOf(data.getPieValue())
+ "%");
}
});
}
((Group) scene.getRoot()).getChildren().addAll(chart, caption);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
When I display the chart I want to call Java Method and update the chart like this:
PieChartUpdate(valueOne, valueTwo, valueThree);
Can you show me how I can edit the code in order to make the live updates more easy to use?
As far as i could see, all classes that are used to establish a PieChart, like PieChart.Data and of course the ObservableList are already designed so that they will update the PieChart the moment something changes, be it the list itself or values inside the Data Objects. See the binding chapters how this is done. But you don't need to write your own bindings for the PieChart.
The code below should do what you want. Use addData(String name, double value) to create a new Data object for your pie chart, or update an existing one which has the same name like the first parameter of the method. The PieChart will automatically play a animation when changes are made to the list (new Data object added) or a Data object got changed.
//adds new Data to the list
public void naiveAddData(String name, double value)
{
pieChartData.add(new Data(name, value));
}
//updates existing Data-Object if name matches
public void addData(String name, double value)
{
for(Data d : pieChartData)
{
if(d.getName().equals(name))
{
d.setPieValue(value);
return;
}
}
naiveAddData(name, value);
}
Just in case someone feels extremely lost and isn't sure how to implement denhackl's answer, here is a working version of what he tried to explain.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.chart.PieChart;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class LivePie extends Application {
ObservableList<PieChart.Data> pieChartData;
#Override
public void start(Stage stage) {
Scene scene = new Scene(new Group());
stage.setTitle("Imported Fruits");
stage.setWidth(500);
stage.setHeight(500);
this.pieChartData =
FXCollections.observableArrayList();
addData("Test", 5.1);
addData("Test2", 15.1);
addData("Test3", 3.1);
addData("Test1", 4.9);
addData("Test2", 15.1);
addData("Test3", 2.1);
addData("Test5", 20.1);
final PieChart chart = new PieChart(pieChartData);
chart.setTitle("Imported Fruits");
final Label caption = new Label("");
caption.setTextFill(Color.DARKORANGE);
caption.setStyle("-fx-font: 24 arial;");
((Group) scene.getRoot()).getChildren().addAll(chart, caption);
stage.setScene(scene);
stage.show();
}
public void naiveAddData(String name, double value)
{
pieChartData.add(new javafx.scene.chart.PieChart.Data(name, value));
}
//updates existing Data-Object if name matches
public void addData(String name, double value)
{
for(javafx.scene.chart.PieChart.Data d : pieChartData)
{
if(d.getName().equals(name))
{
d.setPieValue(value);
return;
}
}
naiveAddData(name, value);
}
public static void main(String[] args) {
launch(args);
}
}
Many thanks to the creator of the topic and the answers provided!
Here's a good introductory article on using properties and binding.
http://docs.oracle.com/javafx/2/binding/jfxpub-binding.htm

How to make the MenuItems in a JavaFX context menu support an onMouseOver event

I'm looking for some guidance on how to proceed with a problem I'm having. I hava a JavaFX scene and within it some nodes (shapes) that connect to each other with one or more lines. I can right-click on a shape to bring up a context menu. Let's say this particular shape that was just right-clicked has 3 lines coming out of it (call them line1, line2, line3) and you want to use the context menu to delete one. You can select "line2" for example, and it will fire the onAction event to remove that line. That all works fine.
The trouble is, you don't know which of the 3 lines on the screen is line1 or line2 or line3 (unless of course they are labeled) and so you don't know which one you are about to remove until you remove it. What I would really like to do, for example, is to place my mouse over "line2" in the context menu and have line2 in the scene change color or something to indicate that it is the one about to be deleted (before I click the mouse). However, the only event I see supported by MenuItem is the onAction event for when it is clicked. Is there some way to give it onMouseOver functionality? if not, how could this feature be implemented?
Thanks!
Try this SSCCE:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.effect.DropShadow;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class ContextMenuDemo extends Application {
private DropShadow ds = new DropShadow();
#Override
public void start(Stage primaryStage) {
final Line line1 = new Line(60, 10, 150, 10);
final Line line2 = new Line(60, 30, 150, 50);
final Line line3 = new Line(60, 60, 150, 90);
final ContextMenu cm = new ContextMenu();
cm.getItems().add(getMenuItemForLine("line 1", line1));
cm.getItems().add(getMenuItemForLine("line 2", line2));
cm.getItems().add(getMenuItemForLine("line 3", line3));
final Rectangle rectangle = new Rectangle(70, 70, Color.TAN);
rectangle.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
if (e.getButton() == MouseButton.SECONDARY) {
cm.show(rectangle, e.getScreenX(), e.getScreenY());
}
}
});
Group root = new Group();
root.getChildren().addAll(rectangle, line1, line2, line3);
Scene scene = new Scene(root, 300, 250);
// load style of modified paddings for menuitems
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
private MenuItem getMenuItemForLine(String menuName, final Line line) {
Label menuLabel = new Label(menuName);
// apply style to occupy larger space for label
menuLabel.setStyle("-fx-padding: 5 10 5 10");
MenuItem mi = new MenuItem();
mi.setGraphic(menuLabel);
line.setStroke(Color.BLUE);
menuLabel.addEventHandler(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
line.setStroke(Color.RED);
line.setEffect(ds);
}
});
menuLabel.addEventHandler(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
line.setStroke(Color.BLUE);
line.setEffect(null);
}
});
return mi;
}
public static void main(String[] args) {
launch(args);
}
}
with style.css
.menu-item {
/* -fx-skin: "com.sun.javafx.scene.control.skin.MenuItemSkin";*/
-fx-background-color: transparent;
-fx-padding: 0em; /* do not pad for item. we want to ccupy all spaces for graphics only */
}
.menu-item:focused {
-fx-background: -fx-accent;
-fx-background-color: -fx-selection-bar;
-fx-text-fill: -fx-selection-bar-text;
}
.menu-item .graphic-container {
-fx-padding: 0em; /* do not pad for graphics, label graphic pads itself */
}
.menu-item .label {
-fx-padding: 0em; /* do not pad for label, since there is no label text set */
-fx-text-fill: -fx-text-base-color;
}
Screenshot:
Description:
This is somewhat a bug that MenuItem does not work for MenuItem.addEventHandler(MouseEvent.MOUSE_ENTERED, ...) I think. As a workaround, we define new Label, register event handlers to it and set it as a graphic of menu item while the text(label) of menuitem intentionally left an empty. But the graphic of menu item does not (by default) occupy all space of menu item, so mouse events are not handled properly at the edges of menu item. To overcome this problem we reset all paddings of menuitem, menuitem's label and graphic through css. You can observe this by commenting out the style loading in the above code.
Here is a sample App I just created on an aproach to identify the lines:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Tooltip;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class MainTest extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
// TODO Auto-generated method stub
AnchorPane anchorPane = new AnchorPane();
Scene scene = new Scene(anchorPane);
stage.setScene(scene);
Line linea = new Line(0, 0, 50, 50);
linea.setFill(Color.BLACK);
final Tooltip t = new Tooltip("Line 1");
linea.setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
Line line = (Line) event.getSource();
line.setStroke(Color.RED);
t.show((Line) event.getSource(), event.getScreenX(),
event.getScreenY());
}
});
linea.setOnMouseExited(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
Line line = (Line) event.getSource();
line.setStroke(Color.BLACK);
t.hide();
}
});
anchorPane.getChildren().add(linea);
stage.show();
}
}
Hope it helps!

Categories

Resources