Get Selected Item from List - java

I'm currently creating an inventory system, in which the user will select an item from a list and the icon on the right will update based on the item that the user has picked.
I need a way to get the list item that the user has currently selected. I then need to use that list item to display an icon which the user will see.
Currently I have tried using the getSelected() method on the inventory list, which seems to be only returning the first item in the list. I need a way to get the item that the user has currently selected.
I need to get the current item selected on the list called 'inventory'.
package com.sps.game.inventory;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.scenes.scene2d.*;
import com.badlogic.gdx.scenes.scene2d.ui.*;
import com.badlogic.gdx.utils.viewport.FitViewport;
import com.badlogic.gdx.utils.viewport.Viewport;
import com.sps.game.controller.InventoryController;
import com.sps.game.controller.PlayerController;
public class PlayerInventory {
public Stage stage;
public SpriteBatch sb;
private Viewport viewport;
private Skin skin = new Skin(Gdx.files.internal("core/assets/pixthulhuui/pixthulhu-ui.json"));
private List<Item> inventory;
private List<Image> itemImages;
private InventoryController inventoryController;
private InputProcessor oldInput;
public PlayerInventory(SpriteBatch sb, PlayerController playerController) {
this.sb = sb;
viewport = new FitViewport(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), new OrthographicCamera());
stage = new Stage(viewport, sb);
inventoryController = new InventoryController();
inventory = inventoryController.getInventoryList();
itemImages = inventoryController.getImageList();
}
private void formatting() {
stage = new Stage();
Label inventoryLabel = new Label("Inventory", skin);
Label imageLabel = new Label("Item", skin);
Table table = new Table(skin);
table.setDebug(true);
table.defaults();
table.center();
table.setFillParent(true);
table.add(inventoryLabel);
table.add(imageLabel);
table.row();
table.add(inventory); //need a way to get the current item selected
table.add(itemImages.getSelected());
stage.addActor(itemImages);
stage.addActor(table);
}
public void setInput() {
oldInput = Gdx.input.getInputProcessor(); //Get the old input from the user.
Gdx.input.setInputProcessor(stage); //Set the input to now work on the inventory.
}
public void update() {
if (Gdx.input.isKeyPressed(Input.Keys.I) && oldInput == null) {
formatting();
setInput();
}
if (Gdx.input.isKeyPressed(Input.Keys.O) && oldInput != null) {
stage.dispose();
Gdx.input.setInputProcessor(oldInput);
oldInput = null;
}
}
public void dispose() {
stage.dispose();
}
}

I have found the solution, through using a Clicklistener.
inventory.addListener(new ClickListener() {
public void clicked(InputEvent event, float x, float y) {
String clickedItem = inventory.getSelected();
table.add(clickedItem);
System.out.println(item.getName());
}
});

Related

How to show/hide the thousand commas in the column of JavaFX?

