JavaFX Right Coordinate of a CustomMenuItem - javafx

I have a Class that extends the CustomMenuItem. This MenuItems are added to a ContextMenu. Now i need to get the X-Coordinates from the right side of the CustomMenuItem.
The Problem is, that I have no idea how I can get the Coordinates.
The CustMenuItem has no function for getting the Coordinates like getX() or getY().
So how can I solve this problem?
This thing I would like to get:
Here we can see a Sample for a Context Menu (red lines). In the Context Menu are a lot of different CustomMenuItems implemented. Now I would like to get the right top corner Coordinate of the CustomMenuItem.
Thank you for your very nice help.

Before dealing with menu items, let's start saying that a ContextMenu is a popup window, so it has Windowproperties. You can ask for (x,y) left, top origin, and for (w,h).
But you have to take into account the effects, since by default it includes a dropshadow. And when it does, there's an extra space added of 24x24 pixels to the right and bottom.
.context-menu {
-fx-effect: dropshadow( gaussian , rgba(0,0,0,0.2) , 12, 0.0 , 0 , 8 );
}
Since this default dropshadow has a radius of 12px, and Y-offset to the bottom of 8px, the right and bottom coordinates of the context menu, including the 24x24 area, are given by:
X=t.getX()+cm.getWidth()-12-24;
Y=t.getY()+cm.getHeight()-(12-8)-24;
where t could be a MouseEvent relative to the scene, and values are hardcoded for simplicity.
Let's see this over an example. Since you don't say how your custom menu items are implemented, I'll just create a simple Menu Item with graphic and text:
private final Label labX = new Label("X: ");
private final Label labY = new Label("Y: ");
#Override
public void start(Stage primaryStage) {
final ContextMenu cm = new ContextMenu();
MenuItem cmItem1 = createMenuItem("mNext", "Next Long Option",t->System.out.println("next"));
MenuItem cmItem2 = createMenuItem("mBack", "Go Back", t->System.out.println("back"));
SeparatorMenuItem sm = new SeparatorMenuItem();
cm.getItems().addAll(cmItem1,cmItem2);
VBox root = new VBox(10,labX,labY);
Scene scene = new Scene(root, 300, 250);
scene.setOnMouseClicked(t->{
if(t.getButton()==MouseButton.SECONDARY || t.isControlDown()){
// t.getX,Y->scene based coordinates
cm.show(scene.getWindow(),t.getX()+scene.getWindow().getX()+scene.getX(),
t.getY()+scene.getWindow().getY()+scene.getY());
labX.setText("Right X: "+(t.getX()+cm.getWidth()-12-24));
labY.setText("Bottom Y: "+(t.getY()+cm.getHeight()-4-24));
}
});
scene.getStylesheets().add(getClass().getResource("root.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
primaryStage.setTitle("Scene: "+scene.getWidth()+"x"+scene.getHeight());
}
private MenuItem createMenuItem(String symbol, String text, EventHandler<ActionEvent> t){
MenuItem m=new MenuItem(text);
StackPane g=new StackPane();
g.setPrefSize(24, 24);
g.setId(symbol);
m.setGraphic(g);
m.setOnAction(t);
return m;
}
If you remove the effect:
.context-menu {
-fx-effect: null;
}
then these coordinates are:
X=t.getX()+cm.getWidth();
Y=t.getY()+cm.getHeight();
Now that we have the window, let's go into the items.
MenuItem skin is derived from a (private) ContextMenuContent.MenuItemContainer class, which is a Region where the graphic and text are layed out.
When the context menu is built, all the items are wrapped in a VBox, and all are equally resized, as you can see if you set the border for the item:
.menu-item {
-fx-border-color: black;
-fx-border-width: 1;
}
This is how it looks like:
So the X coordinates of every item on the custom context menu are the same X from their parent (see above, with or without effect), minus 1 pixel of padding (by default).
Note that you could also go via private methods to get dimensions for the items:
ContextMenuContent cmc= (ContextMenuContent)cm.getSkin().getNode();
System.out.println("cmc: "+cmc.getItemsContainer().getBoundsInParent());
Though this is not recommended since private API can change in the future.
EDIT
By request, this is the same code removing lambdas and css.
private final Label labX = new Label("X: ");
private final Label labY = new Label("Y: ");
#Override
public void start(Stage primaryStage) {
final ContextMenu cm = new ContextMenu();
MenuItem cmItem1 = createMenuItem("mNext", "Next Long Option",action);
MenuItem cmItem2 = createMenuItem("mBack", "Go Back", action);
SeparatorMenuItem sm = new SeparatorMenuItem();
cm.getItems().addAll(cmItem1,cmItem2);
VBox root = new VBox(10,labX,labY);
Scene scene = new Scene(root, 300, 250);
scene.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
if(t.getButton()==MouseButton.SECONDARY || t.isControlDown()){
// t.getX,Y->scene based coordinates
cm.show(scene.getWindow(),t.getX()+scene.getWindow().getX()+scene.getX(),
t.getY()+scene.getWindow().getY()+scene.getY());
labX.setText("Right X: "+(t.getX()+cm.getWidth()-12-24));
labY.setText("Bottom Y: "+(t.getY()+cm.getHeight()-4-24));
}
}
});
primaryStage.setScene(scene);
primaryStage.show();
primaryStage.setTitle("Scene: "+scene.getWidth()+"x"+scene.getHeight());
}
private MenuItem createMenuItem(String symbol, String text, EventHandler<ActionEvent> t){
MenuItem m=new MenuItem(text);
StackPane g=new StackPane();
g.setPrefSize(24, 24);
g.setId(symbol);
SVGPath svg = new SVGPath();
svg.setContent("M0,5H2L4,8L8,0H10L5,10H3Z");
m.setGraphic(svg);
m.setOnAction(t);
return m;
}
private final EventHandler<ActionEvent> action = new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("action");
}
};

