How to handle empty selection in a JFace bound combobox? - java

I am developing a search dialog in my eclipse-rcp application.
In the search dialog I have a combobox as follows:
comboImp = new CCombo(grpColSpet, SWT.BORDER | SWT.READ_ONLY);
comboImp.setBounds(556, 46, 184, 27);
comboImpViewer = new ComboViewer(comboImp);
comboImpViewer.setContentProvider(new ArrayContentProvider());
comboImpViewer.setInput(ImpContentProvider.getInstance().getImps());
comboImpViewer.setLabelProvider(new LabelProvider() {
#Override
public String getText(Object element) {
return ((Imp)element).getImpName();
}
});
Imp is a database entity, ManyToOne to the main entity which is searched, and ImpContentProvider is the model class which speaks to embedded sqlite database via jpa/hibernate.
This combobox is supposed to contain all instances of Imp, but to also let empty selection; it's value is bound to a service bean as follows:
IObservableValue comboImpSelectionObserveWidget =
ViewersObservables.observeSingleSelection(comboImpViewer);
IObservableValue filterByImpObserveValue =
BeansObservables.observeValue(searchPrep, "imp");
bindingContext.bindValue(comboImpSelectionObserveWidget, filterByImpObserveValue
, null, null);
As soon as the user clicks on the combo, a selection (first element) is made: I can see the call to a selectionlistener i added on the viewer. My question is:
after a selection has been made, how do I let the user change his mind and have an empty selection in the combobox? should I add a "fake" empty instance of Imp to the List returned by the ImpContentProvider? or should I implement an alternative to ArrayContentProvider?
and one additional related question is:
why calling deselectAll() and clearSelection() on the combo does NOT set a null value to the bound bean?

ComboViewer.setSelection(StructuredSelection.EMPTY) will fire selection event and set "imp" to null. Combo widget selection event is only triggered when manually selected from dropdown list i think.
Use Converter
Define empty selection obj, let us say EMPTYEnum ( display empty string in label provider)
You can define UpdateValueStrategy for target-to-model and set IConverter when you bind observables.
In the converter, you can convert EMPTYEnum to null.
IConverter:
fromType: Object.class
toType: Object.class
public Object convert(Object fromObject)
{
if(fromObject instanceof EMPTYEnum)
{
return null;
}
return fromObject;
}

make LabelProvider and handle null value
public String getText(Object element) {
if (element == null) {
return "Choose one";
}
return super.getText(element);
}
insert null value at index 0 and handle empty selection
combo.setInput(yourItems);
combo.insert(null, 0);
combo.getCCombo().select(0);
combo.addPostSelectionChangedListener(new ISelectionChangedListener() {
#Override
public void selectionChanged(SelectionChangedEvent event) {
if (combo.getCCombo().getSelectionIndex() == -1) {
combo.getCCombo().select(0);
}
}
});
bind ComboViewer ...

Related

How to add validation to TableViewer?

I want to add some validation to the different cells in the JFace TableViewer.
What is the best way to do it ?
I tried to use Converter and Validator for example
// define a validator to check that only numbers are entered
IValidator validator = new IValidator() {
#Override
public IStatus validate(Object value) {
if (value instanceof Integer) {
if (value.toString().matches(".*\\d.*")) {
return ValidationStatus.ok();
}
}
return ValidationStatus.error(value.toString() +"is not a number");
}
};
// create UpdateValueStrategy and assign
// to the binding
UpdateValueStrategy strategy = new UpdateValueStrategy();
strategy.setBeforeSetValidator(validator);
Binding bindValue = ctx.bindValue(widgetValue, modelValue, strategy, null);
Does it the right way to do it for cells in TableViewer? (for example check String, int, Dates )

Strange TableView.itemsProperty Behavior?

