The high level: I have a JTable that the user can use to edit data.
Whenever the user presses Enter or Tab to finish editing, the data is saved (I'm asusming that "saved" really means "the TableModel's setValueAt() method is called".)
If the user leaves the cell in any other way after making an edit, the new data is not saved and the value stays the way it was. So, for example, if the user changes a value and then clicks on some other widget on the screen, the change doesn't "stick."
I believe that this is the default behavior for a JTable full of Strings, yes?
For a variety of reasons, the desired behavior is for the cell to save any and all edits whenever the user leaves the cell. What's the best/right way to get Swing to do this?
Table Stop Editing explains whats happening and gives a couple simple solutions.
One of the simple solutions proposed
table.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
is good only for String columns. The problem is if I have, for example, Float type of the column being edited, enter an empty string in corresponding cell and then click on any other control of the window – Java throws NullPointerException in CellEditorRemover.propertyChange() method of JTable.java. It uses getCellEditor() call to stop or cancel editing but it returns null in this case. If the value entered is not empty or if I remove terminateEditOnFocusLost flag everything is fine. Probably, the situation described is a bug.
I hope I can provide a solution based on one of the previous posts. It’s not so trivial as I supposed before but seems to me it works.
I had to inherit my own cell editor from default cell editor and my own text field from JTextField which has FocusListener. This focus listener works fine when editing cell loses a focus, and a focus gained by another control of the window. But in the case of cell selection changes focus listener is “deaf”. That’s why I also have to remember previously valid value before editing start to restore it if the entered value will be invalid.
See the code below. Tested with Double, Float and Integer, but I hope this will also work with Byte and String.
Text field with focus listener:
public class TextFieldCell extends JTextField {
public TextFieldCell(JTable cellTable) {
super(); // calling parent constructor
final JTable table = cellTable; // this one is required to get cell editor and stop editing
this.addFocusListener(new FocusListener() {
public void focusGained(FocusEvent e) {
}
// this function successfully provides cell editing stop
// on cell losts focus (but another cell doesn't gain focus)
public void focusLost(FocusEvent e) {
CellEditor cellEditor = table.getCellEditor();
if (cellEditor != null)
if (cellEditor.getCellEditorValue() != null)
cellEditor.stopCellEditing();
else
cellEditor.cancelCellEditing();
}
});
}
}
Default cell editor class:
class TextFieldCellEditor extends DefaultCellEditor {
TextFieldCell textField; // an instance of edit field
Class<?> columnClass; // specifies cell type class
Object valueObject; // for storing correct value before editing
public TextFieldCellEditor(TextFieldCell tf, Class<?> cc) {
super(tf);
textField = tf;
columnClass = cc;
valueObject = null;
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
TextFieldCell tf = (TextFieldCell)super.getTableCellEditorComponent(table, value, isSelected, row, column);
if (value != null) {
tf.setText(value.toString());
}
// we have to save current value to restore it on another cell selection
// if edited value couldn't be parsed to this cell's type
valueObject = value;
return tf;
}
#Override
public Object getCellEditorValue() {
try {
// converting edited value to specified cell's type
if (columnClass.equals(Double.class))
return Double.parseDouble(textField.getText());
else if (columnClass.equals(Float.class))
return Float.parseFloat(textField.getText());
else if (columnClass.equals(Integer.class))
return Integer.parseInt(textField.getText());
else if (columnClass.equals(Byte.class))
return Byte.parseByte(textField.getText());
else if (columnClass.equals(String.class))
return textField.getText();
}
catch (NumberFormatException ex) {
}
// this handles restoring cell's value on jumping to another cell
if (valueObject != null) {
if (valueObject instanceof Double)
return ((Double)valueObject).doubleValue();
else if (valueObject instanceof Float)
return ((Float)valueObject).floatValue();
else if (valueObject instanceof Integer)
return ((Integer)valueObject).intValue();
else if (valueObject instanceof Byte)
return ((Byte)valueObject).byteValue();
else if (valueObject instanceof String)
return (String)valueObject;
}
return null;
}
It the code of table initialization you have to add the following:
myTable.setDefaultEditor(Float.class, new TextFieldCellEditor(new TextFieldCell(myTable), Float.class));
myTable.setDefaultEditor(Double.class, new TextFieldCellEditor(new TextFieldCell(myTable), Double.class));
myTable.setDefaultEditor(Integer.class, new TextFieldCellEditor(new TextFieldCell(myTable), Integer.class));
Hope, this will help somebody who have the same problem.
You need to add a focus listener. Given that JTable is basically a container of its cell components, you actually want the focus listener for every cell in your table that needs to behave in the way you indicated.
To do this, you will need to create custom cell editor, which wraps the cell component that has a registered focus listener. And when you get the callback for the loss of focus event, you do the data save, as you require.
This pretty much details most of what you need to do. The details of implementing the focus listener is not there, but that is fairly straightforward.
Lets say you do use a JTextComponent as your cell component. Then:
public void focusLost(FocusEvent e) {
JTextComponent cell = (JTextComponent) e.getSource();
String data = cell.getText();
// TODO: save the data for this cell
}
[p.s. edit]:
The thread that is calling you with this event is the dispatch thread. Do NOT use it for actions with high latency. But if you are just flipping bits in the heap, it should be ok.
Related
How can I wrap the text in JFXTreeTableView cells?
My JFoenix TableTreeView column cell factory is created as shown below.
// Set column cell factories and width preference
for (JFXTreeTableColumn<LogEntry, String> column : columnList) {
column.setCellFactory(param ->
new GenericEditableTreeTableCell<>(new TextFieldEditorBuilder()));
column.setPrefWidth(100);
}
After hours of searching, I can't figure out how to get the cells in the tree table to wrap text. I even tried to set the wrap text value for every GenericEditableTreeTableCell to true, but I think I'm also supposed to call the setWrappingWidth() method on something. I tried the following block of code, but I ended up getting a NullPointerException.
// Set column cell factories and width preference
for (JFXTreeTableColumn<LogEntry, String> column : columnList) {
column.setCellFactory(param -> {
GenericEditableTreeTableCell<LogEntry, String> cell =
new GenericEditableTreeTableCell<>(new TextFieldEditorBuilder());
Text text = (Text) cell.getGraphic();
text.setWrappingWidth(1); //null pointer exception
cell.setWrapText(true);
return cell;
});
column.setPrefWidth(100);
}
So, I'm left with the following block of code which runs perfectly fine and displays the table, but the cells do not wrap text.
// Set column cell factories and width preference
for (JFXTreeTableColumn<LogEntry, String> column : columnList) {
column.setCellFactory(param -> {
GenericEditableTreeTableCell<LogEntry, String> cell =
new GenericEditableTreeTableCell<>(new TextFieldEditorBuilder());
// I think I should call setWrappingWidth() on some object here
// but I don't know what object
cell.setWrapText(true);
return cell;
});
column.setPrefWidth(100);
}
The documentation for setting up a JFXTreeTableView can be found here. It doesn't seem to mention anything about wrapping cell text.
Edit: I tried doing it with CSS, but didn't get any results. In fact, the cell.isWrapText() returned false after using this CSS code - meaning that it didn't event set the value to true. I know the block of CSS is working correctly because I can change every element's text fill color with it.
* {
-fx-wrap-text: true;
}
Edit 2: Some people said on other semi-related posts that a scroll pane can cause a Node to think it has a much larger width than what is shown to the user. Since a JavaFX TreeTableView uses a scroll bar when the table is too large, I figured I'd try their solutions. I tried setting the preferred width of the cell - still no results.
cell.setWrapText(true);
cell.setPrefWidth(100);
//cell.setMaxWidth(100); doing this too made no difference
//cell.setMinWidth(100); doing this too made no difference
Edit 3: I think I know the problem! It seems that the row height refuses to let the cell wrap text. If I set the rows minimum height to a large enough value, the cell wraps its text! Now I just need to know how to make the row height adjust dynamically to accommodate the cell when it wants to wrap text.
Edit 4: It appears that the row doesn't allow line breaks which may be the cause of the cell failing to wrap text. It can't wrap the text because the new lines it creates are chomped.
I have used one of your approaches, but also made custom EditorBuilder which has JFXTextArea instead of JFXTextField.
The custom TextAreaEditorBuilder is the following:
public class TextAreaEditorBuilder implements EditorNodeBuilder<String> {
private JFXTextArea textArea;
#Override
public void startEdit() {
Platform.runLater(() -> {
textArea.selectAll();
});
}
#Override
public void cancelEdit() {
}
#Override
public void updateItem(String s, boolean isEmpty) {
Platform.runLater(() -> {
textArea.selectAll();
textArea.requestFocus();
});
}
#Override
public Region createNode(
String value,
DoubleBinding minWidthBinding,
EventHandler<KeyEvent> keyEventsHandler,
ChangeListener<Boolean> focusChangeListener) {
StackPane pane = new StackPane();
pane.setStyle("-fx-padding:-10 0 -10 0");
textArea = new JFXTextArea(value);
textArea.setPrefRowCount(4);
textArea.setWrapText(true);
textArea.minWidthProperty().bind(minWidthBinding.subtract(10));
textArea.setOnKeyPressed(keyEventsHandler);
textArea.focusedProperty().addListener(focusChangeListener);
pane.getChildren().add(textArea);
return ControlUtil.styleNodeWithPadding(pane);
}
#Override
public void setValue(String s) {
textArea.setText(s);
}
#Override
public String getValue() {
return textArea.getText();
}
#Override
public void validateValue() throws Exception {
}
}
and then the configuration for your column goes something like this:
negativeCasingColumn.setCellFactory(param -> {
TextAreaEditorBuilder textAreaEditorBuilder = new TextAreaEditorBuilder();
GenericEditableTreeTableCell<DiagnosticMethodFX, String> cell
= new GenericEditableTreeTableCell<>(textAreaEditorBuilder);
cell.setWrapText(true);
return cell;
});
So basically I'm wrapping the cell, but making sure I use TextArea as editing field. For me this works just fine, but if you want more elegant solution maybe you can check the cancelEdit() method in GenericEditableTreeTableCell, where it sets the content display to text only: setContentDisplay(ContentDisplay.TEXT_ONLY) which is setting of content property in the abstract class Labeled, where the text wrap is set to false. I didn't dig too deep into it, but some workaround can be made for sure.
I have a JTable and Given such codes:
jTable.addMouseListener(new MouseAdapter(){
public void mouseClicked(MouseEvent e){
System.out.println(jTable.getRowCount());
System.out.println(jTable.getModel().getValueAt(jTable.getRowCount(), 0));
}
});
If I click on a certain row, like in the picture above, I clicked the second row, how can I get that row's content?(How to get the Canada)?
Personally, I use the Mouse Clicked event. You could try something like this inside your event method:
private void myTableMouseClicked(java.awt.event.MouseEvent evt) {
int row = this.myTable.getSelectedRow();
int column = this.myTable.getSelectedColumn();
this.myTable.getValueAt(selectedRow, selectedColumn);
}
Be aware that the getValueAt method returns an Object. You probably will need to cast the Object returned into the object it is supposed to be. And also you could have a global variable that's going to have the value returned by getValueAt for using it as you need.
I hope it helps.
I have a JTable, and I want validate data in the first column. When the user type the entry in any cell in the first column and click in another cell (focus lost), I want show message that the entry is false, and focus again in the cell until the entry is valid.
First I thought that the cell is like the JTextFiled, so I have tried the method addFocusListener(...) but it doesn't work!
table.getValueAt(0, 0).addFocusListener(
new FocusListener() {
public void focusGained(FocusEvent e) {
}
public void focusLost(FocusEvent e) {
for (int n = 0; n <= table.getValueAt(0, 0).toString().length(); n++) {
if (Character.isDigit(table.getValueAt(0, 0).toString().charAt(n)) == false) {
JOptionPane.showMessageDialog(null,
"Error: code is a number !", "Error Message",
JOptionPane.ERROR_MESSAGE);
break;
} else
break;
}
}
}
}
});
creation of JTable:
String [][]data={ {"-","-","0","-"},
{"-","-","0","-"},
{"-","-","0","-"},
{"-","-","0","-"},
{"-","-","0","-"},
{"-","-","0","-"},
{"-","-","0","-"},
{"-","-","0","-"} };
String[] header = {"Code Projet", "Description", "Duree", "Taches anterieurs"};
table = new JTable(data, header);
The TableCellEditor that is added to a JTable will do any validation on the values that are entered into a JTable.
From the Java Table Tutorial:
The automatic checking of user-entered strings occurs when the default
editor attempts to create a new instance of the class associated with
the cell's column. The default editor creates this instance using a
constructor that takes a String as an argument. For example, in a
column whose cells have type Integer, when the user types in "123" the
default editor creates the corresponding Integer using code equivalent
to new Integer("123"). If the constructor throws an exception, the
cell's outline turns red and refuses to let focus move out of the
cell. If you implement a class used as a column data type, you can use
the default editor if your class supplies a constructor that takes a
single argument of type String.
So, if you want to just make sure the user enters in an Integer (which is what it looks like from your code), you can set the column type to a type of Integer .
table.setDefaultEditor(Integer.class,
new IntegerEditor(0, 100));
Now the DefaultCellEditor will do the checking for you, and throw an exception if the type is not correct. You will just have to make sure you catch the exception and deal with it properly.
I think tutorial I linked above on creating tables would be very helpful for you, especially the part I linked to which talks about validating user text.
This summer is not a real summer, i thought.
A cup of coffee make me feel it as a summer, tough (lol).
I have a little bit naughty JTable. OMG.
Below is my JTable that use my own customized TableModel. You could see it on its method of getColumnClass(), there... it was made for returning as JLabel only.
ANd then, I also customize the DefaultRenderer.
jtbl_inGameEasy = new javax.swing.JTable();
jtbl_inGameEasy.setFont(new java.awt.Font("squeaky chalk sound", 0, 14)); // NOI18N
jtbl_inGameEasy.setForeground(new java.awt.Color(255, 255, 255));
jtbl_inGameEasy.setModel(new javax.swing.table.DefaultTableModel(
new Object [][] {
},
new String [] {
"null", "null", "null", "null", "null"
}
) {
boolean[] canEdit = new boolean [] {
false, false, false, false, false
};
public boolean isCellEditable(int rowIndex, int columnIndex) {
return canEdit [columnIndex];
}
public Class getColumnClass(int columnIndex) {
return JLabel.class;
}
});
jtbl_inGameEasy.setDefaultRenderer(JLabel.class, new JLabelTableRenderer());
jtbl_inGameEasy.setFocusable(false);
jtbl_inGameEasy.setOpaque(false);
jtbl_inGameEasy.setRowHeight(55);
jtbl_inGameEasy.setShowHorizontalLines(false);
jtbl_inGameEasy.setShowVerticalLines(false);
jtbl_inGameEasy.setTableHeader(null);
jtbl_inGameEasy.addMouseListener(new java.awt.event.MouseAdapter() {
public void mousePressed(java.awt.event.MouseEvent evt) {
jtbl_inGameEasyMousePressed(evt);
}
});
And Where is the JTableRenderer? Here...
this is my Custom Renderer below...
public class JLabelTableRenderer extends DefaultTableCellRenderer {
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column) {
if (value instanceof JLabel) {
//This time return only the JLabel without icon
return (JLabel) value;
} else {
return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
}
}
}
And then, I need to pu JLabel inside each of the Cell.
I start to add some JLabel(s) inside the Object of Array over the Cells of the JTable using this code; (which is no problem).
DefaultTableModel o_dtb = jtbl_inGameEasy.getModel();
o_dtb.addRow(myArrayCustomizedObjectofJLabel);
Everything works fine, I guess. But because of My purpose is that ---> To put an Icon or to make it Invisible once the user Click on JTable Cells, thus I I tried to do my MouseEvent once pressed and calling these lines of code;
private void jtbl_inGameEasyMousePressed(java.awt.event.MouseEvent evt) {
// Checking inGameEasy Table Ans
javax.swing.JTable source = (javax.swing.JTable) evt.getSource();
int row = source.rowAtPoint(evt.getPoint());
int column = source.columnAtPoint(evt.getPoint());
DefaultTableModel o_dtb = (DefaultTableModel) jtbl_inGameEasy.getModel();
String s_questAns = "" + Game.getCurrentQuestion().getResult();
String s_userAns = "" + o_dtb.getValueAt(row, column);
// These two lines below are not Working, why yaa??
((JLabel) o_dtb.getValueAt(row, column)).setVisible(false);
((JLabel) o_dtb.getValueAt(row, column)).setIcon(myIcon);
if (s_questAns.equals(s_userAns)) {
Game.correct();
System.err.println("nice ans!");
} else {
jll_txtMiss.setText("Miss : " + Game.wrong());
System.err.println("nope!");
}
nextQuestion();
}
And seems to me that the Marked (below the commented) code above are not working.
Yeah, the JLabel casted couldn't be changed its Icon (image), and nor its visibility as well. Is it the Model cause all of this?
NB: My Cells data added later after the Model created differently.
It seems to me that what you want to do is to just leave all table cells empty, except the ones clicked by the user, where some mark icon should appear, right?
If so, your table model should not contain JLabel instances. A JLabel is a visual component, used to render some data graphically. The data itself is not a JLabel. In your case, I think it should thus be a Boolean (true when clicked by the user, false otherwise).
Now you could use a custom renderer (although the default one for booleans could also be OK) to display your booleans. This renderer would be a subclass of DefaultTableCellRenderer, which is itself a subclass of JLabel. The renderer (the same instance for all the cells configured to use this renderer) would just display a marked icon if the Boolean value to render is true, and a not marked icon (or no icon at all) when the Boolean value to render is false.
Then, your click handler would just have one mission: make the clicked cell contain true rather than false. To do that, it would just have to change the appropriate value in the table model.
To recap: a table model is used to hold data. Think of it as the data you would find in a database. Would you hold a JLabel in the database? No, you would hold a boolean, a string or an integer. The table can render this data as you want, and this is the mission of the renderer.
Side note: stop with the hungarian notation: it doesn't make sense in Java. It makes code hard to read. Everything except primitive types is an object, and you can't assign a meaningful prefix to every possible type. Rather, use readable and meaningful english names : tableModel rather than o_dtb, correctAnswer rather than s_questAns, userAnswer rather than s_userAns.
I'm using the following
org.eclipse.jface.viewers.CheckboxCellEditor.CheckboxCellEditor(Composite parent)
I'm creating a table viewer with cellEditors and doing the following
CellEditor[] editors = new CellEditor[columnNames.length];
editors[7] = new CheckboxCellEditor(table);
I have a CellModifier that has the following
public Object getValue(Object element, String property) {
Object result = null;
...
result = Boolean.valueOf(task.isDfRequested());
return result;
}
public void modify(Object element, String property, Object value) {
item.isSelected(((Boolean)value).booleanValue());
}
Finally I have a LabelProvider that has the following
public String getColumnText(Object element, int columnIndex) {
String result = "";
try {
result = Boolean.toString(item.isSelected());
} catch (Exception ex) { }
break;
However, in my UI instead of having a check box I have the word true or false && clicking it results in switching state to false or true. Any ideas on why I don't have a checkbox??
I've searched in the source code of CheckboxCellEditor class and in the constructor the control associated to the CellEditor is created in the createControl(Composite parent) method. This method is abstract in CellEditor class and it's implemented like this in CheckboxCellEditor:
protected Control createControl(Composite parent) {
return null;
}
So a control is not created, that's why you don't see the checkbox. In the documentation of the Class you can read:
Note that this implementation simply
fakes it and does does not create any
new controls. The mere activation of
this editor means that the value of
the check box is being toggled by the
end users; the listener method
applyEditorValue is immediately called
to signal the change.
I solved this using a ComboBoxCellEditor with yes and no items.
Regards.
Well, I have no idea how SWT works or what component you are even talking about.
But I do know that when using Swing you can have custom editors for a column in a JTable. If you don't tell the table the class of data for the column then the toString() method of the data is invoked. But if you tell the table that Boolean data is displayed in the column then the table will use the check box editor.
Sounds like a similiar symptom, but I don't know your particular solution.
What I've decided to do is to just implement a dirty hack others have been using.
Create two images of check boxes, one checked the other not checked. Switch the state between the two based on the boolean.
It's not perfect, but for now it gets the job done