Related

Fitting rotated ImageView into Application Window / Scene

In JavaFX I am trying to show an rotated ImageView in an Application Window.
Therefore I have put it into a stackPane to have it always centered and I have bound the widths/heights of the ImageView and the stackPane to the scene's width/height to view it just as large as possible.
This works fine as soon as the Image is not rotated.
As soon as I rotate the Image by 90° using stackPane.setRotate(90) (and exchange binding for width/height) then the stackPane is no longer bound to the upper left corner of the Application Window (or scene).
What can I do to place the rotated image correctly?
In the example code [any key] will toggle the rotation 90°/0° so the location problem of the rotated image becomes visible:
public class RotationTest extends Application {
boolean rotated = false;
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Rotation test");
Group root = new Group();
Scene scene = new Scene(root, 1024,768);
//a stackPane is used to center the image
StackPane stackPane = new StackPane();
stackPane.setStyle("-fx-background-color: black;");
stackPane.prefHeightProperty().bind(scene.heightProperty());
stackPane.prefWidthProperty().bind(scene.widthProperty());
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
//toggle rotate 90° / no rotation
rotated = !rotated;
stackPane.prefHeightProperty().unbind();
stackPane.prefWidthProperty().unbind();
if (rotated){
stackPane.setRotate(90);
//rotation: exchange width and height for binding to scene
stackPane.prefWidthProperty().bind(scene.heightProperty());
stackPane.prefHeightProperty().bind(scene.widthProperty());
}else{
stackPane.setRotate(0);
//no rotation: height is height and width is width
stackPane.prefHeightProperty().bind(scene.heightProperty());
stackPane.prefWidthProperty().bind(scene.widthProperty());
}
}
});
final ImageView imageView = new ImageView("file:D:/test.jpg");
imageView.setPreserveRatio(true);
imageView.fitWidthProperty().bind(stackPane.prefWidthProperty());
imageView.fitHeightProperty().bind(stackPane.prefHeightProperty());
stackPane.getChildren().add(imageView);
root.getChildren().add(stackPane);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Results:
Without rotation the stackPane (black) fits the window perfectly and the image has the correct size even if the window is resized with the mouse.
After pressing [any key] the stackPane is rotated. The stackPane (black) seems to have the correct width/height and also the image seems to be correctly rotated. But the stackPane is no longer in the upper left corner??? It moves around when the window is resized with the mouse???
Why not simply leave the Group and the preferred sizes out of the equation?
The root is automatically resized to fit the scene and you can use it's width/height properties to bind the fitWidth and fitHeight properties:
private static void setRotated(boolean rotated, ImageView targetNode, Pane parent) {
double angle;
if (rotated) {
angle = 90;
targetNode.fitWidthProperty().bind(parent.heightProperty());
targetNode.fitHeightProperty().bind(parent.widthProperty());
} else {
angle = 0;
targetNode.fitWidthProperty().bind(parent.widthProperty());
targetNode.fitHeightProperty().bind(parent.heightProperty());
}
targetNode.setRotate(angle);
}
#Override
public void start(Stage primaryStage) {
Image image = new Image("file:D:/test.jpg");
ImageView imageView = new ImageView(image);
imageView.setPreserveRatio(true);
StackPane root = new StackPane(imageView);
root.setStyle("-fx-background-color: black;");
// initialize unrotated
setRotated(false, imageView, root);
Scene scene = new Scene(root, 1024, 768);
scene.setOnKeyPressed(evt -> {
// toggle between 0° and 90° rotation
setRotated(imageView.getRotate() == 0, imageView, root);
});
primaryStage.setScene(scene);
primaryStage.show();
}
Note that this may not result in correct layout, if placed in some other layout, since the size constraints may be calculated wrong.
You could implement your own region though to fix this:
public class CenteredImage extends Region {
private final BooleanProperty rotated = new SimpleBooleanProperty();
private final ImageView imageView = new ImageView();
public CenteredImage() {
// make sure layout gets invalidated when the image changes
InvalidationListener listener = o -> requestLayout();
imageProperty().addListener(listener);
rotated.addListener((o, oldValue, newValue) -> {
imageView.setRotate(newValue ? 90 : 0);
requestLayout();
});
getChildren().add(imageView);
imageView.setPreserveRatio(true);
}
public final BooleanProperty rotatedProperty() {
return rotated;
}
public final void setRotated(boolean value) {
this.rotated.set(value);
}
public boolean isRotated() {
return rotated.get();
}
public final void setImage(Image value) {
imageView.setImage(value);
}
public final Image getImage() {
return imageView.getImage();
}
public final ObjectProperty<Image> imageProperty() {
return imageView.imageProperty();
}
#Override
protected double computeMinWidth(double height) {
return 0;
}
#Override
protected double computeMinHeight(double width) {
return 0;
}
#Override
protected double computePrefWidth(double height) {
Image image = getImage();
Insets insets = getInsets();
double add = 0;
if (image != null && height > 0) {
height -= insets.getBottom() + insets.getTop();
add = isRotated()
? height / image.getWidth() * image.getHeight()
: height / image.getHeight() * image.getWidth();
}
return insets.getLeft() + insets.getRight() + add;
}
#Override
protected double computePrefHeight(double width) {
Image image = getImage();
Insets insets = getInsets();
double add = 0;
if (image != null && width > 0) {
width -= insets.getLeft() + insets.getRight();
add = isRotated()
? width / image.getHeight() * image.getWidth()
: width / image.getWidth() * image.getHeight();
}
return insets.getTop() + insets.getBottom() + add;
}
#Override
protected double computeMaxWidth(double height) {
return Double.MAX_VALUE;
}
#Override
protected double computeMaxHeight(double width) {
return Double.MAX_VALUE;
}
#Override
protected void layoutChildren() {
Insets insets = getInsets();
double left = insets.getLeft();
double top = insets.getTop();
double availableWidth = getWidth() - left - insets.getRight();
double availableHeight = getHeight() - top - insets.getBottom();
// set fit sizes
if (isRotated()) {
imageView.setFitWidth(availableHeight);
imageView.setFitHeight(availableWidth);
} else {
imageView.setFitWidth(availableWidth);
imageView.setFitHeight(availableHeight);
}
// place image
layoutInArea(imageView, left, top, availableWidth, availableHeight, 0, null, false,
false, HPos.CENTER, VPos.CENTER);
}
}
#Override
public void start(Stage primaryStage) {
Image image = new Image("file:D:/test.jpg");
ImageView imageView = new ImageView(image);
imageView.setPreserveRatio(true);
CenteredImage imageArea = new CenteredImage();
imageArea.setImage(image);
imageArea.setStyle("-fx-background-color: black;");
imageArea.setPrefWidth(300);
SplitPane splitPane = new SplitPane(new Region(), imageArea);
SplitPane.setResizableWithParent(imageArea, true);
Scene scene = new Scene(splitPane, 1024, 768);
scene.setOnKeyPressed(evt -> {
// toggle between 0° and 90° rotation
imageArea.setRotated(!imageArea.isRotated());
});
primaryStage.setScene(scene);
primaryStage.show();
}
I found a solution :-) Fabian's approach inspired me (thank you!!) And my old friend Pit helped me with debugging (also thank you!!)
It seems that the layout location algorithm of JavaFX has a problem when resize() is applied to rotated Panes (or even Nodes - I have not tried):
Following Fabian's idea I debugged into the layoutChildren() method of class Pane. I found that the relocation after setRotate() is correct and keeps the center of the child pane as expected. But as soon as resize() is called (which is done because of fitting the rotated child pane again into its father and additionally always when the window is resized by the user) the origin calculation goes wrong:
The picture above depicts a sequence of setRotate(90), resize() and relocate() in green and the same for setRotate(270) in blue. A little blue/green circle depicts the corresponding origin together with its coordinates in the 1024x786 example.
Analysis
It seems that for calculation the position of the Pane resize() does not use the height and width from BoundsInParent-Property (see JavaFX-Docu of Node) but from getWidth() and getHeight() which seem to reflect BoundsInLocal. As a consequence, for rotations of 90° or 270° height and width seem to be interchanged. Therefore the error in the calculation for the new origin is just the half of the difference between width and height (delta=(width-height)/2) when resize() tries to center the child pane again after the resizing.
Solution
A relocation(delta,-delta) needs to be applied after resizing for Panes with rotation=90 or 270 degrees.
The structure of my implementation follows Fabian's basic idea: I have build a layouter RotatablePaneLayouter:Region that just overwrites the layoutChildren() method. In its constructor it gets a Pane (in my example a StackPane) which can contain any number of children (in my example an ImageView) and that can be rotated.
LayoutChildren() then just executes resize() and relocate() for the child pane to fit it completely into the RotateablePaneLayouter respecting the orientation of the child pane.
The Layouter Helper (RotateablePaneLayouter:Region)
public class RotatablePaneLayouter extends Region {
private Pane child;
public RotatablePaneLayouter(Pane child) {
getChildren().add(child);
this.child = child;
// make sure layout gets invalidated when the child orientation changes
child.rotateProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
requestLayout();
}
});
}
#Override
protected void layoutChildren() {
// set fit sizes:
//resize child to fit into RotatablePane and correct movement caused by resizing if necessary
if ((child.getRotate() == 90)||(child.getRotate() == 270)) {
//vertical
child.resize( getHeight(), getWidth() ); //exchange width and height
// and relocate to correct movement caused by resizing
double delta = (getWidth() - getHeight()) / 2;
child.relocate(delta,-delta);
} else {
//horizontal
child.resize( getWidth(), getHeight() ); //keep width and height
//with 0° or 180° resize does no movement to be corrected
child.relocate(0,0);
}
}
}
To use it: Place the Pane to be rotated into the Layouter first instead of placing the Pane directly.
Here the code for the example's main program. You can use the space bar to rotate the child pane by 90, 180, 270 and again 0 degrees. You can also resize the window with the mouse. The layouter always manages to place the rotated pane correctly.
Expample for using the Layouter
public class RotationTest extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
//image in a StackPane to be rotated
final ImageView imageView = new ImageView("file:D:/Test_org.jpg");
imageView.setPreserveRatio(true);
StackPane stackPane = new StackPane(imageView); //a stackPane is used to center the image
stackPane.setStyle("-fx-background-color: black;");
imageView.fitWidthProperty().bind(stackPane.widthProperty());
imageView.fitHeightProperty().bind(stackPane.heightProperty());
//container for layouting rotated Panes
RotatablePaneLayouter root = new RotatablePaneLayouter(stackPane);
root.setStyle("-fx-background-color: blue;");
Scene scene = new Scene(root, 1024,768);
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (event.getCode() == KeyCode.SPACE) {
//rotate additionally 90°
stackPane.setRotate((stackPane.getRotate() + 90) % 360);
}
}
});
primaryStage.setTitle("Rotation test");
primaryStage.setScene(scene);
primaryStage.show();
}
}
For me this seems like a workaround of a javaFX bug in resize().

