In my program have an TableView and I want to register two different row factories to format my rows based on the content.
Here are my factories:
private void highlightReportRowsIfImportant() {
tv_berichte.setRowFactory(new Callback<TableView<DatabaseReport>, TableRow<DatabaseReport>>() {
#Override
public TableRow<DatabaseReport> call(TableView<DatabaseReport> tableView) {
final TableRow<DatabaseReport> row = new TableRow<DatabaseReport>() {
#Override
protected void updateItem(DatabaseReport report, boolean empty) {
super.updateItem(report, empty);
if (report != null) {
if (report.getReport_art().contains("!!!")) {
setStyle("-fx-background-color: #FF0000;");
} else {
setStyle("");
}
} else {
setStyle("");
}
}
};
reports.addListener(new ListChangeListener<DatabaseReport>() {
#Override
public void onChanged(ListChangeListener.Change<? extends DatabaseReport> change) {
if (row.getItem() != null) {
if (row.getItem().getReport_art().contains("!!!")) {
row.setStyle("-fx-background-color: #FF0000;");
} else {
row.setStyle("");
}
} else {
row.setStyle("");
}
}
});
return row;
}
});
}
And here is the other one:
private void registerDragAndDropReportListener(TableView view) {
view.setRowFactory(tv -> {
TableRow<DatabaseReport> row = new TableRow<>();
row.setOnDragDetected(event -> {
if (!row.isEmpty()) {
Dragboard db = row.startDragAndDrop(TransferMode.COPY);
db.setDragView(row.snapshot(null, null));
ClipboardContent cc = new ClipboardContent();
cc.putString(row.getItem().getReport_content());
db.setContent(cc);
event.consume();
}
});
row.setOnDragOver(event -> {
Dragboard db = event.getDragboard();
if (db.hasContent(DataFormat.PLAIN_TEXT)) {
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
event.consume();
}
});
row.setOnDragDropped(event -> {
Dragboard db = event.getDragboard();
if (db.hasContent(DataFormat.PLAIN_TEXT)) {
reportFlowController.addNewReport(username, getSqlTimeStamp().toString(), this.txt_adressnummer.getText(), reports, db.getString());
event.setDropCompleted(true);
event.consume();
}
});
return row;
});
}
Now if I register the factories like this:
registerDragAndDropReportListener(tv_berichte);
highlightReportRowsIfImportant();
only the last one is used.
Could you please explain me, how I can register both of them?
You can't register multiple row factories, and it makes no sense to try to do so. The TableView uses its row factory to create a TableRow object whenever it needs one (typically when it is first laid out on the screen, and perhaps later if it grows vertically and needs to display more rows); remember the row factory is just a function that provides TableRow objects. If two or more row factories were registered, the TableView would have no mechanism for choosing which one to use.
What you can do is define a row factory which uses an existing row factory to generate its row. This effectively replaces the existing row factory with one that adds additional functionality to it:
private void registerDragAndDropReportListener(TableView<DatabaseReport> view) {
Callback<TableView<DatabaseReport>, TableRow<DatabaseReport>> existingRowFactory
= view.getRowFactory();
view.setRowFactory(tv -> {
TableRow<DatabaseReport> row ;
if (existingRowFactory == null) {
row = new TableRow<>();
} else {
row = existingRowFactory.call(view);
}
row.setOnDragDetected(event -> {
if (!row.isEmpty()) {
Dragboard db = row.startDragAndDrop(TransferMode.COPY);
db.setDragView(row.snapshot(null, null));
ClipboardContent cc = new ClipboardContent();
cc.putString(row.getItem().getReport_content());
db.setContent(cc);
event.consume();
}
});
row.setOnDragOver(event -> {
Dragboard db = event.getDragboard();
if (db.hasContent(DataFormat.PLAIN_TEXT)) {
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
event.consume();
}
});
row.setOnDragDropped(event -> {
Dragboard db = event.getDragboard();
if (db.hasContent(DataFormat.PLAIN_TEXT)) {
reportFlowController.addNewReport(username, getSqlTimeStamp().toString(), this.txt_adressnummer.getText(), reports, db.getString());
event.setDropCompleted(true);
event.consume();
}
});
return row;
});
}
With this, the registerDragAndDropReportListener method replaces the existing row factory with one that adds additional functionality to the existing row factory, which I think is what you want. So in this case you would first call your original highlightRowsIfImportant method, and then call the new registerDragAndDropReportListener method shown.
You could make similar changes to the other method, so that you could call the two methods in any order you want. Note that in this case, it would be important to ensure you only call each method once.
Here is my solution, using both listeners in one row factory with the same row object. After you postet your awnser James_D, it was clear for me. I cant register 2 of them, which should be handelt ... first think, then write code ...
private void highlightReportRowsIfImportant(TextField txt_adressnummer) {
tv_berichte.setRowFactory(new Callback<TableView<DatabaseReport>, TableRow<DatabaseReport>>() {
#Override
public TableRow<DatabaseReport> call(TableView<DatabaseReport> tableView) {
final TableRow<DatabaseReport> row = new TableRow<DatabaseReport>() {
#Override
protected void updateItem(DatabaseReport report, boolean empty) {
super.updateItem(report, empty);
if (report != null) {
if (report.getReport_art().contains("!!!")) {
setStyle("-fx-background-color: #FF0000;");
} else {
setStyle("");
}
} else {
setStyle("");
}
}
};
reports.addListener(new ListChangeListener<DatabaseReport>() {
#Override
public void onChanged(ListChangeListener.Change<? extends DatabaseReport> change) {
if (row.getItem() != null) {
if (row.getItem().getReport_art().contains("!!!")) {
row.setStyle("-fx-background-color: #FF0000;");
} else {
row.setStyle("");
}
} else {
row.setStyle("");
}
}
});
row.setOnDragDetected(event -> {
if (!row.isEmpty()) {
Dragboard db = row.startDragAndDrop(TransferMode.COPY);
db.setDragView(row.snapshot(null, null));
ClipboardContent cc = new ClipboardContent();
cc.putString(row.getItem().getReport_content());
db.setContent(cc);
event.consume();
}
});
row.setOnDragOver(event -> {
Dragboard db = event.getDragboard();
if (db.hasContent(DataFormat.PLAIN_TEXT)) {
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
event.consume();
}
});
row.setOnDragDropped(event -> {
Dragboard db = event.getDragboard();
if (db.hasContent(DataFormat.PLAIN_TEXT)) {
reportFlowController.addNewReport(username, getSqlTimeStamp().toString(), txt_adressnummer.getText(), reports, db.getString());
event.setDropCompleted(true);
event.consume();
}
});
return row;
}
});
}
Related
I wanted to add a column if the line wasn't empty. In this one I wanted to put buttons with different ImageView to be able to go to an add or view page.
I'm trying to change the icon of a button according to the content of my row in a TableView (here if dateR (string) is null)
I manage to move it to such and such a page depending on the dateR value, but I can't change the imageView...
Thanks
TableColumn<ModelTab4, Void> visualiserAjouter = new TableColumn<ModelTab4, Void>("Visualiser/Ajouter");
Callback<TableColumn<ModelTab4, Void>, TableCell<ModelTab4, Void>> cellFactory1 = (final TableColumn<ModelTab4, Void> param) -> {
final TableCell<ModelTab4, Void> cell = new TableCell<ModelTab4, Void>() {
private String dateR;
private final Button btn1 = new Button();
{
btn1.setOnAction(click -> {
try {
ModelTab4 analyse = getTableView().getItems().get(getIndex());
dateR = analyse.getDateR();
setDate(dateR);
idAnalyse = analyse.getNum1();
if (dateR!=null){
mainApp.goConsultResult(idPerso, idPatient, idAnalyse);
}
else {
mainApp.goAjoutResult(idPerso, idPatient, idAnalyse);
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
public void setDate(String date) {
this.dateR = date;
}
public String getDate() {
return dateR;
}
public void updateItem(Void item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
}
else {
if (getDate()!=null){
setGraphic(btn1);
ImageView icone1 = new ImageView("consult.png");
icone1.setFitHeight(20);
icone1.setFitWidth(20);
btn1.setGraphic(icone1);
setStyle("-fx-alignment : CENTER;");
}
else if (getDate()==null){
setGraphic(btn1);
ImageView icone2 = new ImageView("ajout.png");
icone2.setFitHeight(20);
icone2.setFitWidth(20);
btn1.setGraphic(icone2);
setStyle("-fx-alignment : CENTER;");
}
}
}
};
return cell;
};
Assuming your ModelTab4 class includes the following:
public class ModelTab4 {
private final StringProperty dateR = new SimpleStringProperty() ;
public StringProperty dateRProperty() {
return dateR ;
}
public final String getDateR() {
return dateRProperty().get();
}
public final void setDateR(String dateR) {
dateRProperty().set(dateR);
}
// other properties ...
}
you should make the value in the visualiserAjouter column the dateR property:
TableColumn<ModelTab4, String> visualiserAjouter = new TableColumn<>("Visualiser/Ajouter");
visualiserAjouter.setCellValueFactory(cellData -> cellData.getValue().dateRProperty());
Now the item in the cell implementation is the dateR property. The issue with the code the way you had it, is that the only way the property changes is after the button is pressed, and there is no notification back to the cell that the value has changed. So in updateItem() you're checking the value of dateR before is has had a chance to change. With the setup above, you can now do:
final TableCell<ModelTab4, Void> cell = new TableCell<ModelTab4, Void>() {
private final Button btn1 = new Button();
{
btn1.setOnAction(click -> {
try {
ModelTab4 analyse = getTableView().getItems().get(getIndex());
idAnalyse = analyse.getNum1();
if (getItem()!=null){
mainApp.goConsultResult(idPerso, idPatient, idAnalyse);
}
else {
mainApp.goAjoutResult(idPerso, idPatient, idAnalyse);
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
public void updateItem(String dateR, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
}
else {
if (dateR!=null){
setGraphic(btn1);
ImageView icone1 = new ImageView("consult.png");
icone1.setFitHeight(20);
icone1.setFitWidth(20);
btn1.setGraphic(icone1);
setStyle("-fx-alignment : CENTER;");
}
else if (dateR==null){
setGraphic(btn1);
ImageView icone2 = new ImageView("ajout.png");
icone2.setFitHeight(20);
icone2.setFitWidth(20);
btn1.setGraphic(icone2);
setStyle("-fx-alignment : CENTER;");
}
}
}
};
I am using a JavaFX tableview that has 5 columns (Data Type of Column): Task Id (int), Task Name (String), Task Description (String), Status (String) and Remark (String). Now, I am loading the data from a database that has the same columns and what I'm trying to do is when the user clicks on the task status column in the software the column cell changes to a Combobox (String) but the list of statuses that are displayed in the Combobox should be different depending on the selected item that the user is trying to edit (aka the status of the task)
I have tried creating a new cellFactory in the OnEdit of the column and I have also tried to override the update item method and using a boolean variable I would set whether to set the graphic to a Combobox or not
myStatusColumn.setOnEditStart(new EventHandler<TableColumn.CellEditEvent<Task, String>>() {
#Override
public void handle(TableColumn.CellEditEvent<Task, String> event) {
try {
dataBeingEdited = true;
Task task = myTasksTable.getSelectionModel().getSelectedItem();
statuses = Login.dbHandler.getStatuses(task.getTaskTypeID());
myStatusColumn.setCellFactory(new Callback<TableColumn<Task, String>, TableCell<Task, String>>() {
#Override
public TableCell<Task, String> call(TableColumn<Task, String> param) {
return new TableCell<Task,String>(){
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if(empty)
setGraphic(null);
else
{
if(dataBeingEdited)
{
ComboBox<String> comboBox = new ComboBox<>(statuses);
setGraphic(comboBox);
}
}
}
};
}
});
} catch (SQLException e) {
e.printStackTrace();
}
}
});
I expect the output to be a Combobox when I double click on the status column but I am not getting a Combobox to appear and when I get
so far I have not found a way to directly use the ComboBoxTableCell class but what I did is redesigned my own using the CellFactory. What I did was before returning the Cell created in the CellFactory I set an onMouseClickedListner that will check for a double click and when the user double clicks I would get the selected item from the table and set the graphic to be a Combobox with values that depend on the row selected and the column clicked
and then I set an onKeyPressedListener that will change the item selected and then refresh the table and update the database
myStatusColumn.setCellFactory(new Callback<TableColumn<Task, String>, TableCell<Task, String>>() {
#Override
public TableCell<Task, String> call(TableColumn<Task, String> param) {
ComboBox<String> box = new ComboBox<>();
TableCell<Task, String> cell = new TableCell<Task, String>() {
#Override
protected void updateItem(String item, boolean empty) {
if (empty)
setGraphic(null);
else {
setEditable(false);
setText(item);
}
}
};
cell.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if(event.getButton().equals(MouseButton.PRIMARY))
{
Task task = myTasksTable.getSelectionModel().getSelectedItem();
if(task!=null)
box.setItems(FXCollections.observableArrayList(task.getStatuses()));
cell.setEditable(true);
}
if(event.getClickCount()==2 && cell.isEditable() ) {
box.getSelectionModel().select(0);
cell.setText(null);
cell.setGraphic(box);
}
}
});
cell.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if(event.getCode().equals(KeyCode.ENTER))
{
try {
TaskLog taskLog = (TaskLog) myTasksTable.getSelectionModel().getSelectedItem();
if(taskLog != null) {
taskLog.setStatues(box.getSelectionModel().getSelectedItem());
taskLog.setStatuesID(Login.dbHandler.getStatusID(taskLog.getStatues()));
System.out.println(taskLog.getStatues());
Login.dbHandler.addNewTaskLog(taskLog);
cell.setEditable(false);
myTasksTable.refresh();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
});
return cell;
}
});
I am building GWT app where I have Tree and TreeItems with CheckBoxes. I have one root CheckBox called allCheckBox and his child elements rootCheckBox(this checkBoxes also have theirs children but that is not matter for this). I want that, when user opens dialog with checkBoxes, this checkBox is selected if every childCheckBox is selected. I have done that when tihs root checkBox is selected that child checkBoxes also are selected.
This is my piece of code:
enter cod GWT_DOMAIN_SERVICE.findAll(new AsyncCallback<List<GwtDomain>>() {
#Override
public void onFailure(Throwable caught) {
exitMessage = MSGS.dialogAddPermissionErrorDomains(caught.getLocalizedMessage());
exitStatus = false;
hide();
}
#Override
public void onSuccess(List<GwtDomain> result) {
for (final GwtDomain gwtDomain : result) {
GWT_DOMAIN_SERVICE.findActionsByDomainName(gwtDomain.name(), new AsyncCallback<List<GwtAction>>() {
#Override
public void onFailure(Throwable caught) {
exitMessage = MSGS.dialogAddPermissionErrorActions(caught.getLocalizedMessage());
exitStatus = false;
hide();
}
#Override
public void onSuccess(List<GwtAction> result) {
checkedItems = new GwtCheckedItems();
checkedItems.setName(gwtDomain);
rootCheckBox = new CheckBox();
rootCheckBox.setBoxLabel(gwtDomain.toString());
listCheckBoxes.add(rootCheckBox);
rootTreeItem = new TreeItem(rootCheckBox);
childCheckBoxMapList = new HashMap<GwtAction, CheckBox>();
checkedItems.setMap(childCheckBoxMapList);
for (GwtAccessPermission gwtAccessPermission : checkedPermissionsList) {
if (gwtAccessPermission.getPermissionDomain().toString().equals(checkedItems.getName().toString())) {
if (gwtAccessPermission.getPermissionAction().toString().equals(GwtAction.ALL.toString())) {
rootCheckBox.setValue(true);
}
}
}
if (listOfNewClass.size() == checkedPermissionsList.size()) {
allCheckBox.setValue(true);
}
for (final GwtAction gwtAction : result) {
final CheckBox childTreeItemCheckox = new CheckBox();
treeItem = new TreeItem(childTreeItemCheckox);
childTreeItemCheckox.setBoxLabel(gwtAction.toString());
rootTreeItem.addItem(treeItem);
childListOfNewClass.add(gwtAction);
allTreeItem.addItem(rootTreeItem);
childCheckBoxMapList.put(gwtAction, childTreeItemCheckox);
for (GwtAccessPermission gwtAccessPermission : checkedPermissionsList) {
if (gwtAccessPermission.getPermissionDomain().toString().equals(gwtDomain.toString())) {
if (gwtAccessPermission.getPermissionAction().toString().equals(gwtAction.toString())) {
childTreeItemCheckox.setValue(true);
}
}
}
}
listOfNewClass.put(checkedItems, rootCheckBox);
}
});
}
allCheckBox.addListener(Events.OnClick, new Listener<BaseEvent>() {
#Override
public void handleEvent(BaseEvent be) {
if (allCheckBox.getValue()) {
for (CheckBox checkBox : listCheckBoxes) {
if (!checkBox.getValue()) {
checkBox.setValue(true);
}
}
} else {
for (CheckBox checkBox : listCheckBoxes) {
checkBox.setValue(false);
}
}
}
});
How to set that when all rootCheckBoxes are checked then allCheckBox become also checked?
EDIT: This checkedPermissionsList is List of rootCheckBox which are checked.
Well the listener would have to iterate the child boxes and see if they are all selected, and set the parent box accordingly
Listener<BaseEvent> listener = new Listener<>() {
public void handleEvent(BaseEvent be) {
boolean allSet = listCheckBoxes.stream().allMatch(CheckBox::getValue);
allCheckBox.setValue(allSet); // this will also unselect if appropriate
}
}
It's the same listener for all boxes, so add it to each
listCheckBoxes.forEach(box -> box.addListener(Event.OnClick, listener));
In the pre-Java 8 version:
Listener<BaseEvent> listener = new Listener<>() {
public void handleEvent(BaseEvent be) {
boolean allSet = true;
for (CheckBox child : listCheckBoxes) {
if (!child.getValue()) {
allSet = false; // found a non-checked box
break;
}
}
allCheckBox.setValue(allSet); // this will also unselect if appropriate
}
// and set the listener to the children with
for (CheckBox box : listCheckBoxes) {
box.addListener(Event.Clicked, listener);
}
I made a Custom ListView Cell so i can add a Context Menu but it keeps opening multiple context menus when you click more than once and the old ones just raise Exceptions when used.
Here is my SongCell class
public SongCell(ListView<Song> list, Playlist playlist) {
setAlignment(Pos.CENTER_LEFT);
ContextMenu listContextMenu = new ContextMenu();
MenuItem removeItem = new MenuItem("Remove");
MenuItem editID3Item = new MenuItem("Edit ID3");
MenuItem playNextItem = new MenuItem("Play Next");
removeItem.setOnAction((ActionEvent event) -> {
list.getItems().remove(getIndex());
});
editID3Item.setOnAction((ActionEvent event) -> {
Optional<Pair<String, String>> show = new FXID3Edit(getItem()).show();
});
playNextItem.setOnAction((ActionEvent event) -> {
Song song = getItem();
list.getItems().remove(song);
playlist.addSongRequest(new SongRequest(song, SongRequest.type.NEXT));
});
listContextMenu.getItems().add(removeItem);
listContextMenu.getItems().add(playNextItem);
listContextMenu.getItems().add(editID3Item);
setOnMousePressed(event -> {
if (event.getButton().equals(MouseButton.SECONDARY)) {
if (getItem() != null) {
if (getItem().equals(playlist.getSong())) {
playNextItem.setDisable(true);
removeItem.setDisable(true);
} else {
playNextItem.setDisable(false);
removeItem.setDisable(false);
}
listContextMenu.show(list, event.getScreenX(), event.getScreenY());
}
}
if (event.getButton().equals(MouseButton.PRIMARY) && event.getClickCount() == 2) {
playlist.setIndex(this.getIndex());
playlist.play();
}
event.consume();
});
}
#Override
protected void updateItem(Song item, boolean empty) {
super.updateItem(item, empty);
if (!empty && item != null) {
this.setText(item.toString());
} else {
this.setText("");
}
}
I also greatly appreciate help on my Coding Style
Instead of registering a mouse listener to show the context menu, use the built-in setContextMenu(...) property. I.e.:
public SongCell(ListView<Song> list, Playlist playlist) {
// ...
setOnMousePressed(event -> {
if (event.getButton().equals(MouseButton.PRIMARY) && event.getClickCount() == 2) {
playlist.setIndex(this.getIndex());
playlist.play();
}
event.consume();
});
}
#Override
public void updateItem(Song item, boolean empty) {
super.updateItem(item, empty);
if (!empty && item != null) {
this.setText(item.toString());
this.setContextMenu(listContextMenu);
} else {
this.setText("");
this.setContextMenu(null);
}
}
I am using mutliselectlistpreference that extends DialogPreference in my application. And i have not use any adapter for building the UI. Please find the below image.
The issue here is I am able to persist the CheckBox checked for Monday and Tuesday but i am not able to make the items read only wherein user will not able to unchecked the items. I want to make both items grey out. could you please help me out on this ?
#Override
protected void onPrepareDialogBuilder(Builder builder) {
super.onPrepareDialogBuilder(builder);
if (entries == null || entryValues == null) {
throw new IllegalStateException(
"MultiSelectListPreference requires an entries array and an entryValues array.");
}
checked = new boolean[entryValues.length];
List<CharSequence> entryValuesList = Arrays.asList(entryValues);
List<CharSequence> entriesList = Arrays.asList(entries);
for (int i = 0; i < entryValues.length; ++i) {
if("Monday".equals(entriesList.get(i).toString())){
checked[i]=true;
}
}
if (values != null) {
for (String value : values) {
int index = entryValuesList.indexOf(value);
if (index != -1) {
checked[index] = true;
}
}
}
builder.setMultiChoiceItems(entries, checked,
new OnMultiChoiceClickListener() {
public void onClick(DialogInterface dialog, int which,
boolean isChecked) {
checked[which] = isChecked;
}
});
}
#Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
if (positiveResult && entryValues != null) {
for (int i = 0; i < entryValues.length; ++i) {
if (checked[i]) {
newValues.add(entryValues[i].toString());
}
}
if (callChangeListener(newValues)) {
setValues(newValues);
}
}
}
#Override
protected Object onGetDefaultValue(TypedArray a, int index) {
CharSequence[] array = a.getTextArray(index);
Set<String> set = new HashSet<String>();
for (CharSequence item : array) {
set.add(item.toString());
}
return set;
}
#Override
protected void onSetInitialValue(boolean restorePersistedValue,
Object defaultValue) {
#SuppressWarnings("unchecked")
Set<String> defaultValues = (Set<String>) defaultValue;
setValues((restorePersistedValue ? getPersistedStringSet(values)
: defaultValues));
}
private Set<String> getPersistedStringSet(Set<String> defaultReturnValue) {
String key = getKey();
//String value = getSharedPreferences().getString("4", "Generic");
return getSharedPreferences().getStringSet(key, defaultReturnValue);
}
private boolean persistStringSet(Set<String> values) {
if (shouldPersist()) {
// Shouldn't store null
if (values == getPersistedStringSet(null)) {
return true;
}
}
SharedPreferences.Editor editor = getEditor();
editor.putStringSet(getKey(), values);
editor.apply();
return true;
}
#Override
protected Parcelable onSaveInstanceState() {
if (isPersistent()) {
return super.onSaveInstanceState();
} else {
throw new IllegalStateException("Must always be persistent");
}
}
You can use them;
checkBox.setEnabled(true); // enable checkbox
checkBox.setEnabled(false); // disable checkbox
Also you can disable them after check the checkBox with this code:
checkBox.setOnCheckedChangeListener(new OnCheckedChangeListener(){
#Override
public void onCheckedChanged(CompoundButton arg0, boolean isChecked) {
if (isChecked){
checkBox.setEnabled(false); // disable checkbox
}
}
});