I have a custom JavaFX component that is basically just a combination of a pane and textfield contained within a VBox.
What I want to do is have the bloody image turn brighter red when the textfield has focus or the mouse is over the VBox as a whole, because I think it would look nice. As of right now, my plan for this is just doing it programmatically. I am unaware of how I could do this using just CSS (Let me know if this is possible though, but this isn't related to the question).
The problem
The problem I am having is that when I try to resize the component along the x-axis using the component's pref width setting, the component doesn't visually stretch / resize, it just stays the same. When I change the pref size of the VBox in the original components fxml file, the textfield and pane expand as well. I could use CSS to set the pref width for all the elements within the custom component / the VBox in the CSS for scenes that would use the component, but I want to have the component automatically expand to fill their containers and I want so just easily be able to resize it within scene builder without changing CSS. Basically the underlying problem is I don't know how to get the VBox to resize when the component resizes. I was thinking I could add a custom field to do this but I am assuming there is an easier way of doing this, as the situation I am in would seem to be common, even if I can't find a solution for it when searching.
For reference here is the fxml for the custom component:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.VBox?>
<VBox maxWidth="1.7976931348623157E308" styleClass="bloody-text-field" stylesheets="#../CSS/BloodyTextField.css" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1">
<children>
<TextField fx:id="textField" maxHeight="-Infinity" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" />
<Pane fx:id="bloodyPane" maxHeight="-Infinity" maxWidth="1.7976931348623157E308" minHeight="35.0" minWidth="-Infinity" styleClass="bloody-pane" />
</children>
</VBox>
Here is the CSS for the custom component. The image shouldn't be important (Its just tileable in the x dimension) so I wont include it.
.bloody-text-field .text-field {
-fx-font-family: chiller;
-fx-font-size: 32px;
-fx-text-fill: #eb0900;
-fx-text-alignment: center;
-fx-alignment: center;
-fx-background-color: rgba(0.5, 0, 0, 0.0);
-fx-prompt-text-fill: #841200;
}
.bloody-text-field .bloody-pane {
-fx-background-repeat: repeat-x;
-fx-background-size: contain;
-fx-background-image: url("../Images/blood-drip-tileable.png");
}
Here is a fxml file that explains what I want to happen.
<?xml version="1.0" encoding="UTF-8"?>
<?import controls.BloodyTextField?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Text?>
<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1">
<children>
<BloodyTextField layoutX="32.0" layoutY="50.0" prefHeight="91.0" prefWidth="386.0" />
<Text layoutX="32.0" layoutY="45.0" strokeType="OUTSIDE" strokeWidth="0.0" text="I would expect this to expand until it fills the pref width" />
<VBox layoutX="32.0" layoutY="186.0" prefHeight="200.0" prefWidth="462.0">
<children>
<BloodyTextField />
<BloodyTextField />
</children>
</VBox>
<Text layoutX="32.0" layoutY="182.0" strokeType="OUTSIDE" strokeWidth="0.0" text="I would expect these two components to expand to fill the vbox's width" />
</children>
</Pane>
Thanks in advance for the guidance and help :)
Related
I want my Chat Window to be reziable and scrollable, so far everything works except the ChatLog itself.
I need a scroll bar, that's why I put the VBox inside of a ScrollPane (which is child of an AnchorPane), but this way only the ScrollPane is responsive (thanks to Anchor Values). If I unwrap VBox I can set Anchor Values, then it works but I'm loosing my scroll bar.
How can I mantain the scroll bar for ChatLog AND make it responsive (attached on the right side)?
FXML:
<AnchorPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" style="-fx-background-color: #5b2529;" xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.lmu.jungejunkervp.ClientWindowController">
<children>
<ScrollPane fx:id="scrollPane" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" prefHeight="265.0" prefWidth="592.0" AnchorPane.bottomAnchor="57.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="5.0">
<content>
<VBox fx:id="chatLog" prefHeight="265.0" prefWidth="575.0" />
</content></ScrollPane>
<TextArea fx:id="messageBox" layoutX="5.0" layoutY="349.0" onKeyPressed="#onEnterSend" prefHeight="47.0" prefWidth="536.0" promptText="enter message..." AnchorPane.bottomAnchor="5.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="61.0" />
<Button fx:id="sendButton" mnemonicParsing="false" onAction="#setSendButtonAction" onMouseClicked="#setSendButtonAction" prefHeight="47.0" prefWidth="52.0" text="Send" AnchorPane.bottomAnchor="5.0" AnchorPane.rightAnchor="5.0" />
</children>
</AnchorPane>
public void addLabel(String message, Pos position) {
HBox hBox = new HBox();
hBox.setAlignment(position);
hBox.setPadding(new Insets(5, 5, 5, 10));
Text text = new Text(message);
TextFlow textFlow = new TextFlow(text);
textFlow.setStyle("-fx-background-color: rgb(233,233,235);" +
"-fx-background-radius: 20px");
textFlow.setPadding(new Insets(5, 10, 5, 10));
hBox.getChildren().add(textFlow);
Platform.runLater(new Runnable() {
#Override
public void run() {
chatLog.getChildren().add(hBox);
}
});
}
public void setSendButtonAction() {
String message = messageBox.getText().replaceAll("[\n\r]", "");
try {
if (!message.isEmpty()) {
// show message on the sending client window
addLabel(message, Pos.CENTER_RIGHT);
}
}
To answer your actual question, below is what you need add to the ScrollPane (in fxml with your current layout).
fitToHeight="true" fitToWidth="true"
The above code will make your VBox responsive with the ScrollPane.
I also suggest to change your layout to get rid of AnchorPane (will all those hardcoded positions). You can use VBox/HBox in conjuction with vgrow/hgrow policies. The optimized fxml will be as below:
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.HBox?>
<?import javafx.geometry.Insets?>
<VBox style="-fx-background-color: #5b2529;" spacing="5" xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.lmu.jungejunkervp.ClientWindowController">
<children>
<ScrollPane fx:id="scrollPane" prefHeight="265.0" prefWidth="592.0" fitToHeight="true" fitToWidth="true" VBox.vgrow="ALWAYS">
<content>
<VBox fx:id="chatLog"/>
</content>
</ScrollPane>
<HBox spacing="5">
<TextArea fx:id="messageBox" onKeyPressed="#onEnterSend" prefHeight="47.0" promptText="enter message..." HBox.hgrow="ALWAYS" />
<Button fx:id="sendButton" mnemonicParsing="false" onAction="#setSendButtonAction" prefHeight="47.0" minWidth="52.0" text="Send"/>
</HBox>
</children>
<padding>
<Insets topRightBottomLeft="5"/>
</padding>
</VBox>
Having said that, I think you need to consider changing your layout to what #jewelsea mentioned in the provided example (using ListView).
I want to add spacing between the ComboBox and the TextField. I added spacing to the Box however since there are 4 nodes in the HBox, it adds spacing to all of them which isn't what I want. I want the TextField to be on the right of the window. I was thinking of adding an invisible separator with a large width however I read that regions can be used but I'm unsure on how to use them in FXML.
This is the code I have currently, but unsure how to add a region to it.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.TextFlow?>
<BorderPane prefHeight = "200.0" prefWidth = "300.0" xmlns =
<top>
<HBox alignment = "CENTER_LEFT" prefWidth = "300.0" spacing = "5">
<padding>
<Insets topRightBottomLeft="50" />
</padding>
<Label>Sort by: </Label>
<ComboBox fx:id = "sortOrder" promptText = "Select" />
</HBox>
</top>
</BorderPane>
Don't hard-code preferred sizes.
To make the text field sit at the right edge of the HBox, add an empty Region between the ComboBox and TextField. Set the region's hgrow parameter to ALWAYS, so any extra space will get allocated to it:
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.TextFlow?>
<BorderPane xmlns = "http://javafx.com/javafx/16" xmlns:fx = "http://javafx.com/fxml/1" >
<top>
<HBox alignment = "CENTER_LEFT" spacing = "5">
<padding>
<Insets topRightBottomLeft="20" />
</padding>
<Label>Sort by: </Label>
<ComboBox fx:id = "sortOrder" promptText = "Select" />
<Label>Search by host name: </Label>
<Region HBox.hgrow="ALWAYS" />
<TextField fx:id = "hostName" />
</HBox>
</top>
</BorderPane>
After stretching the window:
You can further control the behavior if you need; e.g. to let the text field grow, set it's maxWidth to Infinity and set the maxWidth of the Region. Other solutions are possible; e.g. put the label and combo box in a HBox, and then wrap the HBox and TextField in a BorderPane, with the HBox in the left and the TextField in the right.
I have built a layout in SceneBuilder which has a ScrollPane (inside of a StackPane) containing a StackPane containing a Group (aligned to center-left) containing an ImageView. For some reason, whether I preview within SceneBuilder with Ctrl+P or run in my program, the horizontal scrollbar is disabled. The scrollbar does show that there is more to the right to be scrolled to, but I cannot scroll to it. It looks like this:
And here is the FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.Group?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.effect.DropShadow?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.StackPane?>
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" style="-fx-background-color: white;" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<center>
<StackPane prefHeight="150.0" prefWidth="200.0" style="-fx-background-color: white;" BorderPane.alignment="CENTER">
<children>
<ScrollPane id="scoreScrollPane" fitToHeight="true" hbarPolicy="ALWAYS" prefHeight="0.0" prefWidth="0.0" vbarPolicy="NEVER">
<content>
<StackPane alignment="CENTER_LEFT">
<children>
<Group id="scoreGroup" StackPane.alignment="CENTER_LEFT">
<children>
<ImageView id="scoreImage" fitHeight="150.0" fitWidth="3000.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="#Untitled.png" />
</image>
</ImageView>
</children>
</Group>
</children>
</StackPane>
</content>
</ScrollPane>
<HBox id="toolbar" alignment="TOP_CENTER" prefHeight="100.0" prefWidth="200.0" spacing="8.0">
<children>
<Button id="recordButton" mnemonicParsing="false" text="Record" />
<Button id="stopButton" mnemonicParsing="false" text="Stop" />
</children>
<effect>
<DropShadow />
</effect>
</HBox>
</children>
</StackPane>
</center>
</BorderPane>
I've tried a horizontal scroll policy of both AS_NEEDED and ALWAYS.
You have numerous problems (most important is that the HBox with your controls covers your ScrollPane, intercepting that would otherwise go the ScrollPane):
Set preserveRatio="false" instead of preserveRatio="true" for your ImageView, otherwise the image may not grow to the fitWidth you provide (because it may hit the fitHeight limit first and not grow the width anymore).
Set maxHeight="-Infinity" on your HBox, (this will ensure that the maximum height of the HBox does not grow above the preferred height for the HBox, otherwise the HBox will intercept the mouse clicks intended for your ScrollPane). Either this, or set pickOnBounds="false" for the HBox, so that the HBox does not intercept mouse clicks for the ScrollPane even though it overlays the ScrollPane.
Note: to debug layout sizes, sometimes it is useful to temporarily add a background or border to a region to see it's true size, for example style="-fx-background-color: red;".
Also rather than placing your controls and image in a StackPane, which overlays content, maybe you might want to use a VBox instead, which instead lays things out vertically rather than on top of each other.
I would like to have buttons with equal(maximum) width in a gridpane.
When I'm trying to set it directly in FXML - it works perfectly.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.*?>
<AnchorPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="TestController">
<children>
<GridPane AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0" AnchorPane.bottomAnchor="0"
AnchorPane.topAnchor="0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" percentWidth="50.0"/>
<ColumnConstraints hgrow="SOMETIMES" percentWidth="50.0"/>
</columnConstraints>
<children>
<Button maxWidth="Infinity" text="Button1" GridPane.columnIndex="0"/>
<Button maxWidth="Infinity" text="Button2" GridPane.columnIndex="1"/>
</children>
</GridPane>
</children>
</AnchorPane>
But when I wanted to separate the whole grid into a custom control, the buttons stopped to fill the available width.
Here's a FXML:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.*?>
<?import CustomGridPane?>
<AnchorPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="TestController">
<children>
<CustomGridPane AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0" AnchorPane.bottomAnchor="0" AnchorPane.topAnchor="0">
</CustomGridPane>
</children>
</AnchorPane>
And an extension:
public class CustomGridPane extends GridPane {
private Button button1 = new Button("button1");
private Button button2 = new Button("button2");
public CustomGridPane() {
super();
button1.maxWidth(Double.MAX_VALUE);
button2.maxWidth(Double.MAX_VALUE);
getColumnConstraints().add(new ColumnConstraints());
getColumnConstraints().add(new ColumnConstraints());
getColumnConstraints().get(0).setPercentWidth(50);
getColumnConstraints().get(0).setHgrow(Priority.SOMETIMES);
getColumnConstraints().get(1).setPercentWidth(50);
getColumnConstraints().get(1).setHgrow(Priority.SOMETIMES);
add(button1, 0, 0);
add(button2, 1, 0);
}
}
Am I missing something?
You are using maxWidth property getter instead of the method setMaxWidth (setter).
Please see the documentation of Button here
public final double maxWidth(double height)
Called during layout to determine the maximum width for this node. Returns the value from computeMaxWidth(forHeight) unless the application overrode the maximum width by setting the maxWidth property.
Replace the two lines with maxWidth(...) by these ones:
button1.setMaxWidth(Double.MAX_VALUE);
button2.setMaxWidth(Double.MAX_VALUE);
This can be achieved inside scene builder also...
I required this, while creating a calculator, with the buttons inside gridpane.
Happy Coding (:
I want to make a vertical toolbar with buttons arranged vertically. Using JavaFX 2.2 that is included in JDK 7, in Linux Mint.
The screenshot shows the problem:
The FXML I am using looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<?language javascript?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<BorderPane prefHeight="800.0" prefWidth="700.0" styleClass="root" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<top>
<ToolBar>
<items>
<Button text="Test" />
</items>
</ToolBar>
</top>
<left>
<ToolBar orientation="VERTICAL" style="-fx-background-color: blue;">
<items>
<Region style="-fx-padding:10;" />
<Button rotate="-90" text="Project" style="-fx-label-padding:1;"/>
<Region style="-fx-padding:10;" />
<Button rotate="-90" text="Structure" />
</items>
</ToolBar>
</left>
<center>
<HBox>
<children>
</children>
</HBox>
</center>
<bottom>
<ToolBar prefHeight="18.0" prefWidth="472.0">
<items>
<Region styleClass="spacer" />
<HBox>
<children>
</children>
</HBox>
</items>
</ToolBar>
</bottom>
</BorderPane>
The proper toolbar in my definition is: buttons are placed correctly and the toolbar is as wide as the width of the buttons. The blue color indicates how wide the toolbar currently is.
Wrap your rotated tool items in a Group, then the in-built layout of toolbar will know that the rotation is a permanent one which should be taken into account for layout calculations and not just a temporary thing which might be used for animations. Read the javadoc for Group, where it talks about layout bounds calculations to better understand this.
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<HBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="200.0" prefWidth="100.0" style="-fx-background-color: cornsilk;" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<ToolBar orientation="VERTICAL" style="-fx-base: palegreen;">
<items>
<Group>
<children>
<Button rotate="-90.0" style="-fx-base: gold;" text="Project" />
</children>
</Group>
<Group>
<children>
<Button rotate="-90.0" style="-fx-base: khaki;" text="Structure" />
</children>
</Group>
</items>
</ToolBar>
</children>
</HBox>
Update 24th April 2017
The above solution is fine as far as it goes, but does suffer from an issue in that the buttons in the toolbar misalign when they receive focus.
What a group does is size itself based upon its contents. When the size of the contents changes, the size of the group also changes. When a button or control gets focus in JavaFX it gets a focus ring around the control. The display for the focus ring is defined in CSS and contains negative values for background inset display. The result is that, when a control is focused, it is slightly larger than when it is not focused. Normally, when you use a standard layout pane, this is not an issue as the layout pane will just ignore the background insets for layout purposes. A group however takes the full size into account and does not ignore the focus ring. The result is that a group that consists of only a control will change in size slightly when it is focused or unfocused. This presents an issue with the solution above because, when a button becomes focused, it will get slightly larger and the shift in layout in the toolbar, which is not ideal.
The solution to the focus shift problem in the above code is to just rotate the entire ToolBar within a group rather than rotating each button within a group per button. This works fine, but then presents some other issues such as the ToolBar not taking up the entire available area at the left side of the scene (due to wrapping it in a group removing the dynamic layout properties of the ToolBar). To get around this, a binding in code can be used to size the ToolBar to the available area of its parent layout container.
So we end up with the slightly more verbose solution below:
skinsample/toolbar.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.ToggleButton?>
<?import javafx.scene.control.ToolBar?>
<?import javafx.scene.Group?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.control.ToggleGroup?>
<BorderPane fx:id="border" prefHeight="200.0" prefWidth="100.0" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="skinsample.VerticalToolbarController">
<left>
<Group>
<fx:define>
<ToggleGroup fx:id="selectedTool"/>
</fx:define>
<ToolBar fx:id="toolbar" rotate="-90.0" style="-fx-base: palegreen;">
<Pane HBox.hgrow="ALWAYS" />
<ToggleButton style="-fx-base: khaki;" text="Structure" toggleGroup="${selectedTool}"/>
<ToggleButton style="-fx-base: gold;" text="Project" toggleGroup="${selectedTool}" selected="true"/>
</ToolBar>
</Group>
</left>
</BorderPane>
skinsample/VerticalToolbarController.java
package skinsample;
import javafx.beans.binding.Bindings;
import javafx.fxml.FXML;
import javafx.scene.control.ToolBar;
import javafx.scene.layout.BorderPane;
public class VerticalToolbarController {
#FXML
private BorderPane border;
#FXML
private ToolBar toolbar;
public void initialize() {
toolbar.minWidthProperty().bind(Bindings.max(border.heightProperty(), toolbar.prefWidthProperty()));
}
}
skinsample/ToolDisplayApp.java
package skinsample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class ToolDisplayApp extends Application {
#Override
public void start(Stage stage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("toolbar.fxml"));
Scene scene = new Scene(loader.load());
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Notes:
This solution also demonstrates use of ToggleButtons rather than standard buttons within the ToolBar.
We also eliminate the default overflow behavior of the ToolBar (as it seems a little annoying in the vertical toolbar situation), using:
toolbar.minWidthProperty().bind(Bindings.max(border.heightProperty(), toolbar.prefWidthProperty()));
If you want to retain the overflow behavior, then use:
toolbar.prefWidthProperty().bind(border.heightProperty());
An alternate solution to the focus issue (using CSS to remove the focus ring entirely), is presented in:
JavaFX - How to prevent Toolbar from changing width on button state changes.