I need help creating a delete button on a stack pane in JavaFX.
It should remove the chosen stack pane from the group.
RinStackPane is extended javafx StackPane, gp is java fx Group.
Thank you in advance!
Here is my code:
private void setOnCreateMathOperation(Button createMathOperationBtn) {
createMathOperationBtn.setOnAction(event -> {
var mathOperation = createMathOperation();
RinStackPane stack1 = new RinStackPane();
stack1.setNodeType(NodesTypes.MathOperation);
stack1.setUnderlyingNode(mathOperation);
stack1.setName(mathOperation.getName());
stack1.getChildren().add(mathOperation);
TextField newtf1 = new TextField();
GridPane grid = new GridPane();
grid.addRow(1, newtf1);
stack1.getChildren().add(grid);
makeDraggable(stack1);
addToRectCollection(stack1);
gp.getChildren().add(stack1);
});
}
I am not sure if you want to keep the delete button inside or outside the StackPane.
Anyway you can easily bind an ObservableList to the Group's children. When an elemente will be deleted from the list, automatically this event will be reflected on Group's children:
//Create observable list of Node (you can constrain to RinStackPane if you prefer):
ObservableList<Node> list = FXCollections.observableArrayList();
//bind lists to gp's children:
Bindings.bindContentBidirectional(list, gp.getChildren());
Your code:
private void setOnCreateMathOperation(Button createMathOperationBtn) {
createMathOperationBtn.setOnAction(event -> {
RinStackPane stack1 = new RinStackPane();
//...
//setup the stack
//...
//Add the delete button to the stack:
Button deleteButton = new Button("Delete me!");
deleteButton.setOnMouseClicked(event -> {
list.remove(stack1); //<--- remove action
});
stack1.getChildren().add(deleteButton);
//add to the list instead of gp's children
list.add(stack1);
});
}
Note how the stack is not added nor removed to/from the gp's children, instead the list is used.
If you want the deleteButton outside the stack the code is the same, just be sure that both list and stack1 are somehow passed to the button's click handler.
Hope this helps.
Related
I'm using JFXListView and JFXListCell from the library called jfoenix and the purpose and function are same as the regular ListView.
The list contains some Label, Button and AnchorPane. At the very top and bottom of the list, I want to add non-selectable item. The item should not be selectable on mouse click, should not be able to focus and should not be able to scroll.
I though of using updateItem() function and setting the item disable:
#FXML
JFXListView listView;
ObservableList<AnchorPane> list = FXCollections.observableArrayList();
private void initializeListView(){
AnchorPane headerBottomPane = new AnchorPane();
headerBottomPane.setId("headerBottomPane");
....//some property of AnchorPane
list.add(headerBottomPane); //Add header AnchorPane
while(true){
AnchorPane listContainer = new AnchorPane();
Label title = new Label();
Label subtitle = new Label();
Button button = new Button();
Button button2 = new Button();
//Some code here...
listContainer.getChildren().addAll(label, subtitle, button, button2);
list.add(listContainer);
//some code here...
}
list.add(headerBottomPane); //Add bottom AnchorPane
listView.setCellFactory(new CallBack<JFXListView, JFXListCell>(){
#Override
public JFXListCell call(JFXListView param){
return new JFXListCell<AnchorPane>(){
#Override
protected void updateItem(AnchorPane anchorPane, boolean empty){
super.updateItem(anchorPane, empty);
if(anchorPane != null){
if(anchorPane.getId.equals("headerBottomPane")){
setDisable(true);
}
setItem(anchorPane);
}else{
setItem(null);
}
}
};
}
});
}
I am able to disable the top and last item of the list, the item is no longer able to select using mouseClick.
But the problem is, it is focusable when I use the Keyboard arrow up and arrow down another strange thing is when I use the mouse wheel to scroll the list, some of the item are becoming non-selectable too.
I would think of just using a VBox, and putting your top unselectable item first, then the ListView with all the selectable items, then the bottom unselectable item...
I think you have to put your listView.setCellFactory() function at the top of the code where you adding those items, try to initialize it before you add the item.
and inside your updateItem() try to use setMouseTransparent() and setFocusTravesable().
#Override
protected void updateItem(AnchorPane anchorPane, boolean empty){
super.updateItem(anchorPane, empty);
if(anchorPane != null){
if(anchorPane.getId.equals("headerBottomPane")){
setItem(anchorPane); //moved at the top
setMouseTransparent(true); //added this line
setFocusTraversable(false); //added this line
setDisable(true);
}else{
setItem(null);
}
}
I haven't test it but I hope it work.
Here is a code:
package tabpane;
import javafx.application.*;
import javafx.geometry.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.*;
public class HideShowTabContentOnClicked extends Application {
public static void main(String[] args) {
launch(args);
}
private BorderPane createContent() {
BorderPane borderPane = new BorderPane();
TabPane tabPane = new TabPane();
tabPane.setSide(Side.LEFT);
StackPane stackPane = new StackPane();
stackPane.setStyle("-fx-background-color: lightblue");
stackPane.setMinWidth(200);
Tab firstTab = new Tab("First");
firstTab.setClosable(false);
firstTab.setContent(stackPane);
firstTab.selectedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
firstTab.setContent(null);
} else {
firstTab.setContent(stackPane);
}
});
Tab secondTab = new Tab("Second");
StackPane stackPane2 = new StackPane();
stackPane2.setStyle("-fx-background-color: yellow");
secondTab.setContent(stackPane2);
secondTab.setClosable(false);
secondTab.selectedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
secondTab.setContent(null);
} else {
secondTab.setContent(stackPane2);
}
});
StackPane center = new StackPane();
center.setStyle("-fx-background-color: cyan");
borderPane.setCenter(center);
tabPane.getTabs().addAll(firstTab, secondTab);
borderPane.setLeft(tabPane);
return borderPane;
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setMaximized(true);
stage.show();
}
}
Here I tried to solve a problem by using selectedProperty() by setting content to null, but it doesn't working, I want to make Tab like toggle button so that when I click on it showed and hide TabPanes content.
Before
And when clicked
As an example I want to implement TabPane like Intellij IDEA Tool Buttons (like "Project", "Structure" Tool Buttons etc).
If you are going to keep your content into StackPane, you can bind stackPane.visibleProperty() with toggleButton.selectedProperty():
stackPane.visibleProperty()
.bind(Bindings.when(toggleButton.selectedProperty())
.then(false)
.otherwise(true)
);
in this exampl: toggleButton.isSelected() --> !stackPane.isVisible() and !toggleButton.isSelected() --> stackPane.isVisible(),
or listen ToggleButton's events:
// toggleButton.setOnAction(e ->{ //new .setOnAction() -> Override previous
toggleButton.addEventHandler(ActionEvent.ACTION, e ->{ //can add any quantity for your needs
if(toggleButton.isSelected())
stackPane.setVisible(false);
else stackPane.setVisible(true);
});
But the problem is instead of toggle button I want to use Tab, so that it behaves like toggle button. i.e. when click "First Tab" in my example code if content visible it should be invisible and vice versa. I mean only tabs should be shown
I found solution.Tab does not have click-handler... but
Tab tab = new Tab();
tab.setContent(stackPane);
Label lable = new Label("Label"); //create Label
tab.setGraphic(lable); //set Lable as Graphic to Tab
lable.setOnMouseClicked(event ->{ //setOnMouseClicked, for example
if(stackPane.isVisible()){
stackPane.setVisible(false);
}else{
stackPane.setVisible(true);
}
});
, you can use Label(for example) as Tab-text and add setOnMouseClicked()-handler to Label. You can use any Node with Handler/ActionListener -> It's up to you.
For example, you can use CheckBox to show/hide StackPane, and Tab text (you can combine FXML and Java-code to produce graphics):
Tab tab = new Tab("Tab2"); //Tab with Text
tab.setContent(stackPane);
CheckBox checkBox = new CheckBox(); //create CheckBox
tab.setGraphic(checkBox); //set CheckBox as Graphic to Tab
stackPane.visibleProperty()
.bind(Bindings.when(checkBox.selectedProperty())
.then(false)
.otherwise(true)
);
or
#FXML
private Tab tab;
// ...
tab.setGraphic(checkBox);
// ...
I have came up with this solution:
AtomicReference<Tab> currentTab = new AtomicReference<>(tabPane.getSelectionModel().getSelectedItem());
AtomicReference<Tab> lastTab = new AtomicReference<>(null);
tabPane.setOnMouseReleased(event -> {
// Check if current node is actually tab
Node n = event.getPickResult().getIntersectedNode();
while (n != null && !(n.getStyleClass().contains("headers-region"))) {
n = n.getParent();
}
if (n == null)
return;
lastTab.set(currentTab.get());
currentTab.set(tabPane.getSelectionModel().getSelectedItem());
if (currentTab.get() == lastTab.get()) {
// Hide
tabPane.setPrefSize(28, 28);
//tabPane.getSelectionModel().clearSelection(); // notify selection model
currentTab.set(null);
} else {
// Show
tabPane.setPrefSize(-1,-1);
currentTab.set(tabPane.getSelectionModel().getSelectedItem());
}
});
First of all, I have added mouse event to the tabPane. Inside this mouse event, check if node under cursor is actually Tab node. If it is, do some logic to identify what user is trying to do: hide or show. Hiding is a bit tricky, so I ended up with setting preferred size of TabPane to 28 px wide.
I have also tried to notify selection model with an empty newValue:
tabPane.getSelectionModel().clearSelection();
But this it is not working properly. Calling select(-1) should call clearSelection(), but behavior is different somehow.
When I select another tab after calling clearSelection(), selection model handler called with oldValue == null, that possibly does not update internal index and tab does not swithes to selected one.
I'm currently made an Form with JavaFX.
Always i press a Button, i call the "addAnswer()"-Method.
In that I create a RadioButton, a Label and a delete-Button, which i bundle in a HBox. All that HBoxes i pack in a vBox.
The Problem now is the delete-Button. I want to delte just THAT HBox in which the clicked Button is.
Here is my code:
public void addAnswer() {
this.rB = new RadioButton();
checkAnswer.getToggles().add(rB);
hBox = new HBox();
tF = new TextField();
delAnswer = new Button("Löschen");
delAnswer.setId(Integer.toString(counter));
hBox.getChildren().addAll(rB, tF, delAnswer);
hBox.setId(Integer.toString(counter));
delAnswer.setOnAction(e -> delAnswer(Integer.parseInt(hBox.getId())));
System.out.println(delAnswer.getId());
vBox.getChildren().addAll(hBox);
counter++;
}
public void delAnswer(int e){
vBox.getChildren().remove(delAnswer.getId());
}
i tried this one above but i realized, that all the delAnswers-Buttons have the same ID: the number of how often i pressed the add-Button.
Is there any solution where i can just select that one i pressed with that dynamic way? Cause i don't kow how often somebody will press or delete something.
Thanks
hbox is a field and this is why always the HBox last added is used. (hBox is evaluated, when lambda body is executed, not at the time of the lambda creation). This would be different, if you used a (effectively) final local variable:
final HBox hBoxLocal = hBox;
delAnswer.setOnAction(e -> delAnswer(Integer.parseInt(hBoxLocal.getId())));
However I'd like to present a different solution which would allow you to use the same EventHandler<ActionEvent> for all delete Buttons:
You can get the Node that triggered the event using getSource. From this Node you can get the parent, which is the HBox. You can remove this from the VBox using the remove(Object) method
delAnswer.setOnAction(e -> {
// get button
Node source = (Node) e.getSource();
// remove parent of button from VBox
vBox.getChildren().remove(source.getParent());
});
I think your problem is that you give the same event to all your button,Begin by creating a list that stores your buttons and then increments the value of the ID after affecting it to an item :
List<Button> buttons = new ArrayList<>();
/*
Create Button and call IDEvt method to create new event
for each button
*/
private void IDEvt(Button btn){
btn.setId(String.valueOf(IDRank));
btn.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.println(btn.getId());
}
});
IDRank++;
}
Given
I have a model class Model that contains the following fields:
StringProperty stringProperty; ListProperty<String> listOfStringsProperty
I have a view class View extends VBox that has the following:
TextField stringTextField; TextFieldList stringsTextFieldList;
TextFieldList extends VBox is a custom class that I created that handles multiple TextFields with nice + and - buttons to add and remove input text fields.
TextFieldList class contains the field ObservableList<Node> inputTextFields and I can get the data from these InputTextFields by a method call List<String> getData()
Question
I was able to do the following:
stringTextField.textProperty().bindBidirectional(model.getStringProperty());
in order to bind the result of the stringTextField in View to the stringProperty in Model
And I need to do something like
stringsTextFieldList.listProperty().bindBidirectional(model.getListOfStringsProperty());
How can I do that?
If this design would not work, then how do you suggest I fix it? Is there a built-in class that does the same as TextFieldList but instead extends Control?
If you decide to make your own control you should create the "binding manually", that means that in the input ObservableList you add a ListChangeListener then you process the Change like in the example: check whether a new item is added, removed or updated and maintain your TextFields accordingly. It is possible, but my answer is mainly about proposing an existing control to re-use rather than create your own one.
So if you don't want to re-invent the wheel:
I don't know your exact use-case, but maybe it is reasonable to reuse a control that actually support a data model, like a ListView.
In the example I have modified the Model class to have an ObservableList<StringProperty> rather than a ListProperty<String> (note: it is also possible to simply have String objects in the list, I just modified it to make the binding really clear). I have added a ListView and used setCellFactory to draw TextFields as elements in the list which are bidirectionally bounded to the corresponding StringProperty in the list. I have also added several buttons to add and remove elements and a button to print the current content of the model.
Example:
Model.java
public class Model {
public ObservableList<StringProperty> listOfStringsProperty;
public Model(){
listOfStringsProperty = FXCollections.observableArrayList();
}
}
Main.java
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
Model m = new Model();
m.listOfStringsProperty.addAll(new SimpleStringProperty("First"),
new SimpleStringProperty("Second"),
new SimpleStringProperty("Third"));
ListView<StringProperty> lv = new ListView<StringProperty>();
lv.setCellFactory(new Callback<ListView<StringProperty>, ListCell<StringProperty>>() {
#Override
public ListCell<StringProperty> call(ListView<StringProperty> param) {
return new ListCell<StringProperty>(){
#Override
protected void updateItem(StringProperty item, boolean empty) {
super.updateItem(item, empty);
if(item == null){
setText(null);
setGraphic(null);
return;
}
TextField tf = new TextField();
tf.textProperty().bindBidirectional(item);
setGraphic(tf);
}
};
}
});
lv.setItems(m.listOfStringsProperty);
root.setCenter(lv);
// Control buttons
HBox hbox = new HBox();
Button buttonAdd = new Button("Add");
buttonAdd.setOnAction(e -> m.listOfStringsProperty.add(new SimpleStringProperty("")));
Button buttonRemove = new Button("Remove last");
buttonRemove.setOnAction(e -> m.listOfStringsProperty.remove(m.listOfStringsProperty.size()-1));
Button buttonPrintModel = new Button("Print model");
buttonPrintModel.setOnAction(e -> System.out.println(m.listOfStringsProperty.toString()));
hbox.getChildren().addAll(buttonAdd, buttonRemove, buttonPrintModel);
root.setBottom(hbox);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
This will produce the following window:
You can use
Bindings.bindContent(List list1, ObservableList list2).
This is a special binding that keeps the list in sync with the observable list. Keep in mind that this is not bidirectional though.
If bidirectorionality is what you want, you should use invalidation listeners or change listeners to detect changes and synchronize the lists manually. You might need some crude locking mechanism to prevent a stack overflow.
In the application above I have a tabpane (the one with "age", "gender" and "zipcode") where each tab contains a VBox. The VBox is split in two:
The upper part of the VBox lets you view a list of lists, the lower part of the VBox contains a menu that allows you to change the list of lists.
The button in the lower part of the VBox updates the aforementioned list of lists. I want the upper node in the VBox to be updated anew when the underlying lists are changed.
The relevant code snippet might be
Node createHierarchySplitMenu(HierarchiesFromFile hierarchies, String hierarchyName){
VBox vBox = new VBox();
vBox.getChildren().add(createHierarchyScrollPane(hierarchies, hierarchyName));
vBox.getChildren().add(createHierarchyMenu());
return vBox;
}
When the button in the node in the lower part of the VBox (created by createHierarchyMenu()) I want createHierarchyScrollPane() to be called again to show the new list of lists. How do I do that?
Is there a regular pattern/way of updating one node from another (when they are at the same level.)?
What have you tried? Nothing worth mentioning- I am stuck.
If you need more info, please ask. Didn't want to bog you down with code.
From what I understand the solution can be next:
Create class to handle createHierarchyScrollPane():
private class HierarchyScrollPane extends ScrollPane {
public void update(HierarchiesFromFile hierarchies, String hierarchyName) {
// code from createHierarchyScrollPane() which works with "this" instead of new Scroll Pane
}
}
Pass instance to createHierarchyMenu():
Node createHierarchySplitMenu(HierarchiesFromFile hierarchies, String hierarchyName){
VBox vBox = new VBox();
ScrollPaneUpdate hsp = new HierarchyScrollPane();
hsp.update(hierarchies, hierarchyName);
vBox.getChildren().add(hsp);
vBox.getChildren().add(createHierarchyMenu(hsp));
return vBox;
}
Somewhere in createHierarchyMenu():
public void createHierarchyMenu(final HierarchyScrollPane hsp) {
// ...
Button btnSetMin = new Button("Set Min");
btnSetMinsetOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent t) {
// your code for data update
hsp.update(hierarchies, hierarchyName);
}
});
}