JavaFX 8: ComboBox button cell update behavior - combobox

I have a combo box which contains items of type Dog. If all items are replaced with new ones (via setAll on the ObservableList model) , the item renderer can cope with this update, while the button cell renderer cannot:
Here's a minimal example to reproduce the problem (full source incl. imports on GitHub):
public class ComboBoxRefresh extends Application {
private static final class Dog {
private final String name;
public Dog(String name) {
this.name = name;
}
}
private static final class DogListCell extends ListCell<Dog> {
#Override
public void updateItem(Dog item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText("");
} else {
setText(item.name);
}
}
}
private static List<Dog> createThreeDogs() {
return range(0, 3).mapToObj(i -> new Dog("Buddy " + i)).collect(toList());
}
#Override
public void start(Stage stage) throws Exception {
ObservableList<Dog> items = observableArrayList(createThreeDogs());
ComboBox<Dog> comboBox = new ComboBox<>(items);
comboBox.setPrefWidth(400);
comboBox.setCellFactory(listView -> new DogListCell());
comboBox.setButtonCell(new DogListCell());
Button button = new Button("Refresh");
button.setOnAction(event -> {
List<Dog> newItems = createThreeDogs();
items.setAll(newItems);
});
VBox box = new VBox(10, comboBox, button);
box.setPadding(new Insets(10));
Scene scene = new Scene(box);
stage.setScene(scene);
stage.show();
}
}
If I add an equals implementation to the Dog class, everything works, but this is not an option in my real application.
Are there any work-arounds to enforce a proper refresh of the button cell?

It seems to be a bug. Workaround could be
button.setOnAction( event -> {
List<Dog> newItems = createThreeDogs();
items.clear();
items.addAll(newItems);
} );

Related

RecyclerView: how to start Contextual Action Bar CAB with a Checkbox?

I have a RecyclerView list of CardViews and am using AppCompatActivity. Each CardView has a checkbox. When I click on the checkbox I would like to start a Contextual Action Bar. I would like to use an OnCheckedChangeListener and am having no luck. The checkmark correctly becomes visible when the "chkSelected" Checkbox is clicked on and it becomes invisible when the "chkSelected is clicked on again. What am I missing here?
public class MyRecylerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
...
private static ActionMode.Callback actionModeCallback = new ActionMode.Callback() {
#Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
return false;
}
#Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
#Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return false;
}
#Override
public void onDestroyActionMode(ActionMode mode) {
}
};
...
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_contact_item, parent, false);
final ItemHolder itemHolder = new ItemHolder(view);
return itemHolder;
}
private static class ItemHolder extends RecyclerView.ViewHolder {
private CheckBox chkSelected;
private ItemHolder(View itemView) {
super(itemView);
chkSelected = (CheckBox) itemView.findViewById(R.id.chkSelected);
chkSelected.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked)
mActionMode = ((AppCompatActivity) buttonView.getContext()).startSupportActionMode(actionModeCallback);
}
});
}
I also tried an OnClickListener() in the ItemHolder() with no luck. Code is below. The Toast in the onClick() is showing properly so there must be something wrong with the startSupportActionMode().
chkSelected.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Toast.makeText(view.getContext(),"Checkbox was clicked",Toast.LENGTH_LONG).show();
if(mActionMode == null) {
// Start the Contextual Action Bar (CAB) using the ActionMode.Callback defined above
mActionMode = ((AppCompatActivity) view.getContext()).startSupportActionMode(mActionModeCallback);
}
}
});
Solution was to set up a method in onBindViewHolder() that would update the item views for the OnClickListeners in Itemholder().

JavaFX 8: Checkbox in TableView and Add-on into to selected Item?

