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....
});
Related
I'm trying to simplify the following code.
while(results.next())
for(String column : colNames){
if(EmptyUtil.isEmpty(results.getString(column))) {
emptyCount++;
}
}
if(emptyCount == colNames.size()){
break;
}
}
My main goal is to read a CSV file until an empty row and finish reading the file from there. And it's a necessity that I have to use this Csvjdbd driver ).
In the above code, results is a CsvResultSet (I've used Csvjdbc library to get this result set ). This CsvResultSet represents a single row in the CSV file inside the for loop. Basically I'm going through every row in the CSV file
b
colNames is the column headers list in the CSV. By using results.toString(column), I can get the value in the particular cell in the row.
If all the cells are empty in the row, I should break from the while loop.
The above code works as expected. I just need to know how to simplify that more.
Try this.
L: while(results.next()) {
for(String column : colNames){
if(!EmptyUtil.isEmpty(results.getString(column))) {
continue L;
}
}
// all the cells are empty in the row
break;
}
Not sure this is much simpler, but perhaps more readable:
while (results.next() && !allColumnsEmpty(results, colNames)) {
}
...
private boolean allColumsEmpty(ResultSet results, String...colNames) {
for(String column : colNames){
if(!EmptyUtil.isEmpty(results.getString(column))) {
return false;
}
}
return true;
}
I would use Stream API to decide, and an external variable instead of break.
boolean wasEmptyRow = false;
while (!wasEmptyRow && results.next()) {
wasEmptyRow = colNames
.stream()
.map(column -> results.getString(column))
.allMatch(EmptyUtil::isEmpty);
if (! wasEmptyRow) {
// process
}
}
You simply create a stream from the column names, replace all values with the current value from the result, then check if all of them are empty.
The allMatch is optimized, it will not check all columns if one failing case was found. This saves the break for you.
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()
}
}
i am trying to iterate through JComboBox items i.e its connected to Database, whenever I click it it fetches data from Database and updates it. But its adding duplicate values in such case. I am trying to validate it by iterating through each item once added to JComboBox, if the existing item is similar to item I am trying to add then it shall not add and jump to other statement.
However I am then getting Null pointer error, in order to avoid this error first time I added counter -1, but once items are added and want to update, it gives error.
My JComboBox code is given below:
comboBox.addMouseListener(
new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent ev) {
List<Guest> list = null;
list = database.readGuest();
int n = list.size();
if(n <= 0) {
JOptionPane.showMessageDialog(null, "No data found.");
}else {
for(int count = 0; count < n; count++) {
g = list.get(count);
String pass = g.getPassportNp();
//String s = (String) comboBox.getItemAt(count-1);
//for(int i = 0; i < n; ++i) {
if(comboBox.getItemCount() != 0) {
if(comboBox.getItemAt(count-1).equals(pass)) {
continue;
}else {
comboBox.addItem(pass);
}
}else {
comboBox.addItem(pass);
}
//}
}
}
}
});
any solution for this?
You are not iterating over the items in the combobox, but only comparing to the last item in it. Your basic design should be 2 nested for-loops, one for going over your database items and one for going over the combobox items to check if the current database item is already in there. (You may also use List.contains instead of an nested loop which is clearer and shorter).
Side note: registering a mouselistener on the combobox seems like a design smell for this type of work. Fetching items from a database is not something you want to do on the EDT, but rather in a background thread. Also, you don't know what happens first: opening/animating the combobox (done by Swing), or modifying its contents? It makes it hard to think about the control flow, yet another reason to take a different approach.
Here is the solution which I figured it out. I hope helps others.
I created a List, and then added all items to it then..
int size = pass.size();
if(comboBox.getItemCount() != 0) {
comboBox.removeAllItems();
for(int c = 0; c < size; ++c) {
comboBox.addItem(pass.get(c));
}
}else {
for(int c = 0; c < size; ++c) {
comboBox.addItem(pass.get(c));
}
}
One of the rows in my table is a ComboBox. They have the choice between 'Yes', 'No', 'Both'
If they choose Both have to make some modifications to the data array that is building the table and refresh the table. It was suggested in a previous post to build my logic in the else statement for Both.
protected void setValue(Object element, Object value)
{
if((element instanceof AplotDatasetData) && (value instanceof Integer)) {
Integer choice = (Integer)value;
String option = ((AplotDatasetData)element).getMarkupValue();;
if(choice == 0) {
option = "No";
}
else if(choice == 1) {
option = "Yes";
}
else {
option = "Both";
abd.getIndexOfSelectedBoth(); <<<<<<<<<
}
((AplotDatasetData)element).setMarkupValue(option);
getViewer().update(element, null);
}
}
The code above is in class OptionEditingSupport.
The table is in class AplotBaseDailog.
So in the OptionEditingSupport class, I imported the AplotBaseDailog class and assigned it.
AplotBaseDialog abd;
Then I wrote a method in the AplotBaseDailog class to get the row index of the column they just changed to Both. I need the index value to get the data from the array.
public void getIndexOfSelectedBoth() {
int row = viewer.getTable().getSelectionIndex();
AplotDataModel.getInstance().rebuildDataArray(row);
updateTableViewer();
}
Then I am passing in the index of the row to a method in my dataModel class. It is in the dataModel class that has the data array.
I am guessing I am reinventing the wheel here. There has to be a better way to do this process. Right now with all my code in place, I am getting a Null Pointer Error at the line that calls AplotBaseDialog
else {
option = "Both";
abd.getIndexOfSelectedBoth(); <<<<----
}
Can you get the index in the OptionEditingSupport class?
So you want to find the index of the AplotDatasetData for which "both" was selected.
Your ModelProvider (APlotDataModel) contains a List with your data, right?
Each List implements the method indexOf(Object). So you can get the index of your current object by using this method.
AplotDatasetData selected = ...
int index = AplotDataModel.getInstance().getIndexOf(selected);
and within your model:
public int getIndexOf(APlotDatasetData object)
{
return LIST_HOLDING_YOUR_DATA.indexOf(object);
}
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.