Archive for the ‘hcOPF’ Category

hcOPF now supports XE4

Friday, July 5th, 2013

I just updated the sourceforge repo with VCL projects for XE4 with the exception of HengenOPFJVValidators (I don’t have JVCL installed at the moment). Simply define an environment variable “hcOPF” to point to the root folder for hcOPF and you should be able to compile the packages. Rt. Click on all dcl prefixed packages and choose Install from the local menu.

Enjoy!

hcOPF ReadOnly Object Attributes

Monday, October 29th, 2012

For those of you looking at upgrading to XE2 for live bindings, or XE3 to get visual live bindings I thought I would mention that hcOPF supports object binding with earlier versions of Delphi (D7 and up).  Not only that, but you’re not reliant on a black box expression engine.  Bindings in hcOPF are written in Delphi and debuggable in Delphi.  In fact, it’s quite easy to write your own mediators to support any non data aware control you want to use and best of all, since hcOPF is open source, you can modify it to suit your own needs, and there are no undocumented ’secrets’.

hcOPF automatically handles ReadOnly attributes ‘out of the box’, by assuming that the domain object is the source of the truth.  That means if an attribute on the domain object is readonly, then the mediator informs the UI control that it should also be readonly.  There are some situations though, where this is not desirable.  For instance, if you want to display some boolean values on a form when a client is selected, but in order to edit the client information, the user needs to go into the client profile form, you don’t want users inadvertently changing client information by clicking on the checkboxes.  In this scenario you can toggle the AutomaticReadOnly property of the mediator (assuming it’s implemented) to False.   AutomaticReadOnly is normally True by default, and means that the mediator will ensure the UI control mirrors the domain object’s ReadOnly attribute, or will behave as if the attribute is ReadOnly if the object itself is marked as ReadOnly.  By changing AutomaticReadOnly to False, you can control the UI control’s ReadOnly behaviour yourself.  In the example given, the checkboxes would be disabled.

I recently added a new ReadOnly field variable to ThcAttribute since I ran into a scenario where I wanted to use AutomaticReadOnly but wanted to make certain object attributes ReadOnly.  Since the ThcAttribute.ReadOnly property is determined by its MetaData, changing it for one attribute in one object instance effectively changed it for that attribute in all object instances.  Adding the ReadOnly field variable and initializing it from the attribute definition (ThcAttributeDef) effectively solved this issue.

hcOPF - using Attribute OnChange Events

Monday, October 22nd, 2012

Althought hcOPF implements automatic calculations via a ThcCalcObject registered with the object metadata, it’s not the most efficient implementation.  Since the framework has no idea of the attribute dependancies in the calculation, it calls the CalcObject whenever an attribute of the object changes.  Of course it avoids doing so, during mass object attribute changes, such as when initializing the object, or reading it from the objectstore.  Nevertheless, you may encounter situations in which you want to optimize the calculation, such as when you make a database call.

Just like a TField object, a ThcAttribute implements an OnChange event that can be used implement calculations.  This is a much more efficient mechanism, but unfortunately does not benefit from the framework’s knowledge about the calculation, and cannot therefore automatically avoid triggering the calculation event during object reads or other mass object changes, such as object initialization or resetting object attributes after writing them to the objectstore.  It also suffers from the disadvantage that the code for multiple calculations is spread out across different event handlers instead of being in one place.  That said, any good framework does not box you in, so hcOPF allows you to use either method.

If you use the ThcAttribute event to perform the calculation, make sure to subscribe to the event early enough in the lifecycle of the object in an overridden method.  For instance, subscribing to the event for each object processed in the ThcObjectList.Load() method may be sufficient for most cases, but if you create individual objects for consumption, you should subscribe to the event in the ThcObject.Initialize method instead (recommended).  Also, be sure to check objectstate before performing the calculation.  Avoid trying to access attributes while they’re being initialized, or populated.  IOW, make sure the ObjectState = osNone and remember to fire the event in any ThcObject.Read() or ThcObject.ReadAttribute() override.

For example here is a possible event handler:

procedure TMyself.HairColorChanged(Sender :TObject);
begin
  if (ObjectState = osNone) then
  begin
    FQuery.SQL.Text := Format('select HairColor from fnRandomHairColor(%d)',
            [GetTickCount()]);
    FQuery.Open;
    try
      HairColor.Assign(FQuery.Fields[0]);
    finally
      FQuery.Close;
    end;
  end;
end;

and in the Read() override:

procedure TMyself.Read(Source :ThcObjectStore; WithChildren :boolean = True);
begin
  inherited Read(Source,WithChildren);
  HairColor.OnChange.FireEvent(HairColor);  //Sender should always be the attribute
end;

hcOPF - Configuring XE2 for Compilation

Friday, May 4th, 2012