I have a table in JavaFX. I want to control the show/hide of the thousand commas. Currently, I can control the color by column1.setStyle("-fx-text-fill: green"), but how can I show the thousand commas (and then hide them back in some later stage), probably via a similar approach?
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
TableView tableView = new TableView();
TableColumn<Integer, Person> column1 = new TableColumn<>("Salary");
column1.setCellValueFactory(new PropertyValueFactory("salary"));
tableView.getColumns().add(column1);
tableView.getItems().add(new Person(27000));
tableView.getItems().add(new Person(48000));
column1.setStyle("-fx-text-fill: green");
VBox vbox = new VBox(tableView);
Scene scene = new Scene(vbox);
primaryStage.setScene(scene);
primaryStage.show();
}
}
Person:
import javafx.beans.property.SimpleIntegerProperty;
public class Person {
private SimpleIntegerProperty salaryProperty;
public Person() {
}
public Person(int salary) {
this.salaryProperty = new SimpleIntegerProperty(salary);
}
public int getSalary() {
return salaryProperty.get();
}
public void setSalary(int salary) {
this.salaryProperty = new SimpleIntegerProperty(salary);
}
}
I think that it is not possible to do with css. You need to use some kind of NumberFormat like in this example:
App:
package formatter;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.text.NumberFormat;
import java.util.Locale;
public class App extends Application {
#Override
public void start(Stage primaryStage) {
// Create the table view and its column (like you already did):
TableView<Person> tableView = new TableView<>();
TableColumn<Person, Integer> salaryColumn = new TableColumn<>("Salary");
salaryColumn.setCellValueFactory(new PropertyValueFactory<>("salary"));
tableView.getColumns().add(salaryColumn);
// Using a check box in this example to change between formats::
CheckBox useGroupingCheckBox = new CheckBox("use grouping");
// Create a currency formatter with a locale which is important for internationalization:
NumberFormat formatter = NumberFormat.getCurrencyInstance(Locale.CANADA);
formatter.setMaximumFractionDigits(0);
// Create a custom cell:
salaryColumn.setCellFactory(column -> new TableCell<>() {
#Override
protected void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText("");
} else {
// Use grouping when check box selected, don't when not selected:
formatter.setGroupingUsed(useGroupingCheckBox.isSelected());
setText(formatter.format(item));
}
}
});
// Refresh table on check box action:
useGroupingCheckBox.setOnAction(event -> tableView.refresh());
// Add some test data:
tableView.getItems().add(new Person(27000));
tableView.getItems().add(new Person(48000));
// Prepare scene and stage:
VBox vbox = new VBox(useGroupingCheckBox, tableView);
Scene scene = new Scene(vbox);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch();
}
}
Person class:
package formatter;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
public class Person {
private IntegerProperty salary;
public Person() {
salary = new SimpleIntegerProperty();
}
public Person(int salary) {
this();
this.salary.set(salary);
}
public Integer getSalary() {
return salary.get();
}
public IntegerProperty salaryProperty() {
return salary;
}
public void setSalary(int salary) {
this.salary.set(salary);
}
}
Preview:
The accepted answer is just fine though a bit smelly because it requires to manually refresh the table when changing the format's property.
An alternative is
to wrap the format - which is not observable - into something that is observable
implement a custom cell which listens to the change/s and updates itself as needed.
An example of how to implement the first (for the grouping state) is FormattingHandler in the code below. Note that
the wrapping property itself implements the update of the contained format
the NumberFormat is completely hidden inside the handler: that's doing the best to not allow changes of its properties under the feet of the handler (obviously it's not entirely fool-proof because outside code can still keep a reference to the format and change it at will - it's a similar isolation level as f.i. the backing list in core ObservableList implementations)
An example of how to implement the second is FormattingCell. It takes a not-null FormattingHandler, registers a listener to the grouping property and updates itself on invalidation notification. Note that this might introduce a memory leak (even though the listener is weak!) if the observable doesn't change at all (it's a known issue in the design of weak listeners that will not be changed, unfortunately) - the only way out would be to move the listening into a custom cell skin and remove the listener in the skin's dispose.
The code (boilderplate stolen from Anko's answer :)
public class DynamicFormattingCellBinding extends Application {
/**
* Observable wrapper around NumberFormat.
*/
public static class FormattingHandler {
/*
* Property controlling the grouping of the format.
*/
private BooleanProperty useGrouping = new SimpleBooleanProperty(this, "useGrouping", false) {
#Override
protected void invalidated() {
super.invalidated();
groupingInvalidated();
}
};
private NumberFormat formatter;
public FormattingHandler(NumberFormat formatter) {
this.formatter = formatter;
setGrouping(formatter.isGroupingUsed());
}
public BooleanProperty groupingProperty() {
return useGrouping;
}
public boolean isGrouping() {
return groupingProperty().get();
}
public void setGrouping(boolean grouping) {
groupingProperty().set(grouping);
}
public String format(Number number) {
return formatter.format(number);
}
private void groupingInvalidated() {
formatter.setGroupingUsed(isGrouping());
}
}
public static class FormattingCell<T, S extends Number> extends TableCell<T, S> {
private FormattingHandler formattingHandler;
private InvalidationListener groupingListener = o -> updateItem(getItem(), isEmpty());
public FormattingCell(FormattingHandler formattingHandler) {
this.formattingHandler = Objects.requireNonNull(formattingHandler, "formatter must not be null");
// Beware: a weak listener isn't entirely safe
// will introduce memory leaks if the observable doesn't change!
formattingHandler.groupingProperty().addListener(new WeakInvalidationListener(groupingListener));
}
#Override
protected void updateItem(S item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText("");
} else {
setText(formattingHandler.format(item));
}
}
}
#Override
public void start(Stage primaryStage) {
TableView<Person> tableView = new TableView<>();
TableColumn<Person, Integer> salaryColumn = new TableColumn<>("Salary");
salaryColumn.setCellValueFactory(cc -> cc.getValue().salaryProperty().asObject());
tableView.getColumns().add(salaryColumn);
// instantiate the formatting support and register bidi binding with a view element
FormattingHandler formatter = new FormattingHandler(NumberFormat.getCurrencyInstance());
CheckBox useGroupingCheckBox = new CheckBox("use grouping");
useGroupingCheckBox.selectedProperty().bindBidirectional(formatter.groupingProperty());
// install custom formatting cell
salaryColumn.setCellFactory(column -> new FormattingCell<>(formatter));
// Add some test data:
tableView.getItems().add(new Person(27000));
tableView.getItems().add(new Person(48000));
// Prepare scene and stage:
VBox vbox = new VBox(useGroupingCheckBox, tableView);
Scene scene = new Scene(vbox);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch();
}
private static class Person {
// exact same as in the other answer
}
}