I would like to create a payroll program such that when the user tick a CheckBox in TableView, the name (String) will be carried on to the panel on the right and with TextFields to enter more info such as this:
I tried to follow the MVC hierarchy thus far as I code:
PayrollMainApp.java
public class PayrollMainApp extends Application {
private Stage primaryStage;
private BorderPane rootLayout;
private ObservableList<Employee> selectEmployeeTable = FXCollections.observableArrayList();
public PayrollMainApp(){
selectEmployeeTable.add(new Employee(false,"Hans Muster"));
selectEmployeeTable.add(new Employee(true,"Ruth Mueller"));
selectEmployeeTable.add(new Employee(false,"Heinz Kurz"));
selectEmployeeTable.add(new Employee(false,"Cornelia Meier"));
selectEmployeeTable.add(new Employee(false,"Werner Meyer"));
selectEmployeeTable.add(new Employee(false,"Lydia Kunz"));
selectEmployeeTable.add(new Employee(false,"Anna Best"));
selectEmployeeTable.add(new Employee(false,"Stefan Meier"));
selectEmployeeTable.add(new Employee(false,"Martin Mueller"));
}
public ObservableList<Employee> getSelectEmployeeTable(){
return selectEmployeeTable;
}
#Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
this.primaryStage.setTitle("PayrollApp");
initRootLayout();
showEmployeeOverview();
}
/**
* Initializes the root layout.
*/
public void initRootLayout() {
try {
// Load root layout from fxml file.
FXMLLoader loader = new FXMLLoader();
loader.setLocation(PayrollMainApp.class.getResource("view/RootLayout.fxml"));
rootLayout = (BorderPane) loader.load();
// Show the scene containing the root layout.
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
primaryStage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Shows the person overview inside the root layout.
*/
public void showEmployeeOverview() {
try {
// Load person overview.
FXMLLoader loader = new FXMLLoader();
loader.setLocation(PayrollMainApp.class.getResource("view/EmployeeOverview.fxml"));
AnchorPane personOverview = (AnchorPane) loader.load();
// Set person overview into the center of root layout.
rootLayout.setCenter(personOverview);
// Give the controller access to the main app
EmployeeOverviewController controller = loader.getController();
controller.setMainApp(this);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Returns the main stage.
* #return
*/
public Stage getPrimaryStage() {
return primaryStage;
}
public static void main(String[] args) {
launch(args);
}
}
Employee.java
public class Employee {
private BooleanProperty checkedBox = new SimpleBooleanProperty(false);
private StringProperty employeeName = new SimpleStringProperty();
public Employee(){
super();
}
public Employee(boolean checkedBox, String employeeName){
this.checkedBox = new SimpleBooleanProperty(false);
this.employeeName = new SimpleStringProperty(employeeName);
}
public BooleanProperty checkedBoxProperty(){
return this.checkedBox;
}
public StringProperty employeeNameProperty(){
return this.employeeName;
}
public java.lang.Boolean getSelectBox() {
return this.checkedBoxProperty().get();
}
public StringProperty getEmployeeName() {
return employeeName;
}
public void setSelectBox(final java.lang.Boolean checkedBox){
this.checkedBoxProperty().set(checkedBox);
}
public void setEmployeeName(StringProperty employeeName) {
this.employeeName = employeeName;
}
}
EmployeeOverviewController.java
public class EmployeeOverviewController {
#FXML
private TableView<Employee> selectEmployeeTable;
#FXML
private TableColumn<Employee, String> employeeNameColumn;
#FXML
private TableColumn<Employee, Boolean> checkBoxColumn;
private PayrollMainApp mainApp;
public EmployeeOverviewController() {
}
#FXML
public void initialize() {
checkBoxColumn.setCellValueFactory(cellData -> cellData.getValue().checkedBoxProperty());
checkBoxColumn.setCellFactory(param -> new CheckBoxTableCell<Employee, Boolean>());
employeeNameColumn.setCellValueFactory(cellData -> cellData.getValue().employeeNameProperty());
}
public void setMainApp(PayrollMainApp mainApp){
this.mainApp = mainApp;
//Add observable list data to the table
selectEmployeeTable.setItems(mainApp.getSelectEmployeeTable());
}
}
And a util class to make the checkBox visible in the table:
SelectBoxCellFactory.java
public class SelectBoxCellFactory implements Callback {
#Override
public TableCell call(Object param) {
CheckBoxTableCell<Employee,Boolean> checkBoxCell = new CheckBoxTableCell();
return checkBoxCell;
}
}
Here is my output thus far:
I know this has a table in it as compared to the previous output. Honestly I'm still indecisive as to use which, because I think using TextFields would make it look better. But all I hope for now is that this design is not impossible to code...
I really hope you can help me... Thank you for your help in advance.
It's probably easiest to use a TableView for the right panel. You can create a FilteredList from your original list:
FilteredList<Employee> selectedEmployees
= new FilteredList<>(selectEmployeeTable, Employee::getSelectBox);
and then use that for your second table.
If you prefer to use text fields (in what looks like a GridPane?) you can still use the filtered list above, but you will need to register a listener with it and update the layout "by hand" when items are added and removed.

Rendering a check box with a cellFactory in TableView doesn't work as it should

I want to create the following functionality for the serieses of a line chart when the user clicks on a checkbox on a table view. The table view for items has the ObservableList of the serieses of the chart. So the following code in the controller.
The controller class injects the following from the fxml file.
#FXML
public TableView<Series<Number, Number>> tableViewStatisticsOverview;
#FXML
public TableColumn<Series<Number, Number>, String> tableColumnStatisticName;
#FXML
public TableColumn<Series<Number, Number>, Series<Number, Number>> tableColumnShowSeries;
#FXML
public TableColumn<Series<Number, Number>, Series<Number, Number>> tableColumnAction;
//later on the code
tableViewStatisticsOverview.setItems(chart.getData()); // chart is a linexy chart
//setting up the tableColumnShowSeries to show a checkbox and show and hide the series
tableColumnShowSeries.setCellValueFactory(
new Callback<TableColumn.CellDataFeatures<Series<Number, Number>, Series<Number, Number>>, ObservableValue<Series<Number, Number>>>() {
#Override
public ObservableValue<Series<Number, Number>> call(CellDataFeatures<Series<Number, Number>, Series<Number, Number>> param) {
return new ReadOnlyObjectWrapper<Series<Number, Number>>(param
.getValue());
}
});
tableColumnShowSeries.setCellFactory(
new TableCollumnCellWithCheckBoxFactoryWrapper());
As data of the table I set the whole series on the row of the table so to be able to set the node of the series visible or not according to the value of the checkbox
TableCollumnCellWithCheckBoxFactoryWrapper class
package com.nokia.avalanche.client.util.fxhelper;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.util.Callback;
public class TableCollumnCellWithCheckBoxFactoryWrapper implements
Callback<TableColumn<Series<Number, Number>, Series<Number, Number>>, TableCell<Series<Number, Number>, Series<Number, Number>>> {
#Override
public TableCell<Series<Number, Number>, Series<Number, Number>> call(TableColumn<Series<Number, Number>, Series<Number, Number>> param) {
return new CheckBoxCell();
}
}
CheckBoxCell class
public class CheckBoxCell
extends TableCell<Series<Number, Number>, Series<Number, Number>> {
Logger LOG = LogManager.getLogger(CheckBoxCell.class);
private HBox checkBoxContainer;
private CheckBox checkBoxShow;
public CheckBoxCell() {
checkBoxContainer = new HBox();
checkBoxShow = new CheckBox("");
checkBoxContainer.setAlignment(Pos.CENTER);
checkBoxShow.setSelected(true);
checkBoxContainer.getChildren().add(checkBoxShow);
}
private void addListenerOnCheckBox(Series<Number, Number> series) {
checkBoxShow.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
LOG.debug("Show checkbox selected: "
+ checkBoxShow.isSelected());
series.getNode().setVisible(checkBoxShow.isSelected());
if (series.getNode().isVisible()) {
series.getNode().toFront();
for (Data<Number, Number> data : series.getData()) {
data.getNode().toFront();
}
} else {
series.getNode().toBack();
for (Data<Number, Number> data : series.getData()) {
data.getNode().toBack();
}
}
}
});
}
#Override
protected void updateItem(Series<Number, Number> series, boolean empty) {
super.updateItem(series, empty);
if (!empty && series != null) {
addListenerOnCheckBox(series);
LOG.debug("Series is not empty");
LOG.debug("Updating item for series with name: " + series.getName());
setGraphic(checkBoxContainer);
} else {
LOG.debug("Series is empty");
setGraphic(null);
}
}
}
The problem is that after adding some series on the chart and they show on the table and try to click on the check box one more check box behaves the same way as the one I am clicking that is It changes it's status from checked to unchecked according to the first one I click. Debugging shows that when code enters the handle method from the action handler the check box is the same object thus unchecking the one makes the other one unchecked. Have I missed something? Is there another way to use the check box and catch the change of checkbox status?
Two things I can see:
You should set up the listener on the check box once, from the
constructor. Use getItem() to get the current series being
displayed by the cell.
In updateItem you need to update the check box to match the state of whether that series is displayed.
So I think:
public class CheckBoxCell
extends TableCell<Series<Number, Number>, Series<Number, Number>> {
Logger LOG = LogManager.getLogger(CheckBoxCell.class);
private HBox checkBoxContainer;
private CheckBox checkBoxShow;
public CheckBoxCell() {
checkBoxContainer = new HBox();
checkBoxShow = new CheckBox("");
checkBoxContainer.setAlignment(Pos.CENTER);
checkBoxShow.setSelected(true);
checkBoxContainer.getChildren().add(checkBoxShow);
addListenerOnCheckBox();
}
private void addListenerOnCheckBox() {
checkBoxShow.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent arg0) {
Series<Number, Number> series = getItem();
if (series == null) {
return ;
}
LOG.debug("Show checkbox selected: "
+ checkBoxShow.isSelected());
series.getNode().setVisible(checkBoxShow.isSelected());
if (series.getNode().isVisible()) {
series.getNode().toFront();
for (Data<Number, Number> data : series.getData()) {
data.getNode().toFront();
}
} else {
series.getNode().toBack();
for (Data<Number, Number> data : series.getData()) {
data.getNode().toBack();
}
}
}
});
}
#Override
protected void updateItem(Series<Number, Number> series, boolean empty) {
super.updateItem(series, empty);
if (!empty && series != null) {
checkBoxShow.setSelected(series.getNode().isVisible());
LOG.debug("Series is not empty");
LOG.debug("Updating item for series with name: " + series.getName());
setGraphic(checkBoxContainer);
} else {
LOG.debug("Series is empty");
setGraphic(null);
}
}
}
Not tested, so there may be other things I have missed...

