Avoiding code duplication in Delphi - delphi

I have two components A and B. Component B derives from component A and shares most properties and procedures with it. Now I have a lengthy procedure like this:
procedure DoSomething;
begin
Form1.Caption := Component_A.Caption;
// hundreds of additional lines of code calling component A
end;
Depending on whether component B is active or not, I would like to reuse the above procedure and replace the Component_A part with the name of component B. It should look like this then:
procedure DoSomething;
var
C: TheComponentThatIsActive;
begin
if Component_A.Active then
C := Component_A;
if Component_B.Active then
C := Component_B;
Form1.Caption := C.Caption;
end;
How can I do that in Delphi2007?
Thanks!

TheComponentThatIsActive should be the same type that ComponentA is (TComponentA).
Now, if you run into a stumbling block where some properties/methods only belong to ComponentB, then check and typecast it.
procedure DoSomething;
var
C: TComponentA;
begin
if Component_A.Active then
C := Component_A
else if Component_B.Active then
C := Component_B
else
raise EShouldNotReachHere.Create();
Form1.Caption := C.Caption;
if C=Component_B then
Component_B.B_Only_Method;
end;

You can pass ComponentA or ComponentB to DoSomething as a parameter.
ComponentA = class
public
procedure Fuu();
procedure Aqq();
end;
ComponentB = class(ComponentA)
public
procedure Blee();
end;
implementation
procedure DoSomething(context:ComponentA);
begin
context.Fuu();
context.Aqq();
end;
procedure TForm1.Button1Click(Sender: TObject);
var cA:ComponentA;
cB:ComponentB;
begin
cA:= ComponentA.Create();
cB:= ComponentB.Create();
DoSomething(cA);
DoSomething(cB);
cA.Free;
cB.Free;
end;

Related

Cannot change TEdit Text in Delphi

