JavaFX Iterate through TableView cells (row wise) and set background color - tableview

I have 31 columns(day 1-day 31) and 12(monthnames) rows on my tableview. I want to search complete table (row wise search) for a data(day) and need to set a background color only for that matching cell.I could see that it is possible to set background color for a cell in a particular column using cellfactory function.
birthdayColumn.setCellFactory(column -> {
return new TableCell<Person, LocalDate>() {
#Override
protected void updateItem(LocalDate item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
setStyle("");
}
// Style all dates in March with a different color.
if (item. == Month.MARCH) {
setTextFill(Color.CHOCOLATE);
setStyle("-fx-background-color: yellow");
} else {
setTextFill(Color.BLACK);
setStyle("");
}
};
});
Here it checking only one column named 'birthdayColumn'.But in my case it should check all the table cells(rowwise) in one single method and need to set a background color only for that matching cell.How i can do that.Thank you in advance

This should work:
LocalDate myBirthday = null; //initialize with the date you need to highlight
birthdayColumn.setCellFactory(column -> {
return new TableCell<Person, LocalDate>() {
#Override
protected void updateItem(LocalDate item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
setGraphic(null);
}
else {
setText(item.toString());
setGraphic(null);
if (item.equals(myBirthday)) {
setTextFill(Color.CHOCOLATE);
setStyle("-fx-background-color: yellow");
}
else {
setTextFill(Color.BLACK);
setStyle("");
}
}
}};
});

Related

Handling ScrollEvent on TableView takes too much of the CPU Time

I have a TableView named tableVerre and I want to have every row of it checked for a criteria ( stock column value ) and execute some code on them as I scroll so I wrote this code but it makes the program consume a lot of CPU time, I'm not familiar with Lambda expressions so is there a simpler way to write this ? :
tableVerre.addEventFilter(ScrollEvent.ANY, new EventHandler<ScrollEvent>() {
#Override
public void handle(ScrollEvent scrollEvent) {
stock.setCellFactory(column -> {
return new TableCell<VerreFX, Number>() {
#Override
protected void updateItem(Number item, boolean empty) {
super.updateItem(item, empty);
TableRow<VerreFX> currentRow = getTableRow();
if (empty || item == null) {
setText("");
setGraphic(null);
currentRow.setStyle(tableVerre.getStyle());
} else {
setText(getItem().toString());
}
if (!isEmpty()) {
if ((int) item == 0 && st.getVerresBOX()) currentRow.setStyle("-fx-background-color:lightcoral");
}
}
};
});
}
});
The table view will reuse cells as the user scrolls, and will automatically call updateItem on the cells when they are reused for new items. So you should set the cell factory only once, and then just let the table view take care of doing the job it is designed to do. You can set the cell factory in the initialize() method if you are using FXML, or just wherever you create the table and columns otherwise.
Your cell implementation isn't quite correct: because a cell may be reused to display any two different items arbitrarily, you need to account for all possible conditions. In your implementation, if a cell shows an item for which item.intValue()==0 and is then reused to show an item for which item.intValue() != 0, then the style will not be updated correctly.
Also note that you should "convert" a Number to an int by calling intValue().
TableColumn<VerreFX, Number> stock ;
// ...
stock.setCellFactory(column -> new TableCell<VerreFX, Number>() {
#Override
protected void updateItem(Number item, boolean empty) {
super.updateItem(item, empty);
TableRow<VerreFX> currentRow = getTableRow();
if (empty || item == null) {
setText("");
setGraphic(null);
currentRow.setStyle(tableVerre.getStyle());
} else {
setText(getItem().toString());
}
if (!isEmpty()) {
if (item.intValue() == 0 && st.getVerresBOX()) {
currentRow.setStyle("-fx-background-color:lightcoral");
} else {
currentRow.setStyle(tableVerre.getStyle());
}
}
}
});
You should be able to remove the scroll event handler entirely.
Firstly beside over CPU time using you didn't cover all the scrolling situations because if the user scrolls using the key Down/Up or using the scroll bar the scroll event will not be fired. So you have to add two more EventFilter, the first will handle the Scrolling using the Scrollbar.
tableVerre.addEventFilter(MouseEvent.MOUSE_CLICKED,(
MouseEvent event)->
{
if ((event.getTarget() instanceof TableColumnHeader) | event.isDragDetect()) {
System.err.println("Mouse Draged : " + event.toString());
stock.setCellFactory((TableColumn<VerreFX, Number> column) -> {
return new TableCell<VerreFX, Number>() {
#Override
protected void updateItem(Number item, boolean empty) {
super.updateItem(item, empty);
TableRow<VerreFX> currentRow = getTableRow();
if (empty || item == null) {
setText("");
setGraphic(null);
currentRow.setStyle(tableVerre.getStyle());
} else {
setText(getItem().toString());
}
if (!isEmpty()) {
if ((int) item == 0 && st.getVerresBOX()) {
currentRow.setStyle("-fx-background-color:lightcoral");
}
}
}
};
});
}
});
And the second one will handle the scrolling using the keyboard Key DOWN/UP.
tableVerre.addEventFilter(KeyEvent.KEY_PRESSED,new EventHandler<KeyEvent>(){
#Override
public void handle(KeyEvent event) {
if (event.getCode() == KeyCode.DOWN | event.getCode() == KeyCode.UP) {
stock.setCellFactory(column -> {
return new TableCell<VerreFX, Number>() {
#Override
protected void updateItem(Number item, boolean empty) {
super.updateItem(item, empty);
TableRow<VerreFX> currentRow = getTableRow();
if (empty || item == null) {
setText("");
setGraphic(null);
currentRow.setStyle(tableVerre.getStyle());
} else {
setText(getItem().toString());
}
if (!isEmpty()) {
if ((int) item == 0 && st.getVerresBOX()) {
currentRow.setStyle("-fx-background-color:lightcoral");
}
}
}
};
});
}
System.err.println("Key Pressed : " + event.toString());
}
});