Let's say I'll add a ChangeListener to a TableView's itemsProperty. When would the ChangeListener's changed method be called?
I tried adding to the empty List where the TableView's items points. The result - The ChangeListener's changed method didn't get called.
tableView.itemsProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue ov, Object t, Object t1) {
System.out.println("Changed!");
}
});
final ObservableList data = FXCollections.observableArrayList(new ArrayList());
data.clear();
//data.add(new Object()); don't call this yet
tableView.setItems(data);
data.clear();
data.add(new Object());
tableView.setItems(data);
However, I also tried adding to an empty List and then let TableView's items point on it. The result - The ChangeListener's changed method got called.
tableView.itemsProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue ov, Object t, Object t1) {
System.out.println("Changed!");
}
});
final ObservableList data = FXCollections.observableArrayList(new ArrayList());
data.clear();
data.add(new Object());
tableView.setItems(data);
data.clear();
data.add(new Object());
tableView.setItems(data);
I looked it up on http://docs.oracle.com/javafx/2/api/javafx/scene/control/TableView.html#itemsProperty() but it only says "The underlying data model for the TableView. Note that it has a generic type that must match the type of the TableView itself."
I'm asking this because I might miss out on some other important circumstances.
A not fully documented fact (aka: implementation detail) is that ObjectProperty only fires on
!oldValue.equals(newValue); // modulo null checking
That's critical for a list-valued object property, as lists are specified to be equal if all their elements are equal. In particular, all empty lists are equal to each other, thus replacing one empty list by another empty list as in your first snippet will not make the property fire:
// items empty initially
TableView table = new TableView()
table.itemsProperty().addListener(....)
ObservableList empty = FXCollections.observableArrayList();
// replace initial empty list by new empty list
table.setItems(empty);
// no change event was fired!
That's nasty if your code wants to listen to changes of the content - it would need to re-wire the ListChangeListeners whenever the identity of the items value changes but can't with a changeListener because that fires based on equality. BTW, even fx-internal code got that wrong and hot-fixed by a dirty hack
And no nice solution available, just a couple of suboptimal options
use an InvalidationListener instead of a changeListener
bind (unidirectionally!) a ListProperty to the list-valued object property and listen to the latter
use an adapter that combines the above to at least have it out off the way
A code snippet I use:
public static <T> ListProperty<T> listProperty(final Property<ObservableList<T>> property) {
Objects.requireNonNull(property, "property must not be null");
ListProperty<T> adapter = new ListPropertyBase<T>() {
// PENDING JW: need weakListener?
private InvalidationListener hack15793;
{
Bindings.bindBidirectional(this, property);
hack15793 = o -> {
ObservableList<T> newItems =property.getValue();
ObservableList<T> oldItems = get();
// force rewiring to new list if equals
boolean changedEquals = (newItems != null) && (oldItems != null)
&& newItems.equals(oldItems);
if (changedEquals) {
set(newItems);
}
};
property.addListener(hack15793);
}
#Override
public Object getBean() {
return null; // virtual property, no bean
}
#Override
public String getName() {
return property.getName();
}
#Override
protected void finalize() throws Throwable {
try {
Bindings.unbindBidirectional(property, this);
property.removeListener(hack15793);
} finally {
super.finalize();
}
}
};
return adapter;
}
TableView does not have implemented the method add view documentation
My aproach is the following:
To initialize the TableView itemList:
tableX.setItems(itemListX);
you could also initialize it by using the default list of the TableView:
tableX.getItems.addAll(itemListX);
in this case it will copy the list.
And the aproach to add items dynamically:
1-If you still have a reference to itemListX:
itemListX.add(item);
this you will update the TableView since the table observes the ObservableList itemListX.
2-Else, if you dont any more:
tableX.getItems().add(item);

Search for "hidden Data" in FilteredTree

