[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Newsgroup Home]
|
[news.eclipse.platform.rcp] Re: [DataBinding] Does there exist a BufferedObservable?
|
- From: Daniel Krügler <dsp@xxxxxxx>
- Date: Fri, 11 Sep 2009 08:48:36 +0200
- Newsgroups: eclipse.platform.rcp
- Organization: EclipseCorner
- User-agent: Thunderbird 2.0.0.23 (Windows/20090812)
Matthew Hall wrote:
Daniel, see responses below:
Daniel Krügler wrote:
JGoodies databinding provides two useful concepts among others -
BufferedValueModel and PresentationModel - where I currently
found no counter parts in jface databinding, even though I'm
sure they could be easily built on top of the API. Before I roll
my own implementation I would like to ask, whether they are
already provided, but possibly with a different name (which would
be not surprising to me, because jface databinding clearly and
intentionally defines it's own concepts).
Here a short description of what I mean:
A BufferedValueModel is actually some IObservableValue which holds
a reference to another IObservableValue (like a delegate). We could
represent it by the following interface
interface IBufferedObservableValue extends IObservableValue {
void triggerCommit();
void triggerFlush();
}
Any modifications on the wrapper *only* act on the wrapper (which
has it's own value element), and *no* delegation happens to the
wrapped observable. But if anyone invokes triggerCommit() on the
IBufferedObservableValue, then the internally hold observable's
value is set with the current value of the buffer. On the other
hand, if triggerFlush() is used, the buffer is reset to the value of
the internally hold observable - quite simple but useful.
After having written this description out of my head I tried to code
it down and found out that I gave some misleading descriptions :-(
Here a corrected form:
1) BufferedValueModel: Like in the following hierarchy:
public interface IBufferedObservable extends IObservable {
// Observable with value type boolean.class
IObservableValue getTriggerChannel();
void setTriggerChannel(IObservableValue trigger);
// Signals whether the buffered observable returns data from
// the wrapped observable or it's copy of the data:
boolean isBuffering();
}
public interface IBufferedObservableValue extends IBufferedObservable,
IObservableValue {
// The actually wrapped observable. It's value type is
// the value type of IBufferedObservableValue:
IObservableValue getObservable();
void setObservable(IObservableValue value);
}
Where are the triggerCommit() and triggerFlush() methods I were talking
about? They are provided by the presentation model described in bullet
(2), not by IBufferedObservableValue! The trigger channel is the
"button" that ensures that the buffered value will be flushed or
committed depending on it's value (true or false). PresentationModel is
the one who's pressing the "button".
2) PresentationModel:
interface PresentationModel extends IBufferedObservable {
// Flush all buffered models/observables:
void triggerFlush();
// Commit all buffered models/observables:
void triggerCommit();
// Get the observable that corresponds to the property name:
IObservableValue getModel(String property);
// Get the buffered observable that corresponds to the property name:
IBufferedObservableValue getBufferedModel(String property);
}
I omitted some further details, but the rough picture should now be
corrected, I hope ;-)
In Eclipse DataBinding we don't have an equivalent observable, but we do
have a method of handling these situations:
1. To hold off on updating the model until a certain trigger action
occurs (e.g. the user clicks "Apply"), use an
UpdateValueStrategy(UpdateValueStrategy.POLICY_ON_REQUEST) for the
target-to-model update strategy:
bindingContext.bindValue(
WidgetProperties.text(SWT.Modify).observe(nameText),
BeanProperties.value("name").observe(person),
new UpdateValueStrategy(UpdateValueStrategy.POLICY_ON_REQUEST),
null);
When you are ready to update the model, you can either call
DataBindingContext.updateModels() (to update all bindings at once) or
Binding.updateTargetToModel() to update a single binding.
I agree that this is also some form of buffering strategy, but it is
local to one binding (which sometimes is an advantage, of-course) and
validation is also deferred according to my understanding of
POLICY_ON_REQUEST. This is an important difference, because we use
at several locations buffered models, which do validate on-the-fly,
but still don't transfer it's data until
PresentationModel.triggerCommit() is performed.
2. To hold off on updating the model until some cross-field validation
constraint is satisfied (e.g. in a date range, the start date must be on
or before the end date) you can use MultiValidator:
final IObservableValue startDateObs =
WidgetProperties.selection().observe(startDate);
final IObservableValue endDateObs =
WidgetProperties.selection().observe(endDate);
MultiValidator dateRangeValidator = new MultiValidator() {
protected IStatus validate() {
// Using ObservableTracker magic, MultiValidator knows which
// observables you access inside this method and listens for
// changes to them so that it can revalidate as necessary
Date start = startDateObs.getValue();
Date end = endDateObs.getValue();
if (start.compareTo(end) > 0)
return ValidationStatus.error(
"Start date cannot be later than end date");
return ValidationStatus.ok();
}
};
bindingContext.addValidationStatusProvider(dateRangeValidator);
// Wrap the start and end date observables in validated wrappers so that
// they only change when the validation constraints are satisfied
bindingContext.bindValue(
dateRangeValidator.observeValidatedValue(startDateObs),
BeanProperties.value("startDate").observe(eventObject));
bindingContext.bindValue(
dateRangeValidator.observeValidatedValue(endDateObs),
BeanProperties.value("endDate").observe(eventObject));
Thanks, this looks very cool! Nevertheless, I assume that I need
at least some form of adaption to the jgoodies binding for easier
adaption of the new binding.
A Presentation model is more or less a access point for observables.
It is typically constructed with a bean and is used as the actual
API for clients that ask for observables. Clients aren't aware,
which kind of data is wrapped, they just ask for a property from a
bean (This is very similar to querying BeanProperties, but the
difference is that the PresentationModel already knows the bean,
so the client query *only* provides the property name to access the
corresponding property).
This sounds like an inverse of BeanProperties, where instead of:
BeanValueProperty {
PropertyDescriptor property;
public Object getValue(Object source);
public void setValue(Object source, Object value);
}
you have:
BeanPropertyAccessor {
Object bean;
public Object getValue(String propertyName);
public void setValue(String propertyName, Object value);
}
If my understanding is correct, then no, we don't have anything like
that. Could you provide some sample code to show how this would be useful?
I wasn't very clear in my first descriptions and even made some
blatant errors describing the BufferedValueModel and PresentationModel.
So one difference is that PresentationModel is more or less a map
from property to observable (not to the value, even though for
convenience reasons they are also accessible). Let me give a sketch
in which way we use PresentationModel's and BufferedValueModel's in
our code:
1) Per service we get some simple value object (POJO):
class SomeDTO {
....
}
2) We currently need to wrap this POJO in a Bean, that simply forwards
all properties of the POJO but sends proper Java Bean property change
events (I'm very impressed that JFace databinding allows direct usage
of POJO's!).
3) Before entering the UI layer, the beans are wrapped as presentation
models:
PresentationModel pres = new PresentationModel(bean);
4) The UI layer gets the presentation model(s) and simply binds it's
observables "by name" to the corresponding widgets:
Text txt = new Text(parent, ...);
...
BufferedModel firstName = pres.getBufferedModel("firstName");
...
BindingFacade.bind(txt, firstName);
They are actually not aware of the direct nature of the complete
bean. They only need to now that there are some properties to
bind to widget's.
5) Especially inside a local scope (dialog/wizard) it's nice to
simply say on Ok-pressed: pres.triggerCommit() and on any cancelling
step (either cancel-pressed or undo) triggerFlush() which unrolls
the current changes back to the original ones.
Does that give a somewhat clearer picture?
Finally let me add one further remark: In the jgoodies databinding
framework you don't always need to have an individual observable
just to observe changes of some characteristic property: E.g. I
can add or remove change listener's to the buffering state of
the IBufferedObservableValue without access to the actual observable,
because in this case I only need the change deltas in the property
observer and nowhere else. I sometimes miss that fact in Jface
databinding, although it (usually) provides what I need: With further
wrapper techniques I could provide a *constant* view onto the
buffering state or the trigger channel (jface has ConstantObservable)
which do only exist such that clients can register
IValueChangeListener's on them.
Thanks for your interest,
Daniel