I have a scene showing 3 images, and I want each of them to take a third of the width of the scene.
From now, I have made 3 Pane of each 30% of it, it works.
But in those Pane, I can't make my ImageView use only the width of the Pane.
<GridPane GridPane.rowIndex="1" GridPane.columnIndex="0">
<ImageView GridPane.columnIndex="0" fx:id="imgChasseur" preserveRatio="true" onMouseClicked="#handleChoixChasseur"/>
<ImageView GridPane.columnIndex="1" fx:id="imgMage" preserveRatio="true" onMouseClicked="#handleChoixMage"/>
<ImageView GridPane.columnIndex="2" fx:id="imgGuerrier" preserveRatio="true" onMouseClicked="#handleChoixGuerrier"/>
<columnConstraints>
<ColumnConstraints percentWidth="33" />
<ColumnConstraints percentWidth="33" />
<ColumnConstraints percentWidth="33" />
</columnConstraints>
<rowConstraints>
<RowConstraints percentHeight="100" />
</rowConstraints>
</GridPane>
With that sample of code, I can't see the three because they are huge, and with the 'fitWidth="100"' in the 3 ImageView, they are too small.
The way I understand it is that the 'fitWidth' works in pixels, but it's not a responsive way, so it doesn't help me very much...
The GridPane is the only Pane which seems to have percentage values, so I thought it could help me make them responsive, but it doesn't seem so.
Is there a way, regardless of the Pane I should use?
I found a solution :
I added a listener on the surrounding Pane which modifies the fitWidth of my ImageView's in the Controller
paneOriginal.widthProperty().addListener((ChangeListener<? super Number>) new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> arg0, Number arg1, Number arg2) {
imgChasseur.setFitWidth(paneOriginal.getWidth() / 3);
imgMage.setFitWidth(paneOriginal.getWidth() / 3);
imgGuerrier.setFitWidth(paneOriginal.getWidth() / 3);
}
});
With this, I can resize my scene whenever I want and it still fits (and I have to call those three lines at the loading of the FXML one time at least of course.
Related
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);
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.
I have a TableView inside a JavaFX ScrollPane, together with some other Views parallel to it. Something like this here:
<ScrollPane maxHeight="Infinity" maxWidth="Infinity" VBox.vgrow="ALWAYS">
<GridPane maxHeight="Infinity" maxWidth="Infinity" prefWidth="1100">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="Infinity" minWidth="0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="Infinity" minWidth="0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="Infinity" minHeight="10.0" vgrow="ALWAYS" />
</rowConstraints>
<VBox GridPane.columnIndex="1" GridPane.rowSpan="2147483647">
<PieChart fx:id="amountPerState" legendVisible="false" minHeight="300" minWidth="300" />
<PieChart fx:id="amountPerComponent" legendVisible="false" minHeight="300" minWidth="300" />
</VBox>
<TableView fx:id="componentTable" maxHeight="Infinity" maxWidth="Infinity" GridPane.rowIndex="6">
<columns>
...
</columns>
</TableView>
</GridPane>
</ScrollPane>
Now this double scrolling from the ScrollPane and the TableView is ackward. When I try to scroll inside the TableView the ScrollPane also scrolls. I would love to have the TableView go as high as necessary for it's content, so that there is only the scrolling of the ScrollPane. Any Idea how I can do that? Or other ideas?
Edit: For extra clarification, I assume the TableView should have prefered size so high that all it's content is shown without any scrolling necessary. Then there are probably no scroll bars, or I can just not show them with the policy. This is how I think you would achieve this, but I don't know how to figure out the tables content hight to make it it's preferred size.
I think it will be a bit worse in performance, since TableView has this row reusing, but I know that there are not to many rows in the TableView, so that there will be no performance problems.
Edit: Both the charts and the table are to big for the windows, so they all need to be in the ScrollPane
So here's the deal, i'm trying to code a GUI that shows live data on a linechart.
So far so good, i can get the linechart working but not into the GUI.
Using scenebuilder, i made a view with a linechart object, in order to link it to my generated chart. But for some reason this does not seem to work with this code in my mainApp.
public void showSes() {
try {
// Load the fxml file and set into the center of the main layout
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MainApp.class.getResource("view/Session.fxml"));
AnchorPane Session = (AnchorPane) loader.load();
rootLayout.setCenter(Session);
SessionController controller = loader.getController();
controller.setMainApp(this);
controller.initGraph();
} catch (IOException e) {
// Exception gets thrown if the fxml file could not be loaded
e.printStackTrace();
}
}
This simply shows the view with an empty linechart.
I know the chart should know however, because i can use it to create a scene, and show that into the GUI, but the view i made in scenebuilder also has some other fields i want to show...
Does anybody have an idea?
FXML
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.text.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.chart.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane prefHeight="900.0" prefWidth="1280.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="gui.view.SessionController">
<children>
<Pane prefHeight="900.0" prefWidth="1280.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<LineChart fx:id="linechart" layoutX="29.0" layoutY="194.0" prefHeight="416.0" prefWidth="1222.0" title="Temperature of session">
<xAxis>
<CategoryAxis label="Time (s)" fx:id="xAxis" />
</xAxis>
<yAxis>
<NumberAxis fx:id="yAxis" label="Temperature (°C)" side="LEFT" upperBound="160.0" />
</yAxis>
</LineChart>
<GridPane layoutX="254.0" layoutY="87.0" prefHeight="150.0" prefWidth="771.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="274.0" minWidth="10.0" prefWidth="274.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="273.0" minWidth="10.0" prefWidth="273.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="273.0" minWidth="10.0" prefWidth="273.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label prefHeight="53.0" prefWidth="153.0" text="Temperature fluid:" GridPane.halignment="CENTER" GridPane.valignment="TOP">
<font>
<Font size="16.0" />
</font>
</Label>
<Label prefHeight="52.0" prefWidth="181.0" text="Temperature vapor:" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.valignment="TOP">
<font>
<Font size="16.0" />
</font>
</Label>
<TextField fx:id="fluidT" editable="false" />
<TextField fx:id="gasT" editable="false" GridPane.columnIndex="2" />
</children>
</GridPane>
<Label layoutX="474.0" layoutY="14.0" text="TempTracker">
<font>
<Font size="50.0" />
</font>
</Label>
<TextArea editable="false" layoutX="190.0" layoutY="638.0" prefHeight="160.0" prefWidth="900.0" promptText=" ------Warning log------- " wrapText="true" />
<Button layoutX="540.0" layoutY="808.0" mnemonicParsing="false" onAction="#handleStop" prefHeight="65.0" prefWidth="200.0" text="STOP">
<font>
<Font size="22.0" />
</font>
</Button>
</children></Pane>
</children>
</AnchorPane>
CONTROLLER
private static final int MAX_DATA_POINTS = 50;
private String xSeriesData = "";
private XYChart.Series series1;
private XYChart.Series series2;
private ExecutorService executor;
private BlockingQueue<Number> dataQ1 = new ArrayBlockingQueue<Number>(1024);
private BlockingQueue<Number> dataQ2 = new ArrayBlockingQueue<Number>(1024);
#FXML
private CategoryAxis xAxis = new CategoryAxis();
#FXML
final NumberAxis yAxis = new NumberAxis();
#FXML
final LineChart<String, Number> linechart = new LineChart<String, Number>(xAxis, yAxis);
public void initGraph(){
xAxis.setAutoRanging(false);
xAxis.setTickLabelsVisible(false);
xAxis.setTickMarkVisible(false);
NumberAxis yAxis = new NumberAxis();
yAxis.setAutoRanging(true);
//Graph
final LineChart<String, Number> lc = new LineChart<String, Number>(xAxis, yAxis){
#Override
protected void dataItemAdded(Series<String, Number> series, int itemIndex, Data<String, Number> item){}
};
lc.setAnimated(false);
lc.setId("liveLineChart");
lc.setTitle("Animated Line Chart");
//Graph Series
series1 = new XYChart.Series<Number, Number>();
series2 = new XYChart.Series<Number, Number>();
linechart.getData().addAll(series1, series2);
series1.setName("T1");
series2.setName("T2");
fluidT.setText("0000");
gasT.setText("0000");
prepareTimeline();
Runnable con = new Consumer(this);
Thread c = new Thread(con);
c.start();
}
Don't create new objects for #FXML injected members
Never use new in conjunction with #FXML, i.e., never write:
#FXML
private CategoryAxis xAxis = new CategoryAxis();
Instead, just write:
#FXML
private CategoryAxis xAxis;
The FXMLLoader will automatically generate, i.e., create, a new object for each element in the FXML file and inject a reference to that into your controller where you provide an #FXML annotation. So if you reset the #FXML member reference to a new object you create in the controller there will be no association between that object and objects created by the loader.
Additionally, don't create another new LineChart within your initGraph() function. You already have a LineChart created by the FXMLLoader, just reference that. Same for NumberAxis and the other elements you are using #FXML injection with.
If you use an #FXML annotation also use <fx:id>
You have:
#FXML
private CategoryAxis xAxis;
So in your fxml, you should define:
<xAxis fx:id="xAxis">
Otherwise, the FXMLLoader will not be able to inject a reference to the axis you defined in your FXML.
Aside
You may have other errors in your code (e.g., around concurrency and threading). So the above might not be all of your errors. In general, when creating an mcve, try to eliminate anything which is not relevant to the question at hand (e.g. the threading code and non-linechart portions of the FXML), but include everything that somebody could use to copy and paste your code to compile and run it to replicate your issue.
Note: The Ensemble sample application contains a sample program which updates a graph in real-time based upon audio spectrum input data.
I'm using Accelerator in my application, and when I hit the F1 key it should open a dialog just once, but when I close the dialog it appears again. The runnable is been called twice.
Does anyone know what it could be? Is it a bug? I'm using a Macbook to develop.
This is my controller:
public class Screen1Controller {
#FXML
private TextField txtCode;
#FXML
private TableView tblProducts;
#FXML
private Button buttonAskUserDecision;
#FXML
public void initialize () {
Platform.runLater(new Runnable() {
#Override
public void run() {
initializeKeyCombinations();
}
});
}
#FXML
private void executePrint (ActionEvent event) {
System.out.println("ExecutePrint");
}
public void initializeKeyCombinations() {
buttonAskUserDecision.getScene().getAccelerators().clear();
buttonAskUserDecision.getScene().getAccelerators().put(
new KeyCodeCombination(KeyCode.F1),
new Runnable() {
#Override
public void run() {
System.out.println("Executed F1");
}
}
);
}
}
This is my FXML:
<StackPane prefHeight="600.0" prefWidth="800.0" stylesheets="#../../../css/main.css" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="Screen1Controller">
<children>
<BorderPane prefHeight="200.0" prefWidth="200.0">
<center>
<ScrollPane fitToHeight="true" fitToWidth="true" BorderPane.alignment="CENTER">
<content>
<VBox alignment="TOP_CENTER">
<children>
<GridPane hgap="10.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" percentWidth="20.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" percentWidth="20.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" percentWidth="30.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" percentWidth="30.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints fillHeight="false" minHeight="30.0" vgrow="NEVER" />
</rowConstraints>
<children>
<TextField fx:id="txtCode" GridPane.columnSpan="2" />
</children>
<VBox.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</VBox.margin>
</GridPane>
<TableView fx:id="tblProducts" prefHeight="200.0" prefWidth="200.0">
<columns>
<TableColumn prefWidth="75.0" text="C1" />
<TableColumn prefWidth="75.0" text="C2" />
</columns>
</TableView>
</children>
</VBox>
</content>
</ScrollPane>
</center>
<bottom>
<ToolBar maxHeight="80.0" minHeight="80.0" prefHeight="80.0" styleClass="default-skin-pdv-toolbar" stylesheets="#../../../css/main.css">
<items>
<Button fx:id="buttonAskUserDecision" layoutX="10.0" layoutY="20.0" mnemonicParsing="false" onAction="#executePrint" prefHeight="60.0" prefWidth="69.0" styleClass="default-skin-pdv-button">
</Button>
</items>
<padding>
<Insets left="10.0" />
</padding>
</ToolBar>
</bottom>
</BorderPane>
</children>
Inside this screen I have a textfield, a tableview and a toolbar with a button.
I did some new tests. Every time I give focus on my tableview and hit the F1 button, the execution is incremented by 1. For instance, if I click on the tableview and hit F1, it prints "Executed F1" just once on the console and the focus go back to the textfield, if i give focus to the tableview again and hit F1, It prints two times the text on the console and give focus back to the textfield, and if I give focus to the tableview for the third time and press the F1 again it prints three more lines on the console, and so on.
It only happens when I use the F1 button.. When I click the button with the mouse it prints only once.
I removed the TableView from the screen and it stopped duplicating the executions.
The problem is: I need this tableView in my screen. What could be happening here?
That's my Java version details:
java version "1.8.0_25"
Java(TM) SE Runtime Environment (build 1.8.0_25-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode)
The problem was in a component (Dialog) from ControlsFX library.
I created a new Project and included a textfield, viewTable and a button. The duplication didn't happened.
When I included the ControlsFX Dialog, the started to happen. When I click the button, with the mouse, the dialog opens just once. But when I use the accelerator to fire the button, the dialog opens twice.
Edited:
I created my own dialog, but it happened again. Instead of using the stage.showAndWait() I tried the stage.show() and it stopped to open twice.
I searched the stackoverflow and found this answer https://stackoverflow.com/a/22463857/916804 ... When I tried to wrap the call to the Dialog with the Platform.runLater the execution happened only once.