5

我正在尝试将自定义工具栏添加到通用 TitledPane。我的代码似乎可以工作(下面是最小的示例),但我的布局有问题。具体来说:我正在使用 setGraphic() 添加我的工具栏,但这似乎有一个固定的宽度,而我希望它能够扩展,这样我就可以在将标题保持在左侧的同时让按钮向右刷新。

(我无法发布图像,所以我将恢复为 ASCII 艺术)这是以下代码的实际结果:

+------------------------------------+
| > Node 1 [a][b][c][d]              |
+------------------------------------+
| > Node                             |
+------------------------------------+

虽然我想得到类似的东西:

+------------------------------------+
| > Node 1              [a][b][c][d] |
+------------------------------------+
| > Node                             |
+------------------------------------+

我可以明确地伪造结果设置 BorderPane 宽度,但它不会跟随调整大小!


这里开始代码:

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);
}
}

相关的 FXML 是:

<?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>

控制器是:

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);
    }
}
4

2 回答 2

4

你需要做一些基本的数学运算。按照下面的代码修改:
1)在 fxml 文件中向 HBox 添加属性:

<HBox alignment="CENTER_RIGHT" HBox.hgrow="ALWAYS">

结果是,当标题宽度有额外空间(通过调整大小)时,此 HBox 将优先扩展,并且其子项将始终向右(居中)对齐。

2)现在我们需要这样计算剩余的标题宽度

REMAINING_WIDTH = TITLE_TOTAL_WIDTH - TITLE_TEXT_WIDTH - ARROW_BUTTON_WIDTH - RIGHT_LEFT_PADDINGS

计算出的宽度将是我们工具按钮窗格(即 HBox)的首选宽度。

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;
    }

computeTextWidth()代码取自,com.sun.javafx.scene.control.skin.Utils仅供参考:

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());
}

您的其他代码保持不变。

于 2013-07-22T14:19:10.000 回答
2

我想出了这个解决方案:

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class TestApp extends Application {

    public static void main(String[] args) {
        launch();
    }

    @Override
    public void start(Stage primaryStage) {

        TitledPane title = new TitledPane();
        //title.setContent(new Label("Content"));

        Node graphic = new Button("Click");
        Label label = new Label("Titled Pane Header");
        // Allow the label to grow to max width
        label.setMaxWidth(Double.MAX_VALUE);
        // Let it actually grow
        HBox.setHgrow(label, Priority.ALWAYS);
        HBox hBox = new HBox(label, graphic);
        hBox.setAlignment(Pos.CENTER_LEFT);
        hBox.setPadding(new Insets(0, 3, 0, 0));

        // Bind the HBox's min width to the TitledPane's width and subtract arrow (15) and left padding (10)
        hBox.minWidthProperty().bind(title.widthProperty().subtract(25));

        title.setGraphic(hBox);

        Scene scene = new Scene(new VBox(title), 300, 250);

        primaryStage.setScene(scene);
        primaryStage.show();
    }
}
于 2016-09-20T14:32:10.643 回答