Making child collapse with parent - javafx

i will try to expose my problem as good as i can.
This is the XML
<VBox fx:id="main" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
prefHeight="500.0" prefWidth="800.0" style="-fx-background-color: #DEE2E6;" xmlns="http://javafx.com/javafx/17"
xmlns:fx="http://javafx.com/fxml/1">
<BorderPane prefHeight="408.0" prefWidth="600.0" VBox.vgrow="ALWAYS">
<left>
<AnchorPane fx:id="section_to_hide" prefHeight="200.0" prefWidth="200.0" style="-fx-background-color: red;" BorderPane.alignment="CENTER">
<Label fx:id="hide_btn1" layoutX="14.0" layoutY="54.0" onMouseClicked="#hide" text="Hide me">
<font>
<Font name="Prompt-Black" size="24.0"/>
</font>
</Label>
</AnchorPane>
</left>
<center>
<BorderPane fx:id="main_content" prefHeight="442.0" prefWidth="600.0" BorderPane.alignment="CENTER">
<left>
<VBox fx:id="side_section_mails" prefHeight="500.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<AnchorPane prefHeight="59.0" prefWidth="200.0">
<Label fx:id="hide_btn" layoutX="14.0" layoutY="19.0" onMouseClicked="#hide" text="< Hide it">
<font>
<Font name="Prompt-Black" size="24.0"/>
</font>
</Label>
</AnchorPane>
</VBox>
</left>
<center>
<AnchorPane prefHeight="200.0" prefWidth="200.0" style="-fx-background-color: #CED4DA;" BorderPane.alignment="CENTER"/>
</center>
</BorderPane>
</center>
</BorderPane>
</VBox>
Controller:
public class Controller {
#FXML
private Label hide_btn;
#FXML
private AnchorPane section_to_hide;
#FXML
private void hide() {
Animation animation = new Timeline(
new KeyFrame(Duration.millis(300),
new KeyValue(section_to_hide.prefWidthProperty(), 0),
new KeyValue(section_to_hide.minWidthProperty(), 0)
)
);
animation.play();
}
#FXML
public void initialize() { }
}
The problem is: i want the content of the AnchorPane (section_to_hide) to collapse with its parent. Because this is what happens:
https://gyazo.com/41dfc5340a8e4495938cbc43d801fea2
It seems that the label inside the AnchorPane (section_to_hide) does not collapse with the parent.
Is there a way to make it work?

+1 for #Oboe answer. I would like add a bit more to his answer.
If you set managed/visible to false after the animation is finished, you may not see a smooth transition of the label. Means the complete Label is still visible while the pane closes (as in below gif) and gets invisible only after the animation is finished.
On the other hand, if you bind the clip with the bounds of the section that is hiding, you will get an effect of hiding something with a piece of cover (as in below gif) but not like hiding by sliding the section.
If the above behaviour is Ok for you, then no issues :). But if your are looking for a peferct sliding animation (as in below gif), you need to add another change of translating the clip position also.
But for this, you will need to wrap the hiding pane with an extra container.
The end implementation will be :
Outer container width is reduced to 0
Hiding container is translated in negative direction
And the clip is translated in positive direction.
Wrap section_to_hide with StackPane in fxml:
<StackPane fx:id="section_to_hide_container" prefHeight="200.0" prefWidth="200.0" alignment="CENTER_LEFT">
<AnchorPane fx:id="section_to_hide" prefHeight="200.0" prefWidth="200.0" style="-fx-background-color: red;" >
<children>
<Label fx:id="hide_btn1" layoutX="14.0" layoutY="54.0" onMouseClicked="#hide"
text="Hide me">
<font>
<Font name="Prompt-Black" size="24.0"/>
</font>
</Label>
</children>
</AnchorPane>
</StackPane>
And you controller code will change as below:
#FXML
private StackPane section_to_hide_container;
#FXML
private void hide() {
double width = section_to_hide_container.getWidth();
// Create a clip and apply on the pane to hide
Rectangle clip = new Rectangle();
clip.setWidth(width);
clip.heightProperty().bind(section_to_hide.heightProperty());
section_to_hide.setClip(clip);
Animation animation = new Timeline(
new KeyFrame(Duration.millis(300),
new KeyValue(section_to_hide_container.prefWidthProperty(), 0),
new KeyValue(section_to_hide.translateXProperty(), -width),
new KeyValue(clip.translateXProperty(), width)
)
);
animation.setOnFinished(e -> {
// Removing the clips
clip.heightProperty().unbind();
section_to_hide.setClip(null);
//Hiding the panes at the end
section_to_hide_container.setVisible(false);
section_to_hide.setVisible(false);
});
animation.play();
}
#FXML
public void initialize() {
// Binding min/max to pref, to not allow the panes width change.
section_to_hide_container.minWidthProperty().bind(section_to_hide_container.prefWidthProperty());
section_to_hide_container.maxWidthProperty().bind(section_to_hide_container.prefWidthProperty());
section_to_hide.minWidthProperty().bind(section_to_hide.prefWidthProperty());
section_to_hide.maxWidthProperty().bind(section_to_hide.prefWidthProperty());
}
I wrote a detailed blog about this particular feature. Please do check to get more inputs on this. Also please note that this blog was written almost 10 years back with my knowledge at that time. So some code and wordings may not be relevant now :)

