Well it appears I have been stumped by one of the simplest ListView implementations out there. For a few days I have found it impossible to properly reallocate a JavaFX ListView. I am working on making an EntityView completely dynamic, being able to remove elements whenever needed through a ContextMenu. So I have a ListView that is populated by an ArrayList, which we will refer to as "renderable". When you select "Remove" in the context menu, it removes the Entity from the renderable list, which also happens to be the "value" of the List Cell on whom I right clicked. Afterwards, I wish to refresh the ListView and remove now the nonexistent cell. So by creating a new ObservableList with the new renderable list (which removes the correct entity and that works fine), I set the items in the ListView, which does jack shit. I can set the list to null in this case, which removes all the elements. But I cannot reset the list with the new array and it remove the now missing entity. Somebody point me in the right direction please!
When I use the method I stated above, it removes it from the list, but not visually. There becomes an unusable cell at the bottom of the list, which has its name.
public void createContextMenu(final Entity curr, MouseEvent me){
MenuItem[] items = {new MenuItem("EDIT TYPE"), new MenuItem("REMOVE")};
ContextMenu menu = new ContextMenu(items);
menu.show(list, me.getScreenX(), me.getScreenY());
items[1].setOnAction(new EventHandler<ActionEvent>(){
public void handle(ActionEvent arg0) {
CanvasTab tab = (CanvasTab) core.canvasTabbedPane.getSelectionModel().getSelectedItem();
Kanvas k = tab.canvas;
k.renderable.remove(curr);
System.out.println(k.renderable);
k.redraw();
EntityView.this.list.getItems().remove(curr);
ObservableList<Entity> temp = FXCollections.observableList(k.renderable);
EntityView.this.list.setItems(temp);
}
});
}
This is the context menu:
Related
Once again stuck dead in my coding tracks, I come seeking knowledge from the omniscient StackOverflow Community...
My problem is fairly simple. I have populated a Navigation View drawer with objects. These objects toString() methods have been overwritten to display each objects own name in the list. Clicking each object in the drawer list brings up an alert message that should say ("you clicked "+ myDrawerObject.getName()) The problem is in the onNavigationItemSelected listener.
This method returns a MenuItem item which is a view. I am having trouble understanding why i can not treat this view like the object it is supposed to contain. Help is greatly appreciated and thanks in advance!
I understand that the item parameter is returning which menu item has been clicked but if the menu item is an object why cant I call those objects methods?
I have tried grabbing the item return value and calling its getName() method, to no avail. item.getName(); (I understand that item is a view so it doesnt have a "getName()" method
I have tried casting the item value back into an object to no avail
MyObject myObject = (MyObject)item;
//Item is still a view and a view cant be cast into an object it seems
I have even tried putting all created objects of the MyObject type into a static arrayList then trying to match up id's also to no avail.
//in the onNavigationItemSelected listener
int id = item.getItemId()
//somewhere else in the code
for (anotherMyObject : listOfEveryMyObjectEverMade){
anotherMyObject.get(id)
}
/*returns the id of the item in the listview. as i understand a specific id
is assigned to every item in the list. so if an object has never been in that
particular list, it wont have that same id
*/
I must add the the contents of the drawer (the string list) was created dynamically so there is no xml file for the menu or menItems!
I ran into a very similar situation and literally blockaded me for days!
I'm almost 90% positive that there has to be a way out there...
but I personally didn't find it, so instead I went with a work around:
private void setMenus() {
int temp = 0;
//Mine was inside of a fragment, hints the getActivity() call
NavigationView navView22 = getActivity().findViewById(R.id.navigation_view);
MenuItem oneItem = navView22.getMenu().findItem(0);
if (oneItem == null){
Menu m = navView22.getMenu();
SubMenu sm0 = m.addSubMenu(
getResources().getString(R.string.clickToSelectRookies));
sm0.add(0,
temp, temp, getResources().getString(R.string.signAllString));
tempRooks.add(round1.get(0));
/*I had to create the entire menu dynamically, so I did so by creating
subMenus in my menus in numerical order
I then created an arraylist to hold all my objects in that same order
though in retrospect I should have just used a HashMap!!
finally in the onClick I was able to listen for which subMenu item was clicked
and since the menu's and my arraylist of objects corresponded I then pulled
the correct object from my arraylist. I realize this is a little hacky, but
hopefully if you do not find the answer you can do this temporarily.
If you do find a better solution please post so we all can learn. Thank YOU!!
*/
temp++;
SubMenu sm1 = m.addSubMenu(
getResources().getString(R.string.firstRoundPicks));
for (int x = 0; x < round1.size(); x++) {
sm1.add(0, temp, temp, round1.get(x).toString());
tempRooks.add(round1.get(x));
temp++;
}
It is hard to explain so I'll use an example:
#Override
public void start(Stage primaryStage) throws Exception
{
final VBox vbox = new VBox();
final Scene sc = new Scene(vbox);
primaryStage.setScene(sc);
final TableView<Person> table = new TableView<>();
final TableColumn<Person, String> columnName = new TableColumn<Person, String>("Name");
table.getColumns().add(columnName);
final ObservableList<Person> list = FXCollections.observableArrayList();
list.add(new Person("Hello"));
list.add(new Person("World"));
Bindings.bindContent(table.getItems(), list);
columnName.setCellValueFactory(new PropertyValueFactory<>("name"));
vbox.getChildren().add(table);
final Button button = new Button("test");
button.setOnAction(event ->
{
final Person removed = list.remove(0);
removed.setName("Bye");
list.add(0, removed);
});
vbox.getChildren().add(button);
primaryStage.show();
}
public static class Person
{
private String name = "";
public Person(String n)
{
name = n;
}
public String getName()
{
return name;
}
public void setName(String n)
{
name = n;
}
}
In this example, I show a TableView with a single column named "Name". Running this sample code, you will get two rows: first row with "Hello" in "Name" column; and second row with "World" in "Name" column.
Additionally, there is a button, this button removes the first Person object from the list, then makes some changes to the object, then adds it back in at the same index. Doing so would cause any ListChangeListener added to the ObservableList to be triggered, and I have tested this to be true.
I would expect the row with "Hello" to be replaced with "Bye", but it seems like the TableView continues to show "Hello". If I used a TimeLine to add delay before I add the removed Person object back to the list, it would change to "Bye".
final Timeline tl = new Timeline(new KeyFrame(Duration.millis(30), ae -> list.add(0, removed)));
tl.play();
Is there something weird with the API? Is there any way to do this without this problem?
This is essentially expected behavior.
Note that (and I'm guessing you are trying to work around this issue), if you simply called
list.get(0).setName("Bye");
which has the same effect in terms of the underlying data, the table would not update as it has no way of being notified that the String field name in the element of the list has changed.
The code
Person removed = list.remove(0);
removed.setName("Bye");
list.add(0, removed);
is really equivalent to list.get(0).setName("Bye");: you just temporarily remove the item from the list before changing it, and then add it back. As far as the list is concerned, the net result is the same. I guess you are doing this in the hope that removing and replacing the item from the list will persuade the table to notice the state of the item has changed. There's no guarantee this will be the case. Here is what's happening:
The binding between your two lists:
Bindings.bindContent(table.getItems(), list);
works like any other binding: it defines how to get the value of the binding (the elements of list), and marks the data as invalid if list is invalidated at any time. The latter happens when you add and remove elements from list.
The TableView will not perform layout every time the binding to the list changes; instead, when then binding is invalidated (add or remove an element), then the table view marks itself as potentially needing to be redrawn. Then, on the next rendering pulse, the table will check the data and see if it really needs to be redrawn, and re-render if needed. There are obvious performance-saving features of this implementation.
So what happens with your code is that an item is removed from the list, causing the binding to be marked as invalid. The item is then changed (by calling setName(...)), and the same item is then added back into the list at the same position. This also causes the binding to be marked as invalid, which has no effect (it is already invalid).
No rendering pulse can occur between the removal and re-addition of this element. Consequently, the first time the table actually looks at the changes that were made to the list has to be after the entire remove-change-add process. At that point, the table will see that the list still contains the exact same elements in the exact same order that it previously contained. (The internal state of one of the elements has changed, but since this is not an observable value - not a JavaFX property - the table is unaware of this.) Consequently, the table sees no changes (or sees that all the changes have cancelled each other out), and doesn't re-render.
In the case where you add the pause, then a rendering frame (or two) occurs between the removal of the item and its re-addition. Consequently, the table actually renders one or two frames without the item, and when it is added back in, it adds it back and renders the current value. (You might, possibly, be able to make the behavior unpredictable, by pausing for 16 or 17 milliseconds, which is right on the cusp of the time for one rendering frame.)
It's not clear what you really intend to do. If you are trying to persuade the table to update without using JavaFX properties, you can do
list.get(0).setName("Bye");
table.refresh();
though this is not a very satisfactory solution.
Note too that
list.remove(0);
list.add(0, new Person("Bye"));
will also work (since now the added element is not the same as the removed element).
The better approach is to implement your model class with JavaFX properties:
public static class Person
{
private final StringProperty name = new SimpleStringProperty("");
public Person(String n)
{
setName(n);
}
public StringProperty nameProperty() {
return name ;
}
public final String getName()
{
return nameProperty().get();
}
public final void setName(String n)
{
nameProperty().set(n);
}
}
and then simply calling
list.get(0).setName("Bye");
will update the table (because the cell will be observing the property).
I am using this link for creating a ContextMenu for each table row. Right now I'm running into problems because I'm not sure how to attach a ContextMenu after the 'type' has been inserted into a row.
Lets say I'm using a .zip editor program, and it lists the contents. I have an Image, and a text file, and some other stuff, all of them are under a class called Entry. My table's generic type is 'Entry', and I'd like to be able to create a context menu for each entry based on it's underlying subclass type (like an ImageEntry might return a menu item to open it up in an image editor...etc).
Right now I have a generic context menu for everything, but it's not great displaying a menu item about opening a text file with an image editor...
Is this possible to do? If so, what is the proper way to go about doing it?
Add a listener to the row's itemProperty (which represents the item displayed in the row) and update the context menu when it changes:
table.setRowFactory(new Callback<TableView<Person>, TableRow<Person>>() {
#Override
public TableRow<Person> call(TableView<Person> tableView) {
final TableRow<Person> row = new TableRow<>();
final ContextMenu contextMenu = new ContextMenu();
row.itemProperty().addListener((obs, oldPerson, newPerson) -> {
contextMenu.getItems().clear();
// add items to context menu depending on value of newPerson
// ...
});
// Set context menu on row, but use a binding to make it only show for non-empty rows:
row.contextMenuProperty().bind(
Bindings.when(row.emptyProperty())
.then((ContextMenu)null)
.otherwise(contextMenu)
);
return row ;
}
});
what I want to do is press a button to add previously declared TableRow objects (refers to TableRow objects already created in XML file) that I have hidden using table.removeView(row1) etc on program start.
Then I want to be able to click a button to add each TableRow back to the view one at a time until all the rows are visible again. I have tried a few different ways without any luck and the last method I tried was creating an array list in my onCreate method like so:
final ArrayList<TableRow> rowlist = new ArrayList<TableRow>();
rowlist.add(row4);
rowlist.add(row5);
rowlist.add(row6);
rowlist.add(row7);
rowlist.add(row8);
rowlist.add(row9);
rowlist.add(row10);
Then trying to iterate through like this:
public void onClick(View v) {
Iterator<TableRow> rowiterator = rowlist.iterator();
while (rowiterator.hasNext()) {
table.addView(rowiterator.next());
}
}
What I get now is when I press my button it just adds all the rows back in at once, when I want it to iterate through the list adding rows one at a time.
Can anyone help me resolve this problem, or tell me if I'm being a complete idiot and suggest an entirely new and better method of achieving what I want to achieve?
Note: I'm pretty new to Java programming and on this problem I am absolutely stumped!
You're iterating over the entire array when you click the button and adding them all back in. Something like this is probably more like what you want:
public void onClick(View v) {
if(rowlist.size() > 0)
{
table.addView(rowlist.get(0));
rowlist.remove(0);
}
}
I have a Jlist which is populated with books, however, what I would like to do is that once one of the books is selected I press a button called return book that should make the book removed from the list.
I have a members class that has a return book method as follows
public void returnBook(Book aBook)
{
currentLoans.remove(aBook);
aBook.setBorrower(null);
}
On my main application I have the following code under the return book button
private void theButtonActionPerformed(java.awt.event.ActionEvent evt) {
//!!!Return book
DefaultListModel model = (DefaultListModel) BooksOnLoan.getModel();
Book selectedBook;
selectedBook = (Book)BooksOnLoan.getModel();
model.remove(selectedBook);
}
As you can see I am quite not sure how to remove the item from the list once the button is clicked.
The method "remove" from DefaultListModel works with index, so you first need to get the index of the element that you want to remove and provide that to the remove method. You can use methods on your list for that: getSelectedIndex method for single selection mode (you will get -1 if there is no selection), or getSelectedIndices for multiselect.
If in any case your list stays the same after this, you need to refresh GUI after the model has been changed. Although I am almost certain that you need not do that, but keep this principle in mind for future.