How to use a modal pop up to update data in a grid with a custom cell

I would like to be able to use a modal window to present the contents of a column to the user for editing. I am not able to make this work, and I am not sure where I am going wrong.
I have provided a button in the table which will indicate if there are additional details (in this case comments). When the user selects the button, I want to open a modal dialog to enter the data and when it closes, update the field.
I have gotten the majority of this wired up, but the data is not making it back to my model. I have tried several things, and all without results. It appears that the commit edit call I am making is not seeing the field as in "edit mode" and just skips.
This is my code for my custom table cell:
public class CommentTableCell<T> extends TableCell<T, String> {
private Button actionBtn;
private TextArea textArea;
public CommentTableCell(TableColumn<T, String> column) {
super();
actionBtn = new Button("my action");
actionBtn.setTooltip(new Tooltip("Select to add/edit comments..."));
actionBtn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
startEdit();
System.out.println("Action: "+getItem());
Stage commentStage = new Stage();
AnchorPane ap = new AnchorPane();
textArea = new TextArea();
AnchorPane.setTopAnchor(textArea, 5.0);
AnchorPane.setBottomAnchor(textArea, 5.0);
AnchorPane.setLeftAnchor(textArea, 5.0);
AnchorPane.setRightAnchor(textArea, 5.0);
ap.getChildren().add(textArea);
Scene commentScene = new Scene (ap, 200, 200);
commentStage.setScene(commentScene);
commentStage.show();
commentStage.setOnCloseRequest(a -> {
commitEdit(textArea.getText());
});
// I have tried with an column.setOnEditCommit() as well as what is noted below which I found here, passing in the column.
final TableView<T> tableView = getTableView();
tableView.getSelectionModel().select(getTableRow().getIndex());
tableView.edit(tableView.getSelectionModel().getSelectedIndex(), column);
}
});
setText(null);
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
actionBtn.getStyleClass().clear();
setEditable(false);
if (item != null && item.length() > 0) {
actionBtn.getStyleClass().add(CSSConstants.GRID_BUTTON_EDIT_COMMNET);
setGraphic(actionBtn);
} else if (!empty) {
actionBtn.getStyleClass().add(CSSConstants.GRID_BUTTON_ADD_COMMNET);
setGraphic(actionBtn);
} else {
setGraphic(null);
}
}
}
During the execution it hits the commitEdit() call and the following has isEditing in the TableCell as null:
#Override public void commitEdit(T newValue) {
if (! isEditing()) return;
My table looks basically like this:
TableView<SomeDTO> addressTableView = new TableView()
addressTableView.setItems(sortedItems);
addressTableView.setEditable(true);
commentsColumn.setCellValueFactory(cellValue -> cellValue.getValue().commentsProperty());
commentsColumn.setCellFactory(tc -> new CommentTableCell<SomeDTO>(commentsColumn));
I have found a solution to my issue - though I am not sure it is the best way or not.
I have changed my CommentTableCell as follows and it seems to work like a charm..
public class CommentTableCell<T> extends TableCell<T, String> {
private Button actionBtn;
public CommentTableCell() {
super();
actionBtn = new Button("my action");
actionBtn.setTooltip(new Tooltip("Select to add/edit comments..."));
actionBtn.setOnAction(event ->
{
Stage commentStage = new Stage();
AnchorPane ap = new AnchorPane();
TextArea textArea = new TextArea();
AnchorPane.setTopAnchor(textArea, 5.0);
AnchorPane.setBottomAnchor(textArea, 5.0);
AnchorPane.setLeftAnchor(textArea, 5.0);
AnchorPane.setRightAnchor(textArea, 5.0);
ap.getChildren().add(textArea);
Scene commentScene = new Scene (ap, 200, 200);
commentStage.setScene(commentScene);
if(getItem() != null) {
String myValue = getItem();
textArea.setText(myValue);
textArea.selectAll();
}
commentStage.show();
commentStage.setOnCloseRequest(a -> {
commitEdit(textArea.getText());
});
});
}
#Override
#SuppressWarnings({"unchecked", "rawtypes"})
public void commitEdit(String item) {
if (isEditing()) {
super.commitEdit(item);
} else {
final TableView table = getTableView();
if (table != null) {
TablePosition position = new TablePosition(getTableView(),
getTableRow().getIndex(), getTableColumn());
CellEditEvent editEvent = new CellEditEvent(table, position,
TableColumn.editCommitEvent(), item);
Event.fireEvent(getTableColumn(), editEvent);
}
updateItem(item, false);
if (table != null) {
table.edit(-1, null);
}
}
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
actionBtn.getStyleClass().clear();
setEditable(false);
if (item != null && item.length() > 0) {
actionBtn.getStyleClass().add(CSSConstants.GRID_BUTTON_EDIT_COMMNET);
setGraphic(actionBtn);
} else if (!empty) {
actionBtn.getStyleClass().add(CSSConstants.GRID_BUTTON_ADD_COMMNET);
setGraphic(actionBtn);
} else {
setGraphic(null);
}
}
}

