пятница, 28 марта 2008 г.

Making objects "UI'ish".

I'm here again to describe another challenge that I've come across (and successfully solved).

This challenge came up from the fact that my project used some functionality of another parallel project (let me call it “parental project”), which was built on .Net 2.0. Nobody thought of implementing objects in WPF-friendly way of course, why should they? So here is the point - I've got a bunch of classes that I want to render and edit in WPF UI. I want to use data binding. I want to write in elegant way Paul Stovel calls Binding Oriented Programming (here and here). I don't want to write wrappers or inherit from that objects (let’s call them “raw”) every time I come across this case.

And then I've recalled a little hook rock-star Josh Smith found to refresh UI. It is described here. Thiss code solved one huge problem – lack of refresh when you bind your UI to the whole object (in spite of it was implementing INotifyPropertyChanged (INPC) interface). So I've taken that solution and expanded it a little to fit my needs.

The essence of that hook is substitution of wrapper object in place of an expected one. This wrapper raises PropertyChanged event every time UI tries to edit some property and renew itself (the wrapper) forcing UI to refresh. This wrapper is injected in UI via binding converter.

In my case there can be no INPC implementation in an object. Idea, described above, plus the generic type argument are my enhancements to Josh Smith's BuisnessObjectHolder (ObservableObjectWrapper in my version):

This class is a descendant of base class implementing INPC - ObservableObject, that was described by Josh here.

There are 2 internal methods - SetValueProperty и GetValueProperty. They are used by binding converter... later on.

Wrapper accepts a wrapping object in constructor or in Value setter. Then it gets property descriptors list by means of type descriptor and caches them. And it subscribes to a PropertyChanged event in there is one.

So now it is time to describe binding converter that substitutes our wrapper in UI or wrapped object property values, that can be obtained by name in ConverterParameter.

Converter works in the following way:

  • In a forward process (binding source => binding target) it checks if there is a created wrapper for an object (BindingSource) and in case wrapper is not exist yet it creates a new one. It returns the value in UI depending on received parameter: ConverterParameter = null - wrapper; ConverterParameter = propertyName - a value of the property of wrapped object with name = propertyName, by means of GetValueProperty() method from wrapper;

  • In backward process (binding target => binding source) it checks ConverterParameter value and if it is not null calls SetValueProperty() method in wrapper with ConverterParameter and value, recieved from UI, as method parameters.

UI is forced to refresh by the Value_PropertyChanged method (that raises PropertyChanged on the whole object), that is triggered by SetValueProperty() method.

Please note, that first we need to set wrapped object to null and restore it after the first call to SendPropertyChanged. It is necessary to force WPF binding engine to refresh as every time it gets PropertyChanged notification it checks the values and rejects update if it finds them equal.

And now when solution is almost ready one little question comes up - how do I create my generic converter without code-behind in Xaml? And again it struck to me that the guru already wrote about it - Mike Hilberg's post on limited generics support in Xaml. The essence of this approach is to use markup extension to create generic types in Xaml. Here is a slightly modified code of main markup's method:

And at last whole scenario assembled one:

One of disadvantages is you can't reference to this converter from StaticResource anywhere except StaticResource in Style in resources. Otherwise you will have to create this converter every time you use it in every binding.

PS: another approach was demonstrated by Paul Stovell. It uses custom typedescriptor and wrapper that can simulate fake properties and interfaces. This approach is useful not only in WPF but in other frameworks too! Here is the original article - http://www.paulstovell.net/blog/index.php/runtime-ui-binding-behavior-ieditableobject-adapter/

must read.

Good luck!

Комментариев нет: