"Object Aware" GUI Controls - delphi

I have some business objects written in Delphi with a custom scheme of database persistence which is finally working for my needs. Ok, great. Now it's time for GUI implementations. And here begins the problems.
How to bind my object to the GUI properly?
I cannot use Data Aware controls since I isolated all data access components into the ORM layer, so I start to write some "Object Aware" controls using the RTTI unit (I'm working with Delphi 2010), but I have the feeling I'm going the wrong way...
Some ideas on how to resolve this using only the VCL controls?

You have several patterns for linking ORM with User Interface.
See for instance the Model GUI Mediator pattern. In short, you write an observer which will reflect the ORM content into the UI components, and vice versa. This has been implemented for instance in the tiOpf framework for Delphi (this link has videos).
Another approach is to map your data at runtime: you design your form just like usual, then you fill the content in the OnShow event, then the "Save" or "OK" button will validate then save the content into the ORM record. This is what is done in the main Sample application of our framework. Easy to code in this simple sample, but could lead into spaghetti code if you have a lot of fields and validation to operate.
The last approach is to let your ORM creates the form.
In our framework, you can define some UI properties about each table, in a dedicated structure. Then a single unit will create a form with all editable fields of your ORM object. Links to other records will be displayed as a combo box, booleans as checkboxes, sets as radioboxes, and so on. Then the filtering (e.g. trim a text field from spaces on left or right side) and the validation (e.g. ensure that a field value is unique or a valid IP address) is handled not in the UI part, but in the business logic itself, i.e. the ORM.
IMHO it's mandatory to keep a true multi-tier architecture. That is, the UI has to rely mostly on the business logic. For instance, data validation must be part of the ORM, not of the UI. For instance, if you decide to add a web client to your Delphi client application, you won't have to code the validation another time: it will be common to both clients, separated from the UI implementation details.

What you could do (though I have no code samples) is use a combination of
class helpers or interceptor classes
binding interfaces for single domain objects and/or domain object lists
Class helpers have the disadvantage that they are not officially supported and you cannot add any fields to the class you are helping.
Interceptor classes are simply descendant classes with the same name as their ancestor:
uses
stdctrls;
type
TButton = class(stdctrls.TButton)
end;
You can put interceptor classes in their own unit and use that whereever you want. Just make sure these units are included AFTER the standard unit, so your descendant is used at run time.
Benefit of interceptor classes:
You can continue to design your UI using standard VCL or third party controls.
You get all the advantages of descendants.
You do not need to create or install your own controls.
No need for special mapper classes or use of RTTI.
Easily (well, relatively easily) integrated into a (DUnit-) testable user interface along the lines of Julian Bucknall's article on this in the (distinct) Delphi Magazine as referred to in this question/answer: Unit-testing mouse event handlers
Pseudo sample of interceptor control with binding interface / command interface:
uses
stdctrls;
type
ICommandAction = interface(IInterface)
function IsEnabled: Boolean;
procedure Execute;
procedure Update;
end;
IBindSingle = interface(IInterface)
function GetValueFromControl: string;
procedure LoadValueIntoControl(const aValue: string);
end;
TButton = class(stdctrls.TButton, ICommandAction)
protected
function IsEnabled: Boolean;
procedure Execute;
procedure Update;
end;
TEdit = class(stdctrls.TEdit, IBindSingle)
function GetValueFromControl: string;
procedure LoadValueIntoControl(const aValue: string);
end;
implementation could be along the lines of:
function TButton.IsEnabled: Boolean;
begin
Result := Self.Enabled;
end;
procedure TButton.Execute;
begin
Self.Action.Execute;
end;
procedure TButton.Update;
begin
Self.Action.Update;
end;
function TEdit.GetValueFromControl: string;
begin
Result := Self.Text;
end;
procedure LoadValueIntoControl(const aValue: string);
begin
Self.Text := aValue;
end;

My current customer have made their own "mapper" classes in the past (before I came).
Their data objects have fields (which are objects), and you can map these fields to a control. I extended the framework by using a MVC-like approach:
edtTarraCode: TAdvEdit;
procedure TframTarraTab.InitMapping;
begin
...
Mapper.AddMapping(edtTarraCode, Controller.DataModel.tarID);
...
end;
Per control a simple "mapping" class is created:
TMappingAdvEdit = class(TBaseEditMapping)
protected
procedure InitControl; override;
procedure AppData2Control; override;
procedure Control2AppData; override;
end;
No rocket sience, and maybe better solutions are available in the mean time (this worked in D6 and lower :-) ) but it works good enough for the customer.
Btw: also a data object generator is used. So if a field changes in the database (for example tarra.tarid is changed into tareID) we get a compiler error because "tarid" does not longer exist. This works much better than "fixed string" mapping (runtime errors).