It is not necessary to change the DCP output folder because the defaults automatically take into account compiling for different platforms.  In the Tools - Options - Library settings if you select Win32 you will notice that the Package output directory is set to:

$(BDSCommonDir)\bpl

and the DCP output directory is set to:

$(BDSCommonDir)\dcp

which works great since the IDE is a 32 bit EXE and this folder will be on the search path so the IDE can load the design-time packages.  This provides backwards compatibility, but the moment you start compiling the same package for additional targets it becomes cumbersome.

If you select Win64 or OSX you will notice that the package output directory changes to:

$(BDSCommonDir)\bpl\$(Platform)

It’s interesting that you cannot modify the Library Path globally, so if you have a product that compiles for multiple platforms you have to add the necessary bits into each Platform’s version of the Library Path.  This is an enhancement I have suggested in QC#105378 .  Personally I have always preferred explicit specification rather than implicit, and as such I think the default Package output directory should be:

$(BDSCommonDir)\bpl\$(Platform)

and likewise the default DCP paths should be:

$(BDSCommonDir)\dcp\$(Platform)

This is sort of like defining a class as

TMyObject = class
end;

vs.

TMyObject = class(TObject)
end;

I believe consistency in usage promotes more readable and thus more maintainable code and IDE environments.  From my experience it’s also easier to manually purge your output folders, and confirm the appropriate units are being generated if a consistent directory structure is used.  If you agree, please vote for QC #105377.

In the case of hcOPF the Library Path needs to contain the following:

$(hcOPF)\Lib\D16\$(Platform)\$(Config)
$(hcOPF)\Source\Resources
$(hcOPF)\Source\Include

If you happen to notice that the Path is greyed when you add it to the dialog, don’t panic.  For some reason, the Directories dialog has problems validating Paths that contain $(Platform)  which is evident by the first path in the list

$(BDSLIB)\$(Platform)\release

also appearing in grey.  I have entered a QC report (#105375) for this, so please vote for it.

So DCP and BPL output folders are handled by default in a suitable fashion by the IDE, unless you’re like me and prefer a more uniform directory structure in which case you can change the Win32 DCP and BPL path defaults in the Tools - Options - Library dialog.  If you change these paths, packages which do not have an override value specified in their Project - Options, will output to the new default directories.

At a minimum developers need to make sure their Unit output path does not collide which means using a structure something like .\Lib\D16\$(Platform)\$(Config) as I alluded to in my previous post.  This is also handled by default if you’re creating new packages in XE2.  If you’re upgrading existing packages, make sure to set the unit output path to use $(Platform)\$(Config) as well.

One thing I find intriguing is that under Project - Options for ‘DCP output directory’ there is an entry for the Target ‘Debug Configuration - All Platforms’, yet there is not one for ‘Release Configuration - All Platforms’.  Maybe someone can explain this one to me…

hcOPF - Time to Start Monkeying Around

Monday, April 30th, 2012

I’ve put quite a bit of effort as of late getting hcOPF ready for Win64 compilation, and as part of that effort, re-factoring it to support FireMonkey.  The actual code and package changes were relatively minor when compared with trying to understand what was required.  To me this validates the design of the framework that it can be adapted rather easily to support new frameworks and platforms.

There are no new recommended guidelines for DesignTime and RunTime packages AFAIK now that XE2 supports FMX and VCL.  To complicate things further, add in Unit Scope Names, the fact that the IDE automatically renames FMX package projects when you target the OS/X, and all the conditional directive permutations, and it can drive you bananas!

On top of that, the IDE does not provide default guidance when upgrading packages from earlier IDE versions.  It does not default the unit output directory to .\$(platform)\$(config) as it does for new ones, so when you compile for one platform you overwrite your DCUs for another, and really confuse things.  For this reason, you might notice that for All configurations - All platforms hcOPF uses a unit output directory of $(hcOPF)\Lib\D16\$(Platform)\$(Config) where $(hcOPF) points to the root directory of the framework.  It would be great if there was an environment variable for the IDE version.  Then you could use something like $(hcOPF)\Lib\$(DelphiVersion)\$(Platform)\$(Config).

I am pleased to announce that hcOPF now supports FireMonkey (Win32/64 and OSX32) as well as VCL Win32/64 targets with a few caveats:

1) when compiling for the Mac or any 64 bit target you must skip compilation of the design time packages.

2) The HengenOPFValidatorsFMX/VCL packages do not support 64 bit targets since it uses the open source PerlRegEx component instead of the RegEx support present in XE and above.

3) Certain packages of course cannot be used on certain platforms like ADO and the Validators on the Mac since they have Windows specific implementations.

4) As always some packages require third party commercial libraries, such as the HengenDevExpressXXX packages which require the Developer Express Quantum Grid suite (highly recommended).