JTextArea Do not scrollRectToVisible after Re-set Text

I encounter a strange issue.
The scenario is that I need to replace a keyword in JTextArea with another word.
I have two buttons, one is to find the keyword, and the other is to replace the keyword.
For both buttons, I add the mouse listener and implement function mouseClicked. At current stage I can highlight the found keyword and scroll to the keyword position. But when dealing with replace button, after re-setting text, the JTextArea always scrolls down to the bottom, but I want to keep the position where the replacement happens, what should I do? Below is my code snippet, but it doesn't work.
replaceBtn.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
String keyword = jKeyword.getText();
if (keyword.length() == 0)
return;
String text = jTextArea.getText();
pos = text.indexOf(keyword, 0);
if (pos == -1) {
pos = 0;
JOptionPane.showMessageDialog(null, "can not find " + keyword);
return;
}
jTextArea.setText(text.replaceFirst(keyword, jReplaceKW.getText()));
//jTextArea.revalidate();
//scroll to first keyword occurrence
try {
Rectangle rectangle = jTextArea.modelToView(pos);
jTextArea.scrollRectToVisible(rectangle);
} catch (BadLocationException e1) {
e1.printStackTrace();
}
}
});
I found a workaround: set text in function mousePressed then scroll in function mouseReleased.
I suspect the text has to be represented in the GUI before the component can figure out the scroll dimension? Not sure.
replaceBtn.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
String keyword = jKeyword.getText();
if (keyword.length() == 0)
return;
String text = jTextArea.getText();
pos = text.indexOf(keyword, 0);
if (pos == -1) {
pos = 0;
JOptionPane.showMessageDialog(null, "can not find " + keyword);
return;
}
jTextArea.setText(text.replaceFirst(keyword, jReplaceKW.getText()));
}
#Override
public void mouseReleased(MouseEvent e) {
//scroll to first keyword occurrence
try {
Rectangle rectangle = jTextArea.modelToView(pos);
jTextArea.scrollRectToVisible(rectangle);
} catch (BadLocationException e1) {
e1.printStackTrace();
}
}
});