I am adding components to a form at run time and I am also adding events that change properties of these components in a dictionary to call them later.
TEventBeforeInsert = function(var AComponent: TComponent; AForm: TForm): Boolean of Object;
TFieldBase = class
private
FEvent:TEventBeforeInsert;
....
function TFieldBase.EventBeforeInsert: TEventBeforeInsert;
begin
Result:=FEvent;
end;
function TFieldBase.EventBeforeInsert(AEvent: TEventBeforeInsert): TFieldBase ;
begin
FEvent:=AEvent;
Result:=Self;
end;
....
The Form Call
TFormBase.New
.addStringField
(
TFieldBase.New
.Enabled(True)
.Description('User')
.EventBeforeInsert(TEvents.New.EditFillUser), TTabsNames.Tab1
).Show();
The Form Class
TFormBase = class(TForm)
private
FDictionary: TDictionary<String, TEventBeforeInsert>;
...
function TFormBase.addStringField(AField: TFieldBase; ATab: TTabsNames): TFormBase;
var
FLink: TLinkControlToField;
FEdit: TEdit;
begin
Result := Self;
FEdit := TEdit.Create(Self);
FEdit.Visible := True;
FEdit.Parent := TPanel(PanelParent.FindComponent('PanelTab' + Ord(ATab).ToString));
FEdit.Enabled:=AField.Enabled;
if Assigned(AField.EventBeforeInsert) then
begin
FDictionary.Add(FEdit.Name,AField.EventBeforeInsert);
end;
end;
...
procedure TFormBase.rectInsertClick(Sender: TObject);
var
Item:String;
begin
for Item in FDictionary.Keys do
begin
if Not FDictionary.Items[Item](Self.FindComponent(Item),Self) then
Exit;
end;
end;
I'm having a problem here, when debugging I see the text property being changed correctly, but no changes are made to the form being displayed.
TEvents = class
...
function TEvents.EditFillUser(AComponent: TComponent;AForm: TForm): Boolean;
begin
TEdit(AComponent).Text:=IntToStr(0);
Result:=True;
end
I'm thinking it may be a problem that the variable is being passed by value ... Can someone help me?
Edit 1:
I've tried with the dictionary declared like this:
FDictionary: TDictionary<TComponent, TEventBeforeInsert>;
...
if Not FDictionary.Items[Item](Item,Self) then //call
And I also tried use TForm reference like this:
function TEvents.EditFillUser(AComponent: String;AForm: TForm): Boolean;
begin
TEdit(AForm.FindComponent(AComponent)).Text:=IntToStr(0);
Result:=True;
end
In TFormBase.addStringField(), you are not assigning a Name value to the newly create TEdit object before inserting it into FDictionary.. Only components created at design-time have auto-generated Names. Components created at run-time do not. So, you are tracking your objects using blank Names. If you want to track the objects by Name, you need to actually assign your own value to FEdit.Name, eg:
function TFormBase.addStringField(AField: TFieldBase; ATab: TTabsNames): TFormBase;
var
...
FEdit: TEdit;
FEvent: TEventBeforeInsert;
begin
...
FEdit := TEdit.Create(Self);
FEdit.Name := 'SomeUniqueNameHere'; // <-- for you to decide on...
...
FEvent := AField.EventBeforeInsert;
if Assigned(FEvent) then
FDictionary.Add(FEdit.Name, FEvent);
end;
However, in this particular case, I see no reason to use a TDictionary at all. Consider using a TList instead, then you don't need the Names at all. This will also boost the performance of the iteration in TFormBase.rectInsertClick() since it won't have to hunt for every TComponent object using FindComponent() anymore:
TFormBase = class(TForm)
private
type TEventBeforeInsertPair = TPair<TComponent, TEventBeforeInsert>;
FBeforeInsertEvents: TList<TEventBeforeInsertPair>;
...
public
constructor Create;
destructor Destroy; override;
...
end;
...
constructor TFormBase.Create;
begin
inherited;
FBeforeInsertEvents := TList<TEventBeforeInsertPair>.Create;
end;
destructor TFormBase.Destroy;
begin
FBeforeInsertEvents.Free;
inherited;
end;
function TFormBase.addStringField(AField: TFieldBase; ATab: TTabsNames): TFormBase;
var
...
FEdit: TEdit;
FEvent: TEventBeforeInsert;
begin
...
FEdit := TEdit.Create(Self);
...
FEvent := AField.EventBeforeInsert;
if Assigned(FEvent) then
FBeforeInsertEvents.Add(TEventBeforeInsertPair.Create(FEdit, FEvent));
end;
procedure TFormBase.rectInsertClick(Sender: TObject);
var
Item: TEventBeforeInsertPair;
begin
for Item in FBeforeInsertEvents do
begin
if not Item.Value(Item.Key, Self) then
Exit;
end;
end;
...
Also, your TEvents.EditFillUser() method does not match the definition of TEventBeforeInsert. The 1st parameter of TEventBeforeInsert is declared as passing the TComponent pointer by var reference (why?), but the 1st parameter of EditFillUser() is not doing that. Unless you want your event handlers to alter what the TComponent pointers are pointing at (which won't work the way you are currently using TEventBeforeInsert with TDictionary), then there is no reason to pass around the TComponent pointers by var reference at all:
TEventBeforeInsert = function(AComponent: TComponent; AForm: TForm): Boolean of Object;
Also, your use of TEvents.New appears to be a memory leak, as nobody is taking ownership of the newly created TEvents object (unless its constructor is adding the object to some internal list that we can't see). Same with TFieldBase.New. And even TFormBase.New (assuming there is no OnClose event that sets Action=caFree when the Form is closed). At some point, you need to call Free() any class object that you Create().

Delphi 2010 Can I have a TFrame with Generic properties and methods to pass an event?

I have a TFrame that I use for searching for entities in a Delphi 2010 VCL project, in the TFrame I have a button edit, that allows the user to open a specific form to browse for that entity. (All the browse forms inherit from a common base browse form)
Currently I achieve this by inheriting from the base frame, then implement the Browse event that fires off the specific form. The only difference each time is what form (type) is shown on the click event, is there a way I can achieve this with generics.
That way I can reuse the same base frame without having to rewrite the same code for each entity (there are over 100), and at form create of the host form pass the type constraint to open the appropriate form on browse.
I have tried adding a generic type to the frame:
type
Browser<T: TfrmBrowser, constructor> = class
class function BrowseForm(Owner: Tcomponent): T;
end;
class function Browser<T>.BrowseForm(Owner: Tcomponent): T;
var
_browseForm: T;
begin
_browseForm := T.Create; // 1st problem T.Create(Owner); throws a comile error
Result := _browseForm;
end;
and then in the picker frame I expose Start that can be called from the the host form's create event:
procedure TPickerFrame.Start<T>(const idProp, nameProp, anIniSection: string; aDto: IDto);
begin
_browseForm:= Browser<T>.BrowseForm(self);
_iniSectionName:= anIniSection;
_idField:= idProp;
_descriptionField:= nameProp;
_dto := aDto;
end;
the truth is, I don't really get generics in Delphi, and none of this is working.
Below are excerpts from the frame:
_browseForm: TfrmBrowser;
procedure TPickerFrame.Browse(var DS: TDataSet; var Txt: string; var mr: TModalResult);
begin
// How do I achieve this with Generics
// _browseForm := T.Create(nil); // <-- this line is what needs to know the form type at runtime
// Everything else from here is the same
_browseForm.ProductName := Application.Title;
_browseForm.PageSize := 20;
_browseForm.DatabaseType := bdbtADO;
_browseForm.ADOConnection := dmdbWhereHouse.BaseADOConnection;
_browseForm.INISectionName := _iniSectionName;
_browseForm.DoSelBrowse(DS, Txt, mr, _descriptionField, _text);
if mr = mrOk then
begin
DoSelect(DS);
end;
end;
Does anyone have any experience with a similar requirement? Any help would be appreciated.
Thanks
Below is an example of the rack master browser:
type
TfrmMbfRACK_MASTER = class(TMxfrmBrowseHoster)
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
//...
private
FWHID: Integer;
procedure SetWHID(const Value: Integer);
{ Private declarations }
public
{ Public declarations }
procedure BuildADO(Sender: TObject; Q: TADOQuery); override;
end;
implementation
{$R *.DFM}
{ TfrmMbfRACK_MASTER }
procedure TfrmMbfRACK_MASTER.FormCreate(Sender: TObject);
begin
inherited;
fmeMxFrmBrowseHoster1.KeyField := 'RACK_ID';
// FWHID := -2; // 22/06/04
FWHID := 0; // 22/06/04
end;
procedure TfrmMbfRACK_MASTER.BuildADO(Sender: TObject; Q: TADOQuery);
begin
Q.Close;
Q.SQL.Clear;
Q.SQL.Add(
'SELECT R.RACK_DESC, R.RACK_BARCODE, W.ERP_WH, WC.CLASS_NAME, W.DESCRIPTION WAREHOUSE, R.RACK_PACKING_ORDER, ');
//...
end;
The base class
type
TMxfrmBrowseHoster = class(TfrmMxForm)
protected
// ...
procedure FormCreate(Sender: TObject);
procedure BuildADO(Sender: TObject; ADOQ: TADOQuery); virtual; abstract;
public
procedure TMxfrmBrowseHoster.FormCreate(Sender: TObject);
begin
TMxFormProductName := Application.Title;
fmeMxFrmBrowseHoster1.Initialise;
INISectionName := Name;
AbortAction := False;
fmeMxFrmBrowseHoster1.OnSelect := SelectNormaliser;
fmeMxFrmBrowseHoster1.OnNeedADO := BuildADO;
fmeMxFrmBrowseHoster1.INISectionName := self.Name;
fmeMxFrmBrowseHoster1.MultiSelect := dxBarLargeButton10.Down;
fmeMxFrmBrowseHoster1.AutoSaveGrid := True;
dxBarEdit1.OnChange := ActPageSizeChangedExecute;
FormStorage.RestoreFormPlacement;
ActConfirmDelete.Execute;
end;
I find your question a little on the vague side and I'm not 100% sure I understand exactly what you are asking. However, I know how to deal with your problem when calling the constructor. Perhaps that's all you need help with.
You need to use virtual constructor polymorphism and a bit of casting:
class function Browser<T>.BrowseForm(Owner: Tcomponent): T;
var
_browseForm: T;
begin
_browseForm := TfrmBrowser(T).Create(Owner);
Result := _browseForm;
end;
This relies on virtual constructor polymorphism. So you must make sure that each constructor for every class derived from TfrmBrowser is marked with the override directive.

Passing object in reference / one place to style objects

I got quite a large application which is currently being styled up.
To save me changing all the buttons in the IDE/Object Inspector I am planning on just doing a few functions for the main objects like
procedure StyleButton(AButton : TButton)
begin
AButton.Color := clGreen;
AButton.Font.Style = [fsBold];
end;
etc etc and then add that to the forms onCreates as needed
StyleButton(Button1); whatever etc
There is no issue passing objects in params like this. It does just reference the first object right?
It works fine and I can't think of any issues, but because this is a large application which thousands of users I just want to be sure there will be no issues/memory leaks/resource consumpution issues.
Will also be doing similar things with TAdvStringGrid and TEdit/TMemo components.
Then allows just 1 place to change these settings.
Or someone have a better idea?
This is an excellent idea. The function will modify whichever object you pass to it.
You are not passing by reference. You are passing by value. The value you are passing is a reference. "Passing by reference" means you'd use the var or out keywords, which are not appropriate in this situation.
Your idea is just fine, as the other answerers have already said. Just want to propose a solution that goes even further than David's and something you may want to consider in order to avoid having to add many statements like:
StyleButton(Button1);
StyleButton(Button2);
to each and every form for each and every control you would like to style;
What I would propose is to add a single method call to for example each form's OnShow event:
procedure TForm1.FormShow(Sender: TObject);
begin
TStyler.StyleForm(Self);
end;
The TStyler could be implemented in a separate unit that looks like this:
interface
type
TStyler = class;
TStylerClass = class of TStyler;
TStyler = class(TObject)
public
class procedure StyleForm(const aForm: TCustomForm);
class procedure StyleControl(const aControl: TControl); virtual;
class function GetStyler(const aControl: TControl): TStylerClass;
end;
implementation
uses
Contnrs;
type
TButtonStyler = class(TStyler)
public
class procedure StyleControl(const aControl: TControl); override;
end;
TEditStyler = class(TStyler)
public
class procedure StyleControl(const aControl: TControl); override;
end;
TLabelStyler = class(TStyler)
public
class procedure StyleControl(const aControl: TControl); override;
end;
var
_Controls: TClassList;
_Stylers: TClassList;
{ TStyler }
class function TStyler.GetStyler(const aControl: TControl): TStylerClass;
var
idx: Integer;
begin
Result := TStyler;
idx := _Controls.IndexOf(aControl.ClassType);
if idx > -1 then
Result := TStylerClass(_Stylers[idx]);
end;
class procedure TStyler.StyleForm(const aForm: TCustomForm);
procedure _StyleControl(const aControl: TControl);
var
i: Integer;
StylerClass: TStylerClass;
begin
StylerClass := TStyler.GetStyler(aControl);
StylerClass.StyleControl(aControl);
if (aControl is TWinControl) then
for i := 0 to TWinControl(aControl).ControlCount - 1 do
_StyleControl(TWinControl(aControl).Controls[i]);
end;
var
i: Integer;
begin
_StyleControl(aForm);
end;
class procedure TStyler.StyleControl(const aControl: TControl);
begin
// Do nothing. This is a catch all for all controls that do not need specific styling.
end;
{ TButtonStyler }
class procedure TButtonStyler.StyleControl(const aControl: TControl);
begin
inherited;
if aControl is TButton then
begin
TButton(aControl).Font.Color := clRed;
TButton(aControl).Font.Style := [fsBold];
end;
end;
{ TEditStyler }
class procedure TEditStyler.StyleControl(const aControl: TControl);
begin
inherited;
if aControl is TEdit then
begin
TEdit(aControl).Color := clGreen;
end;
end;
{ TLabelStyler }
class procedure TLabelStyler.StyleControl(const aControl: TControl);
begin
inherited;
if aControl is TLabel then
begin
TLabel(aControl).Font.Color := clPurple;
TLabel(aControl).Font.Style := [fsItalic];
end;
end;
initialization
_Controls := TClassList.Create;
_Stylers := TClassList.Create;
_Controls.Add(TButton);
_Stylers.Add(TButtonStyler);
_Controls.Add(TEdit);
_Stylers.Add(TEditStyler);
_Controls.Add(TLabel);
_Stylers.Add(TLabelStyler);
finalization
FreeAndNiL(_Controls);
FreeAndNiL(_Stylers);
end.
This solution basically employs polymorphism and a registry that links control classes to styler classes. It also uses class procedures and functions to avoid having to instantiate anything.
Please note that the registry is implemented in this example as two lists that need to be kept in sync manually as the code assumes that finding a class at index X will find the styler at the same index in the other list. This can of course be improved upon very much, but is sufficient here to show the concept.
No, There is no issue (in your specific case) passing a object as parameter
procedure StyleButton(AButton : TButton)
when you do this you are passing a address memory (reference) and setting some properties of the referenced object, so there is not problem.
To add to what Rob and RRUZ have already said, you could consider an extra helper using open array parameters:
procedure StyleButtons(const Buttons: array of TButton);
var
i: Integer;
begin
for i := low(Buttons) to high(Buttons) do
StyleButton(Buttons[i]);
end;
You can then call this as:
StyleButtons([btnOK, btnCancel, btnRelease64bitDelphi]);
which is, in my view, more readable at the call-site than:
StyleButton(btnOK);
StyleButton(btnCancel);
StyleButton(btnRelease64bitDelphi);
Note that I passed the open array as a const parameter because that is more efficient when dealing with arrays. Because each element of the array is itself a reference to the button, you are able to modify the actual button. The const just means that you cannot change the reference.

Delphi - Visual Form Inheritance - Components base classe detection

I'm using VFI (Visual Form Inheritance) and I need to check if a component of an instantiated form belongs to the formclass or to the form superclass.
any ideas ?
unit1
TFormStatus = class(TForm)
cpPanel: TPanel;
lblStatus: TLabel;
end;
unit 2
TFormCodigo = class(TFormStatus)
lblCodigo: TLabel;
end;
frmCodigo: TFormCodigo:
In any instances of frmCodigo I want to detect that lblCodigo is local to TFormCodigo and cpPanel / lblStatus are inherited components;
for i:=0 to Self.ComponentCount-1 do begin
if "InheritedComponent" (Self.Components[i]) then ...
end;
Something like this is possible using RTTI for object properties, but I dont know if it is possible for components.
Thanks.
If I understand you correctly, you need TRttiMember.Parent. For example see this article by Rob Love. You'll need Delphi 2010 or later I think.
In fact this is just part of an excellent series of articles - these articles will also tell you how to get hold of the fields, properties etc. without having to know their names.
Maybe something "stupid" like
function TFormStatus.IsStatusComponent(AComponent: TComponent): Boolean;
begin
Result := (AComponent = cpPanel) or (AComponent = lblStatus);
end;
already fulfils your needs?
In your TFormCordigo you can override ReadState method that is called every time a resource is read for a particular form. After inherited called ComponentCount contains the number of components created up to the current member of hierarchy, so after all you have list of borders for components that you can save elsewhere.
The code below illustrates this approach
procedure TInhTestForm.Button3Click(Sender: TObject);
var
i: integer;
begin
inherited;
Memo1.Lines.Clear;
for i:=0 to ComponentCount-1 do
begin
Memo1.Lines.Add(format('%s inroduced in %s', [Components[i].Name, ComponentParent(i).ClassName]));
end;
end;
function TInhTestForm.ComponentParent(Index: integer): TClass;
var
i, j: integer;
begin
Result:=Nil;
for i:=Low(fComponentBorders) to High(fComponentBorders) do
begin
if Index <= fComponentBorders[i] - 1 then
begin
j:=i;
Result:=Self.ClassType;
while j < High(fComponentBorders) do
begin
Result:=Result.ClassParent;
Inc(j);
end;
break;
end;
end;
end;
procedure TInhTestForm.ReadState(Reader: TReader);
begin
inherited;
SetLength(fComponentBorders, Length(fComponentBorders) + 1);
fComponentBorders[High(fComponentBorders)]:=ComponentCount;
end;

Execute a method from a form created by class reference (Delphi)

I have a form (form2) and I implemented the following PUBLIC method:
function ShowInterface(i:integer):boolean;
This form is in a package that will be DYNAMIC LOADED. Now I want to instantiate this form (form2) and execute the method above.
Important: I can't reference form2's unit in form1.
I tryed this code, but it never finds "ShowInterface" pointer (returns nil).
procedure TfrmForm1.Button1Click(Sender: TObject);
var
PackageModule: HModule;
AClass: TPersistentClass;
ShowInterface: function (i:integer):boolean;
frm: TCustomForm;
begin
PackageModule := LoadPackage('form2.bpl');
if PackageModule <> 0 then
begin
AClass := GetClass('TfrmForm2');
if AClass <> nil then // <<-- FINE!! IT FINDS OUT 'TfrmForm2' in 'form2.bpl')
begin
frm := TComponentClass(AClass).Create(Self) as TCustomForm;
ShowInterface := frm.MethodAddress('ShowInterface'); // <<-- HERE!! ALLWAYS RETURNS "NIL"
if #ShowInterface <> nil then
ShowInterface(1);
// but if I call frm.Show, it works fine. frm is "loaded"!!!
frm.Free;
end;
DoUnloadPackage(PackageModule);
end;
end;
Thanks in advance.
MethodAddress only works for published methods. Move it to the published section and it should work.
Or, if you have Delphi 2010, the extended RTTI offers a way to find public methods by name. (Or other visibility levels, if you change it from the default.)
As Mason and TOndrej said, I have to put the method in published section. (Thank you!)
But, some fixes were needed:
procedure TfrmForm1.Button1Click(Sender: TObject);
type
TShowInterface = function(i:integer):boolean of object;
var
PackageModule: HModule;
AClass: TPersistentClass;
Routine: TMethod;
ShowInterface : TShowInterface;
frm: TCustomForm;
begin
PackageModule := LoadPackage('form2.bpl');
if PackageModule <> 0 then
begin
AClass := GetClass('TfrmForm2');
if AClass <> nil then
begin
frm := TComponentClass(AClass).Create(Self) as TCustomForm;
Routine.Data := Pointer(frm);
Routine.Code := frm.MethodAddress('ShowInterface');
if Assigned(Routine.Code) then
begin
ShowInterface := TShowInterface(Routine);
ShowInterface(1); // showinterface executes a "ShowModal", so we can "free" form after this.
end;
frm.Free;
end;
DoUnloadPackage(PackageModule);
end;
end;
In D2007 and some earlier versions, that only works with published methods, or extended RTTI: {$METHODINFO ON}. I haven't used D2010 yet; it seems to have a new RTTI system which has been extended a lot.

Resources