How to disable right click on a menu in javafx

In javaFX code, a menu can popup by left click or right click. How to disable right click?
public void start(Stage primaryStage)
{
BorderPane root = new BorderPane();
MenuBar menuBar = new MenuBar();
Menu hello = new Menu("hello");
menuBar.getMenus().addAll(hello);
Menu world = new Menu("world");
menuBar.getMenus().addAll(world);
root.setCenter(menuBar);
MenuItem item = new MenuItem("laugh");
hello.getItems().add(item);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
When I right click the "hello" menu, it will popup menuitem "laugh".
The basic approach is to register a eventFilter on the MenuBar that consumes the events that should not be delivered to the children.
Doing so manually in your application code:
public class DisableRightClickOpenMenu extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane root = new BorderPane();
MenuBar menuBar = new MenuBar();
menuBar.addEventFilter(MouseEvent.MOUSE_PRESSED, ev -> {
if (ev.getButton() == MouseButton.SECONDARY) {
ev.consume();
}
});
Menu hello = new Menu("hello");
menuBar.getMenus().addAll(hello);
Menu world = new Menu("world");
menuBar.getMenus().addAll(world);
root.setCenter(menuBar);
MenuItem item = new MenuItem("laugh");
hello.getItems().add(item);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If you want this behaviour across all your applications, you can implement a custom menuBarSkin that registers the filter and install the custom skin via a stylesheet.
The skin:
public class ExMenuBarSkin extends MenuBarSkin {
/**
* Instantiates a skin for the given MenuBar. Registers an
* event filter that consumes right mouse press.
*
* #param menuBar
*/
public ExMenuBarSkin(MenuBar menuBar) {
super(menuBar);
menuBar.addEventFilter(MouseEvent.MOUSE_PRESSED, ev -> {
if (ev.getButton() == MouseButton.SECONDARY) {
ev.consume();
}
});
}
}
In your stylesheet (replace with your fully qualified class name):
.menu-bar {
-fx-skin: "de.swingempire.fx.event.ExMenuBarSkin";
}
Its usage (replace the name with your stylesheet file name):
URL uri = getClass().getResource("contextskin.css");
primaryStage.getScene().getStylesheets().add(uri.toExternalForm());
This is usual behavior of menu in many programs. I don't think you can change it. However, you can use some other controls and simulate menu. (Like HBox and Labels).
I agree as far as I know there's no a standard way to do this, but you may want to consider the following workaround.
It is replacing the Menu node with a Menu object composed by an HBox and a Label: an EventHandler is added to the HBox and by checking the mouse button pressed we add/remove on the fly the MenuItem to its parent.
#Override
public void start(final Stage primaryStage) {
final BorderPane root = new BorderPane();
final MenuBar menuBar = new MenuBar();
final Menu menuHello = new Menu();
final Menu menuWorld = new Menu("world");
final MenuItem menuitem = new MenuItem("laugh");
final HBox hbox = new HBox();
menuBar.getMenus().addAll(menuHello, menuWorld);
root.setCenter(menuBar);
hbox.setPrefWidth(30);
hbox.getChildren().add(new Label("hello"));
menuHello.setGraphic(hbox);
menuHello.getItems().add(menuitem);
hbox.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(final MouseEvent e) {
if (e.getButton() == MouseButton.SECONDARY) {
System.out.println("Right click");
menuHello.getItems().remove(menuitem);
} else {
System.out.println("Left click");
if (!menuHello.getItems().contains(menuitem)) {
menuHello.getItems().add(menuitem);
menuHello.show(); // The .show method prevent 'losing' the current click }
}
}
});
final Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
This will produce the following result - preview
Note that I've used an HBox just for habit, there's no a particular reason.
While using a workaround like this, my suggestion would be to fill all the Menus with the same 'pattern', such as the HBox + Label combo in my example, and stylize them via css/code (width/height, background/fill/hover... colors etc.) in order to have them as uniform as possible and avoid creating graphic inconsistencies due to have different nodes types in the same menubar.

How to detect when JavaFx mouse event occurs in the "label" are of a TitledPane?

I have an Accordian with multiple TitledPanes. When a TitledPane is expanded, there are "dead areas" on the pane that do not have sub-components (e.g., buttons, text, etc.).
Right now, when I check MouseEvent.getSource(), it returns an instance of TitledPane for all areas. Is there a way to specifically constrain/check for a mouse-click on the "title" section of the TitledPane?
I don't think there is a public API to detect mouse click on title region, however it is possible to do that this way:
titledPane.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
EventTarget target = event.getTarget();
String clazz = "class com.sun.javafx.scene.control.skin.TitledPaneSkin$TitleRegion";
if(target.getClass().toString().equals(clazz) || // anywhere on title region except title Text
(target instanceof Node && ((Node) target).getParent().getClass().toString().equals(clazz))) // title Text
System.out.println("title was clicked");
}
});
But this method is highly discouraged as it relies on some internal implementation detail that may be subject to change.
May be it's better to think more about what you actually need. Maybe your actual requirement can be fulfilled by uisng TitledPane's public API methods and properties.
For insatnce expandedProperty()'s value gets changed every time mouse click occurs on title region (if isCollapsible() is set to true).
titledPane.expandedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
System.out.println("mouse click changed expanded from " + oldValue + " to " + newValue);
}
});
In the css reference you can find out that there is a child of the TitledPane that has the style class title. It isn't hard to guess that this part is the title. You can go from the pick result up through the scene graph until you either find a node with the style class title or until you reach the Accordion.
The following code colors the rect below the Accordion green, iff the mouse is on a title and red otherwise:
#Override
public void start(Stage primaryStage) {
TitledPane tp1 = new TitledPane("foo", new Rectangle(100, 100));
TitledPane tp2 = new TitledPane("bar", new Circle(100));
Accordion accordion = new Accordion(tp1, tp2);
Rectangle rect = new Rectangle(100, 20, Color.RED);
accordion.addEventFilter(MouseEvent.MOUSE_MOVED, evt -> {
Node n = evt.getPickResult().getIntersectedNode();
boolean title = false;
while (n != accordion) {
if (n.getStyleClass().contains("title")) {
// we're in the title
title = true;
break;
}
n = n.getParent();
}
rect.setFill(title ? Color.LIME : Color.RED);
});
Scene scene = new Scene(new VBox(accordion, rect), 100, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
Note that with an event filter for MouseEvent.MOUSE_CLICKED you could simply consume the event, if the pick result is not in a title...

Color Change in Area Chart

i have a Java-Class that extends the AreaChart. There i would like to implement a method that makes more or less something like this:
public void addNewColorToData(xCoordinate, yCoordinate, redColor, greenColor, blueColor);
-> The Function should get the parameters of the Data for the xCoordinate, yCoordinate and then for the RGB Value of the representes Line.
Is it possible to create with Inline-Styles a new Color for this ?
Here you can see a Sample. There are a lot of Color-Fills for the Area Chart!
Is it possible to add there some new colors?
I need to add a Inline Style stuff like this in CSS:
.default-color0.chart-series-area-fill { -fx-fill: #007Fc350; }
Thank you for your help
Based on this answer, to create an inline style based on r,g,b parameters (given these are integers from 0 to 255) you just need to override the CHART_COLOR_1 (up to CHART_COLOR_8) value to modify the line color and CHART_COLOR_1_TRANS_20 (up to CHART_COLOR_8_TRANS_20) to modify the area color:
private AreaChart<String, Number> areaChart;
private void changeColor(int redColor, int greenColor, int blueColor, double opacity){
/* int redColor=0, greenColor=127, blueColor=195;
double opacity=0.4;
*/
areaChart.setStyle("CHART_COLOR_1: rgb("+redColor+","+greenColor+","+blueColor+");" +
"CHART_COLOR_1_TRANS_20: rgba("+redColor+","+greenColor+","+blueColor+");");
}
EDIT
I'm adding this short MVCE for the sake of clarity:
#Override
public void start(Stage primaryStage) {
AreaChart<String, Number> areaChart=new AreaChart<>(new CategoryAxis(),new NumberAxis());
ObservableList<XYChart.Data<String,Integer>> xyList
= FXCollections.observableArrayList(
new XYChart.Data<>("P1", 30),
new XYChart.Data<>("P2", 40),
new XYChart.Data<>("P3", 30));
XYChart.Series series = new XYChart.Series(xyList);
areaChart.getData().addAll(series);
Button button = new Button("Change style");
button.setOnAction(e->{
int redColor=0, greenColor=127, blueColor=195;
double opacity=0.3;
areaChart.setStyle("CHART_COLOR_1: rgb("+redColor+","+greenColor+","+blueColor+"); "
+ "CHART_COLOR_1_TRANS_20: rgba("+redColor+","+greenColor+","+blueColor+","+opacity+");");
});
VBox root = new VBox(5, button, areaChart);
Scene scene = new Scene(root, 400, 300);
primaryStage.setScene(scene);
primaryStage.show();
}
This would be the result:

How do i make a TabPane fill its parent?

My TabPane only seems to fill it's width horizontally, but not vertically. My workaround for now is to do this:
stage.getScene().heightProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
System.out.println("height changed");
tabPane.setPrefHeight(newValue.doubleValue());
}
});
But if i use that height it's obviously larger than the area that actually remains for it (theres a MenuBar above the TabPane). (Or does it adjust it's size properly?) It also kinda feels wrong, there got to be simple boolean i can set, since it behaves exactly as expected for horizontal changes.
The Scene is setup like this:
public static ResourceBundle BUNDLE = ResourceBundle.getBundle("locales/Bundle", new Locale("en", "GB"));
Scene scene = new Scene(new VBox(0), 800, 600);
MenuBar menuBar = new MenuBar();
Menu menuStart = new Menu(BUNDLE.getString("menu.start"));
Menu menuView = new Menu(BUNDLE.getString("menu.view"));
Menu menuHelp = new Menu(BUNDLE.getString("menu.help"));
menuBar.getMenus().addAll(menuStart, menuView, menuHelp);
((VBox) stage.getScene().getRoot()).getChildren().add(menuBar);
TabPane tabPane = new TabPane();
((VBox) stage.getScene().getRoot()).getChildren().add(tabPane);
stage.setScene(scene);
stage.show();
Ofcourse there is some more code, but it only contains drag'n'drop related listeners, and this problem occurs way before any of them ever did anything (they print to console everytime they do something).
The Tab setup:
final Tab tab = new Tab();
final Label label = new Label("Tab" +text);
tab.setGraphic(label);
StackPane pane = new StackPane();
int red = rng.nextInt(256);
int green = rng.nextInt(256);
int blue = rng.nextInt(256);
String style = String.format("-fx-background-color: rgb(%d, %d, %d);", red, green, blue);
pane.setStyle(style);
Label label = new Label("This is tab " + text);
label.setStyle(String.format("-fx-text-fill: rgb(%d, %d, %d);", 256 - red, 256 - green, 256 - blue));
pane.setPrefSize(500, 500);
pane.getChildren().add(label);
tab.setContent(pane);
Which is from How to drag and drop tab nodes between tab panes
VBox.setVgrow(tabPane, Priority.ALWAYS);

Resources