Update listview with custom adapter and database handler

I can't update listview with custom adapter and database handler.
Items are updated in the database handler, but listview is updated only when I leave the fragment and come back. I would like to see changes just after adding new item.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.shopping_list_fragment_layout, container, false);
super.onCreate(savedInstanceState);
//// adding an item to itemList
nameTxt = (EditText) view.findViewById(R.id.txtItem);
couponsAmount = 3;
dbHandler = new ShoppingListDatabaseHandler(getActivity());
itemListView = (ListView) view.findViewById(R.id.listViewShoppingItems);
final ShoppingListListAdapter itemAdapter = new ShoppingListListAdapter(getActivity(), ITEMS_DATA);
final ImageButton addBtn = (ImageButton) view.findViewById(R.id.btnAddItem);
addBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view){
ShoppingListItem item = new ShoppingListItem(dbHandler.getItemsCount(), String.valueOf(nameTxt.getText()), 3);
if (!itemExists(item)) {
dbHandler.createItem(item);
//getListView().invalidateViews();
itemAdapter.notifyDataSetChanged(); // TODO: is not updating
Toast.makeText(getActivity(), String.valueOf(nameTxt.getText()) + " has been added", Toast.LENGTH_SHORT).show();
nameTxt.setText("");
return;
}
Toast.makeText(getActivity(), String.valueOf(nameTxt.getText()) + " already exists", Toast.LENGTH_SHORT).show();
nameTxt.setText("");
}
});
if (dbHandler.getItemsCount() != 0){
ITEMS_DATA.addAll(dbHandler.getAllItems());
}
//itemListView.getListView().invalidateViews();
//itemAdapter.notifyDataSetChanged();
itemListView.setAdapter(itemAdapter);
return view;
}
and my adapter:
public class ShoppingListListAdapter extends BaseAdapter {
private Context context;
private final List<ShoppingListItem> Items; // = new ArrayList<ShoppingListItem>();
//Constructor to initialize values
public ShoppingListListAdapter(Context context, List<ShoppingListItem> Items) {
this.context = context;
this.Items = Items;
}
#Override
public int getCount() {
// Number of times getView method call depends upon
return Items.size();
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
// Number of times getView method call depends upon
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View gridView;
if (convertView == null) {
gridView = new View(context);
gridView = inflater.inflate(R.layout.shopping_list_list_item, null);
ShoppingListItem currentItem = Items.get(position);
TextView itemNameTxt = (TextView) gridView.findViewById(R.id.itemName);
itemNameTxt.setText(currentItem.getName());
TextView couponsAmount = (TextView) gridView.findViewById(R.id.couponsAmount);
couponsAmount.setText("-" + currentItem.getCouponsAmount().toString());
}
else {
gridView = (View) convertView;
}
return gridView;
}
}
I tried:
- getListView().invalidateViews();
- adapter.notifyDataSetChanged();
But probably I do sth wrongly, because it is not helping me.
Can you look at my code?
You have added the item to your database, but not to the list used to create the adapter.
ITEM_DATA.add(item):
itemAdapter.notifyDataSetChanged();