There's currently no way to do this using only VCL controls. I know that Lazarus has a set of RTTI-based data-aware controls; you might want to look at them for some basic ideas. But it's more difficult than you might think at first. For example, unlike a dataset, an object has no built-in signaling mechanism when its members' values change. Which means that unless your data-binding controls own the object completely and nothing else has access to it, it's possible for some other code to change some value and then that change doesn't get reflected in the UI.
I've heard various things from the Delphi team in the last few years about extending the object model or the RTTI model to allow for better data binding, but whatever that's about is still a few years out.

Take a look at EverClassy Dataset at http://www.inovativa.com.br. It may meet your needs. EverClassy Dataset is a Delphi dataset designed to be populated by objects instead records from a database system.
With this component you will have the chance to interoperate your domain objects with dataware componentes, what will give you great power to build your GUI.

Related

Call a procedure of a class from DWScript

How can I call a procedure from a class that is created in main Form. Can it be done like this pseudo code shows?
type
TDemo = class
procedure test;
constructor Create;
destructor Destroy; override;
end;
var
Form28: TForm28;
Demo:TDemo;
implementation
{$R *.dfm}
procedure TForm28.Button1Click(Sender: TObject);
var
prog : IdwsProgram;
exec : IdwsProgramExecution;
begin
Demo := TDemo.Create;
prog := DelphiWebScript1.Compile('Demo.Test;');
exec := prog.Execute;
end;
There is a limited RTTI exposer and RTTI connector, which allow to access Delphi classes through RTTI.
These RTTI tools haven't been explored much however, as most of the Delphi classes are not "safe" to use for scripting. By that I mean that it's easy to crash the host or leak memory, and so "raw" Delphi classes are typically unsuitable for end-user scripting (ie. end users won't have a right to error, you won't be able to offer stable debugging, etc.).
An alternative to manual exposure and strengthening of the exposed classes in event handlers of a TdwsUnit is to expose your classes as OLE Automation objects, and then you can use the DWScript COM Connector to access them. Then benefits is that to expose the automation objects, you'll usually have had to do at least minimal strengthening vs memory leaks and dangling pointers, and your automation classes will be accessible from other COM-capable environments.
As an example of RTTI going wrong, consider a fully automatically-managed VCL classes like TComponent or TCollection, if you only have raw RTTI exposure, than a script doing something like:
item := myCollection.Add;
myCollection.Clear;
item.Caption := 'hello bug';
will result in a random memory overwrite in the host application, without any safe ways to notify the script user about the potential error.
The upcoming Delphi ARC compilers may offer a way to mitigate the memory overwrites for some classes (though not all, due to the way ARC is currently implemented/circumvented for TComponent and others). Also Delphi ARC compilers are currently unsupported (for a variety of reasons, the most prominent one being I currently don't have access to them).
To do this, you first have to expose your native class to the script engine. Have a look at the TdwsUnit component. It declares a script unit that interfaces with external code. You'd drop one on your form, define the class, define its methods, and hook up event handlers on the OnEval events that call the external routines.

how to define classes inside classes in delphi?

I am in the progress of trying to learn a few new tricks in order to better organize some of the sourcecode in my units in Delphi.
I have noticed that some functions or methods I access appear to be classes inside classes, but I have not yet been successful in making a working class inside a class, although it compiles fine, I still get error messages when executing the code.
What I would like to do is to make kind of a tree with functions, procedures and values to set or get. I would be grateful if somebody could help me out a little bit with an example.
I have today some classes that are Types.
I then assign the types to a variable:
something=TSomething
and Then for something to happen I write "something.action".
My aim is to go further, and define sub-functions or/and sub-procedures.
Lets say I have three or four classes. TSnippet, TAction1, TAction2, TSubAction1, Etc.
I would like to use or assign these to a single variable and use them like:
Snippet.Action1.SubAction1.Organize(param1,param2);
Snippet.Action2.SubAction2.Returns='SomeString';
Snippet.Action1.SubAction1.SomeProcedure;
Is anybody able to help me with a useful example as in how to write code for this approach to work?
And also.. does anybydy know how such an implementation of code will affect CPYCycles needed in order to execute code versus the old fashioned method of having thousands of procedures with all different names, but more direct access (it feels like more direct access).
As of my first text was maybe a bit unclear, this follows.
I would like to make use of the editors automatic suggestions of procedures/functions available in order to simplify programming a little bit.
I started to make a Class for this, and it works great.
Consider a classname "Data". What can we do with data? We can Edit, Add, Delete, Save and Load.
Ok. This is my first Class.
Consider then another Class: "Encrypt". We can do DES, DES3, HASH, BITSHUFFLE.
I can go on with a third Class: "Compress". We can do LZW, ZIP, RAR, MP3, MPG, MP4, etc.
Instead of using these as 3 different classes, I would like to combine them in one, yet keeping them separate.
I would like to make a kind of an OwnerClass for the other classes. We can call this "MyStuff"
Whenever I type "MyStuff." in the editor, I should get up a list of "Data, Encrypt, Compress". Further, When I then choose "Compress", the next list for that class' procedures and functions will list up.
The classes may have some local variables, but the main functionality will be towards global arrays.
Maybe there are other ways of achieving this. I don't know.
My basic aim is to be able to categorize and put together routines that belong together.
This is already done in Units, but this does not help with the automatic list from the editor.
Thank you in advance.
Morten.
I think I understand what it is you're asking, after your edit.
What you're calling "classes in classes" are called properties and methods; they're other classes, variables, or procedures/functions that are declared in their containing class. The "list" you're talking about is called Code Insight, and it shows you the available properties and methods of the class you're referencing in your code at that point.
This should do something like you describe, and give you an idea of how to implement it in your own code:
unit MyStuffUnit;
interface
uses
SysUtils;
type
TEncryptionType = (etDES, etDES3, etHASH, etBITSHUFFLE);
TMyStuffEncryption = class(TObject)
private
FEncryptType: TEncryptionType;
public
constructor Create;
published
property EncryptionType: TEncryptionType read FEncryptType
write FEncryptType;
end;
TCompressionType = (ctLZW, ctZIP, ctRAR, ctMP3, ctMPG, ctMP4);
TMyStuffCompression = class(TObject)
private
FCompressionType: TCompressionType;
public
constructor Create;
published
property CompressionType: TCompressionType read FCompressionType
write FCompressionType;
end;
TMyStuff = class(TObject)
private
FCompression: TMyStuffCompression;
FEncryption: TMyStuffEncryption;
public
constructor Create;
destructor Destroy; override;
published
property Compression: TMyStuffCompression read FCompression
write FCompression;
property Encryption: TMyStuffEncryption read FEncryption
write FEncryption;
end;
implementation
constructor TMyStuffEncryption.Create;
begin
inherited;
FEncryptType := etDES;
end;
constructor TMyStuffCompression.Create;
begin
inherited;
FCompressionType := ctLZW;
end;
constructor TMyStuff.Create;
begin
inherited;
FCompression := TMyStuffCompression.Create;
FEncryption := TMyStuffEncryption.Create;
end;
destructor TMyStuff.Destroy;
begin
FCompression.Free;
FEncryption.Free;
inherited;
end;
end.
If you create an instance of TMyStuff in your code, you should be able to type MyStuff. and get the option of choosing Compression or Encryption. Choosing Compression should allow you to set the CompressionType property.
This should be enough to get you going. :-) Remember that Delphi includes the source code for the VCL and RTL in almost all editions, so you always have that to look at for examples. (They're not always the very best examples, but they should give you ideas on how to do things.)

Dynamically create form by name?

Is there a way to create forms dynamically by only their names;
The concept goes like this. I have a main form, and by some user selection, a number of predefined forms must be created and docked on tabitems on a pagecontols on the main form.
I do know the names of the forms and i do know when to create each one of those, but i would like to know if a better way of creating these forms by a single procedure call, and not having all of these information in my code.
Its Delphi XE3 firemonkey, on win 7.
Thanks in advance for any help
Apparently on Firemonkey Delphi doesn't automatically register form classes to be available by name, so you'll first need to add something like this to the end of the unit that holds your form class:
unit Form10;
[ ... ]
// Right before the final "end."
initialization
RegisterFmxClasses([TForm10]);
end.
This will automatically register TForm10 so it'll be available by name. Next you can use this kind of code to create a form at Runtime by it's class name:
procedure TForm10.Button1Click(Sender: TObject);
var ObjClass: TFmxObjectClass;
NewForm: TCustomForm;
begin
ObjClass := TFmxObjectClass(GetClass(ClassName));
if ObjClass <> nil then
begin
NewForm := ObjClass.Create(Self) as TCustomForm;
if Assigned(NewForm) then
NewForm.Show;
end
end;
You can only create objects when you have a class reference for it. To get a class reference for something given its string name, call FindClass. Call the constructor on the result. You may have to type-cast the result to a different metaclass before the compiler will allow you to access the constructor you want. In the VCL, you might use TFormClass, but plain old TComponentClass will work, too, since all FireMonkey objects are descendants of TComponent; the important part is that you have access to the right constructor, and that's where the one you need is introduced.
It only works for classes that have been registered. Your form classes should be registered by Delphi automatically, but if they're not, you can call RegisterClasses manually, or RegisterFmxClasses if you need to put your classes in groups.
Delphi.About.com has a VCL demonstration.

Possible to change property write methods programmatically using RTTI for creating object-aware controls?

I have a business object that I would like to "connect" to my UI better. I've seen some partial solutions for making objects data-aware, but they all involved significant changes to my business object, including an extra layer of abstraction.
I've been looking into the improved RTTI in new versions of Delphi, and it looks very interesting and useful. I'm wondering if I could use it to programmatically inject new write methods for all properties.
The way this would work is that my TEdit descendant would by given a reference to an object property when the form is built. The TEdit would then insert a reference to itself in an attribute for that property (and of course remove itself on destructor or being given another reference). The TEdit would also ensure that the write method for the property is replaced by one that notifies the TEdit of changes after calling the original write method.
Is this feasible? The big show stopper would be that injecting a new write method isn't possible, hence the title for this question.
There are also potential problems with derived properties, but it should be possible to find a solution for that.
Your question already puts you ahead of me with programming skill so I'll just add how I might approach this:
If I were to try to write something like that I'd probably start with a TList for each field in your TBusinessObject. That list would be used to indicate what needed to be updated when you needed to push out a change.
So when the TEdit is created it would add itself to a list which was associated with a piece of data in your TBusinessObject. When the TBusinessObject updated that piece of data it would then run through the list of attached objects. It would see the TEdit and, knowing it was a TEdit, would run code to update the .Text. If I attached a TCaption then the code would update the .Caption.
The TEdit, as you indicated, would need to tell the TBusinessObject when it's value was updated. I guess this is the tricky spot - you could create a new TEdit and add in a TList to maintain who it should inform when it changes. If you used the .Tag to indicate a field number in the TBusinessObject then the OnChange (or whatever event) could then call something like TBusinessObject.FieldUpdate[TEdit.Tag, NewValue] which then triggers your business logic. That, in turn, might make the TBusinessObject update other fields, which may have their own TLists to fields to update.
Preventing circular updates would require that you have a way of updating a control without triggering events. For one program I wrote I had two methods to update the control: SetValue and ChangeValue. SetValue disabled any events (OnChange, OnValidate), updated the control's value and then reenabled the events. ChangeValue simply changed the value and allowed any of the control's events to fire as required.
There are probably slicker ways to do this but hopefully this gives you food for thought.
Possible to change property write methods programmatically using RTTI for creating object-aware controls?
No, it's not possible. RTTI gives you information, it doesn't give the ability to alter types at runtime.
The big show stopper would be that injecting a new write method isn't possible, hence the title for this question
In order for you to change this at runtime there should be something similar to an event handler that you can set. It's an easy concept, but it has some runtime overhead, both in call time (it would be an indirection where a direct call would normally suffice) and in terms of required memory (each property would require an extra TEvent style field). This is easy for you to implement if you need it, but it would be harmful if the compiler automatically generated such code for all classes "just in case".
If you're thinking of somehow patching the code in memory at runtime, that's not going to work and it would be, at best, unreliable.
In this post entitled Inducing The Great Divide, Cobus Kruger talked about business objects.
The solution he cooked is essentially compliant to your requirements:
Make use of advanced RTTI features introduced in recent Delphi version.
Separation of the business logic from presentation logic.
Any PODO (Plain Old Delphi Object) will do as business object !
The magic lays in the TObjectBinding class which ties any TWinControl with any business object.
Excerpt:
TObjectBinding = class
private
fCtx: TRttiContext;
fControlType: TRttiType;
fObjType: TRttiType;
fPropFieldMapping: TDictionary<TRttiProperty, TRttiField>; // Dictionary of object Properties & corresponding Fields
fControl: TWinControl; // The control (normally form)
fObj: TObject; // Object it represents.
procedure CreateMappings;
function FindField(Prop: TRttiProperty; out Field: TRttiField): Boolean;
function FieldClass(Field: TRttiField): TClass;
// Modify these to change the rules about what should be matched.
function IsValidField(Field: TRttiField): Boolean;
function IsValidProp(Prop: TRttiProperty): Boolean;
// Modify these to change the mappings of property type to VCL control class.
procedure AssignField(Prop: TRttiProperty; Field: TRttiField);
procedure AssignProp(Prop: TRttiProperty; Field: TRttiField);
// Used from AssignField/AssignProp. Extend these to support a wider range of properties.
function GetPropText(Prop: TRttiProperty): string;
procedure SetPropText(Prop: TRttiProperty; const Text: string);
public
constructor Create(Control: TWinControl; Obj: TObject);
destructor Destroy; override;
//
procedure Load;
procedure Save;
end;
I hope that this will be a good starting point for you.

Using another forms event procedure

Is there any way to allow one form to use the event procedures from another form?
E.g. I have a form called PongForm and another called ObstPongForm. There is a ticker on PongForm and another one on ObstPongForm. Is it possible to get ObstPongForm to use the code from PongForm's 'tick' event in it's own 'tick' event? Maybe by letting ObstPongForm inherit from PongForm?
You can simply assign it by code (as long as you have access to both instances):
ObstPongForm.Ticker.OnTick := PongForm.TickerTick;
Yes, forms are just classes like any other, and Delphi supports visual inheritance, so you can call inherited methods normally.
If ObstPongForm is a specialized version of PongForm then inheritance makes sense, but be careful as ObstPongForm will inherit all visual controls from the PongForm, including whatever you may add in the future.
Also since I assume you already have both forms, making one inherit from another is doable but requires some manual DFM editing, mainly changing the
Object ObstPongForm: TObstPongForm
to
Inherited ObstPongForm: TObstPongForm
If the code you want to reuse may be needed in several unrelated forms, then moving the code to a common unit used by these forms may be the best solution
It would be better style to have both of the forms call another class that implements the logic used by both. If you're writing all your program logic in your OnTimer event handler, you're heading down a bad road that many delphi programmers take years to realize was a bad idea
So one form needs to call your method, it does it like this:
procedure TForm1.DoSomething;
begin
DataModule1.LogicMethod;
end;
Elsewhere there is a timer...
procedure TForm2.Timer1Timer(Sender:TObject);
begin
DataModule1.LogicMethod;
end;
And then the method itself:
procedure TDataModule1.LogicMethod;
begin
// Everything that you used to have in Timer1Timer goes here, except the setting of
// UI properties in Form1 which is kept in Form1:
Inc(FCounter);// stupid example.
//
if Assigned(FOnResults) then
FOnResults(Self, FCounter, FDataObject1);
// Form2 is connected to FOnResults event, and stores the
// result in the UI somewhere.
end;
Event handlers are just normal procedures. If your ObstPongForm tick handler has additional code that it needs to run in addition to the PongForm's code, then you can call the PongForm's tick handler manually when needed, eg:
uses
..., PongForm;
procedure ObstPongForm.TickHandler(Sender: TObject);
begin
...
PongForm.TickHandler(Self);
...
end;

Resources