5) Win32 BPLs go into the default $(BPLDir) and those for OSX32 and Win64 go into their respective subdirs

In a future version I hope to add support for iOS as well as providing validator support for FMX.  Currently hcOPF will not compile using FPC because it does not support the implements interface delegation syntax that EMB’s compiler does.  Eventually I plan to support FPC and SQLite.

If you want to start Monkeying around with hcOPF, check out the FireMonkey SVN branch.

FireMonkey vs. VCL

Monday, March 26th, 2012

I have finally started re-factoring hcOPF to support FireMonkey and Win64.  Win64 was a breeze, but supporting FMX is proving to be a bit of a challenge.

If I was EMB, I would be trying to make FireMonkey a write once compile on many platforms solution, and it is across Linux, Mac OS/X and Windows AFAIK, if you build an FMX application.  However, I would venture that most customers are looking to port their existing VCL codebase to access more markets, or at least leverage their existing knowledge, and might be a little hesitant to bet it all on a newly released UI framework sold and supported by a single vendor (especially after CLX).

All developers know, changing code tends to break what once worked, especially when you introduce more conditional compilation (check out my QC request 94287 to make conditional compilation easier to use).  To accomplish this end, the API usable to FireMonkey applications needs to have as much in common as possible with even VCL for Windows applications because at some point a developer will use code to manipulate the controls.  If it’s possible to use the same code for both platforms, you’ve saved in not having to maintain functionally duplicate code and in dealing with conditional compilation.

The ideal scenario would be to be able to specify either a FireMonkey or a VCL form DFM in a conditional directive, and have all the UI code shared (or minimal conditional compilation).  Of course, if you want to use the advanced functionality available in FireMonkey, perhaps this approach isn’t viable.  If you just want to target Mac OS/X without having to re-write your VCL application forms, and are using FMX for basic presentation of data, this would be ideal!  You wouldn’t have to invest the same effort to determine the merits of FireMonkey as a UI replacement for the VCL because you wouldn’t need to keep two separate UI source code trees in sync while FireMonkey matures.

So far, with my brief exposure to FireMonkey with XE2 I can see a number of problems in achieving the Write Once Compile for Many Platforms concept.  FireMonkey uses .Text instead of .Caption for some control window captions (ie: TGroupBox).  So while the control class may be the same, even some simple UI code cannot be shared with a VCL application.

Even non-visual code may be a challenge to re-use.  For instance, I use GetTickCount() to time activity in hcOPF.  I want to keep hcOPF compilable for users of D7 and above.  I personally feel that my coding productivity was higher in D7 with CodeRush than the Delphi XE with GExperts and CnPack.  Part of the reason for this is the code parsing the IDE performs in the main thread.  Type in Begin incorrectly, and you can be waiting for 10 seconds while the IDE tries to figure out what is going on.  That’s pretty sad on a 6 core system…but I digress.

GetTickCount() is implemented as an inline function in the implementation section in the System unit for MacOS and Linux, and the Windows implementation is in Windows.WinAPI.  Not a big deal to add a few {$ifdefs} to handle that, but GetTickCount() as defined in System is not accessible to other units.  So in order to use it, you will have to copy the implementation from System, and expose it.

From what I have seen of XE2 Update 4, FireMonkey is still not usable from a performance standpoint for replacing a normal VCL form. On my 6 core system with a Radeon 4250 the main form appears and you can visibly see the contents of the form background and content being rendered.  In this case, the form is just 4 edit controls in 2 separate group boxes using hcOPF Object Binding to populate the controls.

I think FireMonkey is a great concept, but adoption would be faster with an API more consistent with the VCL, and I think FMX has a long way to go before it becomes a viable replacement for the VCL and X platform Delphi becomes a true reality for more than the most trivial application written from the ground up for FireMonkey.

hcOPF - WYSIWYG with the DevExpress Quantum Grid

Tuesday, November 15th, 2011

When using the DevExpress grid with ObjectLists, there is currently a limitation that you must assign a root object to the hcUIObjectBinder.BoundObject property and this root object needs to expose the ThcParentedObjectList to present in the grid as a child of the root object that exists when the root object is constructed at design-time.  It may be empty, but it must be present, and provide a ListName in order for the object inspector to provide it as a discernible option.  Hopefully sometime in the near future I will have time to make it possible to bind to a standalone object list.  Here is a code snippet that I use in the AfterConstruction override:

  FLoggedInUsers := TftEmployeeList.Create(Self);
  FLoggedInUsers.Name := 'LoggedInUsers';
  AddChild(FLoggedInUsers);

While the ThcObjectDataSource does the majority of the heavy lifting for you in terms of presenting objectlists in the Developer Express Quantum Grid, there are times in which you need to programmatically modify the domain objects and inform the grid to update itself.  This can be achieved by calling:

