Starting Point
I would like to implement a TableView in which each TableRow item will be checked for a certain boolean value. If this value holds true, then this row will be disabled for selection.
For instance I have a TableView with 3 TableRows (each holds a Person object). Now I would like the TableView to make those rows not-selectable whose Person object property is older than 18 years old.
Assume 2nd row fullfills the above condition and is therefore rendered not-selectable. So if my cursor currently is focused on the 1st row and I press the arrow down key, the TableView would skip any not-selectable rows (here: 2nd row) and select the next available selectable row (here: 3rd row)
My Approach
tblLineitems.setRowFactory(new Callback<TableView<Lineitem>, TableRow<Lineitem>>() {
public TableRow<Lineitem> call(TableView<Lineitem> tableview) {
return new TableRow<Lineitem>() {
#Override
protected void updateItem(Lineitem item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
} else {
itemProperty().addListener((ObservableValue<? extends Lineitem> observable, Lineitem oldValue, Lineitem newValue) -> {
if (newValue.isOlderThan18()) {
setDisable(true);
} else {
setDisable(false);
}
});
}
}
};
}
});
My Issue
Although I managed to setDisable(true) the affected rows, yet I can still select/mark them with the arrow down and up keys.
I played around with implementing a custom selection model and a focus model but ended-up using a hack (works for my use case). In my situation, I have to skip at most one row at a time. Therefore, inside the ChangeListener of the selectedItemProperty I ended up using the following function.
If I encounter an item row I trigger a callback, otherwise I skip the category row.
private fun MyListView.skipCategoryRow(
previousSelectionIndex: Int,
currentSelectionIndex: Int
) {
when {
currentSelectionIndex > previousSelectionIndex -> selectionModel.selectNext()
currentSelectionIndex > 1 -> selectionModel.selectPrevious()
/* prevent moving the selection to index 0, which is always a category row */
else -> selectionModel.selectNext()
}
}
Related
new to JavaFX, and been trying to get a TableView cell's position by row, col indices. Looking for something like Swing's JTable.getCellRect;
I've seen solutions for the case where a specific cell's value is needed,
(JavaFX Simplest way to get cell data using table index)
I've also figured it would be enough to get the TableCell of the corresponding row and column, then get bounds and use localToScreen etc, but couldn't find a way to get a specific table-cell as well.
Would love any help with any of that.
Thanks in advance
There's no direct way to do this in JavaFX, by design. Since cells are only created for data that is currently visible, and are reused (for example as the user scrolls around the table), there isn't a 1-1, or even a consistent, relationship between any given cell and the "location" in the table's backing data. Consequently, you really should not even attempt to find a cell for a given data item, and should try to find another approach to whatever you are trying to do.
(From a MVC design approach, this makes sense too: you are asking to find the view (cell) from the model (data); the model is supposed to be completely unaware of the view.)
If you really want to attempt this (and, in case it's not clear, I think you should find another approach), you can try to keep track of the cells for each position as they change, using something like the following:
TableView<SomeDataType> table = new TableView<>();
Map<TableColumn<SomeDataType, ?>, Map<Number, TableCell<SomeDataType, ?>>> cells = new HashMap<>();
TableColumn<SomeDataType, SomeColumnDataType> column = new TableColumn<>(...);
cells.put(column, new HashMap<>();
column.setCellValueFactory(...);
column.setCellFactory(tc -> {
TableCell<SomeDataType, SomeColumnDataType> cell = new TableCell<SomeDataType, SomeColumnDataType>() {
#Override
protected void updateItem(SomeColumnDataType item, boolean empty) {
super.updateItem(item, empty) ;
if (empty) {
setText(null);
} else {
setText(item.toString());
}
}
};
cell.indexProperty().addListener((obs, oldIndex, newIndex) -> {
if (oldIndex != null) {
cells.get(column).remove(oldIndex);
}
if (newIndex != null && newIndex.intValue() != -1) {
cells.get(column).put(newIndex, cell);
}
});
return cell ;
});
// repeat for other columns...
Then you can do
TableCell<SomeDataType, ?> cell = cells.get(someColumn).get(someIndex);
to get a cell for a specific column and row index. Note that you need to check for null (if that data doesn't currently have a cell).
This will probably need very careful debugging, but the approach should work.
Update: I made an SSCCE using this technique here.
You could also try via a lookup. Lookups are not very robust, and won't work at all until CSS has been applied to the scene graph, but you can try something like this:
TableColumn<...> columnOfInterest = ... ;
int rowIndexOfInterest = ... ;
TableCell<?, ?> cell = null ;
for (Node n : table.lookupAll(".table-cell")) {
TableCell<?,?> c = (TableCell<?,?>) n ;
if (c.getTableColumn() == columnOfInterest
&& c.getIndex() == rowIndexOfInterest) {
cell = c ;
break ;
}
}
if (cell != null) {
// ...
}
or in a more Java 8 approach:
table.lookupAll(".table-cell").stream()
.map(TableCell.class::cast)
.filter(c -> c.getTableColumn() == columnOfInterest && c.getIndex() == rowIndexOfInterest)
.findAny()
.ifPresent(cell -> {
// do whatever you need with the cell....
});
I want a JCombox which contain list of invoices. if i select a invoice it will fill the form. invoices are loaded by selecting "buyer" combo box. invoices combox has itemStateChanged event. problem is when i select the buyer, form fills with first invoice(first item in invoice combo box). so i set selected index in to -1 in invoice combo box. same result i got.
Order's toString methos returns the invoice number.
for (Order O : orderList) {
jcbInvoiceNos.addItem(O);
}
jcbInvoiceNos.setSelectedIndex(-1);
private void addInvoiceNoItemChangeListener() {
jcbInvoiceNos.addItemListener(new ItemListener() {
#Override
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
loadInvoiceDetails();
}
}
});
}
public void loadInvoiceDetails() {
System.out.println("Selected index " + jcbInvoiceNos.getSelectedIndex());
}
this always prints Selected index 0 this should be Selected index -1 first time i select the buyer. i want to l fill for by selecting invoice. not the buyer.
please give me a solution for this.
The reason for this is the implementation of DefaultComboBoxModel: when adding the first item into an empty model, it automatically selects that first item. It's slight inconsistency is that it does so only when using the addElement(Object) not when using insertElement(Object, size) So the clean (slight cough, modulo relying on an undocumented implementation detail ;-) is to use the latter:
// loading the invoice ids
combo.removeAllItems();
for (int i = 0; i < 20; i++) {
combo.insertItemAt("combo: " + count + " item: " + i, i);
}
On the other hand, it might be more user-friendly to present an "informational" selected item (like f.i. "no invoice selected") initially. This can be done if you fill the model (instead of the combo, which doesn't accept selected items that are not contained in the list), something like:
// the informational item
Object noInvoice = "no invoice selected";
// loading the invoice ids
model.removeAllElements();
model.setSelectedItem(noInvoice);
for (int i = 0; i < 20; i++) {
model.addElement("model: " + count + " item: " + i);
}
// the itemListener ignoring the informational item
if (ItemEvent.SELECTED == e.getStateChange()) {
if (noInvoice.equals(e.getItem())) return;
doLoadDetails(e.getItem());
}
I would prevent listener calls when adding items in the combo.
There are 2 ways to do this.
Remove the listener before the adding and readd after
Add a flag isAPI. Set it before adding and reset after. In the listener just check the flag and if it's true just return.
Try the following
private void addInvoiceNoItemChangeListener() {
jcbInvoiceNos.addItemListener(new ItemListener(){
#Override
public void itemStateChanged(ItemEvent e){
if(e.getText() != null && 0 < e.getText()){
if(e.getStateChange() == ItemEvent.SELECTED){
loadInvoiceDetails();
}
}
}
});
}
This works because it blocks null event texts, and/or text strings that are zero in length, it might be wise to increase zero to one or better two if you know your minimum string to be bigger than two!.
I would like for my ComboBoxCellEditor to be able to have 3 selections possible. Right now it only has Yes or No. I would like for it to have Yes, No, Both.
Also the combobox selection values does not show up in the table unless the cell is clicked. It is hard to tell if the table cell has a selection possible unless they click in the empty cell. I would like it to at least show the down arrow.
I have read some where that the only way you can get around this is to set a default value.
I am not sure how to add the 3rd value. I will add my code trying to add the 3rd value
How can a get the combobox show up in the table without the cell having to be clicked first?
.
public class OptionEditingSupport extends EditingSupport {
private ComboBoxCellEditor cellEditor;
public OptionEditingSupport(ColumnViewer viewer) {
super(viewer);
cellEditor = new ComboBoxCellEditor(((TableViewer)viewer).getTable(), new String[]{"Yes", "No", "Both"}, SWT.READ_ONLY);
}
protected CellEditor getCellEditor(Object element) {
return cellEditor;
}
protected boolean canEdit(Object element) {
return true;
}
protected Object getValue(Object element) {
return 0;
}
protected void setValue(Object element, Object value)
{
if((element instanceof AplotDatasetData) && (value instanceof Integer)) {
Integer choice = (Integer)value;
String option = (choice == 0? "Yes":"No":"Both"); **<- Error Here
((AplotDatasetData)element).setMarkupValue(option);
getViewer().update(element, null);
}
}
}
The conditional operator
x ? y : z
is a ternary operator, which internally does:
if(x)
y;
else
z;
Thus, you can only use it with three components. Use an if else if else instead:
Integer choice = (Integer)value;
String option = "";
if(choice == 0)
option = "Yes";
else if(choice == 1)
option = "No";
else
option = "Both";
TableEditor can be used to show any Widget on top of Table Cell. It should solve your problem with showing Combobox to let user know there is selection possible for that row and column.
I am not sure I understand your question about 3 selections.
I added EditTextCell(stringTestEditTextCell) to Column(testColumn).
EditTextCell editTextCell = new EditTextCell();
Column stringColumn = new Column(
editTextCell) {
#Override
public String getValue(Record object) {
return object.getValue();
}
};
All cells in testColumn are editable.
I want 1st cell of column such way that 1st cell of column should be Non-Editable.
Following class is answer to my question. I Solved it and works fine. But getting error when user clicking on 1st cell of column.
class CustomEditTextCell extends EditTextCell{
#Override
public void render(com.google.gwt.cell.client.Cell.Context context,
String value, SafeHtmlBuilder sb) {
// context.getColumn()==2 indicate Record ID column and context.getIndex()==0 indicate non editable cell in 1st empty row
if(context.getColumn()==2 && ( context.getIndex()==0 || context.getIndex()%10 == 0)){
sb.appendHtmlConstant("<div contentEditable='false' unselectable='true'></div>");
}else{
super.render(context, value, sb);
}
}
}
You might extend EditTextCell and override the edit()-Method so that it only edits when you have not set a boolean flag that you that you need to set for the first cell.
I use a JTable which has its own cell renderer and cell editor.
Say, this table contains 2 columns and x rows:
The first column contains a boolean value, its own cell rendering and cell editor (a radiobutton)
The second column contains a string value, its own cell renderer: it makes it bold when the first column of the current row is set to true (radiobutton checked)
All the values are correctly updated by the editor but the 2nd row does not become bold when the radio button is set to true...
I have to check a radio button from a different row to see the changes
Where can I fire thoses changes ?
Cheers and thanks for your help
RadiobuttonTableCellEditor.java
public class RadiobuttonTableCellEditor extends DefaultCellEditor
implements ItemListener {
JRadioButton rb = new JRadioButton();
public RadiobuttonTableCellEditor(JCheckBox pCheckBox) {
super(pCheckBox);
}
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
if (value == null)
return null;
rb.addItemListener(this);
rb.setSelected((Boolean)value);
return rb;
}
public void itemStateChanged(ItemEvent e) {
super.fireEditingStopped();
}
public Object getCellEditorValue() {
rb.removeItemListener(this);
return rb.isSelected();
}
}
In your table model whenever your value changes you have to fire appropriate event. If your model is inherited from AbstractTableModel you can use several fireXXX methods. My guess is you should call them from setValueAt method.
If you know exact column and row - you can call fireTableCellUpdated, otherwise you can you probably have to use fireTableChanged since you have to update different column.
And of course you renderer should properly render new value.
It doesn't seem to make any sense to extend DeafultCellEditor there. Implementing a listener interface like that is also not a great idea.
Renderers work best as a thin layer. If another cell should change, then that needs to be reflected in the table model which should fire a relevant update event.
I guess it could help people with a similar problem, make a true radiobutton unique in a row, you'll have to extend the DefaultTableModel to modify its behaviour especially the setValueAt method
Cheers
/**
* When <code>column</code> is the column that contains the Boolean (in fact the radio button):
* If aValue == false and that it had a previous value set to true we don't do anything
* If aValue == true and that it had a previous value set to false, we set all the other booleans to false and this one to true
*/
#Override
public void setValueAt(Object aValue, int row, int column) {
if (column == colonneBoutonradio)
{
if (((Boolean)aValue && !(Boolean)super.getValueAt(row, column)))
for (int i = 0; i < this.getRowCount(); i++)
// i==row permet de vérifier si la ligne courante est celle à modifier (et donc celle à mettre à true)
super.setValueAt(i==row, i, colonneBoutonradio);
}
else
super.setValueAt(aValue, row, column);
}