java - JavaFX ToolBar 调整为儿童动态宽度
问题描述
我不得不使用 ToggleButtons 而不是 Tabs 将 TabPane 切换为 Toolbar。这是因为与开箱即用的 Tab 相比,我需要更好地控制“选项卡”的内容和图形以及拖放功能。我可以让 Tab 表现出我想要的样子,但是因为我需要在 Tab 的图形中的标签上设置我的文本(在我的例子中是一个 HBox),这意味着溢出的 ContextMenu 没有任何可见的文本。我还希望我的选项卡中的其他布局属性可以在 ContextMent 项目中继承,而工具栏很好地为我提供了这个(背景颜色和位于“选项卡文本”旁边的图像。我遇到的问题是图像是现在显示在选项卡上的 ToggleButtons(我的代码中的 FakeTabs)是动态更新的,因此会更改 ToggleButton 的宽度。
//set minSize to prevent Label text being truncated.
`this.setMinSize(Button.USE_PREF_SIZE, Button.USE_PREF_SIZE);`
这样图像旁边的标签上的文本就不会被截断,工具栏不会调整大小,并且用于打开 ContextMenu 的 >> 按钮最终会超过最后一个 ToggleButton 但它也错过了菜单中的一些 ToggleButtons 所以你无法访问它们。有没有办法设置工具栏,以便在更改时将其调整为孩子的宽度,或者我可以强制重新布局?当我说调整大小时,我希望它保持它的实际宽度,但重新计算哪些 ToggleButtons 需要位于 ContextMenu 中,并保持最右边的 >> 按钮不超过 ToggleButton。我在 ToolBar 上尝试了各种 .requestLayout() 方法,但到目前为止没有成功。
我的 reprex 添加了一些我的 FakeTabs(ToggleButtons),然后在应用程序运行后将逐渐添加一些 Circles(替换我的应用程序中添加的图像)。这会导致 ToggleButtons 变宽并且 ToolBar 无法调整大小以适应孩子们的新宽度的问题。移除
this.setMinSize(Button.USE_PREF_SIZE, Button.USE_PREF_SIZE);
FakeTab 导致 FakeTab 的文本在添加圈子时被截断,这对我来说不可行。
谢谢你的帮助。
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToolBar;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeType;
import javafx.stage.Stage;
import java.util.Arrays;
import java.util.List;
public class MainApp extends Application {
private CustomTabPane customTabPane;
@Override
public void start(Stage stage) {
customTabPane = new CustomTabPane();
VBox.setVgrow(customTabPane, Priority.ALWAYS);
VBox vbox = new VBox(2.0);
vbox.getChildren().setAll(customTabPane);
BorderPane root = new BorderPane();
root.setMaxWidth(800);
root.setMaxHeight(400);
root.setCenter(vbox);
//add some fake tabs
List<DataModel> dataModels = Arrays.asList(
new DataModel("Tab1 name ", new int[]{0, 0, 255}),
new DataModel("Tab2 name 22", new int[]{0, 0, 140}),
new DataModel("Tab3 name 333", new int[]{0, 0, 100}),
new DataModel("Tab4 name 4444", new int[]{255, 0, 0}),
new DataModel("Tab5 name 55555", new int[]{100, 0, 0}),
new DataModel("Tab6 name 666666", new int[]{0, 255, 0}),
new DataModel("Tab7 name 7777777", new int[]{0, 140, 0}),
new DataModel("Tab8 name 88888888", new int[]{0, 100, 0}),
new DataModel("Tab9 name 999999999", new int[]{50, 0, 50}),
new DataModel("Tab10 name xxxxxxxxxx", new int[]{110, 0, 50}));
for (DataModel data : dataModels) {
addFakeTab(data);
}
//simulated Images being updated to Tabs after the CustomTabPane has been populated with FakeTabs.
Task task = new Task<Void>() {
@Override
public Void call() throws InterruptedException {
Thread.sleep(3000);
for (int i = 0; i < 3; i++) {
Platform.runLater(() -> {
updateTabIcons();
});
Thread.sleep(3000);
}
return null;
}
};
new Thread(task).start();
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("CustomTabPane");
stage.show();
}
public static void main(String [] args){
launch(args);
}
/**
* Fake updating FakeTabs with 'images'.
*/
private void updateTabIcons() {
for (int i = 0; i < customTabPane.getToolBarNodes().size(); i++) {
Node node = customTabPane.getToolBarNodes().get(i);
if (node instanceof FakeTab) {
((FakeTab) node).addImage(createCircle());
}
}
}
/**
* Add a circle where the image would go
**/
private Circle createCircle() {
Circle circle = new Circle(10);
circle.setFill(Color.BLACK);
circle.setStrokeType(StrokeType.INSIDE);
circle.setStrokeWidth(2.0);
circle.setStrokeLineCap(StrokeLineCap.BUTT);
circle.setStroke(Color.WHITE);
return circle;
}
private void addFakeTab(DataModel data) {
FakeTab fakeTab = new FakeTab(data);
customTabPane.addTab(fakeTab);
}
class CustomTabPane extends VBox {
private StackPane stackPane;
private ToolBar toolBar;
public CustomTabPane() {
toolBar = new ToolBar();
stackPane = new StackPane();
this.setAlignment(Pos.TOP_CENTER);
this.getChildren().setAll(toolBar, stackPane);
}
public ObservableList<Node> getToolBarNodes() {
return toolBar.getItems();
}
public void addTab(ToggleButton toggleButton) {
toolBar.getItems().add(toggleButton);
}
}
class DataModel {
private String id;
private int[] rgbColor;
public DataModel(String id, int[] rgbColor) {
this.id = id;
this.rgbColor = rgbColor;
}
public int[] getRgbColor() {
return rgbColor;
}
public String getRgbString() {
return "" + rgbColor[0] + "," + rgbColor[1] + "," + rgbColor[2];
}
public String getStringId() {
return id;
}
}
}
FakeTab 类:
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
public class FakeTab extends ToggleButton {
private Label label;
private HBox imageHBox;
private MainApp.DataModel dataModel;
public FakeTab(MainApp.DataModel dataModel) {
super("");
this.dataModel = dataModel;
//set minSize to prevent Label text being truncated.
this.setMinSize(Button.USE_PREF_SIZE, Button.USE_PREF_SIZE);
label = new Label();
imageHBox=new HBox(1.0);
HBox hBox = new HBox(imageHBox,label);
hBox.setStyle("-fx-padding: 1.0em;");
hBox.setAlignment(Pos.CENTER_LEFT);
this.setGraphic(hBox);
this.setUserData("" + dataModel.getStringId());
label.setText("" + dataModel.getStringId());
int[] rgb = dataModel.getRgbColor();
Color color = getFXColor(rgb[0], rgb[1], rgb[2], 1.0);
String style = "-fx-background-color:rgb(" + dataModel.getRgbString() + ");-fx-text-fill:" + (isColorDark(color) ? "white" : "black") + ";";
String style2 = "-fx-background-color:rgb(" + dataModel.getRgbString() + ");-fx-text-fill:transparent;";
this.setStyle(style2);
label.setStyle(style);
}
//add a Circle to simulate the effect adding images has.
public void addImage(Circle circle){
imageHBox.getChildren().add(circle);
}
private Color getFXColor(int iR, int iG, int iB, double opacity) {
double r = ((double) iR) / 255;
double g = ((double) iG) / 255;
double b = ((double) iB) / 255;
return new Color(r, g, b, opacity);
}
private boolean isColorDark(Color color) {
boolean isDark = false;
double boundary = 0.4980392156862745;
int colorChk = 0;
if (color.getRed() > boundary) {
colorChk += 1;
}
if (color.getGreen() > boundary) {
colorChk += 2;
}
if (color.getBlue() > boundary) {
colorChk += 4;
}
switch (colorChk) {
case 0:
case 1:
case 4:
isDark = true;
break;
}
return isDark;
}
}
解决方案
我已经查看了 ToolBarSkin 的源代码,因为它以我需要的方式处理溢出(渲染 ToggleButtons 的背景颜色并包括图像等子项)。它有一个私有的布尔值 needsUpdate,它设置为在删除或添加项目时尝试,但在子项目的尺寸发生变化时不尝试。现在我使用的技巧是访问这个私有字段的一些反射,所以我可以在请求工具栏重新布局它的孩子之前将它设置为 true。它可以满足我的需要,但我不想使用反射。
//use reflection to access private member field needsUpdate. This is the boolean
//used by the skin to trigger a relayout of the ToolBar when items are added/removed.
//we need to use this to force a relayout when children change sizes too.
Field f1 = ToolBarSkin.class.getDeclaredField("needsUpdate");
f1.setAccessible(true);
f1.setBoolean(skin, true);
this.requestLayout();
谢谢你的评论。
推荐阅读
- r - 用R中的另一个变量索引一个变量
- php - 我的注册和登录控制器不工作(Codeigniter)
- java - 比较数组 - 方法中的累加器不会增加
- scala - Spark DataFrame - 如何根据条件对数据进行分区
- javascript - ES6 是否支持 Elvis 运算符?
- c++ - 如何在 C++ 中访问子类中父类的私有变量
- c# - Instagram API 审核等待时间
- reactjs - React Not Found 渲染服务器端
- javascript - 使用管道仅显示对象的值
- python - Python TypeError:“_sre.SRE_Match”对象没有属性“__getitem__”