JAVAFX 8 TreeTableView: why doesn't this code detect cell losing focus? - event-handling

I am trying to commit an edit when a JavaFX 8 TreeTableView cell loses focus. I see the question has already been asked, but I would like to understand why my attempt below does not work. More specifically, why a listener to the focusedProperty of the cell does not get invoked.
Item<String, Object> is my data representation, and is an extension of Map<String, Object>.
Essentially, I wrap the standard text cell factory within a new cell factory that uses the standard one to create a cell, and adds a listener to its focusedProperty. When focus is lost, I store the cell text on it.
However, printouts indicate the event listener is never invoked.
I added the listener to the cell's focusedProperty because I could not identity a method that gives me the text control directly. The getGraphic() method (which I read somewhere is a misnomer because it points to whatever node is in the cell) returns a null pointer.
Any idea why the listener is never invoked? Thanks.
// obtain usual cell factory for text editing
Callback<TreeTableColumn<Item<String, Object>, String>, TreeTableCell<Item<String, Object>, String>>
callBackForTreeTableColumn = TextFieldTreeTableCell.forTreeTableColumn();
// create a new cell factory that delegates the cell creation to the standard factory
// and then adds a listener to cell's focusedProperty:
Callback<TreeTableColumn<Item<String, Object>, String>, TreeTableCell<Item<String, Object>, String>>
callBackWithOnFocusedListener = new Callback<TreeTableColumn<Item<String, Object>, String>, TreeTableCell<Item<String, Object>, String>> () {
#Override
public TreeTableCell<Item<String, Object>, String> call(TreeTableColumn<Item<String, Object>, String> column) {
TreeTableCell<Item<String, Object>, String> cell = callBackForTreeTableColumn.call(column);
System.out.println(System.currentTimeMillis() + ": cell created!");
cell.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
System.out.println(System.currentTimeMillis() + ": Focus changed!");
if (! isNowFocused) {
System.out.println(System.currentTimeMillis() + ": Lost focus, going to commit!");
Item<String, Object> item = cell.getTreeTableRow().getTreeItem().getValue();
item.put(header, cell.getText());
}
});
return cell;
};
column.setCellFactory(callBackWithOnFocusedListener);

The short answer to why don't I see a change in the focusedProperty is that there is no change because the property is always false.
Reason being, that the focusedProperty of a Tree/Table/Cell is (arguably mis-) used to represent the focused cell of the Tree/TableView's FocusModel (vs. the "real" focus as being focusOwner), but only if cellSelectionEnabled.
The relevant code snippet in updateFocus (in TableCell) which is called f.i. by an InvalidationListener to the focusedProperty of the FocusModel:
private void updateFocus() {
final boolean isFocused = isFocused();
if (! isInCellSelectionMode()) {
if (isFocused) {
setFocused(false);
}
return;
}
...
}

Related

JavaFX8 TreeTableView notifications for scrolled items

