[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?

Just a short question is all this buffering needed because of undo/redo?
Or is there a deeper reason behind it?

Tom

Daniel Krügler schrieb:
> 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
>