Chapter 3
Adaptors - basics
|
Well, for the start first thing that should be explained is the
difference and commonality between Adaptor and DataSource terms.
Like first DataSource is commonly used property which specifies
object that will provide data. And Adaptor? Adaptor is a class which
specifies object that will provide data. Those two are the same? Well,
yes and no. DataSource is property and Adaptor is class. Which means,
DataSource always points to exact reference, but if it points to
Adaptor object, then his target is always redirected as soon as Adaptor
target changes.
But before I focus on Adaptor insides, let me introduce why this
feature is probably one of the best and most usable in
System.Data.Bindings. Common flaw of all MVC solutions is strain posed
on developer to keep track from where his widgets get their data. By
pointing your widgets on class reference you actually have to update
that reference when you want to swap original data source object. And
here comes adaptor. Instead of pointing widgets to class reference you
rather create blank adaptor.
Adaptor currentDataAdaptor = new Adaptor();
and point adaptor to that exact data source you would point widgets instead.
currentDataAdaptor.Target = currentData;
point all widgets to that same adaptor you just created. (and this
is probably something what will never be done. GUI features also
provide nifty and really simple shortcuts)
myWidget1.DataSource = currentDataAdaptor;
myWidget2.DataSource = currentDataAdaptor;
myWidget3.DataSource = currentDataAdaptor;
myWidget4.DataSource = currentDataAdaptor;
myWidget5.DataSource = currentDataAdaptor;
so what...? Most of the people are probably asking them selves here why additional variable
and additional 2 lines if they could do it simply do what last part of
code does it, but do that in beginging.
Well... yes... you could. But so could you take off your shoes and put
them back on for every step you make. So why don't you do that? If you
follow the steps with adaptor... changing all widgets to new data is as
simple as this.
currentDataAdaptor.Target = someDifferentData;
and everything is updated? Meaning... you can use Adaptor not only
localy, but even across assemblies which never reference directly. Data
binding doesn't care about that. Provide it with source object, specify
mappings and that's it. With a nifty trick using IVirtualObject you
don't even have to know which properties will be needed or what you
want to share across your assembly. But that lesson is one of the most
advanced usage cases of data bindings and will be covered in "Advanced usage of data bindings" not here.
But really best part of this is the fact one doesn't need to track
records what shows what. Even remotely good Adaptor map can simply
provide you with completely alive software where you actually forgot
how it is mapped. Beside single control point for multiple destinations
you actually need to remember less.
Although I promised that first part will not involve GUI, I have to
break that promise to be more explanatory. One nice example where and
how to use adaptors is simple window where you just want to show Name
of currently used object in background in status bar (like for example
file copy) and at the same time you provide dialog with actual progress
(which is optionally shown based on users preferences) and you simply
do following steps:
- create new Adaptor
- create label and put it on main window, set created adaptor as its datasource, set label Mappings to Name
- create dialog and set its widgets datasource to the same adaptor and now set all widgets in that dialog with correct mappings
Ok. this is it... from now you can simply ignore GUI (you are done with it, no more and never again)
- Now... This step can be done anywhere (thread, timer, blocking
thread, main application). Start method to copy files in queue. The
only change from console copy without any feedback is you settting
myCreatedAdaptor.Target = currentFile;
at the start of file copy (if you mapped progress that one will be
shown constantly too). You don't even care if progress is shown or not,
you don't care if gui is doing different jobs, you don't care if thread
you executed method in was blocking. Form is constantly responsive with
its progress feedback. Without one single Application.Invoke or one
single line of setting data besides setting adaptor.
This is a bit different, but at least it shows how progress bar can be
made. And be carefull how it never touches or cares about GUI. Well,
for now this video was just blindly recorded, so it will help you if
you skip a little.
How to make copy progress window (video)
And for actual Adaptor reference you can simply run sample3 provided in tree.
Now to explanation how adaptors work in more technical terms (not needed, but really good to know):
Remember old fashioned pointers pointer can point to pointer, which
can point to another pointer which points to data. A simple linked
list. We could always access end of the chain. Like every pointer
list, Adaptor list can have multiple start points all converging at the
same finish Target. But, in the need to be explanatory, I need to
explain basic Adaptor innards.
For end user (and even here, probably the only cases of use are writing custom
widget or time when developer needs to resolve end of
the chain for some special reason) only two existing properties impose them selves as "good to know how to use it" are Target
and FinalTarget.
FinalTarget returns exactly that. End of chain or
null if end of chain is IAdaptor type with (Target == null).
Target on the other hand is world writeable and
specifies next in chain line.
When resolving FinalTarget, Target simply checks if Target
is IAdaptor and if it is...
return ((Target as IAdaptor).FinalTarget)
and does that until it reaches end of the adaptor chain, otherwise it simply returns its own Target as result.
!!! FinalTarget CAN
NEVER be IAdaptor as it will simply return null. unless one used Advanced approach
Now, the harder to understand part. Widgets actually don’t care what
type its DataSource is (Just to brag a little, even list widgets like
DataTreeView don’t care about that. It has optimized property transfer
for default type, but if type processed is of another type, it will
simply use what it can and avoid what it can't, but more on this latter
when explaining DataTreeView). Doesn’t even care if Property with mapped
name exists in fact. By default Gtk part of System.Data.Bindings simply disables
that widget if (DataSource == null) or property with mapped name
doesn’t exists in object mapped as DataSource, which simplifies form handling.
As widgets DataSource you can specify any object including Adaptor
and as you probably saw before, you will probably only gain by specifying
Adaptor as DataSource, as it offers something static data sources
can’t. Flexibility and by flexibility, I really mean FLEXIBILITY.
Adaptor posts changes trough event messages to all interested
parties which also includes all in chain connected Adaptors. Messages
as
in normal language are “Hey, data changed” and “Hey, my target
changed”. Which simply means that all Adaptors in chain will resolve
their FinalTarget imidietly after TargetChanged message has been
dispatched. And since this is chain link it automatically sends
notification that his Target or Data changed.
Next adaptor type in line (and last for 99.99% if use) is MappedAdaptor. MappedAdaptor resolves
its data target differently. Instead of providing FinalTarget like
usual Adaptor, MappedAdaptor resolves the usual way and then after that
resolves mapped property. Here is a nice example of such data classes:
class Person {
....
}
class Company {
....
Person Manager { .... }
}
Which is following the mapping code:
Adaptor a = new Adaptor();
MappedAdaptor ma = new MappedAdaptor();
ma.Mappings = "Manager";
ma.Target = a;
So, what do we have here now? Lets say we have company1 and company2
objects pointing at two different data.
By simply pointing
a.Target = company1;
all widgets connected to either a or ma adaptors will reflect change
and display/edit new data. As easy as that. Practically same as
“xxx.xxx” type of property mapping, except that it is all part of one
same engine, which means it works the same as everything else Adaptor
based.
There is one another big difference between MS data bindings and
System.Data.Bindings+Gtk.DataBindings. All widgets can have InheritedDataSource, which simply means
it will inherit DataSource from first container specifying its own
DataSource, but if that container specifies InheritedDataSource = true;
then it will inherit it from its parent container and so on.
By using Adaptor, MappedAdaptor, InheritedDataSource and Mapping
correctly, you can simply map gui into one big living thing. Specify
data source once and all gui will reflect it. Never connect or
disconnect. All that connection/disconnection is happening
automatically.