I am trying to add a custom toolbar to a generic TitledPane. My code seems to work (minimal example follows), but I have problems with layout. Specifically: I am adding my toolbar using setGraphic(), but that seems to have a fixed width, while I would like it to expand so I can have the buttons flushed right while keeping title on the left side.
(I cannot post images, so I will revert to ASCII art) This is the actual result of code below:
+------------------------------------+
| > Node 1 [a][b][c][d] |
+------------------------------------+
| > Node |
+------------------------------------+
while I would like to get something like:
+------------------------------------+
| > Node 1 [a][b][c][d] |
+------------------------------------+
| > Node |
+------------------------------------+
I can fake the result setting explicitly the BorderPane width, but it will not follow resizing!
here starts the code:
import java.net.URL;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Accordion;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TitledPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class Test extends Application {
private Node loadMinitool(String title) {
try {
URL u = getClass().getResource("Minitool.fxml");
FXMLLoader l = new FXMLLoader(u);
Node n = (Node) l.load();
Minitool mtc = (Minitool) l.getController();
mtc.setTitle(title);
return n;
} catch (Exception e) {
System.err.println("Unable to load 'Minitool.fxml': "+e.getMessage());
}
return null;
}
public Parent createContent() {
TitledPane t1 = new TitledPane(null, new Button("Button"));
t1.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
t1.setGraphic(loadMinitool("Node 1"));
TitledPane t2 = new TitledPane("Node 2", new Text("String"));
TitledPane t3 = new TitledPane("Node 3", new Rectangle(120, 50,
Color.RED));
Accordion accordion = new Accordion();
accordion.getPanes().add(t1);
accordion.getPanes().add(t2);
accordion.getPanes().add(t3);
accordion.setMinSize(100, 100);
accordion.setPrefSize(200, 400);
return accordion;
}
@Override
public void start(Stage primaryStage) {
primaryStage.setScene(new Scene(createContent()));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
the associated FXML is:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.net.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.text.*?>
<BorderPane maxWidth="1.7976931348623157E308" prefWidth="-1.0" xmlns:fx="http://javafx.com/fxml" fx:controller="com.voith.hyconmde.ui.util.minitool.Minitool">
<left>
<Label fx:id="title" maxWidth="1.7976931348623157E308" text="Title goes here" BorderPane.alignment="CENTER_LEFT" />
</left>
<center>
<Pane fx:id="filler" maxWidth="1.7976931348623157E308" minWidth="0.0" prefHeight="16.0" prefWidth="200.0" />
</center>
<right>
<HBox>
<Button fx:id="add" graphicTextGap="0.0" onAction="#addAction" styleClass="btnToolbar" />
<Button fx:id="del" graphicTextGap="0.0" onAction="#delAction" styleClass="btnToolbar" />
<Button fx:id="sub" graphicTextGap="0.0" onAction="#subAction" styleClass="btnToolbar" />
<Button fx:id="dup" graphicTextGap="0.0" onAction="#dupAction" styleClass="btnToolbar" />
</HBox>
</right>
<stylesheets>
<URL value="@Minitool.css" />
</stylesheets>
</BorderPane>
and the controller is:
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
public class Minitool {
@FXML private ResourceBundle resources;
@FXML private URL location;
@FXML private Button add;
@FXML private Button del;
@FXML private Button dup;
@FXML private Button sub;
@FXML private Label title;
@FXML void addAction(ActionEvent event) {
}
@FXML void delAction(ActionEvent event) {
}
@FXML void dupAction(ActionEvent event) {
}
@FXML void subAction(ActionEvent event) {
}
@FXML void initialize() {
assert add != null : "fx:id=\"add\" was not injected: check your FXML file 'Minitool.fxml'.";
assert del != null : "fx:id=\"del\" was not injected: check your FXML file 'Minitool.fxml'.";
assert dup != null : "fx:id=\"dup\" was not injected: check your FXML file 'Minitool.fxml'.";
assert sub != null : "fx:id=\"sub\" was not injected: check your FXML file 'Minitool.fxml'.";
assert title != null : "fx:id=\"title\" was not injected: check your FXML file 'Minitool.fxml'.";
}
public void setTitle(String title) {
this.title.setText(title);
}
public void setWidth() {
if (parent == null) {
Parent p = minitool.getParent();
if (p != null)
p = p.getParent();
if (p instanceof TitledPane) {
parent = (TitledPane) p;
parent.widthProperty().addListener(new ChangeListener<Object>() {
@Override
public void changed(ObservableValue<?> observable, Object oldValue, Object newValue) {
if (newValue instanceof Number) {
Number n = (Number) newValue;
double d = n.doubleValue();
setFiller(d);
}
}
});
setFiller(parent.getWidth());
}
}
}
protected void setFiller(double d) {
double w = d - title.getWidth() - 180; // XXX: this value has been hand-trimmed!
if (w < 0)
w = 0;
filler.setPrefWidth(w);
}
}
You need to do some basic math. Follow modifications to your code below:
1) In fxml file add properties to HBox:
<HBox alignment="CENTER_RIGHT" HBox.hgrow="ALWAYS">
The result is, when there is an extra space in title width (by resizing), this HBox will have a priority to expand and its children will always be aligned to the right (center).
2) Now we need to calculate the remaining title width this way
REMAINING_WIDTH = TITLE_TOTAL_WIDTH - TITLE_TEXT_WIDTH - ARROW_BUTTON_WIDTH - RIGHT_LEFT_PADDINGS
The calculated width will be our tool buttons pane's (i.e. HBox's) preferred width.
public Parent createContent() {
String titleText = "Node 1";
TitledPane t1 = new TitledPane(null, new Button("Button"));
t1.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
t1.setGraphic(loadMinitool(titleText));
double titleTextWidth = computeTextWidth(t1.getFont(), titleText, 0);
double arrowButtonWidth = 14; // I have given a static value here otherwise
// it must be calculated by
// t1.lookup(".arrow-button").getLayoutBounds().getWidth()
// after the primary stage has been shown. Namely after primaryStage.show(); line.
double paddings = 20; // right (10) and left (10) paddings defined in
// caspian.css for title of the pane. These values can also be obtained
// by lookup.
double total = titleTextWidth + arrowButtonWidth + paddings;
HBox toolButtons = (HBox) ((BorderPane) t1.getGraphic()).getRight();
toolButtons.prefWidthProperty().bind(t1.widthProperty().subtract(total));
TitledPane t2 = new TitledPane("Node 2", new Text("String"));
TitledPane t3 = new TitledPane("Node 3", new Rectangle(120, 50,
Color.RED));
Accordion accordion = new Accordion();
accordion.getPanes().add(t1);
accordion.getPanes().add(t2);
accordion.getPanes().add(t3);
accordion.setMinSize(100, 100);
accordion.setPrefSize(200, 400);
return accordion;
}
The computeTextWidth()
code is taken from com.sun.javafx.scene.control.skin.Utils
, and for reference only:
private double computeTextWidth(Font font, String text, double wrappingWidth) {
Text helper = new Text();
helper.setText(text);
helper.setFont(font);
// Note that the wrapping width needs to be set to zero before
// getting the text's real preferred width.
helper.setWrappingWidth(0);
double w = Math.min(helper.prefWidth(-1), wrappingWidth);
helper.setWrappingWidth((int) Math.ceil(w));
return Math.ceil(helper.getLayoutBounds().getWidth());
}
The other codes of yours remain the same.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With