How can i search for "hidden"-Data in the Filtered-TreeView. Till now I can just search for the TreeItem's names and Filter them. Now I want to search for data stored in my TreeObjects.
Where should I attache the String I want to search for ?
I try to become more detailed:
I've a :
class TreeObject implements IAdaptable {
protected String name;
protected String Data;
private TreeParent parent; ....
Now I want to search for it's
protected String Data;
how can i get access to:
-The TreeObject
-The texfield's text of the Filtered Tree View
Here you see a Picture of the Methodes which can be Overwritten:
This is what I get wehn i use another Filter-Type:
You have to overwrite the select method of the ViewerFilter which you are setting in the FilteredTree. From the Javadoc of select:
Returns whether the given element makes it through this filter.
So in your implementation you can check the fields of your object and decide if it should be filtered out or not.
Example (don't know if this compiles)
TreeViewer viewer = filteredTree.getViewer();
viewer.addFilter(new ViewerFilter() {
#Override
public boolean select(Viewer viewer, Object parentElement, Object element) {
if (element instanceof TreeObject && filterString != null && !filterString.isEmpty()) {
TreeObject obj = (TreeObject) element;
String data = obj.getData().toLowerCase(); // Your Data field!
return data.startsWith(filterString.toLowerCase());
}
return true;
}
});

Trouble with CellList empty widget and AsyncDataProvider

I have a CellList that I'm populating with an AsyncDataProvider:
#UiField(provided = true)
CellList<PlayerDataEntity> friendCellList;
#Inject
FriendListViewImpl(FriendListController controller) {
friendCellList = new CellList<PlayerDataEntity>(new PlayerCell());
initWidget(uiBinder.createAndBindUi(this));
// if we don't set the row data to empty before trying to get the real data,
// the empty list widget will never appear. But if we do set this,
// then the real data won't show up.
friendCellList.setRowData(Lists.<PlayerDataEntity>newArrayList());
new AsyncDataProvider<PlayerDataEntity>() {
#Override
protected void onRangeChanged(final HasData<PlayerDataEntity> display) {
rpcService.getPlayers(new AsyncCallback<List<PlayerDataEntity>>() {
#Override
public void onSuccess(List<PlayerDataEntity> result) {
display.setRowData(0, result);
}
});
}
}.addDataDisplay(friendCellList);
friendCellList.setEmptyListWidget(new Label("No friends found"));
}
If I don't initially set the row data to an empty list, then the empty list widget won't show up if the RPC service returns an empty list. However, if I do initially set the row data to the an empty list, and the RPC returns a non-empty list, that data won't show up in the widget.
I must be misunderstanding some aspect of the CellList's API. What am I doing wrong?
If you know, that the total list (not just the list in the range) is empty, then you can set display.setRowCount(0), so the CellList will display "No friends found".
If your service always returns the entire list, this can be done easily like
public void onSuccess(List<PlayerDataEntity> result) {
display.setRowCount(result.size());
display.setRowData(0, result);
}
Add the following line to the method:
#Override
public void onSuccess(List<PlayerDataEntity> result) {
display.setRowData(0, result);
((CellList<PlayerDataEntity>) display).redraw();
}

How do I check for null values in a Wicket Textfield?

I have a Wicket Textfield which contains an Integer value
currentValueTextField = new TextField<IntParameter>("valueText", new PropertyModel<IntParameter>(model, "value"));
I'm attaching a custom validator to this, as follows
currentValueTextField.add(new IntegerValidator());
The validator class is
class IntegerValidator extends AbstractValidator<IntParameter> {
private static final long serialVersionUID = 5899174401360212883L;
public IntegerValidator() {
}
#Override
public void onValidate(IValidatable<IntParameter> validatable) {
ValidationError error = new ValidationError();
if (model.getValue() == null) {
AttributeAppender redOutline = new AttributeAppender("style", new Model<String>("border-style:solid; border-color:#f86b5c; border-width: 3px"), ";");
currentValueTextField.add(redOutline);
currentValueTextField.getParent().getParent().add(redOutline);
validatable.error(error);
}
}
}
However if I type nothing in the textfield, my onValidate() method is not being called.
What is the recommended way to check for null values in this case?
I would also like to do range checking on the value entered.
just call
currentValueTextField.setRequired(true);
to mark the field as required and have Wicket handle null values on it's own. You can easily combine multiple validators per input field.
Any special error handling, like adding red borders or displaying of error messages can be implemented in the onError method of the form or by adding FeedbackBorders to the appropriate fields.
Override validateOnNullValue() that is false by default.
#Override
public boolean validateOnNullValue()
{
return true;
}
This is the description of validateOnNullValue() method:
Indicates whether or not to validate the value if it is null. It is usually desirable to skip validation if the value is null, unless we want to make sure
the value is in fact null (a rare use case). Validators that extend this and
wish to ensure the value is null should override this method and return
true.
currentValueTextField.setRequired(true);
Now you need to customise the error message. So subclass FeedbackPanel.
you can find more information in the following link
Add this class to your form or component
A better (and reusable) way to do this is to override the isEnabled(Component) method of the behavior:
public class HomePage extends WebPage {
private Integer value;
public HomePage() {
add(new FeedbackPanel("feedback"));
add(new Form("form", new CompoundPropertyModel(this))
.add(new TextField("value")
.setRequired(true)
.add(new ErrorDecorationBehavior()))
.add(new Button("submit") {
#Override
public void onSubmit() {
info(value.toString());
}
}));
}
}
class ErrorDecorationBehavior extends AttributeAppender {
public ErrorDecorationBehavior() {
super("style", true, Model.of("border-style:solid; border-color:#f86b5c; border-width: 3px"), ",");
}
#Override
public boolean isEnabled(Component component) {
return super.isEnabled(component) && component.hasErrorMessage();
}
}

Categories