TableView, managing look of particular cells

I got a method which evaluates whats in the string with what was set by user in TableView cells. (string have values like "343288709789" and each cell contains null or single digit number).
It works, however now I would like TableView to highlight(change background or text color) certain cells where user set wrong value. How can I achive this?
PS. Ive read similiar questions to this but I dont think I can achieve this in TableCell class implementation, because cells should change color only after uses press "Check" option.
private void compareAndEvaluate(String source, NewTableView newTableView){
ObservableList<MyData> data = newTableView.getData();
source = source.replaceAll("\\D+","");
System.out.println("data size: " +data.size() + "\n\n" + source);
int numOfValid = 0,
numOfInvalid = 0;
ObservableList<ObjectProperty<Integer>> rowData;
for(int i=0, n=0; i < data.size(); i++){ //rows(Y)
rowData = data.get(i).returnCellsData();
for(int j = 1; j < rowData.size(); ++j, ++n){ //columns(X)
Integer iNext = Integer.valueOf(String.valueOf(source.charAt(n)));
if( iNext == rowData.get(j).get() )
++numOfValid;
else
++numOfInvalid;
}
}
Dialogs.create().title("Results").masthead(null).message("Correct: " + numOfValid + ", Invalid: " + numOfInvalid).showInformation();
}
If that helps, here is implementation of TableCell used by TableView:
public class EditingCellNumbers extends TableCell<MyData, Integer>{
private TextField textField;
private TableView<MyData> parentTableView;
public EditingCellNumbers(TableView<MyData> parent) {
this.parentTableView = parent;
}
#Override
public void startEdit(){
if (!isEmpty()) {
super.startEdit();
createTextField();
setText(null);
setGraphic(textField);
textField.selectAll();
textField.requestFocus();
}
}
#Override
public void cancelEdit() {
super.cancelEdit();
if(getItem() != null){
setText(String.valueOf(getItem()));
}else{
setText(null);
commitEdit(null);
}
setGraphic(null);
}
#Override
public void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
if (textField != null) {
textField.setText(getString());
}
setText(null);
setGraphic(textField);
} else {
setText(getString());
setGraphic(null);
if(getTableColumn().getText() == "#"){
setStyle("-fx-font-weight: bold;"
+ "-fx-background-color: linear-gradient( from -100.0% 150.0% to 120.0% 100.0%, rgb(128,128,128) 0.0, rgb(255,255,255) 100.0);");
}else{
if(getItem() == null)
setStyle("-fx-border-color: lavender; -fx-border-width: 0 1 0 0;");
else
setStyle("-fx-border-color: palegreen; -fx-border-width: 0 1 1 0;");
}
}
}
}
private void createTextField() {
textField = new TextField(getString());
textField.setStyle("-fx-background-color: ivory; -fx-border-color: red;");
textField.setMinWidth(this.getWidth() - this.getGraphicTextGap()* 2);
textField.focusedProperty().addListener(
(ObservableValue<? extends Boolean> arg0, Boolean arg1, Boolean arg2) -> {
if (!arg2) {
if(getItem() != null){
try{
commitEdit(Integer.valueOf(textField.getText()));
}catch(NumberFormatException f){
commitEdit(null);
}
}else
commitEdit(null);
}
});
textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if(event.getCode() == KeyCode.BACK_SPACE){
if(getItem() != null){
numberOfEmptyCells.set(numberOfEmptyCells.get() + 1);
numberOfFilledCells.set(numberOfFilledCells.get() - 1);
}
commitEdit(null);
}else{
try{
int i = Integer.valueOf(textField.getText());
//digit given...
if( (i>=0) && (i<10) ){//making sure cell is filled with just one digit
if(getItem() == null){
numberOfEmptyCells.set(numberOfEmptyCells.get() - 1);
numberOfFilledCells.set(numberOfFilledCells.get() + 1);
}
commitEdit(Integer.valueOf(textField.getText()));
int selectedColumn = parentTableView.getSelectionModel().getSelectedCells().get(0).getColumn(); // gets the number of selected column
int selectedRow = parentTableView.getSelectionModel().getSelectedCells().get(0).getRow();
//moving to another cell editing
if(selectedColumn < numberOfColumns-1){
parentTableView.getSelectionModel().selectNext();
parentTableView.edit(selectedRow, parentTableView.getColumns().get(selectedColumn+1));
}else{
parentTableView.getSelectionModel().select(selectedRow+1, parentTableView.getColumns().get(1));
parentTableView.edit(selectedRow+1, parentTableView.getColumns().get(1));
}
}else
textField.clear();
}catch(NumberFormatException e){
textField.clear();
}
}
}
});
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
}
}
Instead of making your columns in your data model Integer, make them some kind of an object that stores both the integer and the evaluation result. Use the evaluation result to determine the colour of the cell in your customized TableCell.