GridTableView.DataController.CustomDatasource.DataChanged;

or the DataChanged() method of the TcxGridMediator.

In order to ensure the domain objects are updated immediately, I always set the ImmediatePost property of all grid editors to True.

Another thing to look out for are changes that are pending in the grid that have not been committed to the underlying objects.  For this I use the following code on the TAction.OnExecute event handler in my forms:

  if GridTableView.DataController.IsEditing then
    GridTableView.DataController.Post(True);

Of course you still have to persist the data if you’re using ThcParentedObjectLists that use CachedUpdates.  By default lists do not use CachedUpdates, so the grid Posts the data when you scroll to a different row, and the object layer writes the object data to the database.  The code above also results in a database update for for the current object in such lists.

If you use these tips, using hcOPF objects with the DevExpress Quantum Grid can actually be easier in some cases than a dataset.

TcxEdit Validation

Friday, October 21st, 2011

If you assign the Min and Max Value properties of a TcxEdit descendant, the control will call the OnValidate event handler indicating there is no error, but a subsequent attempt to convert the displayvalue into the target datatype and check if the result is within the range specified will result in an exception being raised. If you’re using something like Eurekalog or MadExcept to trap all unhandled exceptions this can be a major pain in the derrier.

There are only two ways I’ve found to deal with this issue:

1) Tell MadExcept or Eurekalog not to process this exception type

2) Do not define the Min and Max values in the control. Instead let the hcOPF validator deal with this, and I populate the OnValidate event with the following code to prevent an earlier exception from being raised (this code is for a cxDateTimeEdit):

if Error then
begin
  MessageDlg('The Date Entered is not Valid!',mtWarning,[mbOk],0);
  DisplayValue := DateTimeToStr(Now);  //reset the display to a valid value
  dtpBirthDate.SetFocus;
  Error := False;  //don't raise an exception since we've handled the error
end;

hcOPF and XE2 Unit Scope Names

Thursday, September 1st, 2011

I just installed the newly released XE2 and attempted to compile my current client’s project in the new IDE so I could start using it on a daily basis.

One of the first things I needed to do was compile hcOPF for XE2.  I expected not to have any issues since I am currently using hcOPF with Delphi XE.  Much to my surprise, the compiler kept complaining that it could not find Windows.dcu - an odd error considering Delphi has been a Windows development tool for over 15 years but understandable once you know that Unit Scope Names were introduced in this release to accommodate X platform development and units were reorganized.

Unfortunately, unit names in Uses clauses have to be fully qualified (winapi.Windows in this case) and this means using a whole bunch of {$ifdefs} for XE2 to provide fully qualified unit names.  It’s certainly an ugly solution, but I am happy to say that hcOPF now compiles under XE2 with the exception of the DevExpress package which must wait for a vendor code update.  The latest changes are in the SVN repo.

hcOPF - Object Lifetime Management

Monday, June 13th, 2011

In more complex application scenarios, especially multi-threaded ones, it is difficult to determine when an object can be safely destroyed. The last thing you want to do is prematurely free an object that is referenced elsewhere and then try to track down all the mysterious AVs that only result in specific situations that users cannot describe sufficiently for you to isolate the problem.

For example, if you have a customer object they may be referenced by an invoice being processed, and a form to change their contact information. When the invoice is completed, the developer cannot simply assume they can destroy the customer object. There are several possible solutions to such a problem:

1) clone the object whenever it is referenced. This isn’t really a good idea. It chews up more resources, and data modifications on the clones can cause last-write-wins problems with valid data being over written with stale data. One of the benefits of an OPF is requiring fewer round trips to the database to refresh data than a TDataSet approach, but this only works if all changes are made through a single instance of the object.

2) Use reference counted interfaces. This solution has a couple drawbacks that I can think of.  You cannot use indexed properties in an interface definition. For hcOPF that would mean generating individual getter and setter methods for each attribute, rather than using a generic property definition such as:

 property Name :ThcAttribute Index 12 read GetAttribute;

That would require a lot more code generation and a much more brittle object definition that’s difficult to maintain over time.  In addition, hcOPF was designed using a Class model so the framework handles the object internally as class instances rather than interface references.  This would be a significant architecture change, and I don’t think it would be worth it.

3) Use manual reference counting.  With this approach there is minimal overhead; less than even interface ref counting, but the downside is it’s completely up to the developer to implement, and it requires keeping a list of the root objects that need to be destroyed.

The latest commits for hcOPF provide additional support for monitoring the RefCount of an object, adjusting it (Retain/Release) and logging it’s destruction with CodeSite.  Unfortunately, if you’re familiar with Retain/Release you might be hoping for something like Apple’s ARC functionality but it’s just not possible since it’s implemented as part of the compiler.