I am working solo on an employee timekeeping project which uses a database to store time sheet entries. I am using Delphi Pro 10.2.3 Tokyo for the project and have created a library of wrapper classes to facilitate working with datasets like normal classes. For example, to access the FirstName field in the Employee table, I can write LFirstName := FEmployee.FirstName; rather than LFirstName := Dataset.FieldByName('FirstName').AsString;
Some of my classes have a significant number of dependencies (as many as eight) which I am injecting through the class' constructor. I use a domain object to create the required interfaces and inject them into the class being created.
Some of the interfaces being injected themselves are also very complex and it is starting to get difficult to keep track of everything in the domain object.
The dependencies being injected include wrapper interfaces for other tables which provide lookup values for calculated fields, pointers to functions which create create objects used by the class or call back functions which resolve master/detail relationships. These relationships are static and need to be set in the constructor so that any calculated fields will function when the class is created.
Are there any alternatives to constructor injection which might may decrease complexity of the constructor while maintaining decoupled classes. Here is a sample of code from one of my modules for time sheet entries.
unit LevelPay.DbModel.TimesheetEntry;
interface
uses
Data.Db
, FireDAC.Comp.DataSet
, MyLib.Model.Interfaces
, LevelPay.Model.Types
, LevelPay.Model.Constants
, LevelPay.Model.Interfaces
, LevelPay.DbModel.AppModel
;
type
TDbCustomTimesheetEntry = class(
TDbAppModel<ITimesheetEntry>,
ITimesheetEntry
)
strict private
FCopyFunc: TCopyFunc<ITimesheetEntry>;
procedure ClearFilter;
procedure FilterEntries(const ADate: TDate);
strict protected
FTimesheet: ITimesheet;
FID: TField;
FEmployeeID: TField;
FPayPeriodEndDate: TField;
FFiscalYearEndDate: TField;
FFiscalYearStartDate: TField;
FRowNbr: TField;
FEntryTypeID: TField;
FDateIn: TField;
FTimein: TField;
FDateOut: TField;
FTimeOut: TField;
FCreatedBy: TField;
FCreatedTimestamp: TField;
FLastModifiedBy: TField;
FLastModifiedTimestamp: TField;
FNote: TField;
FClockable: TField;
FClockableHours: TField;
FDayOfWeek: TField;
FDifference: TField;
FEmployeeName: TField;
FEntryTypeCaption: TField;
FTimeElapsed: TField;
FDateIndex: TFDIndex;
FTimeScheduled: TField;
FScheduledTimeIn: TField;
FScheduledTimeOut: TField;
FWeekOf: TField;
function GetID: TIdentifier;
function GetModel: ITimesheetEntry; override;
function GetClockable: Boolean;
function GetClockableHours: THours;
function GetDateIn: TDate;
function GetDateOut: TDate;
function GetDifference: THours;
function GetEmployeeID: TIdentifier;
function GetEmployeeName: string;
function GetPayPeriodEndDate: TDate;
function GetFiscalYearStartDate: TDate;
function GetFiscalYearEndDate: TDate;
function GetEntryTypeID: TIdentifier;
function GetEntryTypeCaption: TCaption;
function GetPlaceholder: Boolean;
function GetRowNbr: TRowNbr;
function GetScheduledTimeIn: TTime;
function GetScheduledTimeOut: TTime;
function GetTimeElapsed: THours;
function GetTimein: TTime;
function GetTimeOut: TTime;
function GetTimeScheduled: THours;
function GetWeekOf: TDate;
function GetWeekDay: string;
function GetCreatedBy: TUserName;
function GetCreatedTimestamp: TDateTime;
function GetLastModifiedBy: TUserName;
function GetLastModifiedTimestamp: TDateTime;
function GetNote: AnsiString;
function GetEntry: ITimesheetEntry;
function GetTimesheet: ITimesheet;
function GetHasEntries: Boolean;
function Find(AModel: ITimesheetEntry): Boolean; override;
procedure DoUpdate(AModel: ITimesheetEntry); override;
procedure Load; virtual;
procedure CreateFields; override;
procedure CreateCalcFields; override;
procedure CreateIndexes; override;
procedure FormatFields; override;
procedure OnCalcFields(Dataset: TDataset); override;
procedure OnNewRecord(Dataset: TDataset); override;
public
constructor Create(
ADataset: TFDDataset;
AModelFunc: TModelFunc<ITimesheetEntry>;
ACopyFunc: TCopyFunc<ITimesheetEntry>;
ATimesheet: ITimesheet;
ACreateFields: Boolean
); reintroduce;
property ID: TIdentifier read GetID;
property EmployeeID: TIdentifier read GetEmployeeID;
property PayPeriodEndDate: TDate read GetPayPeriodEndDate;
property FiscalYearEndDate: TDate read GetFiscalYearEndDate;
property FiscalYearStartDate: TDate read GetFiscalYearStartDate;
property EntryTypeID: TIdentifier read GetEntryTypeID;
property EntryTypeCaption: TCaption read GetEntryTypeCaption;
property RowNbr: TRowNbr read GetRowNbr;
property Clockable: Boolean read GetClockable;
property ClockableHours: THours read GetClockableHours;
property DateIn: TDate read GetDateIn;
property EmployeeName: string read GetEmployeeName;
property ScheduledTimeIn: TTime read GetScheduledTimeIn;
property ScheduledTimeOut: TTime read GetScheduledTimeOut;
property TimeIn: TTime read GetTimein;
property DateOut: TDate read GetDateOut;
property TimeOut: TTime read GetTimeOut;
property TimeElapsed: THours read GetTimeElapsed;
property Placeholder: Boolean read GetPlaceholder;
property TimeScheduled: THours read GetTimeScheduled;
property Difference: THours read GetDifference;
property WeekDay: string read GetWeekDay;
property WeekOf: TDate read GetWeekOf;
property CreatedBy: TUserName read GetCreatedBy;
property CreatedTimestamp: TDateTime read GetCreatedTimestamp;
property LastModifiedBy: TUserName read GetLastModifiedBy;
property LastModifiedTimestamp: TDateTime read GetLastModifiedTimestamp;
property Note: AnsiString read GetNote;
property Timesheet: ITimesheet read GetTimesheet;
end;
TDbSourceEntry = class(TDbCustomTimesheetEntry, ISourceEntryList)
strict private
FLoadTimesheetEntries: TLoadTimesheetProc;
FElectionList: ILevelPayElectionList;
FPositionList: IHourlyPositionList;
strict protected
procedure BeforePost(Dataset: TDataset); override;
procedure Load; override;
public
constructor Create(
ADataset: TFDDataset;
AModelFunc: TModelFunc<ITimesheetEntry>;
ATimesheet: ITimesheet;
ACopyFunc: TCopyFunc<ITimesheetEntry>;
AProc: TLoadTimesheetProc;
AElectionList: ILevelPayElectionList;
APositionList: IHourlyPositionList;
ACreateFields: Boolean = True
); reintroduce;
end;
TDbDummyEntry = class(TDbCustomTimesheetEntry, IDummyEntryList)
strict private
FPlaceholderID: TIdentifier;
FClosureList: ISchoolClosureList;
procedure EntryTypeIDOnChange(Sender: TField);
strict protected
procedure AddPlacedholder(ADate: TDate; ARowNbr: TRowNbr);
procedure CreateFields; override;
procedure DoAdd(AModel: ITimesheetEntry); override;
property PlaceholderID: TIdentifier read FPlaceholderID write FPlaceholderID;
public
constructor Create(
ADataset: TFDDataset;
AModelFunc: TModelFunc<ITimesheetEntry>;
ACopyFunc: TCopyFunc<ITimesheetEntry>;
ATimesheet: ITimesheet;
AClosureList: ISchoolClosureList;
ACreateFields: Boolean
); reintroduce;
end;
TDbTimesheetEntry = class(TDbDummyEntry, ITimesheetEntryList)
strict private
FClone: TFDDataset;
FSource: ISourceEntryList;
function GetNextRowNbr: TRowNbr;
strict protected
function WorkweekList: IWorkweekList;
procedure Clear;
procedure Load; override;
public
procedure Add(AModel: ITimesheetEntry); //replaces inherited add
procedure Delete(AModel: ITimesheetEntry); //replace inherited delete
procedure Update(OldModel, NewModel: ITimesheetEntry);
constructor Create(
ADataset: TFDDataset;
AModelFunc: TModelFunc<ITimesheetEntry>;
ACopyFunc: TCopyFunc<ITimesheetEntry>;
ATimesheet: ITimesheet;
ASourceFunc: TSourceListFunc;
AClosureList: ISchoolClosureList;
ACreateFields: Boolean
); reintroduce;
end;
implementation
uses
System.SysUtils
, System.Classes
, System.Variants
, System.DateUtils
, FireDAC.Comp.Client
, DateTimeHelper
, LevelPay.Model.Helpers
;
{ TCustomShift }
procedure TDbCustomTimesheetEntry.ClearFilter;
begin
CancelRange;
end;
constructor TDbCustomTimesheetEntry.Create(ADataset: TFDDataset;
AModelFunc: TModelFunc<ITimesheetEntry>; ACopyFunc: TCopyFunc<ITimesheetEntry>;
ATimesheet: ITimesheet; ACreateFields: Boolean);
begin
inherited Create(ADataset, AModelFunc, ACreateFields);
FCopyFunc := ACopyFunc;
FTimesheet := ATimesheet;
end;
procedure TDbCustomTimesheetEntry.CreateCalcFields;
begin
inherited;
FClockable := CreateCalcBooleanField(k_Clockable);
FClockableHours := CreateCalcFloatField(k_ClockableHours);
FDayOfWeek := CreateCalcStringField(k_WeekDay, 13);
FDifference := CreateCalcFloatField(k_Difference);
FEmployeeName := CreateCalcStringField(k_EmployeeName, 40);
FEntryTypeCaption := CreateCalcStringField(k_EntryTypeCaption, 20);
FFiscalYearEndDate := CreateCalcDateTimeField(k_FiscalYearEndDate);
FFiscalYearStartDate := CreateCalcDateTimeField(k_FiscalYearStartDate);
FScheduledTimeIn := CreateCalcDateTimeField(k_ScheduledTimeIn);
FScheduledTimeOut := CreateCalcDateTimeField(k_ScheduledTimeOut);
FTimeElapsed := CreateCalcFloatField(k_TimeElapsed);
FTimeScheduled := CreateCalcFloatField(k_TimeScheduled);
FWeekOf := CreateCalcDateTimeField(k_WeekOf);
end;
procedure TDbCustomTimesheetEntry.CreateFields;
begin
FID := CreateField(k_Id);
FEmployeeID := CreateField(k_EmployeeID);
FEntryTypeID := CreateField(k_EntryTypeID);
FRowNbr := CreateField(k_RowNbr);
FTimeIn := CreateField(k_TimeIn);
FTimeOut := CreateField(k_TimeOut);
FID := CreateField(k_ID);
FDateIn := CreateField(k_DateIn);
FDateOut := CreateField(k_DateOut);
FCreatedBy := CreateField(k_CreatedBy);
FCreatedTimestamp := CreateField(k_CreatedTimeStamp);
FLastModifiedBy := CreateField(k_LastModifiedBy);
FLastModifiedTimestamp := CreateField(k_LastModifiedTimestamp);
FNote := CreateField(k_Note);
FPayPeriodEndDate := CreateField(k_PayPeriodEndDate);
end;
procedure TDbCustomTimesheetEntry.CreateIndexes;
const
FIELD_LIST = k_DateIn + ';' + k_RowNbr;
begin
inherited;
FDateIndex := CreateIndex('ByDate', FIELD_LIST);
FDateIndex.Selected := True;
Dataset.IndexesActive := True;
end;
function TDbCustomTimesheetEntry.GetClockable: Boolean;
begin
Result := Rules.Clockable;
end;
function TDbCustomTimesheetEntry.GetClockableHours: THours;
begin
Result := Rules.ClockableHours;
end;
function TDbCustomTimesheetEntry.GetCreatedBy: TUserName;
begin
Result := FCreatedBy.AsUserName;
end;
function TDbCustomTimesheetEntry.GetCreatedTimestamp: TDateTime;
begin
Result := FCreatedTimestamp.AsDateTime;
end;
function TDbCustomTimesheetEntry.GetDateIn: TDate;
begin
Result := FDateIn.AsDateTime;
end;
function TDbCustomTimesheetEntry.GetDateOut: TDate;
begin
Result := FDateOut.AsDateTime;
end;
function TDbCustomTimesheetEntry.GetDifference: THours;
begin
Result := Rules.Difference;
end;
function TDbCustomTimesheetEntry.GetEmployeeID: TIdentifier;
begin
Result := FEmployeeID.AsIdentifier;
end;
function TDbCustomTimesheetEntry.GetEmployeeName: string;
begin
Result := Rules.EmployeeName;
end;
function TDbCustomTimesheetEntry.GetEntryTypeID: TIdentifier;
begin
Result := FEntryTypeID.AsIdentifier;
end;
function TDbCustomTimesheetEntry.GetFiscalYearEndDate: TDate;
begin
Result := FFiscalYearEndDate.AsDateTime;
end;
function TDbCustomTimesheetEntry.GetFiscalYearStartDate: TDate;
begin
Result := FFiscalYearStartDate.AsDateTime;
end;
function TDbCustomTimesheetEntry.GetHasEntries: Boolean;
begin
Result := RecordCount > 0;
end;
function TDbCustomTimesheetEntry.GetID: TIdentifier;
begin
Result := FID.AsInteger;
end;
function TDbCustomTimesheetEntry.GetLastModifiedBy: TUserName;
begin
Result := FLastModifiedBy.AsUserName;
end;
function TDbCustomTimesheetEntry.GetLastModifiedTimestamp: TDateTime;
begin
Result := FLastModifiedTimestamp.AsDateTime;
end;
function TDbCustomTimesheetEntry.GetModel: ITimesheetEntry;
var
LResult: ITimesheetEntry;
begin
LResult := FCopyFunc(Self);
Result := LResult;
end;
function TDbCustomTimesheetEntry.GetNote: AnsiString;
begin
Result := FNote.AsAnsiString;
end;
function TDbCustomTimesheetEntry.GetPayPeriodEndDate: TDate;
begin
Result := FPayPeriodEndDate.AsDateTime;
end;
function TDbCustomTimesheetEntry.GetPlaceholder: Boolean;
begin
Result := Rules.Placeholder;
end;
function TDbCustomTimesheetEntry.GetEntry: ITimesheetEntry;
begin
Result := Model;
end;
function TDbCustomTimesheetEntry.GetEntryTypeCaption: TCaption;
begin
Result := Rules.EntryTypeCaption
end;
function TDbCustomTimesheetEntry.GetRowNbr: TRowNbr;
begin
Result := FRowNbr.AsRowNbr;
end;
function TDbCustomTimesheetEntry.GetScheduledTimeIn: TTime;
begin
Result := Rules.ScheduledTimeIn;
end;
function TDbCustomTimesheetEntry.GetScheduledTimeOut: TTime;
begin
Result := Rules.ScheduledTimeOut;
end;
function TDbCustomTimesheetEntry.GetTimeElapsed: THours;
begin
Result := Rules.TimeElapsed;
end;
function TDbCustomTimesheetEntry.GetTimein: TTime;
begin
Result := FTimeIn.AsDateTime;
end;
function TDbCustomTimesheetEntry.GetTimeOut: TTime;
begin
Result := FTimeOut.AsDateTime;
end;
function TDbCustomTimesheetEntry.GetTimeScheduled: THours;
begin
Result := Rules.TimeScheduled;
end;
function TDbCustomTimesheetEntry.GetTimesheet: ITimesheet;
begin
Result := FTimesheet;
end;
function TDbCustomTimesheetEntry.GetWeekDay: string;
begin
Result := Rules.WeekDay;
end;
function TDbCustomTimesheetEntry.GetWeekOf: TDate;
begin
Result := Rules.WeekOf;
end;
procedure TDbCustomTimesheetEntry.Load;
begin
//Stub procedure
end;
procedure TDbCustomTimesheetEntry.OnCalcFields(Dataset: TDataset);
begin
inherited;
if not Assigned(Rules) then Exit;
FClockable.AsBoolean := GetClockable;
FClockableHours.AsHours := GetClockableHours;
FDayOfWeek.AsString := GetWeekDay;
FDifference.AsHours := GetDifference;
FEmployeeName.AsString := GetEmployeeName;
FEntryTypeCaption.AsCaption := GetEntryTypeCaption;
FTimeElapsed.AsHours := GetTimeElapsed;
FTimeScheduled.AsHours := GetTimeScheduled;
FScheduledTimeIn.AsDateTime := GetScheduledTimeIn;
FScheduledTimeOut.AsDateTime := GetScheduledTimeOut;
FWeekOf.AsDateTime := GetWeekOf;
end;
procedure TDbCustomTimesheetEntry.OnNewRecord(Dataset: TDataset);
begin
inherited;
FEmployeeID.AsIdentifier := FTimesheet.EmployeeID;
FFiscalYearEndDate.AsDateTime := FTimesheet.FiscalYearEndDate;
FFiscalYearStartDate.AsDateTime := FTimesheet.FiscalYearStartDate;
FPayPeriodEndDate.AsDateTime := FTimesheet.PayPeriodEndDate;
end;
procedure TDbCustomTimesheetEntry.DoUpdate(AModel: ITimesheetEntry);
begin
inherited;
FEmployeeID.AsIdentifier := AModel.EmployeeID;
FRowNbr.AsRowNbr := AModel.RowNbr;
FEntryTypeID.AsIdentifier := AModel.EntryTypeID;
FDateIn.AsDateTime := AModel.DateIn;
FTimeIn.AsDateTime := AModel.TimeIn;
FDateOut.AsDateTime := AModel.DateOut;
FTimeOut.AsDateTime := AModel.TimeOut;
FNote.AsAnsiString := AModel.Note;
end;
procedure TDbCustomTimesheetEntry.FilterEntries(const ADate: TDate);
begin
FDateIndex.Selected := True;
SetRange([ADate], [ADate]);
end;
function TDbCustomTimesheetEntry.Find(AModel: ITimesheetEntry): Boolean;
begin
Result := Locate(k_ID, AModel.ID);
end;
procedure TDbCustomTimesheetEntry.FormatFields;
begin
inherited;
FTimeElapsed.OnGetText := HoursFieldGetText;
FClockableHours.OnGetText := HoursFieldGetText;
FDifference.OnGetText := HoursFieldGetText;
FTimeScheduled.OnGetText := HoursFieldGetText;
SetTimeFieldDisplayFormat(FTimeIn);
SetTimeFieldDisplayFormat(FTimeOut);
SetDateFieldDisplayFormat(FDateIn);
SetDateFieldDisplayFormat(FDateOut);
SetDateFieldDisplayFormat(FPayPeriodEndDate);
SetSQLTimestampFieldDisplayFormat(FCreatedTimestamp);
SetSQLTimestampFieldDisplayFormat(FLastModifiedTimestamp);
end;
{ TDbDummyEntry }
procedure TDbSourceEntry.BeforePost(Dataset: TDataset);
var
LTimestamp: TDateTime;
begin
inherited;
LTimestamp := Now;
FLastModifiedBy.AsUserName := FTimesheet.User.UserName;
FLastModifiedTimestamp.AsDateTime := LTimestamp;
if State in [dsInsert] then
begin
FCreatedBy.AsUserName := FTimesheet.User.UserName;
FCreatedTimestamp.AsDateTime := LTimestamp;
end;
end;
constructor TDbSourceEntry.Create(ADataset: TFDDataset;
AModelFunc: TModelFunc<ITimesheetEntry>; ATimesheet: ITimesheet;
ACopyFunc: TCopyFunc<ITimesheetEntry>; AProc: TLoadTimesheetProc;
AElectionList: ILevelPayElectionList; APositionList: IHourlyPositionList;
ACreateFields: Boolean);
begin
inherited Create(ADataset, AModelFunc, ACopyFunc, ATimesheet, ACreateFields);
FLoadTimesheetEntries := AProc;
FElectionList := AElectionList;
FPositionList := APositionList;
end;
procedure TDbSourceEntry.Load;
var
LEmployeeID: TIdentifier;
LFirstEntryDate: TDate;
LLastEntryDate: TDate;
begin
LEmployeeID := FTimesheet.EmployeeID;
LFirstEntryDate := FTimesheet.FirstEntryDate;
LLastEntryDate := FTimesheet.LastEntryDate;
FLoadTimesheetEntries(LEmployeeID, LFirstEntryDate, LLastEntryDate);
end;
{ TDbDummyEntry }
procedure TDbDummyEntry.AddPlacedholder(ADate: TDate; ARowNbr: TRowNbr);
var
LEntryTypeID: TIdentifier;
LClosure: ISchoolClosure;
LNote: AnsiString;
LRowNbr: TRowNbr;
begin
Dec(FPlaceholderID);
LNote := '';
LEntryTypeID := 0;
LRowNbr := ARowNbr;
if LRowNbr < 2 then //This is a first entry for the date
begin
if FClosureList.Find(ADate) then
begin
LClosure := FClosureList.Closure;
LEntryTypeID := LClosure.EntryTypeID;
LNote := AnsiString(LClosure.Caption);
end
else
begin
if TDateTime(ADate).DayOfWeek in [MONDAY..FRIDAY] then
LEntryTypeID := k_Regular
else
LEntryTypeID := 0;
end;
end;
Append;
FId.AsIdentifier := FPlaceholderID;
FRowNbr.AsRowNbr := LRowNbr;
FDateIn.AsDateTime := ADate;
FDateOut.AsDateTime := ADate;
FEntryTypeID.AsIdentifier := LEntryTypeID;
FNote.AsAnsiString := LNote;
Post;
end;
constructor TDbDummyEntry.Create(ADataset: TFDDataset;
AModelFunc: TModelFunc<ITimesheetEntry>; ACopyFunc: TCopyFunc<ITimesheetEntry>;
ATimesheet: ITimesheet; AClosureList: ISchoolClosureList; ACreateFields: Boolean);
begin
inherited Create(ADataset, AModelFunc, ACopyFunc, ATimesheet, ACreateFields);
FClosureList := AClosureList;
end;
procedure TDbDummyEntry.CreateFields;
begin
inherited;
FId.ReadOnly := False;
FId.AutoGenerateValue := arNone;
FEntryTypeID.OnChange := EntryTypeIDOnChange;
end;
procedure TDbDummyEntry.DoAdd(AModel: ITimesheetEntry);
begin
inherited;
FId.AsIdentifier := AModel.ID;
end;
procedure TDbDummyEntry.EntryTypeIDOnChange(Sender: TField);
var
LDateIn: TDate;
LDateOut: TDate;
begin
if Sender.AsIdentifier = k_LandSchool then
begin
LDateIn := FDateIn.AsDateTime;
LDateOut := LDateIn + 1;
FDateOut.AsDateTime := LDateOut;
FTimeIn.AsDateTime := GetScheduledTimeOut;
FTimeOut.AsDateTime := GetScheduledTimeIn;
end;
end;
{ TDbTimesheetEntry }
procedure TDbTimesheetEntry.Add(AModel: ITimesheetEntry);
var
LEntry: ITimesheetEntry;
begin
FSource.Add(AModel);
LEntry := FSource.Entry; //Get model with updated ID
FTimesheet.WorkweekList.AddEntry(LEntry);
inherited Add(LEntry);
end;
procedure TDbTimesheetEntry.Clear;
begin
PlaceholderID := 0;
Dataset.CancelUpdates;
DisableControls;
try
First;
while RecordCount > 0 do
Dataset.Delete;
finally
EnableControls;
end;
end;
constructor TDbTimesheetEntry.Create(ADataset: TFDDataset;
AModelFunc: TModelFunc<ITimesheetEntry>;
ACopyFunc: TCopyFunc<ITimesheetEntry>; ATimesheet: ITimesheet;
ASourceFunc: TSourceListFunc; AClosureList: ISchoolClosureList;
ACreateFields: Boolean);
begin
inherited Create(ADataset, AModelFunc, ACopyFunc, ATimesheet, AClosureList, ACreateFields);
FSource := ASourceFunc(FTimesheet);
FClone := TFDMemTable.Create(ADataset);
FClone.CloneCursor(ADataset);
end;
procedure TDbTimesheetEntry.Delete(AModel: ITimesheetEntry);
var
LDate: TDate;
LRowNbr: SmallInt;
LEntry: ITimesheetEntry;
begin
FSource.Delete(AModel);
WorkweekList.DeleteEntry(AModel);
DisableControls;
try
LDate := AModel.DateIn;
LRowNbr := AModel.RowNbr;
inherited Delete(AModel);
if (LRowNbr = 1) then
begin
AddPlacedholder(LDate, 0);
LEntry := Model; //Get placeholder
WorkweekList.AddEntry(LEntry);
end;
finally
EnableControls;
end;
end;
function TDbTimesheetEntry.GetNextRowNbr: TRowNbr;
begin
Result := 0;
FClone.SetRange([GetDateIn], [GetDateIn]);
while not FClone.Eof do
begin
Result := FClone.FieldByName(k_RowNbr).AsRowNbr + 1;
FClone.Next;
end;
FClone.CancelRange;
end;
procedure TDbTimesheetEntry.Load;
var
LDate: TDate;
LFirstEntryDate: TDate;
LCutoffDate: TDate;
LEntry: ITimesheetEntry;
LWeekOf: TDate;
LWorkweekList: IWorkweekList;
begin
LWorkweekList := FTimesheet.WorkweekList;
LFirstEntryDate := FTimesheet.FirstEntryDate;
LCutOffDate := FTimesheet.LastEntryDate;
LWeekOf := TDateTime(LFirstEntryDate).StartOfWeek;
LWorkweekList.Init(LFirstEntryDate, LCutOffDate);
FSource.Load;
DisableControls;
try
Clear;
LDate := LWeekOf;
repeat
FSource.FilterEntries(LDate);
if FSource.HasEntries then
begin
for LEntry in FSource do
begin
if LDate >= LFirstEntryDate then
begin
inherited Add(LEntry); //this must call inherited add to adding to FSource
end;
LWorkweekList.AddEntry(LEntry);
end;
end
else
begin
if LDate >= LFirstEntryDate then
begin
AddPlacedholder(LDate, 0);
LEntry := GetModel; //This retrieves the placeholder
LWorkweekList.AddEntry(LEntry);
end;
end;
LDate := LDate + 1;
until LDate > LCutOffDate;
finally
FSource.ClearFilter;
EnableControls;
First;
end;
end;
procedure TDbTimesheetEntry.Update(OldModel, NewModel: ITimesheetEntry);
var
LEntry: ITimesheetEntry;
begin
DisableControls;
try
if Assigned(OldModel) then
begin
if OldModel.Placeholder then
begin
FSource.Add(NewModel);
end
else
begin
FSource.Update(NewModel);
end;
inherited Delete(OldModel);
WorkweekList.DeleteEntry(OldModel);
end
else
begin
FSource.Add(NewModel);
end;
LEntry := FSource.Entry; //Get entry with new ID
inherited Add(LEntry);
WorkweekList.AddEntry(LEntry);
finally
EnableControls;
end;
end;
function TDbTimesheetEntry.WorkweekList: IWorkweekList;
begin
Result := FTimesheet.WorkweekList;
end;
end.
Generally the saying is that when you have many constructor parameters (that means dependencies) it is a sign that your class might do too much (see single responsible principle).
If certain dependencies most of the time only interact with each other this is a sign that these dependencies might be subject to refactor them into their own component/class which is then injected. This does not only reduce dependencies in the first place but reduces complexity of your components.
I suggest reading the blog of Mark Seemann where he explained many areas that play into properly practicing dependency injection and software design and architecture.
Just two examples that I remember:
http://blog.ploeh.dk/2010/02/02/RefactoringtoAggregateServices/
http://blog.ploeh.dk/2018/08/27/on-constructor-over-injection/
You have default property in root all TDataSet:
property FieldValues[const FieldName: string]: Variant read GetFieldValue write SetFieldValue; default;
and you can use
aTest: TDataSet; //any descant
aValue := aTest['NameField'];
Why complicate this with injections?
Syntax purity?
Like Paradox ObjectPAL from 1994.
Related
I have the two overload methods:
procedure TProps.SetProp(Value: TObject); overload;
procedure TProps.SetProp(const Value: Variant); overload;
They do pretty much the same repeating code except from minor variations depending on whether the Value is Variant or TObject.
I want to use a common method:
procedure TProps.DoSetProp(Value: <what type here?>); // <--
So I can pass both Variant or TObject from SetProp and be able to distinguish between the two types. what are my options?
Edit: for now I used:
procedure TProps.DoSetProp(Value: Pointer; IsValueObject: Boolean);
begin
// common code...
if IsValueObject then
PropValue.Obj := Value
else
PropValue.V := PVariant(Value)^;
// common code...
if IsValueObject then
PropValue.Obj := Value
else
PropValue.V := PVariant(Value)^;
// etc...
end;
and the overload methods:
procedure TProps.SetProp(const Value: Variant); overload;
begin
DoSetProp(#Value, False);
end;
procedure TProps.SetProp(Value: TObject); overload;
begin
DoSetProp(Value, True);
end;
I'm not sure I like this solution because of the IsValueObject. I would rather detect the type from a common type "container".
I could use TVarRec:
VarRec: TVarRec;
// for Variant:
VarRec.VType := vtVariant;
VarRec.VVariant := #Value;
// for TObject
VarRec.VType := vtObject;
VarRec.VObject := Value;
And pass the VarRec to the common method. but I'm not sure I like it either.
EDIT 2: What I am trying to do? I'm trying to extend properties for any TObject similar to SetProp API.
Here is the entire MCVE:
function ComparePointers(A, B: Pointer): Integer;
begin
if Cardinal(A) = Cardinal(B) then
Result := 0
else if Cardinal(A) < Cardinal(B) then
Result := -1
else
Result := 1
end;
type
TPropValue = class
private
V: Variant;
Obj: TObject;
procedure SetValue(const Value: Pointer; IsValueObject: Boolean);
end;
TPropNameValueList = class(TStringList)
public
destructor Destroy; override;
procedure Delete(Index: Integer); override;
end;
TObjectProps = class
private
BaseObject: TObject;
PropList: TPropNameValueList;
public
constructor Create(AObject: TObject);
destructor Destroy; override;
end;
TProps = class(TComponent)
private
FList: TObjectList;
protected
procedure DoSetProp(AObject: TObject; const PropName: string; const Value: Pointer; IsValueObject: Boolean);
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
public
function Find(AObject: TObject; var Index: Integer): Boolean;
procedure SetProp(AObject: TObject; const PropName: string; const Value: Variant); overload;
procedure SetProp(AObject: TObject; const PropName: string; Value: TObject); overload;
function RemoveProp(AObject: TObject; const PropName: string): Boolean;
function RemoveProps(AObject: TObject): Boolean;
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
{ TPropValue }
procedure TPropValue.SetValue(const Value: Pointer; IsValueObject: Boolean);
begin
if IsValueObject then
Obj := Value
else
V := PVariant(Value)^;
end;
{ TPropNameValueList }
destructor TPropNameValueList.Destroy;
var
I: Integer;
begin
for I := 0 to Count - 1 do
Objects[I].Free; // TPropValue
inherited;
end;
procedure TPropNameValueList.Delete(Index: Integer);
begin
Objects[Index].Free;
inherited;
end;
{ TObjectProps }
constructor TObjectProps.Create(AObject: TObject);
begin
BaseObject := AObject;
PropList := TPropNameValueList.Create;
PropList.Sorted := True;
PropList.Duplicates := dupError;
end;
destructor TObjectProps.Destroy;
begin
PropList.Free;
inherited;
end;
{ TProps }
constructor TProps.Create(AOwner: TComponent);
begin
inherited;
FList := TObjectList.Create(true);
end;
procedure TProps.Notification(AComponent: TComponent; Operation: TOperation);
begin
inherited;
if (Operation = opRemove) and (AComponent <> nil) then
begin
RemoveProps(AComponent);
end;
end;
destructor TProps.Destroy;
begin
FList.Free;
inherited;
end;
function TProps.Find(AObject: TObject; var Index: Integer): Boolean;
var
L, H, I, C: Integer;
begin
Result := False;
L := 0;
H := FList.Count - 1;
while L <= H do
begin
I := (L + H) shr 1;
C := ComparePointers(TObjectProps(FList[I]).BaseObject, AObject);
if C < 0 then L := I + 1 else
begin
H := I - 1;
if C = 0 then
begin
Result := True;
L := I;
end;
end;
end;
Index := L;
end;
procedure TProps.DoSetProp(AObject: TObject; const PropName: string; const Value: Pointer;
IsValueObject: Boolean);
var
OP: TObjectProps;
PropValue: TPropValue;
Index, NameIndex: Integer;
Found: Boolean;
I: Integer;
begin
Found := Find(AObject, Index);
if not Found then
begin
OP := TObjectProps.Create(AObject);
if AObject is TComponent then
TComponent(AObject).FreeNotification(Self);
PropValue := TPropValue.Create;
PropValue.SetValue(Value, IsValueObject);
OP.PropList.AddObject(PropName, PropValue);
FList.Insert(Index, OP);
end
else
begin
OP := TObjectProps(FList[Index]);
NameIndex := OP.PropList.IndexOf(PropName);
if NameIndex <> -1 then
begin
PropValue := TPropValue(OP.PropList.Objects[NameIndex]);
PropValue.SetValue(Value, IsValueObject);
end
else
begin
PropValue := TPropValue.Create;
PropValue.SetValue(Value, IsValueObject);
OP.PropList.AddObject(PropName, PropValue);
end;
end;
end;
procedure TProps.SetProp(AObject: TObject; const PropName: string; const Value: Variant);
begin
DoSetProp(AObject, PropName, #Value, False);
end;
procedure TProps.SetProp(AObject: TObject; const PropName: string; Value: TObject);
begin
DoSetProp(AObject, PropName, Value, True);
end;
function TProps.RemoveProp(AObject: TObject; const PropName: string): Boolean;
var
Index, NameIndex: Integer;
OP: TObjectProps;
begin
Result := False;
if not Find(AObject, Index) then Exit;
OP := TObjectProps(FList[Index]);
NameIndex := OP.PropList.IndexOf(PropName);
if NameIndex <> -1 then
begin
OP.PropList.Delete(NameIndex);
Result := True;
end;
end;
function TProps.RemoveProps(AObject: TObject): Boolean;
var
Index: Integer;
OP: TObjectProps;
begin
if not Find(AObject, Index) then
begin
Result := False;
Exit;
end;
OP := TObjectProps(FList[Index]);
Result := FList.Remove(OP) <> -1;
end;
Usage:
Props := TProps.Create(Self);
Props.SetProp(Button1, 'myprop1', Self); // TObject property
Props.SetProp(Button1, 'myprop2', 666); // variant
Props.SetProp(Button2, 'myprop', 'Hello'); // variant
Props.SetProp(MyObject, 'foo', 123.123);
Note: TProps.GetProp is not yet implemented.
You are fighting the compiler; You should continue to use overloads.
"I would rather detect the type from a common type 'container'."
Your choices are variant or untyped pointer. You are going to have to unpack the "Value" parameter. With an untyped pointer you will have to do all the work; with a variant you will have to do most of the work. Very messy.
"They do pretty much the same repeating code except from minor variations depending on whether the Value is Variant or TObject."
If that is really true then you should still continue to use overloads but add an internal "SetProp" method that takes "normalized" data that does the actual work. Your "repeating" code is the setting of the property values. But you still have specific code to write to crack the incoming "Value" parameter whether you have one method that accepts a "container" type or multiple overloaded methods that take the various types you want to accept. In the one-method-container type you will have a (complex) if-then-else block that cracks the Value. In the overloaded-methods type there is no if-testing; you just crack the Value for the type that each method accepts.
The major advantage is that your object is better documented: you can see what types are acceptable for "Value" and, better still, the compiler helps you because it "knows" what types are acceptable. With your one-method approach the compiler will not be able to help you enforce the type of "Value"; you are doing all the work.
Also, using the overloaded methods, I wouldn't have one that accepts variant (although the example below does). Have an separate overload for each of string, integer, double, etc.
type
TNormalizedPropValue = record
// ....
end;
procedure TProps.internalSetProp(Value : TNormalizedPropValue);
begin
//
// Set the property value from the "Normalized" pieces and parts.
//
end;
procedure TProps.SetProp(Value : TObject);
var
NormalizedObjectPropValue : TNormalizedPropValue;
begin
// Copy the pieces and parts from "Value" into NormalizedObjectPropValue
//
internalSetProp(NormalizedObjectPropValue);
end;
procedure TProps.SetProp(Value : variant);
var
NormalizedVariantPropValue : TNormalizedPropValue;
begin
// Crack "Value" variant and copy the pieces and parts into NormalizedVariantPropValue
//
internalSetProp(NormalizedVariantPropValue);
end;
I would like to implement a simple watchdog timer in Delphi XE 7 with two use cases:
• Watchdog ensures that a operation doesn't execute longer than x seconds
• Watchdog ensures that when errors occur then message exception will be stored in log file
Could you please suggest me any solution?
Here is my solution. I'm not sure that is a proper, but its works. I crated a new thread:
type
// will store all running processes
TProcessRecord = record
Handle: THandle;
DateTimeBegin, DateTimeTerminate: TDateTime;
end;
TWatchDogTimerThread = class(TThread)
private
FItems: TList<TProcessRecord>;
FItemsCS: TCriticalSection;
class var FInstance: TWatchDogTimerThread;
function IsProcessRunning(const AItem: TProcessRecord): Boolean;
function IsProcessTimedOut(const AItem: TProcessRecord): Boolean;
procedure InternalKillProcess(const AItem: TProcessRecord);
protected
constructor Create;
procedure Execute; override;
public
class function Instance: TWatchDogTimerThread;
destructor Destroy; override;
procedure AddItem(AProcess: THandle; ADateStart: TDateTime; ATimeOutMS: Cardinal);
end;
const
csPocessThreadLatencyTimeMs = 500;
And here is an implementation part:
procedure TWatchDogTimerThread.Execute;
var
i: Integer;
begin
while not Terminated do
begin
Sleep(csPocessThreadLatencyTimeMs);
FItemsCS.Enter;
try
i := 0;
while i < FItems.Count do
begin
if not IsProcessRunning(FItems[i]) then
begin
FItems.Delete(i);
end
else if IsProcessTimedOut(FItems[i]) then
begin
InternalKillProcess(FItems[i]);
FItems.Delete(i);
end
else
Inc(i);
end;
finally
FItemsCS.Leave;
end;
end;
end;
procedure TWatchDogTimerThread.AddItem(AProcess: THandle; ADateStart: TDateTime; ATimeOutMS: Cardinal);
var
LItem: TProcessRecord;
begin
LItem.Handle := AProcess;
LItem.DateTimeBegin := ADateStart;
LItem.DateTimeTerminate := IncMilliSecond(ADateStart, ATimeOutMS);
FItemsCS.Enter;
try
FItems.Add(LItem);
finally
FItemsCS.Leave;
end;
end;
constructor TWatchDogTimerThread.Create;
begin
inherited Create(False);
FItems := TList<TProcessRecord>.Create;
FItemsCS := TCriticalSection.Create;
end;
destructor TWatchDogTimerThread.Destroy;
begin
FreeAndNil(FItemsCS);
FItems.Free;
FInstance := nil;
inherited;
end;
class function TWatchDogTimerThread.Instance: TWatchDogTimerThread;
begin
if not Assigned(FInstance) then
FInstance := Create;
Result := FInstance;
end;
procedure TWatchDogTimerThread.InternalKillProcess(const AItem: TProcessRecord);
begin
if AItem.Handle <> 0 then
TerminateProcess(AItem.Handle, 0);
end;
function TWatchDogTimerThread.IsProcessRunning(const AItem: TProcessRecord): Boolean;
var
LPID: DWORD;
begin
LPID := 0;
if AItem.Handle <> 0 then
GetWindowThreadProcessId(AItem.Handle, #LPID);
Result := LPID <> 0;
end;
function TWatchDogTimerThread.IsProcessTimedOut(const AItem: TProcessRecord): Boolean;
begin
Result := (AItem.DateTimeTerminate < Now);// and IsProcessRunning(AItem);
end;
end.
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 7 years ago.
Improve this question
I am trying to compile my program but I am getting this error:
Undeclared indentifier 'StreamLn'
i even tried to download PSock.dcu and put it into the library but it doesnt compile, it looks like its compactible with delphi 5,
unit ResourceInfo;
interface
uses
Classes, SysUtils, Windows;
type
TResourceInfo = class;
TDfmMode = ( dfmData, dfmResource, dfmASCII, dfmBinary);
TDfm = class
private
FOwner: TResourceInfo;
FName: string;
FData: TStream;
procedure SetName(const Value: string);
procedure SetOwner(const Value: TResourceInfo);
public
constructor Create(AOwner: TResourceInfo);
destructor Destroy; override;
function SaveToFile(FileName: TFileName; Mode: TDfmMode): Boolean;
property Data: TStream read FData;
property Name: string read FName write SetName;
property Owner: TResourceInfo read FOwner write FOwner;
end; {TDfm}
TResourceInfo = class(TComponent)
private
FActive: Boolean;
FDfms: TList;
FExeFileName: TFileName;
FModule: THandle;
FOnActivate: TNotifyEvent;
FOnDeactivate: TNotifyEvent;
procedure SetExeFileName(const Value: TFileName);
procedure SetActive(const Value: Boolean);
function GetDfms(Index: Cardinal): TDfm;
function GetDfmCount: Cardinal;
protected
procedure Clear;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function AddDfm(const Name: string; AData: TMemoryStream): Integer;
procedure DeleteDfm(const Name: string);
property DfmCount: Cardinal read GetDfmCount;
property Dfms[Index: Cardinal]: TDfm read GetDfms;
procedure EnumDfmNames;
property Module: THandle read FModule;
published
property Active: Boolean read FActive write SetActive;
property ExeFileName: TFileName read FExeFileName write SetExeFileName;
property OnActivate: TNotifyEvent read FOnActivate write FOnActivate;
property OnDeactivate: TNotifyEvent read FOnDeactivate write FOnDeactivate;
end; {TResourceInfo}
procedure Register;
implementation
uses
Winsock;
resourcestring
rsErrorLoadingExeFile = 'An error ocurred loading file %s, it may not be an executable module';
procedure Register;
begin
RegisterComponents('+HCU', [TResourceInfo]);
end; {Register}
{ TResourceInfo }
function TResourceInfo.AddDfm(const Name: string; AData: TMemoryStream): Integer;
var
FDfm: TDfm;
begin
FDfm := TDfm.Create(Self);
FDfm.Name := Name;
FDfm.Data.Size := AData.Size;
FDfm.Data.Seek(0, 0);
AData.Seek(0, 0);
FDfm.Data.CopyFrom(AData, AData.Size);
Result := FDfms.Add(FDfm);
end; {TResourceInfo.AddDfm}
constructor TResourceInfo.Create(AOwner: TComponent);
begin
inherited;
FActive := False;
FDfms := TList.Create;
FModule := 0;
end; {TResourceInfo.Create}
destructor TResourceInfo.Destroy;
begin
Clear;
FDfms.Free;
inherited;
end; {TResourceInfo.Destroy}
function CB_EnumDfmNameProc(hModule: THandle; lpszType, lpszName: PChar; lParam: Integer): Boolean; stdcall;
var
ms: TMemoryStream;
rs: TResourceStream;
Buffer: array of Byte;
begin
with TResourceInfo(lParam) do
begin
rs := TResourceStream.Create(TResourceInfo(lParam).Module, lpszname, lpszType);
try
ms := TMemoryStream.Create;
try
try
SetLength(Buffer, 4);
rs.Read(Buffer[0], SizeOf(Buffer));
if string(Buffer) = 'TPF0' then
begin
rs.Seek(0, 0);
ObjectBinaryToText(rs, ms);
ms.Seek(0, 0);
AddDfm(StrPas(lpszName), ms);
end;
except
raise;
end;
finally
ms.Free;
end;
finally
rs.free;
end;
end;
Result := True;
end; {CB_EnumDfmNameProc}
procedure TResourceInfo.EnumDfmNames;
begin
if FModule > 0 then
EnumResourceNames(FModule, RT_RCDATA, #CB_EnumDfmNameProc, Integer(Self));
end; {TResourceInfo.EnumDfmNames}
procedure TResourceInfo.DeleteDfm(const Name: string);
var
i: Cardinal;
begin
if FDfms.Count > 0 then
for i := Pred(FDfms.Count) downto 0 do
if UpperCase(TDfm(FDfms[i]).Name) = UpperCase(Name) then
begin
FDfms.Delete(i);
Break;
end;
end; {TResourceInfo.DeleteDfm}
procedure TResourceInfo.SetActive(const Value: Boolean);
begin
if FActive <> Value then
begin
if Value then
begin
if FModule > 0 then
FreeLibrary(FModule);
(* LOAD_LIBRARY_AS_DATAFILE
If this value is given, the function does a simple mapping of the file into the
address space. Nothing is done relative to executing or preparing to execute the
code in the mapped file. The function loads the module as if it were a data file.
You can use the module handle that the function returns in this case with the Win32
functions that operate on resources. Use this flag when you want to load a DLL in
order to extract messages or resources from it, and have no intention of executing
its code.If this value is not given, the function maps the file into the address
space in the manner that is normal for an executable module. The behavior of the
function is then identical to that of LoadLibrary in this regard. *)
FModule := LoadLibraryEx(PChar(FExeFileName), 0, LOAD_LIBRARY_AS_DATAFILE);
if not (FModule >= 32) then
raise Exception.CreateFmt(rsErrorLoadingExeFile, [FExeFileName]);
if Assigned(FOnActivate) then
FOnActivate(Self);
end
else
begin
Clear;
if FModule > 0 then
begin
FreeLibrary(FModule);
FModule := 0;
end;
if Assigned(FOnDeactivate) then
FOnDeactivate(Self);
end;
FActive := Value;
end;
end; {TResourceInfo.SetActive}
procedure TResourceInfo.SetExeFileName(const Value: TFileName);
begin
if FExeFileName <> Value then
FExeFileName := Value;
end; {TResourceInfo.SetExeFileName}
function TResourceInfo.GetDfms(Index: Cardinal): TDfm;
begin
Result := TDfm(FDfms[Index]);
end; {TResourceInfo.GetDfms}
function TResourceInfo.GetDfmCount: Cardinal;
begin
Result := FDfms.Count;
end; {TResourceInfo.GetDfmCount}
procedure TResourceInfo.Clear;
begin
if FDfms.Count > 0 then
while FDfms.Count > 0 do
FDfms.Delete(0);
end; {TResourceInfo.Clear}
{ TDfm }
constructor TDfm.Create(AOwner: TResourceInfo);
begin
inherited Create;
FData := TMemoryStream.Create;
FName := '';
SetOwner(AOwner);
end; {TDfm.Create}
destructor TDfm.Destroy;
begin
FData.Free;
inherited;
end; {TDfm.Destroy}
function TDfm.SaveToFile(FileName: TFileName; Mode: TDfmMode): Boolean;
function EndOfStream(Stream: TStream): Boolean;
begin
with Stream do
Result := Position = Size;
end; {EndOfStream}
var
fs: TFileStream;
ms: TMemoryStream;
s: string;
i, j: Byte;
begin
fs := TFileStream.Create(FileName, fmCreate or fmShareExclusive);
try
FData.Seek(0, 0);
case Mode of
dfmASCII:
begin
ms := TMemoryStream.Create;
try
s := FName + ' RCDATA' + #13#10 + '{';
StreamLN(fs, s);
ObjectTextToBinary(FData, ms);
ms.Seek(0, 0);
while not EndOfStream(ms) do
begin
s := '''';
for i := 0 to 15 do
begin
if ms.Read(j, SizeOf(j)) = 0 then
Break;
s := Concat(s, Format('%2.2x', [j]));
if (i = 15) or EndOfStream(ms) then
s := Concat(s, '''')
else
s := Concat(s, ' ');
end;
if EndOfStream(ms) then
s := Concat(s, #13#10 + '}');
StreamLN(fs, s);
end;
finally
ms.Free;
end;
end;
dfmBinary:
ObjectTextToBinary(FData, fs);
end;
finally
fs.Free;
end;
end; {TDfm.SaveToFile}
procedure TDfm.SetName(const Value: string);
begin
if FName <> Value then
FName := Value;
end; {TDfm.SetName}
procedure TDfm.SetOwner(const Value: TResourceInfo);
begin
FOwner := Value;
end; {TDfm.SetOwner}
end.
How can I declare it successfully?
Appears to me that WinSock unit does not have an StreamLn function (as PowerSock's PSock.pas unit uses Winsock as imported unit).
The StreamLn function in PSock.pas just adds an CRLF sequence to the string passed as parameter before calling the TStream.WriteBuffer method of the passed TStream parameter.
Here's the google cache snapshot from the Powersock's source code of PSock.pas
You need to either implement this function, or add a unit where this function is declared to your uses section.
I am modifying the edit control with autocomplete from here:
Auto append/complete from text file to an edit box delphi
I want to load autocomplete strings from DB. I declared new properties on autocomplete control descendant:
FACDataSource : TDataSource;
FACFieldName : string;
I call this to load autocomplete strings:
procedure TAutoCompleteEdit.ReadSuggestions;
begin
FAutoCompleteSourceList.Clear;
if (not Assigned(FACDataSource)) or (not Assigned(FACDataSource.DataSet)) or (not ACEnabled) then
exit;
with FACDataSource.DataSet do
begin
if Active and (RecordCount > 0) and (FACFieldName <> '') then
begin
First;
while not EOF do
begin
FAutoCompleteSourceList.Add(FACDataSource.DataSet.FieldByName(FACFieldName).AsString);
Next;
end;
if FAutoCompleteSourceList.Count > 0 then
ACStrings := FAutoCompleteSourceList;
end;
end;
end;
However, I get AccessViolation when assigning FAutoCompleteSourceList to ACStrings. The setter for ACStrings is:
procedure TAutoCompleteEdit.SetACStrings(const Value: TStringList);
begin
if Value <> FACList.FStrings then
FACList.FStrings.Assign(Value);
end;
I get AccessViolation in the line: FACList.FStrings.Assign(Value); (READ of address XXXYYY). Value is defined and not garbage at that point (e.g. in I can view the string list in the debugger). 'FStrings' is an empty stringlist.
It works fine when the control is dropped on the form. But doesn't if I place it within a custom inplace editor shown when user enters a DBGridEH cell.
The inplace editor is like this:
unit UInplaceAutoCompleteEditor;
interface
uses UDBAutoComplete, UMyInplaceEditor, classes, windows, Controls, Buttons, DB;
type TInplaceAutoCompleteEditor = class(TMyInplaceEditor)
private
FEditor : TAutoCompleteEdit;
FButton : TSpeedButton;
FShowButton : boolean;
procedure SetShowButton(value : boolean);
public
constructor Create(AOwner : TComponent); override;
procedure SetFocus; override;
destructor Destroy; override;
protected
procedure EditorKeyDown(Sender : TObject; var Key : Word; Shift : TShiftState);
function GetACDataSource : TDataSource;
procedure SetACDataSource(value : TDataSource);
function GetACFieldName : string;
procedure SetACFieldName(value : string);
procedure SetACEnabled(value : boolean);
function GetACEnabled : boolean;
published
property Editor : TAutoCompleteEdit read FEditor;
property ACDataSource : TDataSource read GetACDataSource write SetACDataSource;
property ACFieldName : string read GetACFieldName write SetACFieldName;
property ACEnabled : boolean read GetACEnabled write SetACEnabled;
property Button : TSpeedButton read FButton;
property ShowButton : boolean read FShowButton write SetShowButton;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('nikolaev', [ TInplaceAutoCompleteEditor ]);
end;
{ TInplaceAutoCompleteEditor }
constructor TInplaceAutoCompleteEditor.Create(AOwner: TComponent);
begin
inherited;
FEditor := TAutoCompleteEdit.Create(self);
FEditor.Parent := self;
FEditor.Align := alClient;
FEditor.Visible := true;
FEditor.WantTabs := true;
FEditor.OnKeyDown := EditorKeyDown;
FButton := TSpeedButton.Create(self);
FButton.Parent := self;
FButton.Align := alRight;
self.FOwnHeight := -1;
self.FOwnWidth := -1;
SetShowButton(false);
end;
destructor TInplaceAutoCompleteEditor.Destroy;
begin
Feditor.Destroy;
FButton.Destroy;
inherited;
end;
procedure TInplaceAutoCompleteEditor.EditorKeyDown(Sender: TObject;
var Key: Word; Shift: TShiftState);
begin
if Key in [ VK_Return, VK_Tab ] then
begin
self.Value := FEditor.Text;
Key := 0;
ConfirmValue;
end;
if Key = VK_Escape then
begin
Key := 0;
CancelValue;
end;
inherited;
end;
function TInplaceAutoCompleteEditor.GetACDataSource: TDataSource;
begin
Result := FEditor.ACDataSource;
end;
function TInplaceAutoCompleteEditor.GetACEnabled: boolean;
begin
Result := FEditor.ACEnabled;
end;
function TInplaceAutoCompleteEditor.GetACFieldName: string;
begin
Result := FEditor.ACFieldName
end;
procedure TInplaceAutoCompleteEditor.SetACDataSource(value: TDataSource);
begin
FEditor.ACDataSource := value;
end;
procedure TInplaceAutoCompleteEditor.SetACEnabled(value: boolean);
begin
FEditor.ACEnabled := value;
end;
procedure TInplaceAutoCompleteEditor.SetACFieldName(value: string);
begin
FEditor.acfieldname := value;
end;
procedure TInplaceAutoCompleteEditor.SetFocus;
begin
inherited;
FEditor.SetFocus;
end;
procedure TInplaceAutoCompleteEditor.SetShowButton(value: boolean);
begin
if value <> FShowButton then
begin
FShowButton := value;
FButton.Visible := value;
end;
end;
end.
This inplace editor inherits from an abstract class like this:
unit UMyInplaceEditor;
interface
uses Windows, classes, types, dbGridEh, ExtCtrls, Controls;
type TMyInplaceEditor = class (TWinControl)
private
FOnValueConfirmed : TNotifyEvent;
FOnCanceled : TNotifyEvent;
FWantTabs : boolean;
procedure AdjustPosition;
protected
FOwnHeight, FOwnWidth : integer;
FValue : Variant;
function GetIsEditing : boolean;
procedure SetIsEditing(value : boolean); virtual;
procedure ConfirmValue;
procedure CancelValue;
procedure SetValue(val : Variant); virtual;
public
property OnValueConfirmed : TNotifyEvent read FOnValueConfirmed write FOnValueConfirmed;
property OnCanceled : TNotifyEvent read FOnCanceled write FOnCanceled;
property Value : Variant read FValue write SetValue;
property IsEditing : boolean read GetIsEditing write SetIsEditing;
procedure SetPosition(parentControl : TWinControl; rect : TRect); virtual;
function ColumnEditable(column : TColumnEH) : boolean; virtual;
constructor Create(AOwner : TComponent); override;
property WantTabs : boolean read FWantTabs write FWantTabs;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('nikolaev', [TMyInplaceEditor]);
end;
constructor TMyInplaceEditor.Create(AOwner : TComponent);
begin
inherited Create(AOwner);
self.AutoSize := false;
self.Visible := false;
self.FOwnHeight := -1;
self.FOwnWidth := -1;
end;
procedure TMyInplaceEditor.AdjustPosition;
var xOffset, yOffset : integer;
begin
xoffset := self.Left + self.Width - self.Parent.Width;
if xOffset > 0 then
self.Left := self.Left - xOffset;
yOffset := self.Top + self.Height - self.Parent.height;
if yOffset > 0 then
self.Top := self.Top - yOffset;
end;
function TMyInplaceEditor.GetIsEditing : boolean;
begin
Result := self.Visible;
end;
procedure TMyInplaceEditor.SetIsEditing(value: Boolean);
begin
self.Visible := value;
self.BringToFront;
{if Visible then
self.SetFocus;}
end;
procedure TMyInplaceEditor.SetPosition(parentControl : TWinControl; rect: TRect);
begin
self.Parent := parentControl;
self.Top := rect.Top;//parentControl.Top;
self.Left := rect.Left;//parentControl.left;
if self.FOwnWidth = -1 then
self.Width := rect.Right - rect.Left
else
self.Width := self.FOwnWidth;
if self.FOwnHeight = -1 then
self.Height := rect.Bottom - rect.Top
else
self.Height := self.FOwnHeight;
AdjustPosition;
end;
function TMyInplaceEditor.ColumnEditable(column : TColumnEH) : boolean;
begin
Result := true;
end;
procedure TMyInplaceEditor.ConfirmValue;
begin
if Assigned(FOnValueConfirmed) then
FOnValueConfirmed(self);
end;
procedure TMyInplaceEditor.CancelValue;
begin
if Assigned(FOnCanceled) then
FOnCanceled(self);
end;
procedure TMyInplaceEditor.SetValue(val : Variant);
begin
FValue := val;
end;
end.
The InplaceEditor is used in a descendant from DBGridEH. I override ShowEditor and HideEditor to show / hide my editor in certain cases.
Again, the autocomplete control only throws exception when embedded in the inplaceeditor control.
What causes access violation?
The problem is that the code you are using mis-handles interface reference counting. Here are the relevant extracts:
type
TEnumString = class(TInterfacedObject, IEnumString)
....
Note that this class is derived from TInterfacedObject and so it manages its lifetime using reference counting.
Then the code goes on like this:
type
TAutoCompleteEdit = class(TEdit)
private
FACList: TEnumString;
....
So we are going to hold a reference to the object rather than the interface. That looks dubious already.
Then we do this:
constructor TAutoCompleteEdit.Create(AOwner: TComponent);
begin
inherited;
FACList := TEnumString.Create;
....
end;
destructor TAutoCompleteEdit.Destroy;
begin
FACList := nil;
inherited;
end;
There's nothing here to keep the object alive. At other points in the code we take a reference to the IEnumString interface. But then as soon as that reference is released, the object thinks that there are no references left. And so it is deleted. Then, later on, the code refers to FACList which now points at an object that has been destroyed.
A simple way to fix this would be to make sure that the TAutoCompleteEdit control always holds a reference to the interface:
type
TAutoCompleteEdit = class(TEdit)
private
FACList: TEnumString;
FEnumString: IEnumString;
....
constructor TAutoCompleteEdit.Create(AOwner: TComponent);
begin
inherited;
FACList := TEnumString.Create;
FEnumString := FACList;
....
end;
And with this change you can then remove the destructor for TAutoCompleteEdit since the object behind FEnumString will get destroyed by the reference counting mechanism.
Another way to fix this would be to change TEnumString to disable automatic reference counting. That would look like this:
type
TEnumString = class(TObject, IInterface, IEnumString)
private
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
....
end;
function TEnumString.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;
function TEnumString._AddRef: Integer;
begin
Result := -1;
end;
function TEnumString._Release: Integer;
begin
Result := -1;
end;
And then you'd need the TAutoCompleteEdit destructor to look like this:
destructor TAutoCompleteEdit.Destroy;
begin
FACList.Free;
inherited;
end;
And a final option would be to avoid holding a TEnumString at all and instead only hold an IEnumString reference. Let the reference counting manage lifetime as in the first solution. But then you'd need to implement another interface that allowed the TAutoCompleteEdit to obtain the TStrings object.
I still haven't found a truly satisfactory answer to this question, and am now considering rolling my own. I have ModelMaker and GExperts, and neither seems to load the comprehensive class-hierarchy I am looking for. As well, I don't think the folks at DevExpress will fork over the CDK code which compiles a full class list to inherit from... ;-)
SO...
If ALL I want to do is build a self-referencing table of all registered component classes (or even all classes including non-components, if that's just as easy/possible), what would be the best way to go about doing that?
Note: I don't really need property / method details; JUST a complete list of class names (and parent names) I can store to a table and put in a treeview. Anything beyond that, though, is more than welcome as bonus info. :-)
Update later:
One answer that shows up in my "recent" section on SO, but not here on the question (maybe they erased it?), was this:"u may want to take a look on code of Component Search, it may help you to enumrate all components installed." Is that code available? Is so, where is it hiding? Would be interesting to study.
Unfortunately, the code implementing the RegisterClass mechanism is hidden in Classes implementation section.
If you need this for getting the list of components installed in the IDE, you can write a design package, install it into the IDE and use IOTAPackageServices in ToolsAPI unit. This will give you the list of installed packages and their components.
Note: You'll have to add designide.dcp to your 'requires' clause to be able to use Delphi's internal units like ToolsAPI.
A bit more work but a more generic way would be to enumerate all loaded modules. You can call GetPackageInfo (SysUtils) on a package module to enumerate contained unit names and required packages. However this will not give you a list of classes contained in the package.
You could enumerate the package's list of exported functions (e.g. with TJclPeImage in the JCL) and search for those named like this:
#<unit_name>#<class_name>#
for example: '#System#TObject#'.
By calling GetProcAddress with the function name you get the TClass reference. From there you can walk the hierarchy using ClassParent. This way you can enumerate all classes in all packages loaded in a process running a Delphi executable compiled with runtime packages (Delphi IDE, too).
Another idea is to scan for type information which is on top of the list of exported functions so you can skip enumerating further. The type infos are exported with names starting with prefix '#$xp$'. Here's an example:
unit PackageUtils;
interface
uses
Windows, Classes, SysUtils, Contnrs, TypInfo;
type
TDelphiPackageList = class;
TDelphiPackage = class;
TDelphiProcess = class
private
FPackages: TDelphiPackageList;
function GetPackageCount: Integer;
function GetPackages(Index: Integer): TDelphiPackage;
public
constructor Create; virtual;
destructor Destroy; override;
procedure Clear; virtual;
function FindPackage(Handle: HMODULE): TDelphiPackage;
procedure Reload; virtual;
property PackageCount: Integer read GetPackageCount;
property Packages[Index: Integer]: TDelphiPackage read GetPackages;
end;
TDelphiPackageList = class(TObjectList)
protected
function GetItem(Index: Integer): TDelphiPackage;
procedure SetItem(Index: Integer; APackage: TDelphiPackage);
public
function Add(APackage: TDelphiPackage): Integer;
function Extract(APackage: TDelphiPackage): TDelphiPackage;
function Remove(APackage: TDelphiPackage): Integer;
function IndexOf(APackage: TDelphiPackage): Integer;
procedure Insert(Index: Integer; APackage: TDelphiPackage);
function First: TDelphiPackage;
function Last: TDelphiPackage;
property Items[Index: Integer]: TDelphiPackage read GetItem write SetItem; default;
end;
TDelphiPackage = class
private
FHandle: THandle;
FInfoTable: Pointer;
FTypeInfos: TList;
procedure CheckInfoTable;
procedure CheckTypeInfos;
function GetDescription: string;
function GetFileName: string;
function GetInfoName(NameType: TNameType; Index: Integer): string;
function GetShortName: string;
function GetTypeInfoCount(Kinds: TTypeKinds): Integer;
function GetTypeInfos(Kinds: TTypeKinds; Index: Integer): PTypeInfo;
public
constructor Create(AHandle: HMODULE; AInfoTable: Pointer = nil);
destructor Destroy; override;
property Description: string read GetDescription;
property FileName: string read GetFileName;
property Handle: THandle read FHandle;
property ShortName: string read GetShortName;
property TypeInfoCount[Kinds: TTypeKinds]: Integer read GetTypeInfoCount;
property TypeInfos[Kinds: TTypeKinds; Index: Integer]: PTypeInfo read GetTypeInfos;
end;
implementation
uses
RTLConsts, SysConst,
PSAPI, ImageHlp;
{ Package info structures copied from SysUtils.pas }
type
PPkgName = ^TPkgName;
TPkgName = packed record
HashCode: Byte;
Name: array[0..255] of Char;
end;
PUnitName = ^TUnitName;
TUnitName = packed record
Flags : Byte;
HashCode: Byte;
Name: array[0..255] of Char;
end;
PPackageInfoHeader = ^TPackageInfoHeader;
TPackageInfoHeader = packed record
Flags: Cardinal;
RequiresCount: Integer;
{Requires: array[0..9999] of TPkgName;
ContainsCount: Integer;
Contains: array[0..9999] of TUnitName;}
end;
TEnumModulesCallback = function (Module: HMODULE; Data: Pointer = nil): Boolean;
TEnumModulesProc = function (Callback: TEnumModulesCallback; Data: Pointer = nil): Boolean;
const
STypeInfoPrefix = '#$xp$';
var
EnumModules: TEnumModulesProc = nil;
function PackageInfoTable(Module: HMODULE): PPackageInfoHeader; forward;
function AddPackage(Module: HMODULE; Data: {TDelphiPackageList} Pointer): Boolean;
var
InfoTable: Pointer;
begin
Result := False;
if (Module <> HInstance) then
begin
InfoTable := PackageInfoTable(Module);
if Assigned(InfoTable) then
TDelphiPackageList(Data).Add(TDelphiPackage.Create(Module, InfoTable));
end;
end;
function GetPackageDescription(Module: HMODULE): string;
var
ResInfo: HRSRC;
ResData: HGLOBAL;
begin
Result := '';
ResInfo := FindResource(Module, 'DESCRIPTION', RT_RCDATA);
if ResInfo <> 0 then
begin
ResData := LoadResource(Module, ResInfo);
if ResData <> 0 then
try
Result := PWideChar(LockResource(ResData));
UnlockResource(ResData);
finally
FreeResource(ResData);
end;
end;
end;
function EnumModulesPS(Callback: TEnumModulesCallback; Data: Pointer = nil): Boolean;
var
ProcessHandle: THandle;
SizeNeeded: Cardinal;
P, ModuleHandle: PDWORD;
I: Integer;
begin
Result := False;
ProcessHandle := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, False, GetCurrentProcessId);
if ProcessHandle = 0 then
RaiseLastOSError;
try
SizeNeeded := 0;
EnumProcessModules(ProcessHandle, nil, 0, SizeNeeded);
if SizeNeeded = 0 then
Exit;
P := AllocMem(SizeNeeded);
try
if EnumProcessModules(ProcessHandle, P, SizeNeeded, SizeNeeded) then
begin
ModuleHandle := P;
for I := 0 to SizeNeeded div SizeOf(DWORD) - 1 do
begin
if Callback(ModuleHandle^, Data) then
Exit;
Inc(ModuleHandle);
end;
Result := True;
end;
finally
FreeMem(P);
end;
finally
CloseHandle(ProcessHandle);
end;
end;
function EnumModulesTH(Callback: TEnumModulesCallback; Data: Pointer = nil): Boolean;
begin
Result := False;
// todo win9x?
end;
function PackageInfoTable(Module: HMODULE): PPackageInfoHeader;
var
ResInfo: HRSRC;
Data: THandle;
begin
Result := nil;
ResInfo := FindResource(Module, 'PACKAGEINFO', RT_RCDATA);
if ResInfo <> 0 then
begin
Data := LoadResource(Module, ResInfo);
if Data <> 0 then
try
Result := LockResource(Data);
UnlockResource(Data);
finally
FreeResource(Data);
end;
end;
end;
{ TDelphiProcess private }
function TDelphiProcess.GetPackageCount: Integer;
begin
Result := FPackages.Count;
end;
function TDelphiProcess.GetPackages(Index: Integer): TDelphiPackage;
begin
Result := FPackages[Index];
end;
{ TDelphiProcess public }
constructor TDelphiProcess.Create;
begin
inherited Create;
FPackages := TDelphiPackageList.Create;
Reload;
end;
destructor TDelphiProcess.Destroy;
begin
FPackages.Free;
inherited Destroy;
end;
procedure TDelphiProcess.Clear;
begin
FPackages.Clear;
end;
function TDelphiProcess.FindPackage(Handle: HMODULE): TDelphiPackage;
var
I: Integer;
begin
Result := nil;
for I := 0 to FPackages.Count - 1 do
if FPackages[I].Handle = Handle then
begin
Result := FPackages[I];
Break;
end;
end;
procedure TDelphiProcess.Reload;
begin
Clear;
if Assigned(EnumModules) then
EnumModules(AddPackage, FPackages);
end;
{ TDelphiPackageList protected }
function TDelphiPackageList.GetItem(Index: Integer): TDelphiPackage;
begin
Result := TDelphiPackage(inherited GetItem(Index));
end;
procedure TDelphiPackageList.SetItem(Index: Integer; APackage: TDelphiPackage);
begin
inherited SetItem(Index, APackage);
end;
{ TDelphiPackageList public }
function TDelphiPackageList.Add(APackage: TDelphiPackage): Integer;
begin
Result := inherited Add(APackage);
end;
function TDelphiPackageList.Extract(APackage: TDelphiPackage): TDelphiPackage;
begin
Result := TDelphiPackage(inherited Extract(APackage));
end;
function TDelphiPackageList.First: TDelphiPackage;
begin
Result := TDelphiPackage(inherited First);
end;
function TDelphiPackageList.IndexOf(APackage: TDelphiPackage): Integer;
begin
Result := inherited IndexOf(APackage);
end;
procedure TDelphiPackageList.Insert(Index: Integer; APackage: TDelphiPackage);
begin
inherited Insert(Index, APackage);
end;
function TDelphiPackageList.Last: TDelphiPackage;
begin
Result := TDelphiPackage(inherited Last);
end;
function TDelphiPackageList.Remove(APackage: TDelphiPackage): Integer;
begin
Result := inherited Remove(APackage);
end;
{ TDelphiPackage private }
procedure TDelphiPackage.CheckInfoTable;
begin
if not Assigned(FInfoTable) then
FInfoTable := PackageInfoTable(Handle);
if not Assigned(FInfoTable) then
raise EPackageError.CreateFmt(SCannotReadPackageInfo, [ExtractFileName(GetModuleName(Handle))]);
end;
procedure TDelphiPackage.CheckTypeInfos;
var
ExportDir: PImageExportDirectory;
Size: DWORD;
Names: PDWORD;
I: Integer;
begin
if not Assigned(FTypeInfos) then
begin
FTypeInfos := TList.Create;
try
Size := 0;
ExportDir := ImageDirectoryEntryToData(Pointer(Handle), True, IMAGE_DIRECTORY_ENTRY_EXPORT, Size);
if not Assigned(ExportDir) then
Exit;
Names := PDWORD(DWORD(Handle) + DWORD(ExportDir^.AddressOfNames));
for I := 0 to ExportDir^.NumberOfNames - 1 do
begin
if StrLIComp(PChar(DWORD(Handle) + Names^), STypeInfoPrefix, StrLen(STypeInfoPrefix)) <> 0 then
Break;
FTypeInfos.Add(GetProcAddress(Handle, PChar(DWORD(Handle) + Names^)));
Inc(Names);
end;
except
FreeAndNil(FTypeInfos);
raise;
end;
end;
end;
function TDelphiPackage.GetDescription: string;
begin
Result := GetPackageDescription(Handle);
end;
function TDelphiPackage.GetFileName: string;
begin
Result := GetModuleName(FHandle);
end;
function TDelphiPackage.GetInfoName(NameType: TNameType; Index: Integer): string;
var
P: Pointer;
Count: Integer;
I: Integer;
begin
Result := '';
CheckInfoTable;
Count := PPackageInfoHeader(FInfoTable)^.RequiresCount;
P := Pointer(Cardinal(FInfoTable) + SizeOf(TPackageInfoHeader));
case NameType of
ntContainsUnit:
begin
for I := 0 to Count - 1 do
P := Pointer(Cardinal(P) + StrLen(PPkgName(P)^.Name) + 2);
Count := Integer(P^);
P := Pointer(Cardinal(P) + SizeOf(Integer));
if (Index >= 0) and (Index < Count) then
begin
for I := 0 to Count - 1 do
P := Pointer(Cardinal(P) + StrLen(PUnitName(P)^.Name) + 3);
Result := PUnitName(P)^.Name;
end;
end;
ntRequiresPackage:
if (Index >= 0) and (Index < Count) then
begin
for I := 0 to Index - 1 do
P := Pointer(Cardinal(P) + StrLen(PPkgName(P)^.Name) + 2);
Result := PPkgName(P)^.Name;
end;
ntDcpBpiName:
if PPackageInfoHeader(FInfoTable)^.Flags and pfPackageModule <> 0 then
begin
for I := 0 to Count - 1 do
P := Pointer(Cardinal(P) + StrLen(PPkgName(P)^.Name) + 2);
Count := Integer(P^);
P := Pointer(Cardinal(P) + SizeOf(Integer));
for I := 0 to Count - 1 do
P := Pointer(Cardinal(P) + StrLen(PUnitName(P)^.Name) + 3);
Result := PPkgName(P)^.Name;
end;
end;
end;
function TDelphiPackage.GetShortName: string;
begin
Result := GetInfoName(ntDcpBpiName, 0);
end;
function TDelphiPackage.GetTypeInfoCount(Kinds: TTypeKinds): Integer;
var
I: Integer;
begin
CheckTypeInfos;
Result := 0;
for I := 0 to FTypeInfos.Count - 1 do
if (Kinds = []) or (PTypeInfo(FTypeInfos[I])^.Kind in Kinds) then
Inc(Result);
end;
function TDelphiPackage.GetTypeInfos(Kinds: TTypeKinds; Index: Integer): PTypeInfo;
var
I, J: Integer;
begin
CheckTypeInfos;
Result := nil;
J := -1;
for I := 0 to FTypeInfos.Count - 1 do
if (Kinds = []) or (PTypeInfo(FTypeInfos[I])^.Kind in Kinds) then
begin
Inc(J);
if J = Index then
begin
Result := FTypeInfos[I];
Break;
end;
end;
end;
{ TDelphiPackage public }
constructor TDelphiPackage.Create(AHandle: HMODULE; AInfoTable: Pointer = nil);
begin
inherited Create;
FHandle := AHandle;
FInfoTable := AInfoTable;
FTypeInfos := nil;
end;
destructor TDelphiPackage.Destroy;
begin
FTypeInfos.Free;
inherited Destroy;
end;
initialization
case Win32Platform of
VER_PLATFORM_WIN32_WINDOWS:
EnumModules := EnumModulesTH;
VER_PLATFORM_WIN32_NT:
EnumModules := EnumModulesPS;
else
EnumModules := nil;
end;
finalization
end.
Unit of the test design package installed in the IDE:
unit Test;
interface
uses
SysUtils, Classes,
ToolsAPI;
type
TTestWizard = class(TNotifierObject, IOTAWizard, IOTAMenuWizard)
private
{ IOTAWizard }
procedure Execute;
function GetIDString: string;
function GetName: string;
function GetState: TWizardState;
{ IOTAMenuWizard }
function GetMenuText: string;
end;
implementation
uses
TypInfo,
PackageUtils;
function AncestryStr(AClass: TClass): string;
begin
Result := '';
if not Assigned(AClass) then
Exit;
Result := AncestryStr(AClass.ClassParent);
if Result <> '' then
Result := Result + '\';
Result := Result + AClass.ClassName;
end;
procedure ShowMessage(const S: string);
begin
with BorlandIDEServices as IOTAMessageServices do
AddTitleMessage(S);
end;
{ TTestWizard }
procedure TTestWizard.Execute;
var
Process: TDelphiProcess;
I, J: Integer;
Package: TDelphiPackage;
PInfo: PTypeInfo;
PData: PTypeData;
begin
Process := TDelphiProcess.Create;
for I := 0 to Process.PackageCount - 1 do
begin
Package := Process.Packages[I];
for J := 0 to Package.TypeInfoCount[[tkClass]] - 1 do
begin
PInfo := Package.TypeInfos[[tkClass], J];
PData := GetTypeData(PInfo);
ShowMessage(Format('%s: %s.%s (%s)', [Package.ShortName, PData^.UnitName, PInfo^.Name, AncestryStr(PData^.ClassType)]));
end;
end;
end;
function TTestWizard.GetIDString: string;
begin
Result := 'TOndrej.TestWizard';
end;
function TTestWizard.GetName: string;
begin
Result := 'Test';
end;
function TTestWizard.GetState: TWizardState;
begin
Result := [wsEnabled];
end;
function TTestWizard.GetMenuText: string;
begin
Result := 'Test';
end;
var
Index: Integer = -1;
initialization
with BorlandIDEServices as IOTAWizardServices do
Index := AddWizard(TTestWizard.Create);
finalization
if Index <> -1 then
with BorlandIDEServices as IOTAWizardServices do
RemoveWizard(Index);
end.
You have to add designide to your requires clause. When you install this design package a new menu item Test should appear under Delphi's Help menu. Clicking it should display all loaded classes in the Messages window.
Have you tried Delphi's own class browser?
The browser gets loaded with shortcut CTRL-SHIFT-B. I believe you can access its options by right clicking in the browser. Here you have the option to show only the classes in your project or all known classes.
I haven't checked but I expect every descendant from TComponent, including installed components to be visible below the TComponent node. Use CTRL-F to search for a particular class.
Edit: according to this Delphi Wiki page, CTRL+SHIFT+B is only available in Delphi5. I don't have Delphi 2007 to check for this but if you can not find a class browser in your version, I'd suspect there isn't any.