When I start my mediaplayer, and click the next-song button, it starts at the first song and goes down the list as intended. But if I start the program, double-click an mp3 file to play it, then click the next-song button, the first song in the list plays, not the mp3 after the mp3 I double clicked.
btn.setOnAction((ActionEvent e) ->
{
if(doubleClicked)
{
player.stop();
media = new Media(rowData.toURI().toString()); // needs to go to next song
player = new MediaPlayer(media);
player.play();
return;
}
if(music.hasNext())
{
try
{
player.stop();
media = new Media(music.next());
player = new MediaPlayer(media);
player.play();
lbl.setText(media.getSource());
}
catch(MediaException a)
{
System.out.println("Unsupported Format");
}
}
});
rowData is the mp3 file when double clicked. I've tried a bunch of things but nothing seems to work. I have my own data structure for this, but its pretty much the same as the built in Iterator and ArrayList code.
You don't adjust the Iterator's position according to the new position in the playlist. Given the index of the item clicked by the user you can simply replace the Iterator by a new one using List.listIterator.
Here's an simplified example using a ListView and Strings that are displayed in a label:
private Iterator<String> songIterator;
#Override
public void start(Stage primaryStage) {
ObservableList<String> songs = FXCollections.observableArrayList("Song 1", "Song 2", "Song 3", "Song 4");
// start with first song
songIterator = songs.iterator();
Label songLabel = new Label(songIterator.next());
ListView<String> lv = new ListView(songs);
MultipleSelectionModel<String> selectionModel = lv.getSelectionModel();
lv.setCellFactory(t -> new ListCell<String>() {
{
setOnMouseClicked(evt -> {
if (evt.getButton() == MouseButton.PRIMARY && evt.getClickCount() == 2) {
evt.consume();
// update label
songLabel.setText(getItem());
// iterator should return next item next
songIterator = songs.listIterator(selectionModel.getSelectedIndex()+1);
}
});
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setText(empty ? null : item);
}
});
Button btn = new Button();
btn.setText("Next");
btn.setOnAction((ActionEvent event) -> {
if (songIterator.hasNext()) {
songLabel.setText(songIterator.next());
}
});
Scene scene = new Scene(new VBox(songLabel, lv, btn));
primaryStage.setScene(scene);
primaryStage.show();
}
Related
I'm using a FilteredList for my ListView to enable searching. The problem is that FilteredList does not allow mutating the data in any way, it only responds to changes in underlying ObservableList.
Also, it is declared final, so I can't simply extend it to forward the edit requests to the source.
So, how can I use it in an Editable ListView?
Here is the code to reproduce the problem
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
//problematic code
var observableList = FXCollections.observableArrayList("name", "name 2", "name 3");
FilteredList<String> filteredList = new FilteredList<>(observableList);
var list = new ListView<>(filteredList);
list.setEditable(true);
list.setCellFactory(TextFieldListCell.forListView());
//boilerplate code
VBox wrapper = new VBox(list);
primaryStage.setScene(new Scene(wrapper));
primaryStage.show();
}
}
Edit: added an minimal reproducible example
The problem - as you noticed - is that none of the concrete implementations of TransformationList (Sorted/FilteredList) is modifiable. So the default commit handler fails (with UnsupportedOperationException) while trying to set the newValue:
private EventHandler<ListView.EditEvent<T>> DEFAULT_EDIT_COMMIT_HANDLER = t -> {
int index = t.getIndex();
List<T> list = getItems();
if (index < 0 || index >= list.size()) return;
list.set(index, t.getNewValue());
};
The way out is a custom commit handler. Its implementation depends on context, it can
set a new item in the underlying source list
modify a property of the item
Code snippet for setting an item:
// monolithic items
ObservableList<String> data = FXCollections.observableArrayList("afirst", "abString", "other");
FilteredList<String> filteredData = new FilteredList<>(data);
filteredData.setPredicate(text -> text.contains("a"));
// set up an editable listView
ListView<String> list = new ListView<>(filteredData);
list.setEditable(true);
list.setCellFactory(TextFieldListCell.forListView());
// commitHandler resetting the underlying data element
list.setOnEditCommit(v -> {
ObservableList<String> items = list.getItems();
int index = v.getIndex();
if (items instanceof TransformationList<?, ?>) {
TransformationList transformed = (TransformationList) items;
items = transformed.getSource();
index = transformed.getSourceIndex(index);
}
items.set(index, v.getNewValue());
});
Code snippet for changing a property of an item:
// items with properties
ObservableList<MenuItem> data = FXCollections.observableArrayList(
new MenuItem("afirst"), new MenuItem("abString"), new MenuItem("other"));
FilteredList<MenuItem> filteredData = new FilteredList<>(data);
// filter on text property
filteredData.setPredicate(menuItem -> menuItem.getText().contains("a"));
// set up an editable listView
ListView<MenuItem> list = new ListView<>(filteredData);
list.setEditable(true);
// converter for use in TextFieldListCell
StringConverter<MenuItem> converter = new StringConverter<>() {
#Override
public String toString(MenuItem menuItem) {
return menuItem != null ? menuItem.getText() : null;
}
#Override
public MenuItem fromString(String text) {
return new MenuItem(text);
}
};
list.setCellFactory(TextFieldListCell.forListView(converter));
// commitHandler changing a property of the item
list.setOnEditCommit(v -> {
ObservableList<MenuItem> items = list.getItems();
MenuItem column = items.get(v.getIndex());
MenuItem standIn = v.getNewValue();
column.setText(standIn.getText());
});
So I followed this example on using context menu with TableViews from here. I noticed that using this code
row.contextMenuProperty().bind(Bindings.when(Bindings.isNotNull(row.itemProperty()))
.then(rowMenu)
.otherwise((ContextMenu)null));
does not show up on first right click on a row with values. I need to right click on that row again for the context menu to show up. I also tried this code(which is my first approach, but not using it anymore because I've read somewhere that that guide is the best/good practice for anything related about context menu and tableview), and it displays the context menu immediately
if (row.getItem() != null) {
rowMenu.show(row, event.getScreenX(), event.getScreenY());
}
else {
// do nothing
}
but my problem with this code is it throws a NullPointerException whenever i try to right click on a row that has no data.
What could I possibly do to prevent NullPointerException while having the context menu show up immediately after a right click? In my code, I also have a code that a certain menu item in the context menu will be disabled based on the property of the myObject binded to row, that's why i need the context menu to pop up right away.
I noticed this too with the first block of code. Even if the property of myObject has already changed, it still has a menu item enabled/disabled unless I right click on that row again. I hope that you could help me. Thank you!
Here is a MCVE:
public class MCVE_TableView extends Application{
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane myBorderPane = new BorderPane();
TableView<People> myTable = new TableView<>();
TableColumn<People, String> nameColumn = new TableColumn<>();
TableColumn<People, Integer> ageColumn = new TableColumn<>();
ContextMenu rowMenu = new ContextMenu();
ObservableList<People> peopleList = FXCollections.observableArrayList();
peopleList.add(new People("John Doe", 23));
nameColumn.setMinWidth(100);
nameColumn.setCellValueFactory(
new PropertyValueFactory<>("Name"));
ageColumn.setMinWidth(100);
ageColumn.setCellValueFactory(
new PropertyValueFactory<>("Age"));
myTable.setItems(peopleList);
myTable.getColumns().addAll(nameColumn, ageColumn);
myTable.setRowFactory(tv -> {
TableRow<People> row = new TableRow<>();
row.setOnContextMenuRequested((event) -> {
People selectedRow = row.getItem();
rowMenu.getItems().clear();
MenuItem sampleMenuItem = new MenuItem("Sample Button");
if (selectedRow != null) {
if (selectedRow.getAge() > 100) {
sampleMenuItem.setDisable(true);
}
rowMenu.getItems().add(sampleMenuItem);
}
else {
event.consume();
}
/*if (row.getItem() != null) { // this block comment displays the context menu instantly
rowMenu.show(row, event.getScreenX(), event.getScreenY());
}
else {
// do nothing
}*/
// this requires the row to be right clicked 2 times before displaying the context menu
row.contextMenuProperty().bind(Bindings.when(Bindings.isNotNull(row.itemProperty()))
.then(rowMenu)
.otherwise((ContextMenu)null));
});
return row;
});
myBorderPane.setCenter(myTable);
Scene scene = new Scene(myBorderPane, 500, 500);
primaryStage.setTitle("MCVE");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main (String[] args) {
launch(args);
}
}
Here is the People Class
public class People {
SimpleStringProperty name;
SimpleIntegerProperty age;
public People(String name, int age) {
this.name = new SimpleStringProperty(name);
this.age = new SimpleIntegerProperty(age);
}
public SimpleStringProperty NameProperty() {
return this.name;
}
public SimpleIntegerProperty AgeProperty() {
return this.age;
}
public String getName() {
return this.name.get();
}
public int getAge() {
return this.age.get();
}
}
Edit: MCVE added
Edit2: Updated the MCVE. Still requires to be right-clicked twice before the contextMenu pops up
Below's a code snippet as a quick demonstration of how-to/where-to instantiate and configure a per-row ContextMenu. It
creates a ContextMenu/MenuItem for each TableRow at the row's instantiation time
creates a conditional binding that binds the menu to the row's contextMenuProperty if not empty (just the same as you did)
configures the contextMenu in an onShowing handler, depending on the current item (note: no need for a guard against null, because the conditional binding will implicitly guarantee to not show the the menu in that case)
The snippet:
myTable.setRowFactory(tv -> {
TableRow<People> row = new TableRow<>() {
ContextMenu rowMenu = new ContextMenu();
MenuItem sampleMenuItem = new MenuItem("Sample Button");
{
rowMenu.getItems().addAll(sampleMenuItem);
contextMenuProperty()
.bind(Bindings
.when(Bindings.isNotNull(itemProperty()))
.then(rowMenu).otherwise((ContextMenu) null));
rowMenu.setOnShowing(e -> {
People selectedRow = getItem();
sampleMenuItem.setDisable(selectedRow.getAge() > 100);
});
}
};
return row;
});
apologies for the length of my code. I realized last night that I was on the wrong path and now have gotten stuck on an issue that I think relates to JavaFX event handling. Initially I had the logic functioning outside a GUI in a basic loop that depended on interaction through the console. Everything was working great. I've now tried to get this to work in a GUI with interaction from the user.
I have two main problems with the code below.
The first is that the text in textArea is not updating with additional text after the startButton executes the start of my main logic sequence. The first append starts right under the first while loop. I was hoping to have this show up in the GUI as the logic executes. I'm not sure if I need to tell the GUI to update at certain intervals or if there's something else wrong.
Second, I'm not sure how to get the program to wait for the user to type in something into textField before hitting the textButton I created to continue on. I used to have a scanner created which caused the program to wait in the console for input. I realize I need some way of telling it to wait for a button press when it's running inside JavaFX.
I chose not to include the rest of the code to make things easier to read, but I can add it on if it will help resolve this issue.
Thank you everyone for your help!
public class FxApp extends Application {
//Creates FileParser object with methods that alter the incoming Array of Strings into the format we need
FileParser fileParser = new FileParser();
Configure configure = new Configure();
private String text;
private String initialState;
private ArrayList<Machine> machines = new ArrayList<Machine>();
private Map<String, String> initialStates = new HashMap<String, String>();
private Map<String, String> states = new HashMap<String, String>();
private Map<String, ArrayDeque<String>> queues = new HashMap<String, ArrayDeque<String>>();
private Map<Integer, ArrayList<String>> parsedData = new HashMap<Integer, ArrayList<String>>();
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("File Chooser");
FileChooser fileChooser = new FileChooser();
fileChooser.getExtensionFilters().addAll(new ExtensionFilter("Text Files", "*.txt"));
Button startButton = new Button("Start");
Button openButton = new Button("Click to open a file...");
openButton.setPrefSize(200, 80);
Button textButton = new Button("Enter");
TextArea textArea = new TextArea();
textArea.setWrapText(true);
TextField textField = new TextField();
Label lbl = new Label();
VBox vbox = new VBox(lbl, openButton, startButton, textArea, textField, textButton);
vbox.setSpacing(10);
vbox.setPadding(new Insets(15));
lbl.setText("This tool creates virtual automata based \ron the file.");
Scene scene = new Scene(vbox, 640, 480);
primaryStage.setScene(scene);
primaryStage.show();
openButton.setOnAction(
new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent e) {
File file = fileChooser.showOpenDialog(primaryStage);
if (file != null) {
//Execute the method to convert to string array before sending to file parser
try {
fileParser.convertFile(file);
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
});
textButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
text = textField.getText();
}
});
startButton.setOnAction(new EventHandler <ActionEvent>()
{
public void handle(ActionEvent event)
{
machineCreation();
String exit = "no";
String nextLine = null;
ArrayList<String> listOfCurrentTransitions = new ArrayList<String>();
int nextInt = 0;
states = initialStates;
while(!(exit.toLowerCase().equals("yes"))) {
textArea.appendText("Choose a state to load");
//Print out the states possible for each machine
ArrayList<String> tempTrans = machines.get(nextInt).getTransitions();
//This loops through the list of transitions of the machine and pulls possible transitions from its current state
for(int i = 0; i < tempTrans.size(); i++) {
String pull = tempTrans.get(i);
String[] apart = pull.split(" ");
pull = apart[0];
if(states.get(Integer.toString(nextInt)).equals(pull)) {
listOfCurrentTransitions.add(tempTrans.get(i));
}
}
if(!(listOfCurrentTransitions.isEmpty())) {
textArea.appendText("The following transitions are possible. Choose one: " + listOfCurrentTransitions);
}
else {
textArea.appendText("No transitions for this machine exist from its current state");
}
//Tell GUI to wait for user input in textField and execute textButton which assigns to String text. Resume on button click.
The while loop blocks the JavaFX application thread which prevents updates of the GUI and handling of events.
You need to execute the logic of a single iteration of the loop on each "text commit" instead:
private TextArea textArea;
private void activateState(int nextInt) {
ArrayList<String> listOfCurrentTransitions = new ArrayList<String>();
textArea.appendText("Choose a state to load");
//Print out the states possible for each machine
ArrayList<String> tempTrans = machines.get(nextInt).getTransitions();
//This loops through the list of transitions of the machine and pulls possible transitions from its current state
for(int i = 0; i < tempTrans.size(); i++) {
String pull = tempTrans.get(i);
String[] apart = pull.split(" ");
pull = apart[0];
if(states.get(Integer.toString(nextInt)).equals(pull)) {
listOfCurrentTransitions.add(tempTrans.get(i));
}
}
if(listOfCurrentTransitions.isEmpty()) {
textArea.appendText("No transitions for this machine exist from its current state");
} else {
textArea.appendText("The following transitions are possible. Choose one: " + listOfCurrentTransitions);
}
}
#Override
public void start(Stage primaryStage) throws Exception {
...
textArea = new TextArea();
...
startButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
machineCreation();
activateState(0);
}
});
textButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
// read input and ask for more input...
int nextState = Integer.parseInt(textField.getText()); // TODO: deal with invalid input
activateState(nextState);
}
});
You probably need to fix the logic a bit to verify a transition is valid, change the values of some fields ect...
I have a method inside of one of my controllers that requires some seconds to process. I would like to have a gif animation popup while this occurs but I only get a static image. This is my code:
#FXML
public void search(ActionEvent e) {
final Stage dialog = new Stage();
Group popup = new Group();
Image image = new Image("file:resources/images/bender.gif");
ImageView view = new ImageView(image);
popup.getChildren().add(view);
Scene dialogScene = new Scene(popup);
dialog.setScene(dialogScene);
dialog.show();
Platform.runLater(new Runnable() {
#Override
public void run() {
Match msg = stablishSearchConditions();
TreeItem<String> root = new TreeItem<>("ROOT");
int indexName = 1;
String mensaje = "Mensaje ";
treeLabelResults.setText("");
arbol.setRoot(root);
for (Match message : msg.each()) {
TreeItem<String> nodo = new TreeItem<String>(mensaje + indexName);
root.getChildren().add(nodo);
root.setExpanded(true);
String mens = message.getMessage();
TreeItem<String> nodo2 = new TreeItem<String>(mens);
nodo.getChildren().add(nodo2);
indexName++;
}
dialog.close();
}
});
}
You are blocking the fx application thread by running the expensive operation on this thread. This prevents your UI from updating, including animating the GIF.
Move the expensive operations to a non-application thread instead and only use Platform.runLater() to "commit" the ui updates:
Runnable expensiveTask = () -> {
// expensive operations that should not run on the application thread
Match msg = stablishSearchConditions();
TreeItem<String> root = new TreeItem<>("ROOT");
int indexName = 1;
String mensaje = "Mensaje ";
for (Match message : msg.each()) {
TreeItem<String> nodo = new TreeItem<String>(mensaje + indexName);
root.getChildren().add(nodo);
root.setExpanded(true);
String mens = message.getMessage();
TreeItem<String> nodo2 = new TreeItem<String>(mens);
nodo.getChildren().add(nodo2);
indexName++;
}
// update ui -> application thread
Platform.runLater(() -> {
treeLabelResults.setText("");
arbol.setRoot(root);
dialog.close();
});
};
// start new thread for expensiveTask
new Thread(expensiveTask).start();
Everything works fine until I create a new tab. Then when I go to the previous and try to use any of the buttons they affect the latest tab not the one I have selected. But if I go to the latest tab it works like normal. Here is the class that I use to make my tabs. So, why is the previous tabs affecting the lastest? And how do I fix it?
public class JTab {
private javafx.scene.control.Tab tab;
private ImageView imgView;
private Image logo;
private BorderPane root;
private Button reloadButton, backButton, forwardButton;
private TextField field;
private WebView view;
private WebEngine engine;
private static JTab instance;
private JBrowser jBrowser;
private JTab(JBrowser jBrowser) {
this.jBrowser = jBrowser;
}
public static JTab getInstance(JBrowser browser) {
if(instance == null)
instance = new JTab(browser);
return instance;
}
public javafx.scene.control.Tab addTab() {
tab = new Tab();
tab.setText("New Tab");
tab.setOnClosed(event2 -> {
if(jBrowser.getTabPane().getTabs().size() == 1) {
jBrowser.getTabPane().setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
}
});
logo = new Image("unknown-document.png");
imgView = new ImageView(logo);
tab.setGraphic(imgView);
HBox hBox = new HBox(5);
hBox.setAlignment(Pos.CENTER);
reloadButton = new Button("Reload");
backButton = new Button("<");
forwardButton = new Button(">");
reloadButton.setOnAction(event1 -> engine.reload());
backButton.setOnAction(event1 -> loadData(goBack()));
forwardButton.setOnAction(event1 -> loadData(goForward()));
//The TextField for entering web addresses.
field = new TextField("Enter URL");
field.setPrefColumnCount(50); //make the field at least 50 columns wide.
field.focusedProperty().addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> { //When click on field entire thing selected
Platform.runLater(() -> {
if (field.isFocused() && !field.getText().isEmpty()) {
field.selectAll();
}
});
});
field.setOnKeyPressed(event -> { //When ENTER is pressed it will load page
if (event.getCode() == KeyCode.ENTER) {
if (!field.getText().isEmpty()) {
loadData(field.getText());
}
}
});
//Add all out navigation nodes to the vbox.
hBox.getChildren().addAll(backButton, forwardButton, reloadButton, field);
view = new WebView();
engine = view.getEngine();
engine.setJavaScriptEnabled(true);
engine.getLoadWorker().stateProperty().addListener(
(ov, oldState, newState) -> {
if (newState == Worker.State.SUCCEEDED) {
tab.setText(getTitle());
//TODO setGraphic
}
});
loadData("google.com");
root = new BorderPane();
root.setPrefSize(1024, 768);
root.setTop(hBox);
root.setCenter(view);
tab.setContent(root);
return tab;
}
public void loadData(String URL) {
if(!URL.startsWith("http://")) {
URL = "http://" + URL;
}
field.setText(URL);
tab.setText(URL);
engine.load(URL);
}
private String getTitle() {
Document doc = engine.getDocument();
NodeList heads = doc.getElementsByTagName("head");
String titleText = engine.getLocation() ; // use location if page does not define a title
if (heads.getLength() > 0) {
Element head = (Element)heads.item(0);
NodeList titles = head.getElementsByTagName("title");
if (titles.getLength() > 0) {
Node title = titles.item(0);
titleText = title.getTextContent();
}
}
return titleText;
}
private String goBack() {
final WebHistory history = engine.getHistory();
ObservableList<WebHistory.Entry> entryList = history.getEntries();
int currentIndex=history.getCurrentIndex();
Platform.runLater(() -> history.go(-1));
return entryList.get(currentIndex>0?currentIndex-1:currentIndex).getUrl();
}
private String goForward() {
final WebHistory history = engine.getHistory();
ObservableList<WebHistory.Entry> entryList=history.getEntries();
int currentIndex=history.getCurrentIndex();
Platform.runLater(() -> history.go(1));
return entryList.get(currentIndex<entryList.size()-1?currentIndex+1:currentIndex).getUrl();
}
}
Remove getInstance(jBrowser) method
Make the constructor public.
Then to add a tab to a tabPane do
tabPane.getTabs().add(new JTab(jBrowser).addTab());