I have multiple CheckBoxes in the user interface I created in the FXML file.
<CheckBox..>
<contextMenu>
<ContextMenu>
<items>
<MenuItem text="%uncheckall" onAction="#uncheckAll" />
<MenuItem text="%checkall" onAction="#checkAll" />
</items>
</ContextMenu>
</contextMenu>
</CheckBox>
...
All CheckBoxes use the same methods, i.e. uncheckAll and checkAll.
How can I return ContextMenu's source Node i.e. CheckBox in the handling methods from Event?
#FXML private void uncheckAll(Event event) {
MenuItem mni = (MenuItem)event.getSource();
ContextMenu cm = mni.getParentPopup();
...???
}
There doesn't appear to be anything in the API for obtaining the owner of a Node's context menu, however the ancestor class of ContextMenu has method setUserData. You can set the CheckBox as the user data of the ContextMenu. Here is an example using code only, i.e. not FXML.
CheckBox cb = new CheckBox("check");
MenuItem mi = new MenuItem("menu item");
mi.setOnAction(this::uncheckAll);
ContextMenu cm = new ContextMenu(mi);
cm.setUserData(cb);
cb.setContextMenu(cm);
Then, in your uncheckAll method, you can retrieve the CheckBox via method getUserData.
#FXML private void uncheckAll(Event event) {
MenuItem mi = (MenuItem) event.getSource();
ContextMenu cm = mi.getParentPopup();
CheckBox cb = (CheckBox) cm.getUserData();
}
Edit
In FXML simply add a userData attribute to the ContextMenu tag.
<ContextMenu userData="cbxGroup1">
You probably don’t actually need to work out which checkbox the context menu is attached to.
Your task can probably be accomplished through standard fxml injection into a controller.
Assign the checkbox an fx:id in your fxml:
fx:id="cb"
Inject it in your controller:
#FXML private CheckBox cb;
After the fxml is loaded, you can refer to the check box by name cb anywhere in the controller, including in an event handler implementation.
#FXML private void uncheckAll(Event e) {
// you can refer to cb directly here.
}
As you have multiple checkboxes in your interface, do all of the above multiple times, including the event handler definition.
If you want to share common processing for the event handlers, you can call a separate method, passing in the reference to the checkbox associated with the event handler.
#FXML private CheckBox cb1;
#FXML private CheckBox cb2;
#FXML private void uncheckAllForCb1(Event e) {
uncheckAll(cb1);
}
#FXML private void uncheckAllForCb2(Event e) {
uncheckAll(cb2);
}
private void uncheckAll(CheckBox sourceBox) {
// take action for box.
}
Related
I want to create a JavaFX fxml based dialog, where the user can enter a bunch of integer and double values. I created the dialog in SceneBuilder using for each of the values a dedicated TextField.
Intentionally I am not using Binding between the TextFields and the model. In order to NOT add a ChangeListener or set a TextFormatter to each of these TextFields in the controller again and again, I created a dedicated IntegerTextField and DoubleTextField class, e.g.
public class IntegerTextField extends TextField {
protected static Pattern decimalPattern = Pattern.compile("^-?\\d+$"); // Double ("-?\\d*(\\.\\d{0,1})?");
public IntegerTextField() {
super();
setTextFormatter(new TextFormatter<>(c -> (decimalPattern.matcher(c.getControlNewText()).matches()) ? c : null ));
}
public int getInt() {
try {
return Integer.parseInt(getText());
}
catch (NumberFormatException e) {
return 0;
}
}
}
and in the Controller class I replaced the previous
#FXML private TextField setsTextField;
with
#FXML private IntegerTextField setsTextField;
When I got the
javafx.fxml.LoadException:...Can not set util.IntegerTextField field ctrl.ExerciseEditorCtrl.setsTextField to javafx.scene.control.TextField
I realized that this implicit downcasting doesn't work.
Is there a way to do this properly with fxml or is it neccessary to have the dialog setup in a java class when using IntegerTextField?
I have a pane with a label, a text field and a combo box inside a VBox in fxml file. Let´s call it tempPane.
In the same stage I have a button.
Once the button is pressed I need to add to the VBox a pane exactly the same as tempPane. This is, adding dynamically a pane to the VBOX.
I am able to add individual controls such as buttons or labels or text fields to the VBox, but I can´t obtain the same results when trying to add this new pane.
Part of the controller code:
#FXML
private Pane tempPane;
#FXML
private Button btnAddNewPane;;
#FXML
private VBox vBox;
#FXML
void addNewPane(ActionEvent event) {
...
Pane newPane = new Pane();
newPane = tempPane;
// New ID is set to the newPane, this String (NewID) should be
//different each time button is pressed
newPane.setId(newID);
vBox.getChildren().add(newPane);
...
}
And the error I´m getting is:
Exception in thread "JavaFX Application Thread" java.lang.IllegalArgumentException: Children: duplicate children added: parent = VBox[id=filterBox]
at javafx.graphics/javafx.scene.Parent$3.onProposedChange(Parent.java:580)
at javafx.base/com.sun.javafx.collections.VetoableListDecorator.add(VetoableListDecorator.java:206)
at com.sener.dbgui.controller.SearchController$1.run(SearchController.java:53)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$9(PlatformImpl.java:418)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:417)
at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:175)
at java.base/java.lang.Thread.run(Thread.java:844)
So, why am I getting this duplicate children error? I´m changing the newPane ID before adding it to the VBox.
Pane newPane = new Pane();
newPane = tempPane;
...
vBox.getChildren().add(newPane);
This code does create a new Pane (first line) but immediately drops the new instance by overwriting it with the old one (second line).
The error happens since the contract of Node does not allow it to be placed twice in a scene and you're adding the same Pane that is already a child of vBox again. Modifying the id property does not change that fact.
You need to create a new copy of the subscene rooted at tempPane if this is supposed to work.
You could create a custom Pane for this scene:
subFXML.fxml
<fx:root xmlns:fx="http://javafx.com/fxml" type="javafx.scene.layout.Pane">
<!-- content of tempPane from old fxml goes here -->
...
<Button fx:id="btnAddNewPane" />
...
</fx:root>
public class MyPane extends Pane {
public MyPane() {
FXMLLoader loader = getClass().getResource("subFXML.fxml");
loader.setRoot(this);
loader.setController(this);
try {
fxmlLoader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
#FXML
private Button btnAddNewPane;
public void setOnAction(EventHandler<ActionEvent> handler) {
btnAddNewPane.setOnAction(handler);
}
public EventHandler<ActionEvent> getOnAction() {
return btnAddNewPane.getOnAction();
}
}
old fxml
Be sure to import MyPane.
...
<VBox fx:id="vBox">
<children>
<!-- replace tempPane with MyPane -->
<MyPane onAction="#addNewPane"/>
</children>
</VBox>
...
old controller
#FXML
private VBox vBox;
#FXML
void addNewPane(ActionEvent event) {
...
MyPane newPane = new MyPane();
newPane.setId(newID); // Don't know why setting the CSS id is necessary here
newPane.setOnAction(this::addNewPane); // set onAction property
vBox.getChildren().add(newPane);
...
}
It is written in your comments already why you are getting duplicate ID.
// New ID is set to the newPane, this String (NewID) should be
//different each time button is pressed
You are passing the same string as a parameter.
newPane.setId("NewID");
try using a dynamically generated and unique id for each pane.
String newId; //generate the id by user input or internally
newPane.setId(newId);
I have written a controller for two windows /stages.
The first window is opened in the MainClass. The second in the Controller, if the user clicks onto a button.
How can I get the TextFields from second.fxml in the applyFor()-method?
Thanks.
#FXML
protected void requestNewAccount(ActionEvent event) {
try {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("second.fxml")); // TextFields in there
Parent root = (Parent) fxmlLoader.load();
Stage stage = new Stage();
stage.initModality(Modality.APPLICATION_MODAL);
stage.setTitle("Second Window");
Scene scene = new Scene(root);
String css = MainOnlineCustomer.class.getResource("/style.css").toExternalForm();
scene.getStylesheets().clear();
scene.getStylesheets().add(css);
stage.setScene(scene);
stage.show();
} catch (IOException e) {
logger.error(e);
}
}
/**
* closes the "second"-Window
* #param event
*/
#FXML
protected void cancel(ActionEvent event) {
final Node source = (Node) event.getSource();
final Stage stage = (Stage) source.getScene().getWindow();
stage.close();
}
#FXML
protected void applyFor(ActionEvent event) {
// get values from TextField in second.fxml here!!!
}
It's not good to share controllers between fxmls unless they serve the same purpose. Here both fxml seem to serve a different purpose (account management, login or something similar for one of them and creating a new account for the other). What is even worse is that those classes do not share the same controller instance, which means the small (and probably only) benefit you could get from using the same controller, is not used here. You should better use different controllers.
Since you use Modality.APPLICATION_MODAL as modality, I'd recommend using showAndWait instead of show to open the new stage. This will enter a nested event loop, which allows the UI to remain responsive and continues after the invocation of showAndWait once the stage is closed.
Furthermore add a method to the controller of second.fxml that allows you to retrieve the result.
Example
This creates a Person object with given name and family name.
"primary window (opening the "inner" stage)
FXMLLoader loader = new FXMLLoader(getClass().getResource("second.fxml"));
Stage subStage = new Stage();
subStage.initModality(Modality.APPLICATION_MODAL);
subStage.setTitle("Second Window");
Scene scene = new Scene(loader.load());
subStage.setScene(scene);
subStage.showAndWait();
Optional<Person> result = loader.<Supplier<Optional<Person>>>getController().get();
if (result.isPresent()) {
// do something with the result
}
controller for "inner" content
public class SecondController implements Supplier<Optional<Person>> {
#FXML
private TextField givenName;
#FXML
private TextField familyName;
private boolean submitted = false;
// handler for submit action
#FXML
private void submit() {
submitted = true;
givenName.getScene().getWindow().hide();
}
// handler for cancel action
#FXML
private void cancel() {
givenName.getScene().getWindow().hide();
}
#Override
public Optional<Person> get() {
return submitted ? Optional.of(new Person(givenName.getText(), familyName.getText())) : Optional.empty();
}
}
Note that you can gain access to any data available to the controller this way. I wouldn't recommend accessing any nodes (like TextFields) directly though, since this makes changing the UI harder.
Using the Supplier interface here is not necessary, but I chose to do this to achieve a loose coupling between SecondController and the main window.
I'm very new to javafx and scenebuilder.
I was trying to create a new label when a button is clicked. Following is the method for the button to create a label:
#FXML
public void enterTextMouse(MouseEvent e){
Label lbl1 = new Label(txtCmd.getText());
}
But it doesn't work. :(
Is it because I didn't create lbl1 in my fxml?
Is it possible to create a label in my java code without creating in fxml?
Please help me!
Let's make one pane and set fx:id(paneLoadLabel) in scenebuilder
Write this in controller
#FXML
private Pane panelLoadLabel;
#FXML
public void enterTextMouse(MouseEvent e){
Label lbl1 = new Label(txtCmd.getText());
paneLoadLabel.getChildren().add(lbl1);
}
I'm trying to set a variable value when a MenuItem i chosen in a MenuButton object.
I've tried to search for this but I've came up empty handed.
Here's the code to set the MenuItems:
private ObservableList<MenuItem> templateMenuItems = FXCollections.observableArrayList();
#FXML private MenuButton menu = new MenuButton();
#FXML
protected void getTemplates() throws IOException {
CaspReturn tls = this.socket.runCmd(new Tls(""));
String tlsList = tls.getResponse();
String[] tlsListSplitt = tlsList.split("\\n");
for (int i = 0; i < tlsListSplitt.length; i++) {
String[] tlsLine = tlsListSplitt[i].split("\"");
this.templateMenuItems.add(new MenuItem(tlsLine[1]));
}
this.menu.getItems().setAll(this.templateMenuItems);
}
I'm not sure how to write the code to get the text from a menuItem or which field in scenebuilder the method should be in.
It's not clear what your asking, but I'll assume that you want to know the text of a menu item when it is clicked. To do that, you need to add an event handler onto the menu item. The following is clipped from the JavaDoc for ContextMenu:
MenuItem item1 = new MenuItem("About");
item1.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent e) {
System.out.println("About");
}
});
You can get the event source, cast in to MenuItem and then get the text from that.
There's a real problem with your code the way it's written, though. You have calls to an external database in getTemplates, and as it's implemented as an #FXML element that almost guarantees that it'll be run on the FXAT, which is really, really bad.
I'd refactor that so that the database access is in a Task, and then the MenuItem creation is a handler for the onSucceeded event of the Task. Then you need instantiate a ContextMenu and install the MenuItem's on it in that event handler.
The getTemplates() method should be called as the onAction event for the button.