I am writing an application that is using a JavaFX8 TreeTableView. The tree table has three columns, two of which are String properties (name and value) and one which has a Canvas widget in it that draws a picture from from data from a database (waveforms). There is also a control on the application that allows the display (of all of the drawings) to be zoomed out or in (or for that matter scrolled left and right).
The name and value columns use StringProperty values from my data model so there are CellValueFactory set for those columns. The drawing column uses both a CellFactory and CellValueFactory like this:
// Waveform column
TreeTableColumn<DrawRow, WaveformTraceBox> waveColumn = new TreeTableColumn<>();
waveColumn.setCellFactory(new Callback<TreeTableColumn<DrawRow, WaveformTraceBox>, TreeTableCell<DrawRow, WaveformTraceBox>>() {
#Override
public TreeTableCell<DrawRow, WaveformTraceBox> call(TreeTableColumn<DrawRow, WaveformTraceBox> param) {
return new WaveformTraceBoxTreeTableViewCell<>();
}
});
waveColumn.setCellValueFactory(new Callback<TreeTableColumn.CellDataFeatures<DrawRow, WaveformTraceBox>, ObservableValue<WaveformTraceBox>>() {
#Override
public ObservableValue<WaveformTraceBox> call(TreeTableColumn.CellDataFeatures<DrawRow, WaveformTraceBox> param) {
return new ReadOnlyObjectWrapper<>(new WaveformTraceBox());
}
});
Where WaveformTraceBoxTreeTableViewCell is:
protected static class WaveformTraceBoxTreeTableViewCell<T> extends TreeTableCell<DrawRow, T> {
public WaveformTraceBoxTreeTableViewCell() {
super();
}
#Override
protected void updateItem(T value, boolean empty) {
super.updateItem(value, empty);
setText(null);
setGraphic(null);
if (!empty && getTreeTableRow().getItem() != null) {
getTreeTableRow().getItem().setTraceBox((WaveformTraceBox)value);
setGraphic((WaveformTraceBox) value);
}
}
DrawRow is my data model. When the user zooms out or in via the controls on the window the draw row model will notify it's associated Canvas drawing item to re-draw its display. The drawing of the display can take some time to do because of the large amount of data that needs to be processed to generate the display.
Now my problem: As the TreeTableView widget is scrolled it will ask for new Canvas widgets -- which get associated with DrawRow items from the data model. However widgets from the list that get scrolled off the screen will get thrown away by the tree widget.
I have found no way to tell if the item I am working with has been thrown away or is not being used any more. So the code is doing much more work than it needs to because it is trying to draw cells that are no longer being used or shown. Eventually this will cause other problems because of garbage collection I think.
So my real question is how can I tell if a cell has been abandoned by the tree table so I can stop trying to update it? Any help with this would greatly be appreciated. I am not able to find this anywhere on the various web searches I have done.
Do you need to worry here? What is still "drawing cells that are no longer being used"? Are you running some kind of update in a background thread on the WaveformTraceBox?
In any event, you've structured this pretty strangely.
First, (less important) why is your WaveformTraceBoxTreeTableViewCell generic? Surely you want
protected static class WaveformTraceBoxTreeTableViewCell extends TreeTableCell<DrawRow, WaveformTraceBox>
and then you can replace T with WaveformTraceBox throughout and get rid of the casts, etc.
Second: if I understand this correctly, WaveformTraceBox is a custom Node subclass of some kind; i.e. it's a UI component. The cell value factory shouldn't really return a UI component - it should return the data to display. The cell factory should then use some UI component to display the data.
That way, you can create a single WaveFormTraceBox in the cell implementation, and update the data it displays in the updateItem(...) method.
So something like:
// Waveform column
TreeTableColumn<DrawRow, WaveformData> waveColumn = new TreeTableColumn<>();
waveColumn.setCellFactory(new Callback<TreeTableColumn<DrawRow, WaveformData>, TreeTableCell<DrawRow, WaveformData>>() {
#Override
public TreeTableCell<DrawRow, WaveformData> call(TreeTableColumn<DrawRow, WaveformData> param) {
return new WaveformTraceBoxTreeTableViewCell();
}
});
waveColumn.setCellValueFactory(new Callback<TreeTableColumn.CellDataFeatures<DrawRow, WaveformData>, ObservableValue<WaveformData>>() {
#Override
public ObservableValue<WaveformTraceBox> call(TreeTableColumn.CellDataFeatures<DrawRow, WaveformTraceBox> param) {
return new ReadOnlyObjectWrapper<>(getDataToDisplayForItem(param.getValue()));
}
});
protected static class WaveformTraceBoxTreeTableViewCell extends TreeTableCell<DrawRow, WaveFormData> {
private WaveformTraceBox traceBox = new WaveformTraceBox();
public WaveformTraceBoxTreeTableViewCell() {
super();
}
#Override
protected void updateItem(WaveFormData value, boolean empty) {
super.updateItem(value, empty);
setText(null);
setGraphic(null);
if (!empty && getTreeTableRow().getItem() != null) {
traceBox.setData(value);
setGraphic(traceBox);
} else {
setGraphic(null);
}
}
}
Obviously you need to define the WaveFormData class to encapsulate the data your WaveFormTraceBox will display, and give the WaveFormTraceBox a setData(WaveFormData) method. If you are using any resources that need to be cleaned up, the invocation of setData(...) will indicate that the previous data is no longer being accessed by that WaveformTraceBox.

How can I observe the changed state of model items in an ObservableList?

I have an ObservableList of model items. The model item is enabled for property binding (the setter fires a property changed event). The list is the content provider to a TableViewer which allows cell editing. I also intend to add a way of adding new rows (model items) via the TableViewer so the number of items in the list may vary with time.
So far, so good.
As this is all within an eclipse editor, I would like to know when the model gets changed. I just need one changed event from any changed model item in order to set the editor 'dirty'. I guess I could attach some kind of listener to each individual list item object but I wonder if there is a clever way to do it.
I think that I might have a solution. The following class is an inline Text editor. Changes to the model bean (all instances) are picked up using the listener added in doCreateElementObservable. My eclipse editor just needs to add its' own change listener to be kept informed.
public class InlineEditingSupport extends ObservableValueEditingSupport
{
private CellEditor cellEditor;
private String property;
private DataBindingContext dbc;
IChangeListener changeListener = new IChangeListener()
{
#Override
public void handleChange(ChangeEvent event)
{
for (ITableEditorChangeListener listener : listenersChange)
{
listener.changed();
}
}
};
public InlineEditingSupport(ColumnViewer viewer, DataBindingContext dbc, String property)
{
super(viewer, dbc);
cellEditor = new TextCellEditor((Composite) viewer.getControl());
this.property = property;
this.dbc = dbc;
}
protected CellEditor getCellEditor(Object element)
{
return cellEditor;
}
#Override
protected IObservableValue doCreateCellEditorObservable(CellEditor cellEditor)
{
return SWTObservables.observeText(cellEditor.getControl(), SWT.Modify);
}
#Override
protected IObservableValue doCreateElementObservable(Object element, ViewerCell cell)
{
IObservableValue value = BeansObservables.observeValue(element, property);
value.addChangeListener(changeListener); // ADD THIS LINE TO GET CHANGE EVENTS
return value;
}
private List<ITableEditorChangeListener> listenersChange = new ArrayList<ITableEditorChangeListener>();
public void addChangeListener(ITableEditorChangeListener listener)
{
listenersChange.remove(listener);
listenersChange.add(listener);
}
public void removeChangeListener(ITableEditorChangeListener listener)
{
listenersChange.remove(listener);
}
}

Default option in SelectionCell

When I pass a string list to selectionCell, the first element will be choosen as a default value, how can I change the default value? For example at runtime I want to see the item at index=i as a default value.
There is no way to set an index in that cell
You can use only String there
SelectionCell statusOptionsCell = new SelectionCell(statusType);
Column<InvitedUser, String> statusColumn = new Column<Object, String>(
statusOptionsCell) {
#Override
public String getValue(Object object) {
return object.getStatus(); //value set at runtime
}
};
And if you need array only then
String[] myaaray= {"one","two","three"};
SelectionCell statusOptionsCell = new SelectionCell(statusType);
Column<InvitedUser, String> statusColumn = new Column<Object, String>(
statusOptionsCell) {
#Override
public String getValue(Object object) {
return st[1]; //pass integer as i here at runtime
}
};
Selection Cells are used in Cell widgets. So lets take CellTable as an example. In a Cell Table each row corresponds to one record ie a model/POJO. Each column corresponds to one property in the model/POJO. So, your selection cell column should be bound to one property in the model.
Set that property's value to whatever you want as a default value before supplying that model to the Cell Table.
Once you set the model's property its all Cell table and co's responsibility to set the value in to the selection cell.
I have a real-life example of changing SelectionCell's value at runtime. I want to have a list of possible actions to be performed for each row. After selecting (and performing) an action I want the select to be set back to it's default value.
ArrayList<String> options = new ArrayList<String>();
options.add("Choose...");
options.add("Action 1");
options.add("Action 2");
final SelectionCell cell = new SelectionCell(options);
Column<MyObject, String> column = new Column<MyObject, String>(cell) {
#Override
public String getValue(MyObject object) {
return null;
}
};
column.setFieldUpdater(new FieldUpdater<MyObject, String>() {
#Override
public void update(final int index, MyObject object, String value) {
// perform selected action
// action name is stored in the `value` parameter
// ...
// back to default value
cell.clearViewData(object);
// ... or back to other value
// cell.setViewData(object, "Action 1");
table.redrawRow(index);
}
});
table.addColumn(column, "Test");
Notice that the action is hooked-up on update method so it will be fired only if selected value has changed. It will never be fired with default value ("Choose...").

Events handling in GWT

I want to do something like this:
I want a cell table in which each cell is a class which I have written. I need to add a click event for the cell table. So How can I get which cell was clicked. As Cell is a class which I have defined, based on the clicked cell I need to perform some action. Can I somehow get the object details of the cell which was clicked. For e.g
I need a excel sheet which is like cell table, each cell in the excel sheet is a class I have defined, say the class holds the values like:
CellClass{
boolean isempty;
string name;
int id;
}
Now If i click in the excel sheet, how can I get which cell was clicked, so that I can tell user the name of the cell and whether it is empty or not.
Use ClickableTextCell to make clickable cell. Refer below Code detail:
nameCell = new ClickableTextCell() {
#Override
public void render(com.google.gwt.cell.client.Cell.Context context,
String data, SafeHtmlBuilder sb) {
super.render(context, "", sb);
sb.appendHtmlConstant("<input type=text value='"+data+"' />);
};
nameColumn = new Column<Document, String>(nameCell) {
#Override
public String getValue(Document object) {
//code
}
};
Note that there is only one instance of a Cell for each Column, not every value is represented by a Cell instance. The structure you describe above would be the structure of the data you set in CellTable.setRowData.
A Cell has to declare the events it's interested in, via the super constructor in AbstractCell or the getConsumedEvents method defined in Cell. In your case the "click" event.
You can then implement the onBrowserEvent method in your custom cell and react to the click. A context will be passed to this method indicating which row and column the event refers to (see Cell.Context) as well as the key and value associated with the click.
In your case, the custom Cell would look something like this:
public class MyCell extends AbstractCell<String> {
public MyCell() {
super("click");
}
#Override
public void onBrowserEvent(Context context, Element parent, String value, NativeEvent event, ValueUpdater<String> valueUpdater) {
// Handle the click event.
if ("click".equals(event.getType())) {
// Ignore clicks that occur outside of the outermost element.
EventTarget eventTarget = event.getEventTarget();
if (parent.getFirstChildElement().isOrHasChild(Element.as(eventTarget))) {
doAction(value, valueUpdater);
}
}
}
}
You can also intercept the event at the Column level.
You can find more information in the Dev Guide

gwt celltable: Possible to only make some cells in a column editable?

I'm using GWT 2.4. When using a CellTable, I've seen its possible to add a column in which all of the cells are editable ...
final TextInputCell nameCell = new TextInputCell();
Column<Contact, String> nameColumn = new Column<Contact, String>(nameCell) {
#Override
public String getValue(Contact object) {
return object.name;
}
};
table.addColumn(nameColumn, "Name");
but what if I don't want every cell in the column to be editable, only specific ones, based on properties in my "Contact" object? How would I set this up? Thanks, - Dave
The way I would do it is extend the TextInputCell and override the render method to render something else, if you don't want the value in that particular row editable.
Something like this:
public class MyTextInputCell extends TextInputCell {
#Override
public void render(Context context, String value, SafeHtmlBuilder sb) {
YourObject object = getYourObject();
if ( object.isThisCellEditable() ) {
super.render(context,value,sb);
} else {
sb.appendEscaped(value); // our some other HTML. Whatever you want.
}
}
}
In the render method you have access to the cell's context. Context.getIndex() returns the absolute index of the object. I can't remember of the top of my wad right now, but if you do not provide a ProvidesKey implementation when creating your CellTable you will get one that will use the object itself as the key. So you can get the object using Context.getKey().