Updating image in table

I'm currently creating an inventory system in which the user can select an item from their inventory list collection, and the icon on the right will update.
As of now, I've implemented a ClickListener to get the currently selected item from the inventory list and called the .getImage() method that I created to get the icon of the selected item.
I used table.add(image); to add the icon to the table. However, when the icon is added to the table, it fills up that space, and another column is created to the right when the user selects on another item. This is the issue.
How do I get the image area to update when the user selects another item, rather than creating another column to the right?
This is currently how it is: https://imgur.com/a/O6SW8gi
I want the area where the sword is to be updated with the latest item that the user has clicked on.
Here is my code:
package com.sps.game.inventory;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.scenes.scene2d.*;
import com.badlogic.gdx.scenes.scene2d.ui.*;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.utils.viewport.FitViewport;
import com.badlogic.gdx.utils.viewport.Viewport;
import com.sps.game.controller.InventoryController;
import com.sps.game.controller.PlayerController;
import java.util.ArrayList;
public class PlayerInventory {
public Stage stage;
public SpriteBatch sb;
private Viewport viewport;
private Skin skin = new Skin(Gdx.files.internal("core/assets/pixthulhuui/pixthulhu-ui.json"));
private List<Item> inventory;
private List<Image> itemImages;
private InventoryController inventoryController;
private InputProcessor oldInput;
Table table = new Table(skin);
public PlayerInventory(SpriteBatch sb, PlayerController playerController) {
this.sb = sb;
viewport = new FitViewport(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), new OrthographicCamera());
stage = new Stage(viewport, sb);
inventoryController = new InventoryController();
inventory = inventoryController.getInventoryList();
// itemImages = inventoryController.getImageList();
}
//THIS IS WHERE THE IMAGE IS ADDED
private void formatting() {
stage = new Stage();
Label inventoryLabel = new Label("Inventory", skin);
final Label imageLabel = new Label("Item", skin);
table.setDebug(true);
table.defaults();
table.center();
table.setFillParent(true);
table.add(inventoryLabel);
table.add(imageLabel);
table.row();
table.add(inventory); //need a way to get the current item selected
inventory.addListener(new ClickListener() {
public void clicked(InputEvent event, float x, float y) {
Item clickedItem = inventory.getSelected();
Image clickedImage = clickedItem.getImage();
table.add(clickedImage);
System.out.println(clickedItem.getName());
}
});
// stage.addActor(itemImages);
stage.addActor(table);
}
public void setInput() {
oldInput = Gdx.input.getInputProcessor(); //Get the old input from the user.
Gdx.input.setInputProcessor(stage); //Set the input to now work on the inventory.
}
public void update() {
if (Gdx.input.isKeyPressed(Input.Keys.I) && oldInput == null) {
formatting();
setInput();
}
if (Gdx.input.isKeyPressed(Input.Keys.O) && oldInput != null) {
stage.dispose();
Gdx.input.setInputProcessor(oldInput);
oldInput = null;
}
}
public void dispose() {
stage.dispose();
}
}
The image is added in the formatting method()-
inventory.addListener(new ClickListener() {
public void clicked(InputEvent event, float x, float y) {
Item clickedItem = inventory.getSelected();
Image clickedImage = clickedItem.getImage();
table.add(clickedImage);
System.out.println(clickedItem.getName());
}
I found the problem, looking into the LibGDX Wiki Table - Adding Cells it seems you are using the add() method that does not replace the previous actor in the cell, it only adds another one in the row. Instead, you should save your displayed image apart from the List
public class PlayerInventory {
[..........................]
Item clickedItem; // This will save your clicked item
Image clickedImage; // This will save your clicked image;
[..........................]
inventory.addListener(new ClickListener() {
public void clicked(InputEvent event, float x, float y) {
if (clickedItem == null && clickedImage == null) {
clickedItem = inventory.getSelected(); // This line changed
clickedImage = clickedItem.getImage(); // This line changed
table.add(clickedImage);
} else {
clickedItem = inventory.getSelected(); // This line changed
clickedImage = clickedItem.getImage(); // This line changed
}
System.out.println(clickedItem.getName());
}
}
This will add the image if there was no image before and replace the image if there was one before. Hope this helps!
Solved. You shouldn't use table.add(clickedImage) every time the screen is clicked. add() creates a new cell. Instead, add a placeholder Image just once during initial layout and keep a reference to it. In my ClickListener, i used clickedImage.setDrawable() instead to change what image is displayed.

How to programatically select ComboBox value within Cell Factory?

My TableView uses a custom CellFactory to display a ComboBox in one column, allowing the user to select from available options. Those options are loaded after the TableView is populated (as they can change based on the user's selections elsewhere in the scene).
In the MCVE below, I have two columns for my Item class: Name and Color. Within the Color column, I have the ComboBox which will display the current value of the Item's itemColor property.
You will see that the ComboBox is not populated with a list of values yet and item "Three" has no value selected.
What I need is this:
When the user clicks on the "Load Available Colors" button, the list for the ComboBox is created. The user can now select any of the available colors. However, if there is not already a value for the item's color, I want the first color in the ComboBoxes to be selected automatically; so item "Three" would now show the color "Red" as being selected.
THE MCVE
Item.java:
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Item {
private StringProperty itemName = new SimpleStringProperty();
private StringProperty itemColor = new SimpleStringProperty();
public Item(String name, String color) {
this.itemName.set(name);
this.itemColor.set(color);
}
public String getItemName() {
return itemName.get();
}
public void setItemName(String itemName) {
this.itemName.set(itemName);
}
public StringProperty itemNameProperty() {
return itemName;
}
public String getItemColor() {
return itemColor.get();
}
public void setItemColor(String itemColor) {
this.itemColor.set(itemColor);
}
public StringProperty itemColorProperty() {
return itemColor;
}
}
Main.java:
import javafx.application.Application;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
// List of items
private static ObservableList<Item> listOfItems = FXCollections.observableArrayList();
// List of available Colors. These will be selectable from the ComboBox
private static ObservableList<String> availableColors = FXCollections.observableArrayList();
public static void main(String[] args) {
launch(args);
}
private static void buildSampleData() {
availableColors.addAll("Red", "Blue", "Green", "Yellow", "Black");
}
#Override
public void start(Stage primaryStage) {
// Simple Interface
VBox root = new VBox(10);
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(10));
// Build a list of sample data. This data is loaded from my data model and passed to the constructor
// of this editor in my real application.
listOfItems.addAll(
new Item("One", "Black"),
new Item("Two", "Black"),
new Item("Three", null),
new Item("Four", "Green"),
new Item("Five", "Red")
);
// TableView to display the list of items
TableView<Item> tableView = new TableView<>();
// Create the TableColumn
TableColumn<Item, String> colName = new TableColumn<>("Name");
TableColumn<Item, String> colColor = new TableColumn<>("Color");
// Cell Property Factories
colName.setCellValueFactory(column -> new SimpleObjectProperty<>(column.getValue().getItemName()));
colColor.setCellValueFactory(column -> new SimpleObjectProperty<>(column.getValue().getItemColor()));
// Add ComboBox to the Color column, populated with the list of availableColors
colColor.setCellFactory(tc -> {
ComboBox<String> comboBox = new ComboBox<>(availableColors);
comboBox.setMaxWidth(Double.MAX_VALUE);
TableCell<Item, String> cell = new TableCell<Item, String>() {
#Override
protected void updateItem(String color, boolean empty) {
super.updateItem(color, empty);
if (empty) {
setGraphic(null);
} else {
setGraphic(comboBox);
comboBox.setValue(color);
}
}
};
// Set the action of the ComboBox to set the right Value to the ValuePair
comboBox.setOnAction(event -> {
listOfItems.get(cell.getIndex()).setItemColor(comboBox.getValue());
});
return cell;
});
// Add the column to the TableView
tableView.getColumns().addAll(colName, colColor);
tableView.setItems(listOfItems);
// Add button to load the data
Button btnLoadData = new Button("Load Available Colors");
btnLoadData.setOnAction(event -> {
buildSampleData();
});
root.getChildren().add(btnLoadData);
// Add the TableView to the root layout
root.getChildren().add(tableView);
Button btnPrintAll = new Button("Print All");
btnPrintAll.setOnAction(event -> {
for (Item item : listOfItems) {
System.out.println(item.getItemName() + " : " + item.getItemColor());
}
});
root.getChildren().add(btnPrintAll);
// Show the stage
primaryStage.setScene(new Scene(root));
primaryStage.setTitle("Sample");
primaryStage.show();
}
}
Now, with a regular ComboBox, a simple call to comboBox.getSelectionModel().selectFirst() after loading the availableColors would be fine. But since this ComboBox is created within the CellFactory, I am not sure how to update it once the list of colors is populated.
Indidentally, I use this CellFactory implementation instead of a ComboBoxTableCell because I want them to be visible without having to enter edit mode on the TableView.
I actually took kleopatra's advice and updated my data model to include a default value instead. I agree this is cleaner and more appropriate approach.

JavaFX combobox, on item clicked

My problem is as follows,
For the sake of this question I reproduced the problem in a new project.
Say I have this application with a combobox in it, there could be 1 or more items in there. And I would like it to be so that when the user clicks an item in the combobox that 'something' happens.
I produced the following code:
obsvList.add("item1");
cbTest.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Item clicked");
}
});
This works when the application starts and an item is selected for the first time. This also works when there are 2 or more items in the combobox (when the user clicks item 1, then item 2, then item 1 for example)
However my problem is that when there is only 1 item in the combobox, let's say "item1". And the user reopens the combobox and clicks "item1" again then it won't redo the action.
It will only print the line "Item Clicked" when a 'new' item is clicked.
I hope it made it clear what the problem i'm experiencing is, if not please ask for clarification and I will give so where needed.
Thanks in advance!
The functionality of a combo box is to present the user with a list of options from which to choose. When you are using a control which implies selection, you should really ensure that the UI is always consistent with the option that is selected. If you do this, then it makes no sense to "repeat an action" when the user "reselects" the same option (because the UI is already in the required state). One approach to this is to use binding or listeners on the combo box's value:
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class ComboBoxExample extends Application {
#Override
public void start(Stage primaryStage) {
ComboBox<Item> choices = new ComboBox<>();
for (int i = 1 ; i <=3 ; i++) {
choices.getItems().add(new Item("Choice "+i, "These are the details for choice "+i));
}
Label label = new Label();
choices.valueProperty().addListener((obs, oldItem, newItem) -> {
label.textProperty().unbind();
if (newItem == null) {
label.setText("");
} else {
label.textProperty().bind(newItem.detailsProperty());
}
});
BorderPane root = new BorderPane();
root.setCenter(label);
root.setTop(choices);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public class Item {
private final String name ;
private final StringProperty details = new SimpleStringProperty() ;
public Item(String name, String details) {
this.name = name ;
setDetails(details) ;
}
public String getName() {
return name ;
}
#Override
public String toString() {
return getName();
}
public final StringProperty detailsProperty() {
return this.details;
}
public final String getDetails() {
return this.detailsProperty().get();
}
public final void setDetails(final String details) {
this.detailsProperty().set(details);
}
}
public static void main(String[] args) {
launch(args);
}
}
In this case, there is never a need to repeat an action when the user "reselects" the same option, because the code always assures that the UI is consistent with what is selected anyway (there is necessarily nothing to do if the user selects the option that is already selected). By using bindings in the part of the UI showing the details (just a simple label in this case), we are assured that the UI stays up to date if the data changes externally. (Obviously in a real application, this may be far more complex, but the basic strategy is still exactly the same.)
On the other hand, functionality that requires an action to be repeated if the user selects the same functionality is better considered as presenting the user with a set of "actions". The appropriate controls for this are things like menus, toolbars with buttons, and MenuButtons.
An example of a set of repeatable actions is:
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class MenuButtonExample extends Application {
#Override
public void start(Stage primaryStage) {
MenuButton menuButton = new MenuButton("Items");
Label label = new Label();
Item[] items = new Item[3];
for (int i = 1 ; i <=3 ; i++) {
items[i-1] = new Item("Item "+i);
}
for (Item item : items) {
MenuItem menuItem = new MenuItem(item.getName());
menuItem.setOnAction(e -> item.setTimesChosen(item.getTimesChosen() + 1));
menuButton.getItems().add(menuItem);
}
label.textProperty().bind(Bindings.createStringBinding(() ->
Stream.of(items)
.map(item -> String.format("%s chosen %d times", item.getName(), item.getTimesChosen()))
.collect(Collectors.joining("\n")),
Stream.of(items)
.map(Item::timesChosenProperty)
.collect(Collectors.toList()).toArray(new IntegerProperty[0])));
BorderPane root = new BorderPane();
root.setCenter(label);
root.setTop(menuButton);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class Item {
private final String name ;
private final IntegerProperty timesChosen = new SimpleIntegerProperty();
public Item(String name) {
this.name = name ;
}
public String getName() {
return name ;
}
#Override
public String toString() {
return getName();
}
public final IntegerProperty timesChosenProperty() {
return this.timesChosen;
}
public final int getTimesChosen() {
return this.timesChosenProperty().get();
}
public final void setTimesChosen(final int timesChosen) {
this.timesChosenProperty().set(timesChosen);
}
}
public static void main(String[] args) {
launch(args);
}
}
The idea is to set a listener on the ListView pane, that appears whenever you click on the ComboBox. The ListView instance is created once the ComboBox is first loaded in the JavaFX scene. Therefore, we add a listener on the ComboBox to check when it appears on the scene, and then through the "lookup" method we get the ListView and add a listener to it.
private EventHandler<MouseEvent> cboxMouseEventHandler;
private void initComboBox() {
ComboBox<String> comboBox = new ComboBox<String>();
comboBox.getItems().add("Item 1");
comboBox.getItems().add("Item 2");
comboBox.getItems().add("Item 3");
comboBox.sceneProperty().addListener((a,oldScene,newScene) -> {
if(newScene == null || cboxMouseEventHandler != null)
return;
ListView<?> listView = (ListView<?>) comboBox.lookup(".list-view");
if(listView != null) {
cboxMouseEventHandler = (e) -> {
Platform.runLater(()-> {
String selectedValue = (String) listView.getSelectionModel().getSelectedItem();
if(selectedValue.equals("Item 1"))
System.out.println("Item 1 clicked");
});
}; // cboxMouseEventHandler
listView.addEventFilter(MouseEvent.MOUSE_PRESSED, cboxMouseEventHandler);
} // if
});
} // initComboBox

How to pause/resume a song in javafx?

I'm making a playlist based mp3 player using javafx and I got everything working except how to pause/resume a song. I tried simply checking the player.Status()and using that but it didn't work so I stored the time of the song when pause()is clicked, in a Duration pausetime variable and it works that way but only once. What happens is: I click pause(), it works, click play(), it resumes the song but after that the pause button stops doing anything.
Btw I'm using two separate ToggleButton for pause and play because of the style I'm going for.
Here's the part of the code i'm talking about:
public void play(){
if (player != null){
player.stop();
}
if (pausebutton.isSelected()){
pausebutton.setSelected(false); //resume part
slider.setValue(pausetime.toSeconds());
play();
}
this.player = players.get(i);
player.setStartTime(pausetime);
player.play();
slide(i);
csong.setText(playlist.get(i).getName());
player.setOnEndOfMedia(new Runnable(){
#Override public void run(){
if (shuffle.isSelected()){
i = rand.nextInt(players.size() + 1);
}
else{
i++;
}
if(loop.isSelected()){
if (i == players.size()){
i = 0;
}}
list.getSelectionModel().select(i);
play();
}
});
}
public void pause(){
player.pause();
pausetime = player.getCurrentTime();
playbutton.setSelected(false);
}
I have created a very simple mp3 player, which has most (if not all) of the components said above.
It has play and pause toggle buttons, which do work ;)
It has labels updating the time elapsed for the song
It has a sliderbar, which can be used to move forward/backward. It automatically updates on song play.
Complete Code
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.text.DecimalFormat;
public class MediaPlayerSample extends Application {
private MediaPlayer player;
private final ToggleButton playButton = new ToggleButton("Play");
private final ToggleButton pauseButton = new ToggleButton("Pause");
private final ToggleGroup group = new ToggleGroup();
private final Label totalDuration = new Label();
private final Label currentDuration = new Label();
private final DecimalFormat formatter = new DecimalFormat("00.00");
private final SliderBar timeSlider = new SliderBar();
private Duration totalTime;
#Override
public void start(Stage primaryStage)
{
//Add a scene
VBox root = new VBox(10);
root.setAlignment(Pos.CENTER);
root.setStyle("-fx-background-color: ANTIQUEWHITE");
HBox playPause = new HBox(playButton, pauseButton);
HBox sliderBox = new HBox(timeSlider, currentDuration, totalDuration);
HBox.setHgrow(sliderBox, Priority.ALWAYS);
root.getChildren().addAll(sliderBox, playPause);
Scene scene = new Scene(root, 300, 100);
Media pick = new Media(getClass().getResource("/delete/abc/abc.mp3").toExternalForm());
player = new MediaPlayer(pick);
// Play the track and select the playButton
player.play();
playButton.setSelected(true);
player.currentTimeProperty().addListener(new ChangeListener<Duration>() {
#Override
public void changed(ObservableValue<? extends Duration> observable, Duration oldValue, Duration newValue) {
timeSlider
.sliderValueProperty()
.setValue(newValue.divide(totalTime.toMillis()).toMillis() * 100.0);
currentDuration.setText(String.valueOf(formatter.format(newValue.toSeconds())));
}
});
player.setOnReady(() -> {
// Set the total duration
totalTime = player.getMedia().getDuration();
totalDuration.setText(" / " + String.valueOf(formatter.format(Math.floor(totalTime.toSeconds()))));
});
// Slider Binding
timeSlider.sliderValueProperty().addListener((ov) -> {
if (timeSlider.isTheValueChanging()) {
if (null != player)
// multiply duration by percentage calculated by
// slider position
player.seek(totalTime.multiply(timeSlider
.sliderValueProperty().getValue() / 100.0));
else
timeSlider.sliderValueProperty().setValue(0);
}
});
//Applying Toggle Group to Buttons
playButton.setToggleGroup(group);
pauseButton.setToggleGroup(group);
// Action for Buttons
playButton.setOnAction(e -> {
play();
});
pauseButton.setOnAction(e -> {
pause();
});
//show the stage
primaryStage.setTitle("Media Player");
primaryStage.setScene(scene);
primaryStage.show();
}
public void play(){
player.play();
playButton.setSelected(true);
}
public void pause(){
player.pause();
playButton.setSelected(false);
}
private class SliderBar extends StackPane {
private Slider slider = new Slider();
private ProgressBar progressBar = new ProgressBar();
public SliderBar() {
getChildren().addAll(progressBar, slider);
bindValues();
}
private void bindValues(){
progressBar.prefWidthProperty().bind(slider.widthProperty());
progressBar.progressProperty().bind(slider.valueProperty().divide(100));
}
public DoubleProperty sliderValueProperty() {
return slider.valueProperty();
}
public boolean isTheValueChanging() {
return slider.isValueChanging();
}
}
public static void main(String[] args) {
launch(args);
}
}
A simple look while it plays
You can style the mediaplayer using stylesheet. You can go through JavaFX CSS Reference for advanced topic on css.
For a mediaplayer with advanced features like:
Adding songs to a Playlist
Playing songs one after another
Drag and drop songs into mediaview
and much more, you can go visit this example.

Categories

Resources