I've got a list definition in fxml
<ListView fx:id="select_values" editable="true" prefHeight="200.0" prefWidth="200.0" />
My controller class for the list looks like this
public class ScriptGeneratorController implements Initializable {
#FXML
private ListView<String> select_values;
private List<String> selectValueList = new ArrayList<>(Arrays.asList("", "", "", "", "", "", ""));
private ListProperty<String> selectValueListProperty = new SimpleListProperty<>();
#FXML
private void handleQuestionGenerationAction(ActionEvent event) {
System.out.println(selectValueList); // list of [,,,,,]
}
#Override
public void initialize(URL location, ResourceBundle resources) {
select_values.itemsProperty().bind(selectValueListProperty);
select_values.setCellFactory(TextFieldListCell.forListView());
select_values.setOnEditCommit(event -> select_values.getItems().set(event.getIndex(), event.getNewValue()));
selectValueListProperty.set(FXCollections.observableArrayList(selectValueList));
}
}
But when I click on the button to do the action where I print list to console I get the list of empty strings (the same as initial).
But on my form I clearly populate the values
What's the problem?
The documentation for FXCollections.observableArrayList(...) says it
Creates a new observable array list and adds a content of collection col to it.
So your code creates a new list which is used as the backing list for the list view, and copies the content of selectValueList to that new list. When the user edits the content, the backing list is changed, but the changes are not propagated back to selectValueList.
You seem to have too many layers of data here. Probably all you need is
public class ScriptGeneratorController implements Initializable {
#FXML
private ListView<String> select_values;
private ObservableList<String> selectValueList = FXCollections.observableArrayList("", "", "", "", "", "", "");
#FXML
private void handleQuestionGenerationAction(ActionEvent event) {
System.out.println(selectValueList);
}
#Override
public void initialize(URL location, ResourceBundle resources) {
select_values.setItems(selectValueList);
select_values.setCellFactory(TextFieldListCell.forListView());
}
}
Related
I found this post and I think it's very similar but I need some help adapting it to my case: How to set the style of list view cells based on a condition in JavaFX
Basically, I have a ListView and I want to change the background color of individual cells based on a condition. I have an arrayList locations of location objects, and I want to check a property of a single location to determine the background color of the cell. the ListView is populated by iterating through locations and getting the name of each location, then storing it in arraylist, then finally populating the listView. Here is relevant code:
#FXML
private ListView<String> locationListView;
private ArrayList<String> locationList = new ArrayList();
private ListProperty<String> listProperty = new SimpleListProperty<>();
#Override
public void initialize(URL url, ResourceBundle rb) {
for (int i = 0; i < locations.size(); i++) {
locationList.add(locations.get(i).name());
}
}
locationListView.itemsProperty().bind(listProperty);
listProperty.set(FXCollections.observableArrayList(locationList));
}
And here is where I am struggling, I copied/modified from the post I linked above.
locationListView.setCellFactory(lv -> new ListCell<String>() {
#Override
protected void updateItem(String c, boolean empty) {
super.updateItem(c, empty);
for (int i = 0; i < locations.size(); i++) {
if (locations.get(i).visited) {
setStyle("-fx-background-color: green");
}
}
}
});
I'm aware this is long ways from working as intended but I'm not sure where to go from here, I think I'm approaching it incorrectly.
Overall I have maybe made this more complicated than it needs to be (new to javafx), but any help is appreciated.
In JavaFX I use ListView to display items added or removed to/from a Set. I have made an observableSet to use with the ListView to show the updates but the ListView is not updating properly when the Set changes. Here is my code with a work around. But why it's not working as expected?
public class FXMLDocumentController implements Initializable {
#FXML
private ListView listView;
ObservableSet<String> observableSet; //ObservableSet to prevent dublicates
Integer i = 3;
#Override
public void initialize(URL url, ResourceBundle rb) {
listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
observableSet = FXCollections.observableSet();
observableSet.addAll(Arrays.asList("Item1", "Item2", "Item3"));
//Setting up ListView to the ObservableSet
listView.setItems(FXCollections.observableArrayList(observableSet));
}
#FXML
private void handleButtonAction(ActionEvent event) {
observableSet.add("Item" + i++);
//Setting up ListView to the ObservableSet otherwise it won't update
//My understanding that I don't have to do this with an observableSet
listView.setItems(FXCollections.observableArrayList(observableSet));
}
#FXML
private void handleRemoveAction(ActionEvent event) {
observableSet.removeAll(listView.getSelectionModel().getSelectedItems());
//Setting up ListView to the ObservableSet otherwise it won't update
listView.setItems(FXCollections.observableArrayList(observableSet));
}
}
The Issue:
I have to keep setting up the ListView as demonstrated above and below everytime the observableSet is updated. Otherwise the change won't show in the ListView.
listView.setItems(FXCollections.observableArrayList(observableSet));
Try calling
listview.refresh();
after adding and removing items from the list.
or you can add a change listener on the list and call refresh method from it.
but i prefer first method, because adding a listener sometimes will not update the list view.
This is the expected behavior. The method you are calling to create the observable list "Creates a new observable list and adds the contents of [the set] to it". So subsequent changes to the set will not change the list.
Another option is just to register a listener with the observable set, and update the list view's items by adding or removing an element appropriately:
public class FXMLDocumentController implements Initializable {
#FXML
private ListView listView;
private ObservableSet<String> observableSet; //ObservableSet to prevent dublicates
private int i = 3;
#Override
public void initialize(URL url, ResourceBundle rb) {
listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
observableSet = FXCollections.observableSet();
observableSet.addListener((SetChangeListener.Change<? extends String> c) -> {
if (c.wasAdded()) {
listView.getItems().add(c.getElementAdded());
}
if (c.wasRemoved()) {
listView.getItems().remove(c.getElementRemoved());
}
});
observableSet.addAll(Arrays.asList("Item1", "Item2", "Item3"));
}
#FXML
private void handleButtonAction(ActionEvent event) {
observableSet.add("Item" + i++);
}
#FXML
private void handleRemoveAction(ActionEvent event) {
observableSet.removeAll(listView.getSelectionModel().getSelectedItems());
}
}
I want to show different pages in the same window tab. I use classes Empty and Empty1. The class Empty is a main page and view Empty1 is loaded into Empty.
The class called Empty:
#Theme("valo")
#SpringUI(path = "empty")
public class Empty extends UI {
#Autowired
private SpringViewProvider viewProvider;
#Override
protected void init(VaadinRequest request) {
System.out.println("init Empty");
final VerticalLayout root = new VerticalLayout();
root.setSizeFull();
setContent(root);
Navigator navigator = new Navigator(this, this);
navigator.addProvider(viewProvider);
navigator.navigateTo(Empty1.VIEW_NAME);
}
}
and class called Empty1 (view):
#SpringView(name = Empty1.VIEW_NAME)
public class Empty1 extends VerticalLayout implements View {
public static final String VIEW_NAME = "empty1";
#PostConstruct
void init() {
addComponent(new Label("This is the default view"));
System.out.println("init Empty1");
}
#Override
public void enter(ViewChangeListener.ViewChangeEvent event) {
// the view is constructed in the init() method()
}
}
When I open new window I see sentence "This is the default view" and it's ok. But when I looked in trackstace I noticed sentence "init Empty1" that is shown two times, why?
It means that content of view Empty1 is loaded two times. I want to the view had been loaded only one time.
The final effect is supposed to look something like this in the 'Empty' class:
#Theme("valo")
#SpringUI(path = "empty")
public class Empty extends UI {
#Autowired
private SpringViewProvider viewProvider;
#Override
protected void init(VaadinRequest request) {
System.out.println("init Empty");
final VerticalLayout root = new VerticalLayout();
root.setSizeFull();
setContent(root);
Navigator navigator = new Navigator(this, this);
navigator.addProvider(viewProvider);
int optionAuto = 2;
switch (optionAuto) {
case 1:
getUI().getNavigator().navigateTo(Empty1.VIEW_NAME);
break;
case 2:
getUI().getNavigator().navigateTo(Empty2.VIEW_NAME);
break;
}
}
}
And It works but view 'Empty1' or 'Empty2' are called two times. How can I modifying the code in order to the view was called only one time?
The reason why it happens is because you call Navigator.navigateTo() in your Empty.init() method. Every navigation action creates new view bean instance (views are not singletons). After you call Navigator.navigateTo(), it's later called again in UI.doInit() method, which results in second bean creation.
If you want to make your "empty1" view a default view (the one which shows up if no other view is specified in url), then something like this should work:
if ("".equals(navigator.getState())) {
getPage().setUriFragment("!" + Empty1.VIEW_NAME, false);
}
Of course you can also name the view as "" to make it default.
If you add #UIScope above your class Empty1 then you can use getNavigator().navigateTo() without creating a new instance of your view bean every time.
I really apologize if this question doesn't make much sense in your perspective but in my perspective, this is really important to write cleaner and more maintainable code.
I have A.fxml, AController.java (controller class for A.fxml). I have a TableView with custom cell factories defined. I prefer defining my cell factories in a separate class so that I can reuse them if needed. I prefer writing all my event handling code in my controller class. But if I use custom cell factories then I am forced to write the event handling in the cell factory class itself.
Is there a way i can handle the custom cell factory events in my controller class itself? or atleast just throw the event from the custom cell factory class to my controller class and handle?
Thanks in Advance.
You can pass a object to the factory that determines when an context menu should be opened and that prepares the menu.
Example:
public interface CellContextMenuProvider<S, T> {
/**
* Prepares the context menu for opening.
* #param cell the cell the menu was requested for
* #param menu the menu to prepare
*/
public void prepareContextMenu(TableCell<S, T> cell, ContextMenu menu);
/**
* Checks, if a cell continaing a certain item should have an active context
* menu.
* #param empty if the cell is empty
* #param item the item of the cell
* #return {#literal true} iff the context menu should be enabled.
*/
public boolean enableContextMenu(boolean empty, T item);
/**
* Prepares the intial menu. This menu must not be empty, otherwise it won't
* be shown when it's requested for the first time.
* #param menu the menu to prepare
*/
public void prepareInitialContextMenu(ContextMenu menu);
}
public class CellFactory<S, T> implements Callback<TableColumn<S, T>, TableCell<S, T>> {
private final CellContextMenuProvider<S, T> menuProvider;
private final ContextMenu contextMenu;
public CellFactory(#NamedArg("menuProvider") CellContextMenuProvider<S, T> menuProvider) {
this.menuProvider = menuProvider;
if (menuProvider == null) {
this.contextMenu = null;
} else {
this.contextMenu = new ContextMenu();
menuProvider.prepareInitialContextMenu(contextMenu);
}
this.menuEventHandler = evt -> {
if (this.contextMenu != null) {
TableCell<S, T> source = (TableCell<S, T>) evt.getSource();
this.menuProvider.prepareContextMenu(source, this.contextMenu);
}
};
}
public CellFactory() {
this(null);
}
private final EventHandler<ContextMenuEvent> menuEventHandler;
#Override
public TableCell<S, T> call(TableColumn<S, T> param) {
TableCell<S, T> result = new TableCell<S, T>() {
#Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
setText(Objects.toString(item, ""));
setContextMenu(menuProvider != null && menuProvider.enableContextMenu(empty, item) ? contextMenu : null);
}
};
result.setOnContextMenuRequested(menuEventHandler);
if (menuProvider != null && menuProvider.enableContextMenu(true, null)) {
result.setContextMenu(contextMenu);
}
return result;
}
}
public class AController {
#FXML
private TableView<Item<Integer>> table;
public void initialize() {
for (int i = 0; i < 100; i++) {
table.getItems().add(new Item<>(i));
}
}
public CellContextMenuProvider<Item<Integer>, Integer> getMenuProvider() {
return new CellContextMenuProvider<Item<Integer>, Integer>() {
private final MenuItem item = new MenuItem("Say Hello World");
{
item.setOnAction(evt -> System.out.println("Hello World"));
}
#Override
public void prepareContextMenu(TableCell<Item<Integer>, Integer> cell, ContextMenu menu) {
}
#Override
public void prepareInitialContextMenu(ContextMenu menu) {
menu.getItems().setAll(item);
}
#Override
public boolean enableContextMenu(boolean empty, Integer item) {
// only for odd items
return !empty && (item % 2) != 0;
}
};
}
}
A.fxml
<TableView fx:id="table" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fxml.table.AController">
<columns>
<TableColumn prefWidth="159.0" text="C1">
<cellValueFactory>
<PropertyValueFactory property="value" />
</cellValueFactory>
<cellFactory>
<CellFactory menuProvider="$controller.menuProvider"/>
</cellFactory>
</TableColumn>
</columns>
</TableView>
Note: if the context menu is always the same you can also add EventHandler properties to the factory and use them like e.g. onAction attributes for buttons, which would allow you to pass event handlers of the controller, wich would result in shorter/simpler code.
Wouldnt it be possible to give your cell factory a FunctionalInterface as parameter with your event handling?(not sure if a good idea tho)
I imagine your code as follow:
Controller:
myTableView.setCellFactory(new MyOwnCellFactory<>(() -> {
// event handling
}));
MyOwnCellFactory:
public MyOwnCellFactory(MyFunctionalInterface myInterface) {
functionalInterface = myInterface;
}
// something something
functionalInterface.handleEvent();
FunctionalInterface:
#FunctionalInterface
public interface MyFunctionalInterface {
public void handleEvent();
}
Not sure if I understand your idea correct. Didnt test the code, just wrote it out of my head.
I'm trying to make a TableView with an embedded ListView, but I'm not exactly sure what to do, I've been researching how to embed Buttons into a TableColumn and I've seen that I should create a custom class that extends TableColumn and overrides updateItem().
I have a:
#FXML
private TableColumn<FoodModel, ObservableSet<Food>> storeFood;
for the tableColumn, set by the FXML editor. It's value is set by this.storeFood.setCellValueFactory(val -> val.getValue().getFood); and this.storeFood.setCellFactory(value -> new ListViewCell<>()); upon initialization.
I've been encountering a problem in which the list on screen is not being populated. Can I have a checklist of things to do to embed a list into a TableColumn?
Cell:
private static final class ListViewCell<T, V> extends TableCell<T, V> {
private ListView<T> list;
ListViewCell() {
this.list = new ListView<>();
this.list.setPrefHeight(60);
}
#Override
protected void updateItem(V item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setGraphic(this.list);
}
}
}