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());
}
}
Related
I have recyclerview with each item holding some buttons and text.
I am setting my onViewClickListener in the ViewHolder. I have a Room database initialized in the MainActivity.
In terms of good app architecture, how should I change the data in my database once my OnClickListener triggers?
Do I pass the database as a parameter to the adapter then the view holder?
OR
Do I get the database through a getDatabase method that I implement?
What is the best way to go about this? I am open to any suggestion/design pattern.
How is something like this generally handled?
Best practise is to keep the database in your AppCompatActivity.
If you display database data in that recyclerView you should create an ArrayList filled with those data and pass it to the recyclerViewAdapter in the constructor like MyRecyclerViewAdapter myRecyclerViewAdapter = new MyRecyclerViewAdapter(myData)
Then everytime you change something from that list (from your activity) you just call notify method based on your action. General one is myRecyclerViewAdapter.notifyDataSetChanged(). But with lot data it's more efficient to use more specific notify methods.
And if the database should change based on some event in recyclerView, for example by the onViewClickListener. You should create an interface in which you would pass the change and then apply the change to the database in your AppCompatActivity.
You should keep your database (and strongly advise you to use the data with ViewModel) in your activity. You can make changes on data of recyclerview item by using weakreference and interface. Here is example of activity and adapter.
public class MyActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private MyObjectAdapter adapter;
private final List<MyObject> myObjectList = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
//define your views here..
setAdapter();
getData();
}
private void setAdapter(){
adapter = new MyObjectAdapter(myObjectList, new
MyObjectAdapter.IObjectClickListener() {
#Override
public void onButtonOneClick(MyObject myObject) {
//do button one operation with object item;
}
#Override
public void onButtonTwoClick(MyObject myObject) {
//do button two operation with object item;
}
});
}
private void getData(){
//Get your data from database and pass add them all into our
//myObjectList
myObjectList.addAll(objects); //Suppose objects come from database
adapter.notifyDataSetChanged();
}
}
And adapter class is like
public class MyObjectAdapter extends RecyclerView.Adapter<MyObjectAdapter.ObjectViewHolder> {
private final List<MyObject> myObjects;
private final IObjectClickListener listener; //defined at the bottom
public MyObjectAdapter(List<MyObject> myObjects, IObjectClickListener listener) {
this.myObjects = myObjects;
this.listener = listener;
}
/*
..
Other methods of adapter are here
..
*/
public class ObjectViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
private Button buttonOne, buttonTwo;
private WeakReference<IObjectClickListener> reference;
public ObjectViewHolder(#NonNull View itemView) {
super(itemView);
//define your views here
reference = new WeakReference<>(listener);
buttonOne.setOnClickListener(this);
buttonTwo.setOnClickListener(this);
}
#Override
public void onClick(View v) {
if (v == buttonOne){
reference.get().onButtonOneClick(myObjects.get(getAdapterPosition()));
} else if (v == buttonTwo){
reference.get().onButtonTwoClick(myObjects.get(getAdapterPosition()));
}
}
public interface IObjectClickListener{
void onButtonOneClick(MyObject myObject);
void onButtonTwoClick(MyObject myObject);
}
}
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.
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());
}
}
I am trying to create a Gluon mobile application using javafx. I want to create a login page in which on a successful login i need to load another(Second View) view in a button click. I didn't get a proper example for this. If anyone knows this please help. I have two views Primary presenter and secondary presenter.(gluon application with FXML).Below is my primary view's controller.
public class PrimaryPresenter {
#FXML
private View primary;
private Label label;
#FXML
private TextField username;
#FXML
private Button loginBt;
private Alert alert;
#FXML
private PasswordField password;
public void initialize() {
primary.showingProperty().addListener((obs, oldValue, newValue) -> {
if (newValue) {
AppBar appBar = MobileApplication.getInstance().getAppBar();
appBar.setNavIcon(MaterialDesignIcon.MENU.button(e
-> MobileApplication.getInstance().showLayer(ArjunsApp.MENU_LAYER)));
appBar.setTitleText("Primary");
appBar.getActionItems().add(MaterialDesignIcon.SEARCH.button(e
-> System.out.println("Search")));
}
});
}
#FXML
private void buttonClick(ActionEvent event) {
if(username.getText().equals("")){
alert = new Alert(AlertType.ERROR,"Enter username");
alert.showAndWait();
}else if(password.getText().equals("")){
alert = new Alert(AlertType.ERROR,"Enter password");
alert.showAndWait();
}else{
//Code to load my secondary view
}
}
}
Assuming you are using the Gluon plugin - Multi View project with FXML template, you can switch views easily with MobileApplication.getInstance().switchView(viewName).
In your case:
#FXML
private void buttonClick(ActionEvent event) {
...
MobileApplication.getInstance().switchView("SECONDARY_VIEW");
}
If you are using the Glisten-Afterburner template instead (it also uses FXML), you could use something like:
#FXML
private void buttonClick(ActionEvent event) {
...
AppViewManager.SECONDARY_VIEW.switchView();
}
You can find more about the Gluon Mobile API here.
Javafx newbie here.. I need help with the best way to populate listview. Here is my setup..
I am developing UI tool that is supposed to track the number of virtual machines running in my environment. I get a callback whenever a machine comes up or goes down. How do I update listview based on that data. Controller code -
public class MainOverviewController implements Initializable
{
#FXML
private ListView<String> devicesListView; // Points to the listview
#Override
public void initialize(URL location, ResourceBundle resources) {
ObservableList<String> items = FXCollections.observableArrayList("Machines connected");
devicesListView.setItems(items);
...
}
Callback code where I am getting the virtual machine notifications -
class VMChangeListener extends vmlistener
{
...
#Override
public void vmStarted(VM vm)
{
vms.add(vm);
}
#Override
public void vmDisconnected(VM vm)
{
vms.remove(vm);
}
Now my question is, whats the best way to update observablelist, items, from vmStarted and vmDisconnected functions. I could pass the observablelist to the VMChangeListener or use some sort of callbacks? Should I do this in seperate thread?
public class MainOverviewController extends vmListener implements Initializable
{
#FXML
private ListView<VM> devicesListView; // Points to the listview
#Override
public void vmStarted(final VM vm)
{
Platform.runLater(new Runnable()
{
#Override
public void run()
{
devicesListView.getItems().add(vm);
}
});
}
#Override
public void vmDisconnected(final VM vm)
{
Platform.runLater(new Runnable()
{
#Override
public void run()
{
devicesListView.getItems().remove(vm);
}
});
}
...
}