As #James_D noticed in the comments, you should check if an AchorPane is the right container for your needs. For the specific example, a StackPane may be better suited.
If you need to use an AchorPane, one way to hide the pane completely is by applying .setManaged(false) to the pane at the end of the animation:
animation.setOnFinished(e -> section_to_hide.setManaged(false));
As #Slaw noticed in the comments, you can also use a clip on the AnchorPane:
Rectangle clip = new Rectangle();
clip.widthProperty().bind(section_to_hide.widthProperty());
clip.heightProperty().bind(section_to_hide.heightProperty());
section_to_hide.setClip(clip);

Related

How to make VBox inside of ScrollPane responsive (Chat Application)

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).

FXML arithmetic in prefHeight attribute to auto rescale on window resize?

I want to set the prefHeight of an AnchorPane to 4 times the height of a GridPane. I have read in another post that this should be possible, at least for other elements:
Is it possible to use arithmetic expression in FXML?
I am able to reference the height of the GridPane alone by itself fine like so:
<AnchorPane fx:id="imagesAnchorPane" prefHeight="${buttonsGridPane.height}" minHeight="250.0" prefWidth="135.0">
but once I try to use prefHeight="${buttonsGridPane.height * 4}" the * 4 is highlighted red and the mouseover IntelliJ displays the error:
Cannot resolve symbol 'height * 4' with references to
javafx.scene.layout.Region private
javafx.beans.property.DoubleProperty prefHeight
Same issue using 4 or 4.0 for all of the steps I have tried.
In an effort to more closely follow the proposed solutions in the linked post I also tried defining a double with the same arithmetic as to use it directly for the prefHeight but this will not even recognise the height reference alone, instead erroring with
Invalid value: Unable to coerce to java.lang.Double
I am able to define it as a String, but when I then try to then multiple that String by 4 I receive the same error about unable to coerce to double:
<fx:define>
<String fx:id="xHeight" fx:value="${buttonsGridPane.height}"/>
<Double fx:id="yHeight" fx:value="${xHeight * 4}"/>
</fx:define>
I also have a method that will change the value, which I can call with onMouseClicked, but I have not been able to find a way to use it for an attribute as I expect this is not allowed:
private void fixScale(){
imagesAnchorPane.setPrefHeight(buttonsGridPane.getHeight() * 4);
}
For reference this is all in an effort to make it so that the AnchorPane, which is holding a GridPane of images will scale with the rest of the program when the window is resized as it is not. Although this AnchorPane may become a Java object, right now it has been implemented using SceneBuilder and opening tags of the FXML code is the following:
<SplitPane dividerPositions="0.503708281829419" layoutX="24.0" layoutY="45.0" prefHeight="265.0" prefWidth="324.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<items>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0" style="-fx-background-color: #bbc4c4; -fx-border-color: #323636; -fx-border-insets: 3; -fx-border-radius: 3; -fx-border-width: 2;">
<children>
<ScrollPane fitToWidth="true" hbarPolicy="NEVER" layoutX="-20.0" layoutY="33.0" prefHeight="265.0" prefWidth="160.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<content>
<AnchorPane fx:id="imagesAnchorPane" minHeight="250.0" onMouseClicked="#fixScale" prefWidth="135.0">
<children>
<GridPane prefHeight="574.0" prefWidth="135.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="0.0">
And this is the current structure from the top level AnchorPane down to the imagesGridPane:

Request focus to shape is getting to the wrong control

I am trying to request focus to a rectangle shape in my scene. When I do that using the mouse the focus property listener hasn't fired even the shape is traversable to focus. I tried to add an EventListener to the shape to call the requestFocus() method on it but after the rectangle gain the focus another button that doesn't have any listeners or additional code stole the focus immediately. After I removed that button the same thing is done by another control (all the controls that stole the focus are before the rectangle in the order of addition to the root node). I tried to traverse the focusing using the Tab button and it works. So, how can I request the focusing to the rectangle using the mouse?
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="232.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<ToolBar prefHeight="40.0" prefWidth="200.0">
<items>
<Button mnemonicParsing="false" text="Button" />
</items>
</ToolBar>
<HBox prefHeight="100.0" prefWidth="200.0" VBox.vgrow="ALWAYS">
<children>
<VBox prefHeight="200.0" prefWidth="100.0">
<children>
<ListView id="slotsList" prefHeight="200.0" prefWidth="200.0" />
</children>
</VBox>
<FilmTimeLine HBox.hgrow="ALWAYS" fx:id="timeLine"/>
</children>
</HBox>
</children>
</VBox>
Rectangle class that I am adding it to the scene inside the FilmTimeLine (the FilmTimeLine is a ScrollPane that has a Pane child. The rectangle is inside that Pane):
public class EventSlot extends Rectangle {
public EventSlot() {
setFocusTraversable(true);
setWidth(100);
setHeight(25);
setFill(Color.web("#9DD3DF"));
focusedProperty().addListener((observable, oldValue, newValue) -> {
if (isFocused()) {
setEffect(FOCUSED_EFFECT);
} else {
setEffect(INNER_SHADOW);
}
});
addEventHandler(MouseEvent.MOUSE_PRESSED, (mouseEvent) -> {
requestFocus();
});
}
}
gif of what is happening
When an action occurs, the system constructs event route.
Looks like your mouse event is handled by Rectangle (I assume it is inherited from Node), Pane and FilmTimeLine.
So we need to construct event route to ignore/filter all nodes except of selected rectangle.
Options to modify the route:
Consuming of an Event. Invoke mouseEvent.consume() to stop event propagation.
addEventHandler(MouseEvent.MOUSE_CLECKED, (mouseEvent) -> {
requestFocus();
mouseEvent.consume();
});
Making parent node mouse transparent Node.setMouseTransparent(true). If true, this node (together with all its children) is completely transparent so make sure that all rectagles mouseTransparent property is false.
pane.setMouseTransparent(true);//the FilmTimeLine is a ScrollPane that has a Pane child
Adding event filter Node.addEventFilter
pane.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) { event.consume(); };
});

I have chosen a Pane, but I would like to set the alignment, is it possible?