GWT - setText and setValue not working for TextBox on initial load

I have a couple of text-boxes on a page where the user can enter some numeric values; however, try as I might, I can't fill those text-boxes with default values - specifically I would like 0.0 displayed in both upon page load.
Here is how I create them and what I have tried -
GroupSection engineering_group = new GroupSection();
KSTextBox engrDesignTextBox = new KSTextBox();
engrDesignTextBox.setWidth("2.875em");
//engrDesignTextBox.setWatermarkText("0.0"); ==> this works, but not what I need
//engrDesignTextBox.setText("0.0"); ==> this doesn't work
engrDesignTextBox.setValue("0.0"); // doesn't work either
KSTextBox engrScienceTextBox = new KSTextBox();
engrScienceTextBox.setWidth("2.875em");
//engrScienceTextBox.setWatermarkText("0.0"); ==> this works, but not what I need
//engrScienceTextBox.setText("0.0"); ==> this doesn't work
engrScienceTextBox.setValue("0.0"); // doesn't work either
I'm thinking that I need to attach an "onload" event listener and then try the setText in there? That seems overkill for something that should be rather simple.
Incidentally, I have attached onBlurHandlers for both these text boxes and they work as expected (see code below)
The following code will simply insert0.0 if the user clicks or tabs out of the text-box while it is EMPTY.
engrDesignTextBox.addBlurHandler(new BlurHandler() {
#Override
public void onBlur(BlurEvent blurEvent) {
if(((KSTextBox)blurEvent.getSource()).getText().length() < 1) {
((KSTextBox)blurEvent.getSource()).setText("0.0");
}
}
});
engrScienceTextBox.addBlurHandler(new BlurHandler() {
#Override
public void onBlur(BlurEvent blurEvent) {
if(((KSTextBox)blurEvent.getSource()).getText().length() < 1) {
((KSTextBox)blurEvent.getSource()).setText("0.0");
}
}
});
EDIT : As requested here is how I have defined the setText and setValue methods in KSTextBox
public class KSTextBox extends TextBox implements HasWatermark {
.
.
.
#Override
public void setText(String text) {
String oldValue = super.getText();
if(hasWatermark) {
if(text == null || (text != null && text.isEmpty())){
super.setText(watermarkText);
addStyleName("watermark-text");
watermarkShowing = true;
}
else{
super.setText(text);
removeStyleName("watermark-text");
watermarkShowing = false;
}
}
else{
super.setText(text);
}
ValueChangeEvent.fireIfNotEqual(this, oldValue, text);
}
#Override
public void setValue(String value) {
if(hasWatermark) {
if(value == null || (value != null && value.isEmpty())){
super.setValue(watermarkText);
addStyleName("watermark-text");
watermarkShowing = true;
}
else{
super.setValue(value);
removeStyleName("watermark-text");
watermarkShowing = false;
}
}
else{
super.setValue(value);
}
}
So, getting back to the original question, how I do I initially set the values for these textboxes to 0.0?
That should have worked.Its very suprising. Is there a possibility that some other code is resetting the value after you did a setText or a setValue? Try debugging it in hosted mode.Put a breakpoint in setText and see when and how many times it is getting invoked

Resources