I have a custom component with some published properties which have been used for a while in many projects. I want to make some particular changes to this component which requires removing these old properties and replacing them with new ones. Or otherwise, I'm not necessarily removing the properties, but let's say I just simply change the name of a property from PropName to MyPropName instead. Well, the next time any project using that component is opened, it will not be able to find PropName.
Is there any way to automate conversion of this? Or is this something people will have to do manually? What would be the proper way to maintain component property values when the names of those properties are changed?
And I mean just in the DFM code, not necessarily inside the source code.
You can use the DefineProperties extension point to help migrate your .dfm files.
type
TMyComponent = class(...)
private
procedure ReadPropName(Reader: TReader);
protected
procedure DefineProperties(Filer: TFiler); override;
published
property MyPropName: string read ... write ...;
end;
procedure TMyComponent.DefineProperties(Filer: TFiler);
begin
inherited;
Filer.DefineProperty('PropName', ReadPropName, nil, False);
end;
procedure TMyComponent.ReadPropName(Reader: TReader);
begin
MyPropName := Reader.ReadString;
end;
This will allow your new component to read in old .dfm files with the old property name. When the .dfm file is written again, the new property name will be used.
Note that such a technique results in the component being able to read .dfm files containing either the old property name or the new property name so you can migrate in a gradual fashion if you wish. Once you have migrated all your .dfm files then it would be worth removing such code for the sake of tidiness.
The Delphi documentation covers this subject area, albeit from a slightly different perspective, in the Storing and Loading Unpublished Properties section of the Component Writer's Guide.
Related
I have some code that works properly at runtime but not in the IDE. I am unable to find any documentation about how the loading of components is different that helps me.
I have developed some components that define resources. I have also developed a custom TDataModule descendant that holds these resource definitions. As I want to be able to share the definitions between projects I have a simple TComponent descendent that can embed a TDataModule within it - the idea being that dropping the appropriate TComponent on a form or data module includes all of the resource definitions on the embedded TDataModule.
This appears to be working as expected at run time, but not in the designer.
I will try to explain the operation as concisely as possible. There is lots of code involved but hopefully the following is enough to understand what's happening.
There is a global TDictionary for registering the resource definitions. The definitions are registered using a Binary UUID (a 16 byte array) as the key. The UUID of the resource is set as a string representation (for ease of streaming). When the string value is set the setter method checks on itself and upwards to see if it or any of the Owner TComponents match csLoading in ComponentState and if any do the string value is saved temporarily. If not the resource definition registers itself in the global dictionary. (This is necessary because a random UUID is assigned in AfterConstruction at which point the csLoading flag is not set and so the parents must be checked).
When a resource definition is fully streamed in the Loaded call the temporary string is used to set the UUID and the resource is registered.
The TComponent that includes a TDataModule of definitions is based on a root class which has the following template code:
type
TITIODataResourcesLoader = class(TDataModule)
...
end;
TITIODataResourcesLoaderClass = class of TITIODataResourcesLoader;
TITIOResourceInclusion = class(TComponent)
protected
_pResources: TITIODataResourcesLoader;
class function _GetModuleClass(): TITIODatResourcesLoaderClass; virtual; abstract;
public
destructor Destroy; override;
procedure AfterConstruction(); override;
end;
destructor TITIOResourceInclusion.Destroy;
begin
FreeAndNil(Self._pResources);
inherited;
end;
procedure TITIOResourceInclusio.AfterConstruction;
begin
Self._pResources:=Self._GetModuleClass().Create(Self);
inherited;
end;
I have component editors for many of the resources which allow selection of a resource by enumerating the Values in the global TDictionary for that class of resource. So, for example, one of the resource definition types is the definition of a database table and another of the resource definition types is the definition of a database table join. In the component editor for the definition of the join the user can chose which tables to join by selecting any of the tables that are registered in the global TDictionary.
In a test project that has a TITIODataResourcesLoader which defines a number of table types, and a TITIOResourceInclusion that includes another TITIODataResourcesLoader (which is in a different BPL file) then at runtime the join editor (which is a VCL Form) will show all of the tables, but in the designer only the tables which are defined on the test project's TITIODataResourcesLoader are available. If I open the TITIODataResourcesLoader included in the BPL file then the resources on that are registered and selectable in the designer on the test project's TITIODataResourcesLoader.
So it appears that at runtime the streaming of the TITIOResourceInclusion does not cause the components in the included TITIODataResourcesLoader to be registered, but at runtime it does work as expected.
So my question and confusion is: How is the streaming of components different in the Designer?
I asked "How is the streaming of components different in the Designer?"
I added Application.MessageBox calls to entry points of methods in my TDataModule derived class and I can confirm that at run time we see the following:
TOuterDM.Create
TInnerDM.Create
TInnerDM.Loaded
TInnerDM.AfterConstruction
TOuterDM.Loaded
TOuterDM.AfterConstruction
Which is a normal order for a component with a DFM file.
However in the Designer we get:
TOuterDM.Create
TOuterDM.AfterConstruction
TInnerDM.Create
TInnerDM.AfterConstruction
TOuterDM.Loaded
Which indicates that instead of calling Create the Designer is calling CreateNew for both the Outer and Inner data modules, and then streaming the Outer one but not the inner one. I haven't checked the source code but I expect it's because the designer is only streaming for the 'root instance'
I have managed to get the behaviour I want by adding this code to my TDataModule derived component:
procedure TITIODataResourcesLoader.AfterConstruction;
begin
// Original code for the deferred load
if(not(csDesigning in Self.ComponentState)) then
begin
// Because this is a component with a DFM file, AfterConstruction is called AFTER
// the streaming is complete
Self._LoadDeferredResources();
// Any 'OnCreate' handler is called AFTER we have initialised ...
end
else
begin
// this code added to stream in the designer
if(Self.Owner is TITIOResourceInclusion ) then
InitInheritedComponent(Self, TDataModule);
end;
inherited;
end;
TMS's FormSize component saves aForm's size and position in an .ini file. This file's path is stored in the component's SaveName property. I would like to assign FormSize.SaveName to a file in the user's AppData folder. I can find the AppData path in my source code.
Does anyone know where (in my code) I assign the AppData path to FormSize.SaveName? I am thinking the FormSize component is created, and a default SaveName initialized, BEFORE aForm is created. In other words, FormSize loads the config file BEFORE aForm's FormCreate event fires; assigning a value to FormSize.SaveName during aForm.FormCreate is too late.
Thanks, as always.
The adjustment of the form is done in the Loaded method of TFormSize, not when you change the SaveName property (although it has been read from the DFM before).
If you set the properties SavePosition and SaveSize to false during designtime, there will nothing be loaded at runtime. In that case you can manually load and save the settings at a convenient place in your code by calling LoadFormSettings and SaveFormSettings.
I'd expect SaveName to be stored in the .dfm file, so it should be assigned to the component at load up.
If you want to determine the save name in code, it should probably be early in the cycle. I just checked a few possibilities:
In the form's constructor (override), before the call to inherited;
in the form's constructor (override), after the call to inherited;
in the form's FormCreate event handler;
in the form's Loaded procedure (override), before the call to inherited and
in the form's Loaded procedure (override), after the call to inherited.
Possibilities 4 and 5 worked as expected. 3 and 2 did nothing and 1 caused an AV. So David's suggestion seems to be fine.
"assigning a value to FormSize.SaveName during aForm.FormCreate is too
late."
I had a similar requirement to modify a component's property owned by a module. The standard "Create" event was too late given the loaded property was already in effect.
Properties persisted in the DFM are assigned (or cached) during the call to the protected virtual ReadState procedure. Conventionally, cached properties are activated during the protected virtual call to Loaded. Both ReadState and Loaded can be overridden.
In my case, I wanted to make sure TADOConnection's Connected property was false in the release build. During development, the component's property is normally true given design needs of dependent data sets.
It was a pain having to set the property to false prior to checking-in the code for subsequent builds/deployment. So instead, I overrode the Loaded method and hacked the streamed property value to false.
interface
type
TMyDataModule = class(TDataModule)
MyAdoConnection: TADOConnection;
protected
procedure Loaded; override;
end;
implementation
type
TADOConnectionHack = class(TADOConnection) end;
procedure TMyDataModule.Loaded;
begin
TADOConnectionHack(MyAdoConnection).StreamedConnected := False;
inherited Loaded;
end;
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.
I am looking for a way to provide a ListSource to a TDBLookupComboBox in delphi without having an actual table on the database server to draw that list from. The DataField for the Combo Box is a 1 character field that contains a coded value such as 'A' = 'Drivers License', 'B' = 'Passport', 'C' = 'Library Card', etc. That is to say that the table only contains A, B, or C. The application is responsible for Displaying 'Drivers License' in the GUI. Normally a database might have a look up table but this database does not and I can not add one. My idea is that the DataSource and ListSource for a DB Look-up control do not have to be the same database, so if it were possible to define a small table in my form that contains the look-up data then I could use that an not require a real database table.
Does anyone know of a delphi component that allows a TDataSet to be defined on a form without having any actual data files behind it?
I know there's different in-memory dataset. Delphi comes with TClientDataSet, which you can use the way you want. You have to deploy the midas.dll with your executable in order to work, or you must include the the MidasLib in your uses clause in order to statically link this library in your executable (no midas.dll needed at runtime).
To get what you want from the TClientDataSet, you can create fields and:
store the records in a xml file (for example with another auxiliary tool you made). At runtime load the data with the LoadFromFile method of the TClientDataSet. Additionally you can store this xml as a resource with the $R directive and manipulate this resource at runtime to feed your ClientDataSet with the contained data, to prevent the deployment (and possible modification) of the xml file with your exe.
use the CreateDataSet method and insert/populate records with what you want at runtime
code sample:
procedure TFrom1.Init;
begin
cdsIDType.CreateDataSet;
cdsIDType.InsertRecord('A', 'Drivers License');
cdsIDType.InsertRecord('B', 'Passport');
//etcetera.
end;
An alternative solution is to use TComboBox rather than TDBLookupComboBox. Use a TDictionary to define a simple in memory lookup.
type
TMyForm = class(TForm)
MyComboBox: TComboBox;
MyDataset: TSimpleDataSet;
procedure MyComboBoxChange(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
ComboLookup: TDictionary<string, Char>;
end;
implementation
{$R *.dfm}
procedure TMyForm.FormCreate(Sender: TObject);
var
Key: string;
begin
ComboLookup := TDictionary<string, Char>.Create;
ComboLookup.Add('Drivers License', 'A');
ComboLookup.Add('Passport', 'B');
ComboLookup.Add('Library Card', 'C');
for Key in ComboLookup.Keys do
begin
MyComboBox.Items.Add(Key);
end;
end;
procedure TMyForm.MyComboBoxChange(Sender: TObject);
begin
// This may be wrong didn't bother to look
//up the correct way to change a field's value in code.
MyDataset.Fields.FieldByName('IDCard').AsString := ComboLookup[MyComboBox.Text];
end;
You could use TComboBox.Items.AddObject instead of a separate lookup table but you'd have to create a wrapper class to store a char as a TObject or use Chr to convert it to an integer then cast it to TObject but a the above is simpler in my opinion.
Use a TClientDataset and define the fields, then connect to a datasource.
In the oncreate event of the form do this:
execute the createdataset method of the clientdataset and then populate it with the A,B,C data.
If you use the jvcl, what you want can be accomplished without involving a dataset. Just use a TjvDBComboBox, use the Items property to set the values you want the UI to display, and use the Values property to set the actual values stored in the database.
Well, my problem is as follows:
I have a Delphi 5 application that I'm essentially porting to Delphi 2010 (replacing old components with their latest versions, fixing the inevitable Ansi/Unicode string issues, etc.) and I've run into kind of a hitch.
Upon creation of one of our forms, an access violation happens. After looking it over, I've come to the conclusion that the reason for this is because one of the setters called in Create attempts to change a property of an object on the form that hasn't been created yet.
I've trimmed it down a little, but the code basically looks like this:
In form declaration:
property EnGrpSndOption:boolean read fEnGrpSndOption write SetGrpSndOption;
In form's Create:
EnGrpSndOption := false;
In Implementation:
procedure Myform.SetGrpSndOption(const Value: boolean);
begin
fEnGrpSndOption := Value;
btGrpSnd.Visible := Value;
end;
By tossing in a ShowMessage(BooltoStr(Assigned(btGrpSend), true)) right before btGrpSnd.Visible := Value, I confirmed that the problem is that btGrpSnd hasn't been created yet.
btGrpSend is an LMDButton, but I'm pretty sure that isn't quite relevant as it hasn't even been created yet.
While I realize I probably should only assign a value after confirming that the control is assigned, this would just result in the value set in create not being set to the actual control.
So what I want to do is find a way to make certain that all the controls on the form are created BEFORE my Create is run.
Any assistance in doing this, or information regarding how Delphi creates forms would be appreciated.
It worked back in Delphi 5, so I imagine the cause of this should be mentioned somewhere among the lists of changes between versions. Delphi 2010 is quite a bit newer than Delphi 5 after all.
Like Tobias mentioned (but advocates against) you can change the creation order (right at the form at change the creation order).
But you can also in the setter method check if the form is creating (csCreating in form.componentstate). And if it is you have to store that property value yourself, and handle it in AfterConstruction.
From your comment that you're getting an AV when placing it at design time, that means there's a problem with the control itself and it hasn't been properly ported forward. To reproduce it at runtime under controlled circumstances, you need to write a little program like this:
Make a new VCL app with a single form. Place a TButton on the form. On the button's OnClick, do something like this:
var
newButton: TLMDButton;
begin
newButton := TLMDButton.Create(self);
newButton.Parent := self;
//assign any other properties you'd like here
end;
Put a breakpoint on the constructor and trace into it until you can find what's causing the access violation.
EDIT: OK, from looking at the comments, I think we found your problem!
A form's subcontrols are initialized by reading the DFM file. When you changed your control to a TCustomForm, did you provide a new DFM to define it? If not, you need to override the form's constructor and create the controls and define their properties manually. There's no "magic" that will initialize it for you.
Your Create is always called first, before the ancestor constructor. That's just how constructors work. You should be able to call the inherited constructor before you do the rest of your initialization:
constructor MyForm.Create(Owner: TComponent);
begin
inherited;
EnGrpSndOption := False;
end;
However, there's a better way of indicating what it is you're trying to make happen. Your class is loading properties from a DFM resource. When it's finished, it will call a virtual method named Loaded. It's usually used to notify all the children that everything is ready, so if any of them hold references to other children on the form, they know it's safe to use those references at that point. You can override it in the form, too.
procedure MyForm.Loaded;
begin
inherited;
EnGrpSndOption := False;
end;
That generally shouldn't make much difference in your case, though. Loaded is called from the constructor right after the form finishes loading itself from the DFM resource. That resource tells the form all the controls it should create for itself. If your button isn't being created, then it's probably not listed correctly in the DFM. It's possible for controls to be listed in the DFM that don't have corresponding fields in the class. On the other hand, if there's a published field that doesn't have a corresponding entry in the DFM, the IDE should warn you about it and offer to remove the declaration each time you bring it up in the Form Designer. View your DFM as text and confirm that there's really an entry for a control named btGrpSnd.
Is this good enough to get you going:
if Assigned(btGrpSnd) and btGrpSnd.HandleAllocated then
btGrpSnd.Visible := ...
I see 2 possibilities: check if btGrpSnd is nil before assigning Value to the Visible property. If it's nil, you could either:
not set the property
create btGrpSnd
I would not mess around with the creation order. It's more complicated and may break with further changes.
from your comment: you can check wether you are in design or in runtime mode. Check wether your are in designtime before setting the visibility.
if not (csDesigning in Componentstate) then
begin
btGrpSnd:=Value;
end;
Answer to your comment:
go for this:
procedure Myform.SetGrpSndOption(const Value: boolean);
begin
fEnGrpSndOption := Value;
if btGrpSnd<>nil then btGrpSnd.Visible := Value;
end;
and one additional property setting btGrpSnd. If it is set to a value <> nil, set the visibility safed in fEnGrpSndOption as well.
If there's no need to set btGrpSnd outside Myform, create a init-procedure that creates everything. E.g.:
constructor Myform.Create(...)
begin
init;
end;
procedure init
begin
btGrpSnd:=TButton.Create;
...
end;
procedure Myform.SetGrpSndOption(const Value: boolean);
begin
fEnGrpSndOption := Value;
if btGrpSnd<>nil then init;
btGrpSnd.Visible := Value;
end;
This is still better then depending on some changed init-code-hack that may break in the future.