I want to do make combobox ala dropdown menu like it's on many websites.
Category A
aa
ab
ac
Category B
ba
bb
bc
Something like:
I didn't found anywhere a similar problem. And I don't know where to start. Should I do a custom combobox using contextmenu instead of a listview, as contextmenu is would do a pretty similar job
If I understood correctly, for that purpose I would use a Button - Context Menu - Menu - CheckMenuItem approach.
ContextMenu contextMenu = new ContextMenu();
Menu menu1 = new Menu("Scrollable Submenu");
Menu menu2 = new Menu("Regular Submenu");
contextMenu.getItems().addAll(menu1, menu2);
for (int i = 1 ; i <= 25; i++) {
CheckMenuItem checkMenuItem = new CheckMenuItem("Option " + i);
menu1.getItems().add(checkMenuItem);
}
Button button = new Button();
button.setContextMenu(contextMenu);
BorderPane root = new BorderPane();
root.setTop(new HBox(button));
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Dropdown Menu");
primaryStage.setScene(scene);
primaryStage.show();
button.setOnAction(e -> button.getContextMenu().show(button.getScene().getWindow()));
Let me know if this approach fits your needs.
Related
This question already has answers here:
Get the height of a node in JavaFX (generate a layout pass)
(2 answers)
Closed 3 years ago.
So I'm not sure how to calculate the height of the node during the .setOnAction event I have tried .requestLayout()/.applyCss() not sure what else to try I am trying to find the height of the vBox after adding a node but it is only printing the height of the node before the new one was added
public class Main extends Application {
#Override
public void start(Stage stage) {
VBox vBoxContainer = new VBox();
vBoxContainer.setAlignment(Pos.CENTER);
vBoxContainer.setPrefSize(200,200);
VBox vBox = new VBox();
vBox.setAlignment(Pos.CENTER);
for (int i = 0; i < 5; i++)
vBox.getChildren().add(new Label("newLabel"));
vBoxContainer.getChildren().add(vBox);
Button button = new Button("Add Label");
button.setOnAction(event -> {
System.out.println("Height Before new Label:"+vBox.getHeight());
vBox.getChildren().add(new Label("newLabel"));
//here is where I was adding code to produce expected result
System.out.println("Height After new Label:"+vBox.getHeight());
});
Button checkButton = new Button("Print VBox Height");
checkButton.setOnAction(event -> System.out.println("VBox Height:"+vBox.getHeight()));
vBoxContainer.getChildren().addAll(button, checkButton);
stage.setScene(new Scene(vBoxContainer));
stage.show();
}
}
Run the example and Click the button that adds a Label to the vBox and it outputs
Actual Result:
Height Before new Label:85.0
Height After new Label:85.0
Expected Result:
Height Before new Label:85.0
Height After new Label:102.0
But if you then click the Print VBox Height Button it will show the correct height of:
VBox Height:102.0
You can try adding a listener to the VBox's height property.
VBox vBoxContainer = new VBox();
vBoxContainer.setAlignment(Pos.CENTER);
vBoxContainer.setPrefSize(200, 200);
VBox vBox = new VBox();
vBox.setAlignment(Pos.CENTER);
vBoxContainer.getChildren().add(vBox);
vBox.heightProperty().addListener((observable, oldValue, newValue) -> {
System.out.println("Height changed to: " + newValue.doubleValue());
if(newValue.doubleValue() > 100)
{
//do something!
}
});
for (int i = 0; i < 5; i++) {
vBox.getChildren().add(new Label("newLabel"));
}
Button button = new Button("Add Label");
button.setOnAction(event -> {
vBox.getChildren().add(new Label("newLabel"));
});
Button checkButton = new Button("Print VBox Height");
checkButton.setOnAction(event -> System.out.println("VBox Height:" + vBox.getHeight()));
vBoxContainer.getChildren().addAll(button, checkButton);
stage.setScene(new Scene(vBoxContainer));
stage.show();
requestLayout does not actually do a layout pass. It simply tells JavaFX, that a layout pass is required which will result in JavaFX doing the layout pass some time after your method returns. To do the layout yourself, you need to call layout yourself, i.e. change the logic in the event handler like this:
button.setOnAction(event -> {
System.out.println("Height Before new Label:"+vBox.getHeight());
vBox.getChildren().add(new Label("newLabel"));
// manually doing layout on the root here
vBoxContainer.applyCss();
vBoxContainer.layout();
System.out.println("Height After new Label:"+vBox.getHeight());
});
Note that I do the layout pass for the root, since the ancestor layouts can also be involved in determining the actual size of a Node...
I'm trying to implement a Gluon Mobile toggle button for a survey page, and when testing, the button jumps to the left a little when I click it. I don't want it to jump at all. You can see it here:
Relevant code is here:
StackPane getToggler() {
ToggleButton toggleButton = new ToggleButton("Yes");
ToggleButtonGroup toggleButtonGroup = new ToggleButtonGroup();
toggleButtonGroup.setSelectionType(SelectionMode.SINGLE);
toggleButtonGroup.setPadding(new Insets(10));
toggleButton = new ToggleButton("Yes");
toggleButton.setStyle("-fx-text-fill:steelblue;");
toggleButton.setUserData("1");
toggleButton.setSelected(false);
toggleButton.selectedProperty().addListener((obv, ov, nv) -> {
if (nv.booleanValue()) {
toggleButtonGroup.setUserData("1");
}
});
toggleButtonGroup.getToggles().add(toggleButton);
toggleButton = new ToggleButton("No");
toggleButton.setStyle("-fx-text-fill:steelblue;");
toggleButton.setSelected(true);
toggleButton.setUserData("0");
toggleButton.setSelected(false);
toggleButton.selectedProperty().addListener((obv, ov, nv) -> {
if (nv.booleanValue()) {
toggleButtonGroup.setUserData("0");
}
});
toggleButtonGroup.getToggles().add(toggleButton);
togglers.add(toggleButtonGroup);
StackPane wrapper = new StackPane();
wrapper.setAlignment(Pos.CENTER);
wrapper.getChildren().add(toggleButtonGroup);
return wrapper;
}
Here's where I get the togglers and their relation to the label to the left:
for (int i = 0; i < this.questions.length; i++) {
HBox row = new HBox();
row.setSpacing(5);
row.setAlignment(Pos.CENTER_LEFT);
Label label = new Label(this.questions[i]);
label.setWrapText(true);
label.setPrefWidth(200);
label.setTextAlignment(TextAlignment.LEFT);
label.setFont(new Font("System", 14));
StackPane wrapper = this.getToggler();
Region region = new Region();
HBox.setHgrow(region, Priority.ALWAYS);
HBox.setHgrow(label, Priority.NEVER);
row.getChildren().addAll(label,region,wrapper);
box.getChildren().add(row);
box.getChildren().add(new Separator());
}
After some debugging, I've realized that the min width value of the toggle buttons is wider than their pref width.
This means that after the user selects one toggle, the min width is applied, and the control is resized with the required min width, shrinking the region as a consequence.
A quick fix (until this gets fixed in the control) can be setting the min width of your toggle buttons:
private StackPane getToggler() {
ToggleButtonGroup toggleButtonGroup = new ToggleButtonGroup();
ToggleButton toggleButtonYes = new ToggleButton("Yes");
toggleButtonYes.minWidthProperty().bind(toggleButtonYes.prefWidthProperty());
toggleButtonGroup.getToggles().add(toggleButtonYes);
ToggleButton toggleButtonNo = new ToggleButton("No");
toggleButtonNo.minWidthProperty().bind(toggleButtonNo.prefWidthProperty());
toggleButtonGroup.getToggles().add(toggleButtonNo);
...;
}
I'm learning javafx and I have a problem.
I'm building an interface with a border pane and a menu bar at the top and when I click on the items I want scenes to be loaded on the center of the border pane. That seems to be working alright. I want to add a button to close the scene but I can't make it work.
See below the code.
stage = primaryStage;
stage.setTitle("My Program");
BorderPane pane = new BorderPane();
MenuBar menuBar = new MenuBar();
Menu menuFile = new Menu("File");
MenuItem load = new MenuItem("Load");
MenuItem save = new MenuItem("Save");
....
BorderPane sp = new BorderPane();
sp.setStyle("-fx-background: #FF0000;");
Button btn = new Button("Close");
btn.setPrefSize(200, 20);
btn.setLayoutX(200);
btn.setLayoutY(200);
sp.getChildren().add(btn);
load.setOnAction(e -> pane.setCenter(sp));
btn.setOnAction(e -> ???????????????????);
scene1 = new Scene(sp);
scene = new Scene(pane, 800, 500);
stage.setScene(scene);
stage.show();
I was wondering if it's something that I can do and what code I should put instead of the question marks.
Any help is appreciated. Thanks in advance.
btn.setOnAction(e -> pane.setCenter(null));
to clear the border pane content, or
btn.setOnAction(e -> stage.hide());
to close the entire window
I'm building a GUI application with javafx that needs PannableProperty from the ScrollPane to work when the user drag the content of it from anywhere.
In oracle docs they say about the "pannableProperty":
Specifies whether the user should be able to pan the viewport by using
the mouse. If mouse events reach the ScrollPane (that is, if mouse
events are not blocked by the contained node or one of its children)
then pannable is consulted to determine if the events should be used
for panning.
So my problem is the mouse event cannot reach the ScrollPane..
Anyone has a clue how to make it possible?
this is a simple code to test it:
ScrollPane root = new ScrollPane();
root.setHbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
root.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
root.setPannable(true);
VBox v = new VBox(10);
TitledPane c1 = new TitledPane("test: ", new HBox(new Label("test: "), new TextField()));
HBox c2 = new HBox(new Label("we are just in HBox "), new TextField());
Label c3 = new Label("I'm just a label and pannableProperty works here");
TitledPane c4 = new TitledPane("test4", new HBox(new Label("test: "), new TextField()));
AnchorPane c5 = new AnchorPane();
c5.setPrefSize(100, 100);
v.getChildren().addAll(c1, c2, c3, c4, c5);
root.setContent(v);
Scene scene = new Scene(root, 300, 300);
primaryStage.setScene(scene);
primaryStage.show();
Another tricky one :-)
The default Skin implementation of the TitledPane is a subclass of SkinBase and the default constructor (which gets invoked by TitledPaneSkin) does this (shortened version):
protected SkinBase(final C control) {
// Default behavior for controls is to consume all mouse events
consumeMouseEvents(true);
}
So we need to reverse this, unfortunately you have to do reflection for this:
TitledPane c1 = new TitledPane("test: ", new HBox(new Label("test: "), new TextField()));
c1.skinProperty().addListener((w,o,n)-> {
if(n instanceof SkinBase) {
SkinBase<?> skinbase = (SkinBase<?>) n;
try {
Method m = SkinBase.class.getDeclaredMethod("consumeMouseEvents", Boolean.TYPE);
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
boolean wasAccessible = m.isAccessible();
try {
m.setAccessible(true);
m.invoke(skinbase, false);
return null;
}
catch(ReflectiveOperationException e) { throw new IllegalStateException(e); }
finally {
m.setAccessible(wasAccessible);
}
});
} catch (ReflectiveOperationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
And now it should work, at least it does in my test application.
EDIT #1:
Doing this resets the focus during mouse operations, which renders the TitledPane somewhat unusable. So we are now messing with the focus system:
ScrollPane root = new ScrollPane();
root.setFocusTraversable(false);
Scene scene = new Scene(root, 300, 300);
scene.focusOwnerProperty().addListener((w,o,n)->
if(n == root && o != null) {
o.requestFocus();
}
});
Basically what we are doing here is that we re-focus the previously focussed component if the newly focused element is the ScrollPane.
I´m trying to create a list with the number of lines of a textarea like in a text editor. I have done it with a VBox item and adding TextField ListCell but when I scroll in the textarea, the VBox doesn´t it . How can I do it?. This is part of code:
TextArea areaNueva = new TextArea();
areas.add(numeroTab, areaNueva);
areas.get(numeroTab).setStyle("-fx-font:15pt \"Times New Roman\";" + "-fx-focus-color: transparent;");
BorderPane bor = new BorderPane();
ObservableList<TextFieldListCell> tf = FXCollections.observableArrayList();
TextFieldListCell cell = new TextFieldListCell();
VBox b = new VBox();
cell.setPrefSize(20,1);
cell.setFont(Font.font("Times New Roman",11.35));
cell.setText("1");
tf.add(0,cell);
b.getChildren().addAll(tf);
b.setSpacing(-2);
b.setPadding(new Insets(3,0,0,0));
bor.setLeft(b);
bor.setCenter(areaNueva);
Tab tabNuevo = new Tab("Sin Titulo");
tabs.add(numeroTab, tabNuevo);
tabs.get(numeroTab).setClosable(true);
tabs.get(numeroTab).setContent(bor);
An with this I add new number of lines:
private ArrayList<ObservableList<TextFieldListCell>> lineas = new ArrayList<ObservableList<TextFieldListCell>>();
String parte = null;
int i = 1;
while ((parte = br.readLine()) != null) {
areaAUtilizar.appendText(parte + "\n");
if(i!=1){
TextFieldListCell c = new TextFieldListCell();
c.setText(Integer.toString(i));
c.setFont(Font.font("Times New Roman",11.35));
c.setPrefSize(20, 13);
lineas.get(a).add(i-1,c);
boxes.get(a).getChildren().setAll(lineas.get(a));
}
i++;
}
I solved it by removing the scroll of the textarea and the listview and put its opacity to 0, and putting both in a borderpane. After I put a scrollbar and the borderpane in a scrollpane. And for it to appear the scrollbar of the scrollpane I increased the height of the textarea and the listview when the lines of the textarea are more great than the prefheight.