I created listview in my application and its item has a delete button:
public Cell() {
super();
this.delete = new Button("delete");
// I have also image and other labels in this cell
this.hBox.getChildren().addAll(productImage, amountLabel, priceLabel, delete);
HBox.setHgrow(pane, Priority.ALWAYS);
delete.setOnAction(event -> getListView().getItems().remove(getItem()));
}
But in my main controller window when I click this button amount field and amountLabel must be changed which are my main controller class:
#FXML
void addToShopCart(ActionEvent event) {
selectedPart.setAmount(amount);
selectedPart.setSumma(amount*selectedPart.getPrice());
shopListView.getItems().add(selectedPart);
summa += amount*selectedPart.getPrice();
totalPriceLabel.setText(summa + "$");
} // this is when I add an item to the list
Now I don't know how to subtract amount from summa that was added. For this I tried return button like this:
Cell cell;
#FXML
void initialize() {
cell = new Cell();
shopListView.setCellFactory(param -> cell));
Button button = cell.getDeleteButton();
button.setOnAction(...//some action)
}
But it doesn't help. I tried to cut the code.
Related
My controller class has a moveButton method that on button click moves the button to a new location. This works fine and is called by a number of buttons which do the same thing. I want to add a key listener so when a button has been clicked once, until a different button is clicked, the user can use the up arrow to move the button (ie call the same moveButton function). The below is how I have tried to implement it, I also tried putting the key listener in the initialize method but neither seem to be working. Any advice would be greatly appreciated!
public void moveButton(ActionEvent actionEvent) {
Button buttonPressed = (Button) actionEvent.getSource();
double newAnchor = getNewAnchor(AnchorPane.getBottomAnchor(buttonPressed)) // separate method that returns new anchor location
AnchorPane.setBottomAnchor(buttonPressed, newAnchor);
buttonPressed.getScene().setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if(event.getCode() == KeyCode.UP){
moveButton(actionEvent);
}
}
});
}
Don't treat the events like data that you need to pass around. Use them as triggers to do work. Generally, don't write generic event handlers that are called from multiple events and multiple nodes. Write short event handlers that just call methods to do something, and pass them the minimum from the event that they need to do the job.
If you do this, then it changes your thinking about how all of this stuff works and then it's just plain old Java, with no magic. And it's simple:
public class MoveButton extends Application {
private Node activeButton;
private Pane pane;
#Override
public void start(Stage primaryStage) throws Exception {
pane = new Pane();
Scene scene = new Scene(pane, 1200, 800);
primaryStage.setScene(scene);
primaryStage.show();
Button button1 = new Button("Button 1");
Button button2 = new Button("Button 2");
button2.setTranslateX(80);
button1.setOnAction(evt -> buttonClick(button1));
button2.setOnAction(evt -> buttonClick(button2));
pane.getChildren().addAll(button1, button2);
pane.setOnKeyPressed(evt -> moveButton(evt.getCode()));
}
private void moveButton(KeyCode keyCode) {
switch (keyCode) {
case UP -> activeButton.setTranslateY(activeButton.getTranslateY() - 30);
case RIGHT -> activeButton.setTranslateX(activeButton.getTranslateX() + 30);
case DOWN -> activeButton.setTranslateY(activeButton.getTranslateY() + 30);
case LEFT -> activeButton.setTranslateX(activeButton.getTranslateX() - 30);
}
}
private void buttonClick(Node button) {
activeButton = button;
pane.requestFocus();
}
}
I want to bind the disable of a button with dynamically created checkboxes. The Button should be enabled if a checkbox is selected.
This is my code
public class DietTabPageController {
#FXML
private FlowPane parent;
#FXML
private Button okButton;
private ObservableList<CheckBox> checkBoxes=FXCollections.observableArrayList();
#FXML
private void initialize() {
ObservableList<Diet> diets = DietDAO.getDiets();
diets.forEach(diet -> checkBoxes.add(new CheckBox(diet.getName())));
//checkboxes added in parent Flowpane
parent.getChildren().addAll(checkBoxes);
}
}
Any suggestions? Thanks
You can use JavaFX's really nice Bindings-class!
Try this:
okButton.disableProperty().bind(
Bindings.createBooleanBinding(
()->!checkBoxes.stream().anyMatch(CheckBox::isSelected),
checkBoxes.stream().map(x->x.selectedProperty()).toArray(Observable[]::new)
)
);
This creates a new Binding, which will listen on every checkbox and then call the given function to calculate the value of your property.
Additional reading here: Bindings
Regarding your comment:
I don't know how much you can edit your Diet class, but if you can, there is a very simple way to display your checkboxes and add the button-binding. Take a look at the following sample:
ListView<Diet> dietsView = new ListView<>(diets);
dietsView.setCellFactory(CheckBoxListCell.forListView(diet ->
diet.selectedProperty()));
btn.disableProperty().bind(
Bindings.createBooleanBinding(
() -> !diets.stream().anyMatch(diet->diet.isSelected()),
diets.stream().map(x->x.selectedProperty())
.toArray(Observable[]::new)
)
);
add this to Diet class:
private final BooleanProperty selected = new SimpleBooleanProperty();
public final BooleanProperty selectedProperty() {
return this.selected;
}
public final boolean isSelected() {
return this.selectedProperty().get();
}
public final void setSelected(final boolean on) {
this.selectedProperty().set(on);
}
You need to add listeners to all the selected properties of the CheckBoxes. Every time one of the property changes, modify the Button's disable property, if necessary. BTW: Making checkBoxes observable doesn't seem necessary:
private List<CheckBox> checkBoxes;
#FXML
private void initialize() {
ObservableList<Diet> diets = DietDAO.getDiets();
checkBoxes = new ArrayList<>(diets.size());
ChangeListener<Boolean> listener = (o, oldValue, newValue) -> {
if (newValue) {
// activate button since at least one CheckBox is selected
okButton.setDisable(false);
} else {
// disable button, if the last CheckBox was unselected
for (CheckBox cb : checkBoxes) {
if (cb.isSelected()) {
return; // don't do anything, if there still is a selected CheckBox
}
}
okButton.setDisable(true);
}
};
for (Diet diet : diets) {
CheckBox cb = new CheckBox(diet.getName());
cb.selectedProperty().addListener(listener);
checkBoxes.add(cb);
}
//checkboxes added in parent Flowpane
parent.getChildren().addAll(checkBoxes);
}
I have a tilepane inside a scrollpane. The tilepane is filled with buttons that have actions like delete or edit. After I delete or edit a button, the scrollpane automatically goes back to the top, which I don't want. I want it to remain at the current position (the position the user scrolled to).
I tried getting and setting the vValue of the scrollpane. getvValue gets the value and the setter sets it, but the scrollpane doesn't respond to it and goes back to the top after the action (delete/ edit). I tried a solution from this question: JavaFX ScrollPane setVvalue() not working as intended, but the layout() method doesn't do anything either. What am I doing wrong here? How do I make the scrollpane stay where it is?
EDIT: I found the problem, but still don't know how to fix it. Apparently setting and changing the visibility on the hBoxes in fxml puts the scrollpane back to the top. If I remove the setVisible methods and run it, I can delete items and the scrollpane stays in position. How to fix this?
On request I created a Minimal, Complete, and Verifiable example of my code. Run it, scroll down a bit, right click a button with a name and price on it and then click delete. It scrolls back up to the top.
This is my code:
public class Controller {
private static LinkedHashMap<String, BigDecimal> mProductMap = new LinkedHashMap<>();
#FXML private TilePane fieldContainer = new TilePane();
#FXML private ScrollPane scroll;
#FXML private Button deletebtn;
#FXML private Button editbtn;
#FXML private HBox homeBar;
#FXML private HBox actionBar;
#FXML
private void initialize() {
addProduct("Coffee", new BigDecimal("2.00"));
addProduct("Tea", new BigDecimal("2.00"));
addProduct("Cappuccino", new BigDecimal("2.00"));
addProduct("Espresso", new BigDecimal("2.00"));
addProduct("Cooky", new BigDecimal("2.00"));
addProduct("Candy", new BigDecimal("2.00"));
addProduct("Chocobar", new BigDecimal("2.00"));
addProduct("Cola", new BigDecimal("2.00"));
addProduct("Fanta", new BigDecimal("2.00"));
addProduct("Beer", new BigDecimal("2.00"));
addProduct("Salad", new BigDecimal("2.00"));
addProduct("Sandwich", new BigDecimal("2.00"));
addProduct("Water", new BigDecimal("2.00"));
addProduct("Cassis", new BigDecimal("2.00"));
}
// makes the delete and edit buttons appear after selecting a button from the tilepane
private void select(String selectedProduct) {
/*
the setVisible method for the hBoxes in fxml cause the scrollbar to go back to the top
without them, the scrollpane stays where it is.
I tried changing visibility with both setVisible and CSS, but they both cause the problem
I need the actionbar to appear when you select a button (right click on it)
*/
homeBar.setVisible(false);
actionBar.setVisible(true);
EventHandler<ActionEvent> delete = event -> {
deleteProduct(selectedProduct); // deletes an item from a LinkedHashMap
homeBar.setVisible(true);
actionBar.setVisible(false);
};
deletebtn.setOnAction(delete);
// I want the same to happen when the edit handler is used, scrollpane needs to remain its position
EventHandler<ActionEvent> edit = event -> {
editProduct(selectedProduct); // edits an item from a LinkedHashMap
homeBar.setVisible(true);
actionBar.setVisible(false);
};
editbtn.setOnAction(edit);
}
/*
Code below does not cause the problem, but I added it as a reference
*/
private void deleteProduct(String product) {
if (mProductMap.containsKey(product)) {
mProductMap.remove(product);
System.out.printf("%s has been deleted!%n", product);
} else {
System.out.printf("%s does not exist. Please try again.%n", product);
}
addButtons();
}
private void editProduct(String product) {
List<String> indexKeys = new ArrayList<>(mProductMap.keySet());
List<BigDecimal> indexValues = new ArrayList<>(mProductMap.values());
BigDecimal price = mProductMap.get(product); // gets the product's value (the price)
int indexKey = indexKeys.indexOf(product);
int indexValue = indexValues.indexOf(price);
if (mProductMap.containsKey(product)) {
int sizeBefore = mProductMap.size();
addingProduct();
int sizeAfter = mProductMap.size();
if (sizeAfter > sizeBefore) {
indexKeys.remove(product);
indexValues.remove(price);
mProductMap.remove(product);
// Make a new list to get the new entry at the end
List<Map.Entry<String,BigDecimal>> entryList = new ArrayList<>(mProductMap.entrySet());
Map.Entry<String, BigDecimal> lastEntry = entryList.get(entryList.size()-1);
String key = lastEntry.getKey();
BigDecimal value = lastEntry.getValue();
indexKeys.add(indexKey, key);
indexValues.add(indexValue, value);
mProductMap.clear();
// Put the keys and values from the two lists back to the map
for (int i=0; i<indexKeys.size(); i++) {
addProduct(indexKeys.get(i), indexValues.get(i));
}
}
} else {
System.out.printf("%s does not exist. Please try again.%n", product);
}
}
void addProduct(String product, BigDecimal price) {
mProductMap.put(product, price);
addButtons();
}
// Adding buttons to the TilePane fieldContainer in center of BorderPane
// One button per key-value pair of mProductMap
private void addButtons() {
// clears the TilePane to prevent duplicate buttons
fieldContainer.getChildren().clear();
for (Map.Entry<String, BigDecimal> entry : mProductMap.entrySet()) {
StackPane newField = new StackPane();
Button main = new Button();
main.setOnMousePressed(me -> {
if (me.getButton() == MouseButton.SECONDARY) { // = right click
select(entry.getKey());
}
});
main.setText(entry.getKey() + "\n" + entry.getValue());
newField.getChildren().add(main);
fieldContainer.setAlignment(Pos.TOP_LEFT);
fieldContainer.getChildren().add(newField);
}
}
// Popup for adding products to the Map with the + button
#FXML
private void addingProduct(){
Stage newStage = new Stage();
VBox popup = new VBox();
final BooleanProperty firstTime = new SimpleBooleanProperty(true); // Variable to store the focus on stage load
TextField product = new TextField("");
product.setId("product");
product.setPromptText("Enter the item name...");
// code to remove the focus from first textfield on stage load
product.focusedProperty().addListener((observable, oldValue, newValue) -> {
if(newValue && firstTime.get()){
popup.requestFocus(); // Delegate the focus to container
firstTime.setValue(false); // Variable value changed for future references
}
});
TextField price = new TextField("");
price.setId("price");
price.setPromptText("Enter the item price...");
Button submit = new Button("Submit");
Label label = new Label();
label.setId("label");
submit.setOnAction(e -> {
if ( (product.getText() != null && !product.getText().isEmpty() &&
price.getText() != null && !price.getText().isEmpty() ) ) {
addProduct(product.getText(), new BigDecimal(price.getText()) );
newStage.close();
} else {
label.setText("Fill in both fields");
}
});
popup.getChildren().add(product);
popup.getChildren().add(price);
popup.getChildren().add(submit);
popup.getChildren().add(label);
Scene stageScene = new Scene(popup, 300, 200);
newStage.setScene(stageScene);
newStage.showAndWait();
}
}
The FXML:
<BorderPane xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<top>
<StackPane>
<HBox fx:id="homeBar" styleClass="main-bar" visible="true">
<Button StackPane.alignment="BOTTOM_LEFT">Home</Button>
<Button onAction="#addingProduct" StackPane.alignment="BOTTOM_RIGHT">Add a new product</Button>
</HBox>
<HBox fx:id="actionBar" styleClass="main-bar" visible="false">
<Button fx:id="deletebtn" StackPane.alignment="BOTTOM_CENTER">Delete</Button>
<Button fx:id="editbtn" StackPane.alignment="BOTTOM_CENTER">Edit</Button>
<Button onAction="#addingProduct" StackPane.alignment="BOTTOM_RIGHT">Add a new product</Button>
</HBox>
</StackPane>
</top>
<center>
<ScrollPane fx:id="scroll" hbarPolicy="NEVER">
<TilePane fx:id="fieldContainer" prefColumns="2" prefTileHeight="100.0" prefTileWidth="144.0">
</TilePane>
</ScrollPane>
</center>
<bottom>
</bottom>
</BorderPane>
Main:
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setScene(new Scene(root, 300, 275));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I fixed it myself, with help from answers from this post: ScrollPane jumps to top when deleting nodes.
After deleting a button from the TilePane, the ScrollPane finds the next node to focus on, which by default is the first node from the pane. By requesting the focus on the TilePane (fieldContainer) the ScrollPane stays in place.
I added this code to both the delete and edit methods:
fieldContainer.requestFocus();
I used grid component, and added row details..
However grid doesn't show details..
It's my code.
====
Grid grid = new Grid("Plain Grid");
grid.setDetailsGenerator(new DetailsGenerator() {
#Override
public Component getDetails(RowReference rowReference) {
// Find the bean to generate details for
final TrainingMemberVo bean = (TrainingMemberVo) rowReference.getItemId();
// A basic label with bean data
Label label = new Label("Extra data for " + bean.getMemberName());
// A button just for the sake of the example
Button button = new Button("Click me", new Button.ClickListener() {
#Override
public void buttonClick(ClickEvent event) {
Notification.show("Button clicked for " + bean.getMemberName());
}
});
// Wrap up all the parts into a vertical layout
VerticalLayout layout = new VerticalLayout(label, button);
layout.setSpacing(true);
layout.setMargin(true);
return layout;
}
});
grid.addItemClickListener(new ItemClickListener() {
#Override
public void itemClick(ItemClickEvent event) {
if (event.isDoubleClick()) {
Object itemId = event.getItemId();
grid.setDetailsVisible(itemId, !grid.isDetailsVisible(itemId));
}
}
});
===
double clicked row, noting shows details..
Works for me without any issues. Have you checked if the event in the itemClick() is correctly fired? I. e. remove the boolean check for event.isDoubleClick() to see if it gets executed.
I would like to add a listener when I am clicking the cell for categories only.
this is the declaration of my columnConfig
ColumnConfig<UserRights, Boolean> unlockConfig = new ColumnConfig<UserRights, Boolean>(properties.hasUnlock(), 50);
unlockConfig.setHeader("Unlock");
cfgs.add(unlockConfig);
ColumnConfig<UserRights, String> catConfig = new ColumnConfig<UserRights, String>(properties.categories(), 150);
catConfig.setHeader("Categories");
cfgs.add(catConfig);
cm = new ColumnModel<UserRights>(cfgs);
grid = new Grid<UserRights>(store, cm);
grid.getView().setAutoFill(true);
grid.addStyleName("margin-10");
grid.setLayoutData(new VerticalLayoutContainer.VerticalLayoutData(1, 1));
grid.addRowClickHandler(new RowClickEvent.RowClickHandler() {
#Override
public void onRowClick(RowClickEvent event) {
index = event.getRowIndex();
}
});
rowEditing = new GridRowEditing<UserRights>(grid);
rowEditing.addEditor(unlockConfig, new CheckBox());
how could I add a listener in the category column?
Thanks in advance.
There is nothing in your categories cell that prompts a user to click, it just contains text. What you should be doing is using the columnConfig.setCell(Cell cell) method to specify a cell that contains an interactive component.
You could still try something along these lines:
columnConfig.setCell(new SimpleSafeHtmlCell<String>(SimpleSafeHtmlRenderer.getInstance(), "click") {
#Override
public void onBrowserEvent(Context context, Element parent, String value, NativeEvent event, ValueUpdater<String> valueUpdater) {
super.onBrowserEvent(context, parent, value, event, valueUpdater);
if (event.getType().equals("click")) {
}
}
});