Saturday, April 30, 2011

Data Bindings

I just published a post about my Delphi data binding implementation on Delphi-Praxis.

Since it is german here is the translation:

Those who already worked with .Net most likely know and like them: data bindings
You can bind almost everything. Like showing collections of objects in tree-, list- or gridviews or editing properties of objects in text-, check- or comboboxes. You can even change the visibitiy or the appearance of controls with them.

You can read about how it works exactly on the MSDN.

So what? In Delphi we have dataset, datasource and those db controls to do this!
True, but there is just one catch: you cannot just add a foo object to them. True, there is TSnapObjectDataset or special controls to do that. The problem is that you have to use those special controls or components and you have to inherit your bindable objects from some base class.

So I took a closer look at how .Net does it and implemented it in Delphi with the following goals in mind:
  • standard out of the box components have to be supported (i.e. TEdit)
  • little implementation necessary for bindable objects
  • little source code to actually bind objects

Ok, lets start. What do we need?

A binding always requires a source and a target (both objects) and the names of the properties that are bound.
For our first example we create some simple class that got 2 properties:

type
  TSettings = class
  private
    FCaption: string;
    FColor: TColor;
    procedure SetCaption(const Value: string);
    procedure SetColor(const Value: TColor);
  public
    property Caption: string read FCaption write SetCaption;
    property Color: TColor read FColor write SetColor;
  end;

implementation

procedure TSettings.SetCaption(const Value: string);
begin
  FCaption := Value;
end;

procedure TSettings.SetColor(const Value: TColor);
begin
  FColor := Value;
end;

We already have those explicit setter methods - we will need them later. For a first example we could get rid of them but since the auto completion created them we just let them where they are.

So, what are we doing with out TSettings object? We create 2 bindings to set the caption and the color of our MainForm.

procedure TMainForm.FormCreate(Sender: TObject);
begin
  FSettings := TSettings.Create;
  FSettings.Caption := 'Binding demo';
  FSettings.Color := clGray;
  TBinding.Create(FSettings, 'Caption', MainForm, 'Caption');
  TBinding.Create(FSettings, 'Color', MainForm, 'Color');
end;

It only requires using the System.Bindings unit. Also we do not need to take care about freeing the objects as long as one of the bound objects if inherited from TComponent (in our case that is the MainForm).

Lets start the application and see what happens. Caption and color of the MainForm are set according to the values in our TSettings object. Awesome, no?
Well, setting those values would have done that too... Sure, but that's not the end of the story!

Lets put a button on our form and set the properties of our settings object there.

procedure TMainForm.Button1Click(Sender: TObject);
begin
  FSettings.Caption := 'Current time: ' + DateTimeToStr(Now);
end;

That is not doing anything for now but we are not done yet. Let's go back to out TSettings class and modify it.
We inherit from another class that already implements the INotifyPropertyChanged interface. Nicely taken from .Net of course. Ah, now we need the setter methods for the properties.

So that is how the modified class looks now:

type
  TSettings = class(TPropertyChangedBase)
  private
    FCaption: string;
    FColor: TColor;
    procedure SetCaption(const Value: string);
    procedure SetColor(const Value: TColor);
  public
    property Caption: string read FCaption write SetCaption;
    property Color: TColor read FColor write SetColor;
  end;

implementation

procedure TSettings.SetCaption(const Value: string);
begin
  FCaption := Value;
  DoPropertyChanged('Caption');
end;

procedure TSettings.SetColor(const Value: TColor);
begin
  FColor := Value;
  DoPropertyChanged('Color');
end;

Now if we click the button the caption of the MainForm changes. Nice, but still not rocking that much, isn't it?

Well, ok. Let's throw some edit on our form and bind that to the caption of the MainForm.

TBinding.Create(FSettings, 'Caption', Edit3, 'Text');

Wow, now the caption is displayed in the edit but it's doing nothing when I type.
Correct, we are missing something. The unit System.Bindings.Controls has to be used as the last unit in the interface section (or to be more precisely after the units with the controls we wanna bind to).
This unit contains subclasses to some standard VCL Controls with the same name that implement the INotifyPropertyChanged interface. Using this unit after the other VCL units makes those classes the ones that are used when the controls are created. That means you don't have to place special controls on your form to use data binding. Just the normal out of the box ones.

Let's start the application again and type something into our edit. Awesome, the caption of the form changes!

To make it clear:
  • no special controls needed (only the subclassing of the controls that you want to be bindable to)
  • no special events needed
  • can use TObject descendants for read-once sources (only implementing of the interface if necessary to notify on changes)
  • little source code to actually bind objects, components and controls

What is missing so far is binding collections to other components but this is already in the works. So you will be able to bind your lists and collections to controls like TListBox or even a TVirtualStringTree.

Your feedback is welcome as always.

The sample and all source code can be downloaded here.

Sunday, April 17, 2011

Yield return and Delphi

Well, some may know that Sergey Antonov already came up with some implementation on the yield functionality for Delphi in 2007. It was pretty impressive but for my taste it involved to much asm stuff and it was before Delphi had generics.

So what if we could do something like this?

for i in Power(2, 10) do
begin
  Writeln(i);
end;

Well, you can! All you need is define the function as follows:

function Power(ANumber, AExponent: Integer): IEnumerable<Integer>;
begin
  Result := TDelegateEnumerable<Integer>.Create(
    procedure
    var
      i, k: Integer;
      Result: Yield<Integer>;
    begin
      k := 1;
      for i := 1 to AExponent do
      begin
        k := k * ANumber;
        Result := k;
      end;
    end);
end;

Looks pretty much simple and straight forward, eh?

So what happens there? Well basically it creates some TEnumerable<T> descendant which takes a TProc as parameter and executes it when GetEnumerator is called. Well but thats only half the truth. The real magic happens when you assign a value to that Yield<Integer> variable. And that's basically a record with some implicit operator overload which makes the worker thread or fiber (for now you can only set that with a compiler switch in the Collections.Yield.pas) continue and make the Enumerator do his MoveNext and provide the value for the GetCurrent.

I also implemented support for Alex Ciobanu's awesome collection library (you can also set that up with a compiler switch in the Collections.Yield.pas). Then you can do something funny like this (provided that you set the result type of your function to IEnexCollection<T>):

for i in Power(2, 10).Reversed do
begin
  Writeln(i);
end;

Ok, you might say, I can do all that with getting my values and putting them into some list and I can easily go through it with any loop I want. True, but then you will either have to wait until your calculation and processing is done and the list is generated. And also you have to carry around the list and the elements in it. What if you just want to set up some parameters and calculate the result later? Well with that implementation you can since the enumeration procedure is just called when GetEnumerator is called.

So, pretty cool, no? What do you think?

P.S.: The full source code is available on google code as always.

Thursday, April 14, 2011

Delphi sorcery project on Google Code

Ok, I just created a project on google code and added the first files.

It's a first version of a data binding class which includes a multicast event implementation and a class to add lifetime management to objects that are not inherited from TComponent.

I also added a small sample to show how easy it is to bind properties to controls with just one line!

Some people might call subclassing the VCL controls and adding that unit as the last in the interface uses a hack... I call it using the force for the greater good. 8)

Wednesday, April 13, 2011

Initialization

Well ok, I finally made my own blog...

I will post here about Delphi related topics and programming stuff in gereral. Mostly about things that my coworker refers to as "magic" or "voodoo" hence the name of the blog. :)