I'm doing a project with javafx. As cointainer I choose the Pane, maybe the worst decision! Is it possible to center the text, using the lenght of the sentence which will be loaded according to the size of the panel?
I'm actually showing the code that i wrote. As you can see, the GeneralQuestion lenght will be various, the question has not a min or max character. How can i center it according to the size of the panel?
This is how does it look like:
This is the output, as you can see it isn't centered. I know there's the VBox or other type of panel but i've already chosen this one and before changing everything i would like to know if there's any way to make it look better!
This is the controller:
public void nextQuest(ActionEvent e) throws IOException
{
secondaryImg.setVisible(false);
nextQuestionButton.setVisible(false);
getQuestion(cont);
cont++;
}
public void getQuestion(int cont) throws FileNotFoundException,
IOException
{
FileManager f = new FileManager();
numQuest();
QuestionManager m = new QuestionManager(f.loadQuestion());
GeneralQuestion.setText(m.getAllQuestion().get(cont).getBody());
answ=m.getAllQuestion().get(cont).getAnswer();
if(m.getAllQuestion().get(cont).getType().equals("normale")||m.getAllQuestion().get(cont).getType().equals("perditutto"))
answS.setVisible(true);
else if(m.getAllQuestion().get(cont).getType().equals("verofalso")){
RbVero.setVisible(true);
RbFalso.setVisible(true);
}
}
This is (a part of) the FXML:
<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" style="-fx-background-color: #b8ffe0; -fx-border-width: 8px; -fx-border-color: #0e3d73;" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="guiz.controller.GameScreenController">
<children>
<Label id="numquest" fx:id="numquest" layoutX="286.0" layoutY="35.0" text="Label">
<font>
<Font size="15.0" />
</font></Label>
<Label fx:id="GeneralQuestion" layoutX="225.0" layoutY="123.0" style="-fx-font-weight: 800;" text="Quest">
<font>
<Font size="18.0" />
</font>
</Label>
</children>
</Pane>
In JavaFX you achieve a desired layout by nesting various layout panes inside each other. Pane is indeed a bad descision because a Pane just does not do any layout at all. In your case a VBox would indeed be a better alternative which you might put into a BorderPane.

JavaFX TextArea does not append text properly

I'm trying to build a simple calculator using TDD, so I have a TextArea that is right aligned to display the results and 20 buttons. Everytime one of the digit buttons are pressed it just appends the digit to the TextArea, the same thing was supposed to happen when I press the dot button. But instead it places the dot as the first character in the TextArea, only when I press a new digit the dot goes to its proper place.
So for example, if I press 9 and then 8 the TextArea shows "98", now if the dot is pressed the result will be ".98", finally if 7 is pressed the result is "98.7".
The expected result when I pressed the dot button should have been "98.".
I created a Minimal, Complete, and Verifiable example bellow
FXMLDocument.fxml:
<AnchorPane id="AnchorPane" prefHeight="175.0" prefWidth="256.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.141" fx:controller="mcv.FXMLDocumentController">
<children>
<Button fx:id="buttonNine" layoutX="14.0" layoutY="100.0" onAction="#handleButtonNine" prefHeight="60.0" prefWidth="69.0" text="9">
<font>
<Font size="24.0" />
</font></Button>
<TextArea fx:id="textArea" editable="false" focusTraversable="false" layoutX="15.0" layoutY="14.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" nodeOrientation="RIGHT_TO_LEFT" prefHeight="66.0" prefWidth="221.0" wrapText="true">
<font>
<Font size="35.0" />
</font>
</TextArea>
<Button fx:id="buttonDot" layoutX="169.0" layoutY="100.0" mnemonicParsing="false" onAction="#handleButtonDot" prefHeight="60.0" prefWidth="69.0" text=".">
<font>
<Font size="24.0" />
</font>
</Button>
</children>
</AnchorPane>
FXMLDocumentController.java:
#FXML
private TextArea textArea;
#Override
public void initialize(URL location, ResourceBundle resources) {
}
private void appenToResult(String ch){
textArea.appendText(ch);
}
#FXML
void handleButtonDot(ActionEvent event) {
appenToResult(".");
}
#FXML
void handleButtonNine(ActionEvent event) {
appenToResult("9");
}
As you can see the code is very simple. It should be working, my only clue is that this is a bug with the TextArea when the Node Orientation is set to RIGHT_TO_LEFT. If node orientation is set to INHERIT or LEFT_TO_RIGHT it works as expected. Thank you.

Resources