JavaFX ComboBox set Items from ObservableList

i need help to get a specified list of items from ObservableList and add them to a combo box.
My ObservableList contains value received from DB (in specific a table with just 3 columns) and i want to show only one column value in combo box. When combo box is selected the other values are charged in 2 textfield.
Code as follows.
ImportAccettazioniModel:
public ObservableList<Impostazioni> listImpostazioni = FXCollections.observableArrayList();
public static class Impostazioni {
private final StringProperty rowid;
private final StringProperty nome;
private final StringProperty operatore;
private final StringProperty delimitatore;
private Impostazioni(String Rowid, String Nome, String Operatore, String Delimitatore) {
this.rowid = new SimpleStringProperty(Rowid);
this.nome = new SimpleStringProperty(Nome);
this.operatore = new SimpleStringProperty(Operatore);
this.delimitatore = new SimpleStringProperty(Delimitatore);
}
public StringProperty rowidProperty() { return rowid; }
public StringProperty nomeProperty() { return nome; }
public StringProperty operatoreProperty() { return operatore; }
public StringProperty delimitatoreProperty() { return delimitatore; }
}
ImportAccettazioniController:
#FXML
private ComboBox<ImportAccettazioniModel.Impostazioni> comboCaricaNome;
// get data from model to popupate combobox
public final void getImpostazioniDataFields() {
comboCaricaNome.getItems().clear();
comboCaricaNome.setItems(model.listImpostazioni);
comboCaricaNome.setCellFactory(new Callback<ListView<Impostazioni>, ListCell<Impostazioni>>() {
#Override public ListCell<Impostazioni> call(ListView<Impostazioni> p) {
return new ListCell<Impostazioni>() {
#Override
protected void updateItem(Impostazioni t, boolean bln) {
super.updateItem(t, bln);
if(t != null){
setText(t.nomeProperty().toString().toUpperCase());
System.out.println("SET PROPERTY " + t.nomeProperty().toString());
} else {
setText(null);
}
}
};
}
});
}
comboCaricaNome.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<ImportAccettazioniModel.Impostazioni>() {
#Override public void changed(ObservableValue<? extends ImportAccettazioniModel.Impostazioni> observable,ImportAccettazioniModel.Impostazioni oldValue, ImportAccettazioniModel.Impostazioni newValue) {
setTextFields(newValue);
}
});
//set data to textfield with the selected combo box
private void setTextFields(Impostazioni listImpostazioni) {
//setRowid(Impostazioni.rowidProperty().getValue());
if (comboCaricaNome.getItems().isEmpty()) {
editCaricaOperatore.setText("");
editCaricaDelimitatore.setText("");
} else {
editCaricaOperatore.setText(listImpostazioni.operatoreProperty().getValue());
editCaricaDelimitatore.setText(listImpostazioni.delimitatoreProperty().getValue());
}
}
Now, the logic seems to work but my combo box doesn't contain the value nomeProperty().
How can i solve?
Thanks in advance
To get the observed value of the JavaFX Property use Property.get() or Property.getValue().
By convention, the Java developers override toString() method to not show message for customers, instead it is used for internal usages by another developers.
As a result, the line
setText(t.nomeProperty().toString().toUpperCase());
should be
setText(t.nomeProperty().getValue().toUpperCase());
UPDATE:
Since you are using the ComboBox rather than ChoiceBox, you should also override default button cell property of combobox, according to your needs:
comboCaricaNome.setButtonCell(new ListCell<Impostazioni>() {
#Override
protected void updateItem(Impostazioni t, boolean bln) {
super.updateItem(t, bln);
if (t != null) {
setText(t.nomeProperty().getValue().toUpperCase());
} else {
setText(null);
}
}
});
As you can see this is the same ListCell which is set for cellFactory. Refactoring is up to you, or of course you can implement a listcell with different content as well.

Resources