When you create a rounded VBox in JavaFX, and the background color for the VBox is black, assume the rounded corners after filling the button is white, How can I fill this region with another color (Assume I want it to be totally transparent).
I assume, the questioneer wants to finally hava a floating round VBox.
This can probably be achieved in many ways, without thinking to much about it, I would rather make use of a clipping Node instead of a pure CSS approach (which should be doable as well).
As he already wrote, you would also need to make the Scenes fill Color.TRANSPARENT (and probably the Stage as well).
My approach looks like this. First the FXML file:
<?import java.lang.*?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<BorderPane xmlns:fx="http://javafx.com/fxml" fx:id="root" fx:controller="application.ClipExampleController" style="-fx-background-color: steelblue;">
<center>
<VBox fx:id="vbox" alignment="CENTER" spacing="5" maxWidth="150">
<Label text="Bla"/>
<TextField promptText="Blub"/>
<Button text="Do it"/>
</VBox>
</center>
</BorderPane>
Then the Controller class:
package application;
import javafx.beans.binding.DoubleBinding;
import javafx.fxml.FXML;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
public class ClipExampleController {
#FXML
private BorderPane root;
#FXML
private VBox vbox;
#FXML
protected void initialize() {
Circle clip = new Circle();
clip.radiusProperty().bind(new DoubleBinding() {
{
bind(vbox.widthProperty());
}
#Override
protected double computeValue() {
return vbox.getWidth() / 2 + 25;
}
});
clip.centerXProperty().bind(new DoubleBinding() {
{
bind(root.widthProperty());
}
#Override
protected double computeValue() {
return root.getWidth() / 2;
}
});
clip.centerYProperty().bind(new DoubleBinding() {
{
bind(root.heightProperty());
}
#Override
protected double computeValue() {
return root.getHeight() / 2;
}
});
root.setClip(clip);
}
}
and finally the glue code - the main Application:
package application;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("ClipExample.fxml"));
Parent root = loader.load();
Scene scene = new Scene(root, 300, 300);
scene.setFill(Color.TRANSPARENT);
stage.initStyle(StageStyle.TRANSPARENT);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
So. Assuming, that I understand you correctly, this would be my solution.
assume the rounded corners after filling the button is white, How can I fill this region with another color
Are you talking about the "Stroke?" There is "Fill" (Inside) and "Stroke" (outline, outer-rim, edge, etc)
https://docs.oracle.com/javase/8/javafx/api/javafx/scene/shape/Shape.html
The Shape class provides definitions of common properties for objects that represent some form of geometric shape.
These properties include:
The Paint to be applied to the fillable interior of the shape (see setFill).
The Paint to be applied to stroke the outline of the shape (see setStroke).
https://docs.oracle.com/javase/8/javafx/api/javafx/scene/shape/Shape.html#setStroke-javafx.scene.paint.Paint- (in case you didn't click setStroke above)
public final void setStroke(Paint value)
Sets the value of the property stroke.
Property description:
Defines parameters of a stroke that is drawn around the outline of a Shape using the settings of the specified Paint. The default value is null for all shapes except Line, Polyline, and Path. The default value is Color.BLACK for those shapes.
From here you fill it with a "Paint" Object which is a base class for many different Classes such as "Color"
https://docs.oracle.com/javase/8/javafx/api/javafx/scene/paint/Color.html#TRANSPARENT
public static final Color TRANSPARENT
A fully transparent color with an ARGB value of #00000000.
So... To sum this up, you are going to want to do.
button.setStroke(Color.TRANSPARENT);
If this, of course, is what you're asking, since it's hard to tell...; However, it seems what I have provided is what you ask, but if not I'll try again :).
In order to set the container's background of the VBox to be transparent, then you need to set the fill property of the scene that contains the VBox to TRANSPARENT COLOR, the following piece of code clarifies that:
This is the vbox style:
.vbox
{
-fx-background-color: black;
-fx-background-radius: 300%;
-fx-alignment:center;
}
if you applied the above style to a vbox with width and height = 200 you will get a circle with black background while the rounded corners filled with white. To make this white corners transparent, you need to add this piece of code:
loader.setLocation(MainApp.class.getResource("view/Test.fxml"));
VBox page = (VBox) loader.load();
Stage testStage = new Stage();
Scene scene = new Scene(page);
scene.setFill(Color.TRANSPARENT);
testStage.setScene(scene);
Related
I have some code snipped out of a much bigger app, which renders some white text on a black background in a JavaFX WebView. The background colour of the page is set to transparent, using some code from Transparent background in the WebView in JavaFX
import java.lang.reflect.Field;
import org.w3c.dom.Document;
import com.sun.webkit.WebPage;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class TestWebView extends Application {
public void start(Stage stage) throws Exception {
StackPane stackpane = new StackPane();
Scene scene = new Scene(stackpane, stage.getWidth(), stage.getHeight(), Color.BLACK);
stage.setScene(scene);
scene.setFill(Color.BLACK);
stackpane.setStyle("-fx-background-color: BLACK");
WebView webview = new WebView();
stackpane.getChildren().add(webview);
WebEngine webengine = webview.getEngine();
webengine.documentProperty().addListener(new WebDocumentListener(webengine));
webengine.loadContent("<p style='color:white'>Hello World</p>");
stage.show();
}
public static void main(String[] args) {
launch(args);
}
protected class WebDocumentListener implements ChangeListener<Document> {
private final WebEngine wdl_webEngine;
public WebDocumentListener(WebEngine webEngine) {
wdl_webEngine = webEngine;
}
#Override
public void changed(ObservableValue<? extends Document> arg0, Document arg1, Document arg2) {
try {
Field f = wdl_webEngine.getClass().getDeclaredField("page");
f.setAccessible(true);
com.sun.webkit.WebPage page = (WebPage) f.get(wdl_webEngine);
page.setBackgroundColor((new java.awt.Color(0, 0, 0, 0)).getRGB());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Testing on MacOS 10.11.6, with Oracle's JDK:
With JDK 1.8.0_152, this code works nicely - I get white text on black. (And the transparency works too when I layer things underneath it in the stackpane)
With JDK 9 (9+181), com.sun.webkit.WebPage is no longer accessible, so I have to compile and run it with --add-exports javafx.web/com.sun.webkit=ALL-UNNAMED - but having done that, I get black text on a black screen. I can tell the text is there by selecting the text and dragging it, which makes the text appear white while being dragged.
Ideally, I'd like to keep a single codebase that works for both JDK 8 and 9. (Java's usually been good to me with backward compatibility). Or as a second best, how do I get the white text I'm expecting in JDK 9?
Can anyone point me in the right direction? Many thanks in advance.
I had the same issue, I solved it by going further in the reflective process :
Field f = webEngine.getClass().getDeclaredField("page");
f.setAccessible(true);
Object page = f.get(webEngine);
Method m = page.getClass().getMethod("setBackgroundColor", int.class);
m.setAccessible(true);
m.invoke(page, (new java.awt.Color(0, 0, 0, 0)).getRGB());
I need to make a group of toggle button like the following, with the white background being the selected button, and two buttons take 50% width of the parent container. Two toggle buttons are place inside HBox. The styling
So far I tried, stuck like this.
.viewType .toggle-button {
-fx-padding: 0 2 0 2;
-fx-background-color: #000;
-fx-text-fill: white;
}
.viewType .toggle-button:selected {
-fx-padding: 0;
-fx-background-color: white;
-fx-text-fill: black;
-fx-border-wdith: 2;
-fx-border-color: black;
-fx-border-radius: 4;
}
You can set the buttons maxWidth to max double in java side. This will provide your buttons to same width in HBox. Hope it is useful:
btn1.setMaxWidth(Double.MAX_VALUE);
btn2.setMaxWidth(Double.MAX_VALUE);
You can check the following link for useful information related with sizing and aligning nodes:
Sizing and Aligning Nodes
If you want both buttons to have equal width, use a GridPane instead of a HBox, and use column constraints to make the two columns equal widths:
GridPane grid = new GridPane();
grid.getColumnConstraints().addAll(createCol(), createCol());
ToggleButton toggle1 = new ToggleButton("...");
toggle1.setMaxWidth(Double.MAX_VALUE);
ToggleButton toggle2 = new ToggleButton("...");
toggle2.setMaxWidth(Double.MAX_VALUE);
grid.addRow(0, toggle1, toggle2);
// ...
private ColumnConstraints createCol() {
ColumnConstraints col = new ColumnConstraints();
col.setPercentWidth(50);
col.setFillWidth(true);
return col ;
}
You can further control how big the grid pane is in its parent by configuring the parent (details depend on what type of pane is used for the parent).
SSCCE:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class EqualSizedButtons extends Application {
#Override
public void start(Stage primaryStage) {
GridPane grid = new GridPane();
grid.getStyleClass().add("viewType");
grid.getColumnConstraints().addAll(createCol(), createCol());
ToggleButton toggle1 = new ToggleButton("A");
toggle1.setMaxWidth(Double.MAX_VALUE);
ToggleButton toggle2 = new ToggleButton("This is really big button B");
toggle2.setMaxWidth(Double.MAX_VALUE);
grid.addRow(0, toggle1, toggle2);
new ToggleGroup().getToggles().addAll(toggle1, toggle2);
BorderPane root = new BorderPane();
root.setBottom(grid);
Scene scene = new Scene(root, 600, 600);
scene.getStylesheets().add("style.css");
primaryStage.setScene(scene);
primaryStage.show();
}
private ColumnConstraints createCol() {
ColumnConstraints col = new ColumnConstraints();
col.setPercentWidth(50);
col.setFillWidth(true);
return col ;
}
public static void main(String[] args) {
launch(args);
}
}
Can I force the TextArea control to automatic expanding the height?
In the following case, I would like to see the scrollbar at ScrollPane control, not at TextArea control.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.VBox?>
<ScrollPane fitToHeight="true" fitToWidth="true" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity"
minWidth="-Infinity" xmlns="http://javafx.com/javafx/8.0.91" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="sample.Controller">
<VBox style="-fx-background-color: bisque">
<TextField/>
<TextArea VBox.vgrow="ALWAYS">
<VBox.margin>
<Insets top="20.0"/>
</VBox.margin>
</TextArea>
</VBox>
</ScrollPane>
For now the only solution that is close to your problem is given by #Uluk Biy, that i found here, what i did is just fit his logic, and hide the ScrollBars. The only problem is the size of the TextArea which is binded to that of the Text and so it starts with a minimum height at the beginning of the edition, here is the complete code :
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.Pane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class Launcher extends Application{
private Pane root = new Pane();
private Scene scene;
private ScrollPane scroller;
private Pane content = new Pane();
private TextField textF = new TextField();
private TextArea textA = new TextArea();
private Text textHolder = new Text();
private double oldHeight = 0;
#Override
public void start(Stage stage) throws Exception {
root.getChildren().addAll(yourSP());
scene = new Scene(root,300,316);
stage.setScene(scene);
stage.show();
}
private ScrollPane yourSP(){
content.setMinSize(300, 300);
textF.setPrefSize(260, 40);
textF.setLayoutX(20);
textF.setLayoutY(20);
textA.setPrefSize(260, 200);
textA.setLayoutX(20);
textA.setLayoutY(80);
textA.getStylesheets().add(getClass().getResource("texta.css").toExternalForm());
content.getChildren().addAll(textA,textF);
/*************************#Uluk Biy Code**************************/
textA.setWrapText(true);
textHolder.textProperty().bind(textA.textProperty());
textHolder.layoutBoundsProperty().addListener(new ChangeListener<Bounds>() {
#Override
public void changed(ObservableValue<? extends Bounds> observable, Bounds oldValue, Bounds newValue) {
if (oldHeight != newValue.getHeight()) {
oldHeight = newValue.getHeight();
textA.setPrefHeight(textHolder.getLayoutBounds().getHeight() + 20);
System.out.println(textHolder.getLayoutBounds().getHeight());
}
}
});
/****************************************************************/
scroller = new ScrollPane(content);
scroller.setHbarPolicy(ScrollBarPolicy.NEVER);
scroller.setPrefSize(300, 316);
return scroller;
}
public static void main(String[] args) {
launch(args);
}
}
Of course the code can be adapted to fxml format, I just have not had enough time to do it, and here is the style of the TextArea:
.text-area > .scroll-pane{
-fx-hbar-policy:never;
-fx-vbar-policy:never;
}
good luck for the continuation !
I'm not saying that I like the solution. It's hacky as hell but actually works a lot better then the one with textHolder - is more stable. It's written in Kotlin with TornadoFX. For Java it should work the same, but probably with a lot more lines of code ;)
The principle is basically the same, but instead of dedicated textHolder object we are using the actual Text node inside the TextArea.
class ExpandableTextArea : TextArea() {
init {
addClass("expandable")
isWrapText = true
children.onChange { a ->
val scrollPane = a.list.first() as ScrollPane
val contentView = scrollPane.content as Region
contentView.childrenUnmodifiable.onChange { b ->
b.next()
if (b.list.size == 2) {
val group = b.list[1] as Group
group.children.onChange { c ->
val text = c.list.first() as Text
text.layoutBoundsProperty().onChange {
if (it != null) {
val targetHeight = it.height + font.size
prefHeight = targetHeight
minHeight = targetHeight
}
}
}
}
}
}
}
}
Of course you still need the same CSS to turn the scrollbars off:
.text-area > .scroll-pane{
-fx-hbar-policy:never;
-fx-vbar-policy:never;
}
Please note that here I'm also setting the minHeight because I want this node to expand "brutally" so it never has to scroll. You can remove that and leave setting the prefHeight but if there is not enough space the node will stop growing and will be scrollable by mouse but without the scrollbars which can be confusing.
BE WARNED: if for some forsaken reason the TextArea children tree structure changes this will blow up.
I'm very green when it comes to JavaFX and programming in generally, especially object oriented. I've worked in the Main() method initially and have been able to create more complex shapes using multiple Shapes and employing Unions and Subtractions. Now I'd like to be able to create a new Class/Object that I can call on to reuse that code.
I thought it'd be along the lines of creating a "complexshape" class and extending Shapes. Then build up my complex shape the same as I did in main(). Then go back to main and Instantiate an object via a constructor and place the object into my layout via layout.getChildren().add(objectname);
But my IDE tells me the "complexshape" class must be abstract, Which I have a partial clue what that means. But I'm not exactly sure what to do about it.
Any ideas on why my logic is wrong here?
Main.java
package sample;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane();
Scene scene = new Scene(root, 100,100,Color.WHITE);
complexShape A = new complexShape();
root.getChildren().add(A);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
complexShape.java
package sample;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
public class complexShape extends Shape {
public Shape complesShape() {
Circle A = new Circle(50,50,10);
Rectangle B = new Rectangle(50,50,100,10);
Shape C = Shape.union(A, B);
C.setFill(Color.RED);
return C;
}
}
Best way to deal with this is to use a layout as a container for all of your objects (i.e. Rectangle, Circle, etc.) and create field or properties you can alter after the fact. So create a class that extends some type of layout (HBox, VBox, GridPane, etc.) and place all your shape objects in there. If you need to subtract to one shape from another and it needs to be dynamic, create a method that'll rebuild the shape as it is called.
Simplified Problem:
Make one Node "A" that is on top of another Node "B" to be half transparent to MouseEvents, so the Events will reach the underlying Node "B". Both Nodes are of equal size but Node "A" has a half transparent background image so one half of Node "B" is visible.
Real Problem:
I have a menu of tabs. Each tab can be dragged to expand the corresponding menu layer. Therefore each tab layer is a Pane with a partially transparent background (basically a polygon) of which the transparent part should be also transparent to MouseEvents.
The illustration (which I can't post yet, see link: Illustration of tabs, the dark green line is the border of the green Pane) shows the basic principle: just imagine only the tabs are visible and the layer itself can be pulled to the right to view it's content.
So the question is, how do I make a region of a Node transparent to MouseEvents without making the whole Node transparent?
Thank you for your help!
Update:
To clarify the simple problem here is the corresponding code:
//Create parent group
Group root = new Group();
//Create linear gradient, so one side is transparent
Stop[] stops = new Stop[] { new Stop(0, Color.rgb(0, 255, 0, 0.0)), new Stop(1, Color.rgb(0, 255, 0, 1.0))};
LinearGradient lg1 = new LinearGradient(0, 0, 1, 0, true, CycleMethod.NO_CYCLE, stops);
//Create the rectangles
Rectangle A = new Rectangle(100, 50, lg1);
Rectangle B = new Rectangle(100,50, Color.RED);
//Add eventHandlers
A.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
System.out.println("Clicked A");
}
});
B.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
System.out.println("Clicked B");
}
});
root.getChildren().addAll(B, A);
//Add to Scene..
Hope this helps.
Consider the pickOnBounds property, it may help in your situation, but it is not clear to me without seeing your code attempt which fails for the simplified problem.
node.setPickOnBounds(true)
If pickOnBounds is true, then picking is computed by intersecting with the bounds of this node, else picking is computed by intersecting with the geometric shape of this node.
The code below demonstrates how this may be used by creating a square overlaid by an ImageView for an Image which contains tranparent pixels. If pickOnBounds is set to true for the ImageView, then, even if you click on the transparent pixels in the image, the ImageView will receive the mouseClick event. If pickOnBounds is set to false for the ImageView, then, even if you click on the transparent pixels in the image, the ImageView will not process the click and the click event will be received by the node behind the image.
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class PickOnBoundsDemo extends Application {
public static void main(String[] args) { Application.launch(args); }
#Override public void start(Stage stage) {
final Rectangle back = new Rectangle(128, 128);
back.setFill(Color.FORESTGREEN);
final ImageView front = new ImageView("http://icons.iconarchive.com/icons/aha-soft/free-large-boss/128/Wizard-icon.png");
// icon: Linkware (Backlink to http://www.aha-soft.com required)
final StackPane pickArea = new StackPane();
pickArea.getChildren().addAll(
back,
front
);
final ToggleButton pickTypeSelection = new ToggleButton("Pick On Bounds");
final Label pickResult = new Label();
Bindings.bindBidirectional(front.pickOnBoundsProperty(), pickTypeSelection.selectedProperty());
front.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent t) {
pickResult.setText("Front clicked");
}
});
back.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent t) {
pickResult.setText("Back clicked");
}
});
VBox layout = new VBox(10);
layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 10;");
layout.getChildren().setAll(
pickArea,
new Label("Click inside the above area to test mouse picking."),
pickTypeSelection,
pickResult
);
layout.setAlignment(Pos.CENTER);
stage.setScene(new Scene(layout));
stage.show();
}
}