Why don't a ClientDataSet descendant's fields appear at design time? - delphi

I'm trying to write a component that inherits from TClientDataset. On the create of the component in design time I want to instantiate a list of common fields that are used within my framework. The code below will execute without errors and the field will appear at run time but not design time. Can anyone help me? I'm sure its something trivial!
{ InheritedClientDataset }
constructor InheritedClientDataset.Create(AOwner: TComponent);
var
Field : TField;
begin
inherited;
Field := TField.Create(self);
Field.Name := 'ATestField';
Field.FieldName := 'Test';
Field.SetFieldType(ftInteger);
//Field.DataType := ftInteger;
Field.Size := 0;
Field.FieldKind := fkData;
self.Fields.Add(Field);
end;

Try creating your field using its fieldtype. For example, TIntegerField.
MyField := TIntegerField.Create(Self);
MyField.FieldName := 'MyFieldName';
MyField.DataSet := Self;
MyField.Name := Self.Name + '_' + MyField.FieldName;
That should work.
It will be available to controls but not the Fields Editor.

Totally untested, but you should probably be adding to FieldDefs instead:
with FieldDefs.AddFieldDef do
begin
DataType := ftInteger;
Name := 'Field1';
end;
with FieldDefs.AddFieldDef do
begin
DataType := ftString;
Size := 25;
Name := 'Field2';
end;
You may also have to add a call to CreateDataSet after the FieldDefs are added:
// After above code...
inherited CreateDataSet;

I have a feeling that in cases like this, you might be going against the design intention of the VCL component designtime. Fields are typically defined by someone who places a table object onto a data module, then set the dataset properties to a particular SQL or other table and selects the fields from that table, rather than a component with a fixed set of fields, which might be something problematic for the current architecture to support, even though you have a fix, are you sure there aren't problems with that approach?
Have you thought about an alternative approach? (Write a component with a public property that allows it to be connected to a dataset or datasource and put all your framework logic in that component). Leave the dataset class alone.
Do you really NEED to do an "IS A" relationship in OOP terms, or would your code actually be cleaner if you considered "HAS A link to a dataset" instead?

Related

Getting RTTI information on a smart pointer

Using Spring, when I declare a smart pointer for a record
SmartPerson = IShared<Pperson>;
And then create it
Smartperson := Shared<PPerson>.Make;
*by the way, think this is really cool.
How then do I go about getting RTTI on the smart pointer? Obviously, I know it's based on a TPerson record, but what steps are needed to sort of reverse what happens when Spring allocates the pointer in the first place?
I see when the record pointer is created it uses code like this
tkPointer: IShared<Pointer>(Result) := Shared.TRecordFinalizer.Create(TypeInfo(T));
followed by code like this
constructor Shared.TRecordFinalizer.Create(typeInfo: PTypeInfo);
var
Size : integer;
begin
inherited Create;
fTypeInfo := typeInfo.TypeData.RefType^;
size := GetTypeSize(fTypeInfo);
fValue := AllocMem(Size);
end;
My Question is how do I get, for example, a field of the record the smart pointer points to by using the smart pointer itself?
Hope that makes sense, and maybe another dumb question.
To give some more background to problem. I have a factory which uses RTTI to build a control for a field of a record.
procedure TForm3.Button1Click(Sender: TObject);
begin
Task := Shared<pTaskRecord>.make;
Task.AnalysisDates.ES := now;
Task.TaskType := TTaskTypes.DelayTask;
ControlFactory := TControlFactory.create(Self);
Edit := Controlfactory.GetControl('TAnalysisDates','ES');
if assigned(Edit) then
begin
AddObject(Edit.Invoke);
Edit.Value := #Task.AnalysisDates;
end;
end;
Which works. It returns an TDateEdit based on attribute tags on a record
I was thinking perhaps what I could do is this
Edit := Controlfactory.GetControl(Task,Task.ES);
//passing in my smart pointer, along with field
then the factory would have everything it needs to hook the control up without having to do it in code myself.
Edit.Value := #Task.AnalysisDates;
the above line could be handled by the factory. Anyway just an idea

FindComponent Not Finding Components Created at Runtime

I use Delphi 7 with a number of third party components. My main stub application loads a number of DLLs, which are various modules like creditors, debtors, purchase orders, and so on.
I have an issue with FindComponent(). 99% of the time, it works how it should. But not for the code below.
I was trying to create a form reports, where I keep all the details of the reports selection criteria in a table, and then create the criteria on the fly. In theory, it should work perfectly, but for some reason after creating the components, FindComponent() cannot find them.
try
for i := gbSelectionCriteria.ComponentCount - 1 downto 0 do begin
ShowMessage(gbSelectionCriteria.Components[i].Name);
gbSelectionCriteria.Components[i].Free;
end;
// The above loop to remove the components from the groupbox works fine
// Creating the components works
fSysData.tbSelectionCriteria.First;
while not fSysData.tbSelectionCriteria.EOF do begin
case fSysData.tbSelectionCriteriaComponentType.AsInteger of
1 : begin // TMyAdvEdit
with TMyAdvEdit.Create(gbSelectionCriteria) do begin
Visible := False;
Parent := gbSelectionCriteria;
Name := fSysData.tbSelectionCriteriaName.AsString;
Left := fSysData.tbSelectionCriteriaLeft.AsInteger;
Top := fSysData.tbSelectionCriteriaTop.AsInteger;
Width := fSysData.tbSelectionCriteriaWidth.AsInteger;
LabelCaption := fSysData.tbSelectionCriteriaCaption.AsString;
LabelPosition := AdvEdit.lpLeftCenter;
LabelAlwaysEnabled := True;
LabelTransparent := True;
EditType := MyEditType[fSysData.tbSelectionCriteriaDataType.AsInteger];
Text := '';
OnClick := GetClickEvent(fSysData.tbSelectionCriteriaOnClickEvent.AsString);
OnDblClick := GetClickEvent(fSysData.tbSelectionCriteriaOnDblClickEvent.AsString);
OnKeyPress := GetKeyPressEvent(fSysData.tbSelectionCriteriaOnKeyPressEvent.AsString);
Visible := True;
// at this point findComponent finds nothing
if FindComponent(Name) <> nil then
ShowMessage(Name+' Created');
end;
edEdit.OnClick := GetClickEvent(fSysData.tbSelectionCriteriaOnClickEvent.AsString);
edEdit.OnDblClick := GetClickEvent(fSysData.tbSelectionCriteriaOnDblClickEvent.AsString);
edEdit.OnKeyPress := GetKeyPressEvent(fSysData.tbSelectionCriteriaOnKeyPressEvent.AsString);
edEdit.Visible := True;
if FindComponent(edEdit.Name) <> nil then
ShowMessage(edEdit.Name+' Created');
end;
2 : begin
end;
3 : begin
end;
4 : begin
end;
5 : begin
end;
6 : begin
end;
7 : begin
end;
8 : begin
end;
end;
fSysData.tbSelectionCriteria.Next;
end;
if fSysData.tbSysReports.Locate('ReportID', TAdvOfficeRadioButton(Sender).Tag, []) then begin
ReportData.ReportID := TAdvOfficeRadioButton(Sender).Tag;
ReportData.RepName := fSysData.tbSysReportsReportName.AsString;
ReportData.RepTitle := fSysData.tbSysReportsReportTitle.AsString;
ReportData.RepModule := fSysData.tbSysReportsModule.AsString;
ReportData.RepOrientation := fSysData.tbSysReportsReportOrientaton.AsString;
ReportData.RepPageIndex := fSysData.tbSysReportsCriteriaPageIndex.AsInteger;
end;
finally
end;
The Process of the reports is:
User clicks a button
Radio buttons are created from the button click
User clicks a radio button
Report criteria is created from the radio button click
User enters data or DblClicks to select data from a list.
User Clicks Preview button to view Report - this is where FindComponent fails and returns nil..
All the code worked before when I had created all the criteria at design time, then added the code above.
The code below is part of what needs to be added to the query to retrieve the data for the report:
if Length(TMyAdvEdit(FindComponent('edQuoteReference')).Text) > 0 then
qryTempTable.SQL.Add(' and q.UserReference = "' + TMyAdvEdit(FindComponent('edQuoteReference')).Text + '"');
This is the first time FindComponent() fails and goes no further.
I have tried various ways to create the components, but each of them results in an Access Violation because the component is nil.
I have looked everywhere, and tried everything I can think of, for a solution to this problem.
FindComponent searches for components owned by the subject of the method call. You call FindComponent on the form, and so look for the component amongst those components owned by the form. But the control you search for is not owned by the form, it is owned by gbSelectionCriteria, which is what you passed to the control's constructor as the Owner argument.
If you wish to use FindComponent in the way you do you therefore need to make the form be the owner of the controls that you create. Then when you call FindComponent on the form, it can find the control because it is the owner. Pass Self to the control's constructor to make this come to pass:
TMyAdvEdit.Create(Self)
I'm having to make some reasonably large guesses here. Perhaps this code actually resides in a data module rather than a form. But the essential principle will be as I say.
Firstly I do apologize if this is in the wrong spot..
Thanks for the response and the answer, I have been doing this for a lot of years and I can't believe I missed something so small.
this,
if FindComponent(Name) <> nil then
should have been this,
if gbSelectionCriteria.FindComponent(Name) <> nil then
I don't normally use with, it was just one way to test create the component.
I set the components visibility to false before and then to true after it is created to stop flicker as it creates.
Thanks again..

Setting up ADT fields in code

I'm experimenting with a simple ClientDataSet project that uses a TADTField named Address which contains two subfields, Line1 and City, both of which are strings, size 20. THe CDS is connected to a DBGrid and DBNavigator.
If I set up the CDS using TFieldDefs in the Object Inspector and the ChildDefs property of the ADT field, the project compiles and executes fine.
However, if I try to set up the ATD field and its children in code, I get two problems:
Unlike when I use the FieldDefs method, the ATD "cell" doesn't appear in the DBGrid, so the Line1 and City sub-fields dont display inside it. Instead they appear as ordinary fields and they are duplicated. There is one "Line1" column, then a "City" one, then another "Line1" ...
When I close the form, I get a "double-free" AV inside a second (?) call to TFields.Destroy.
Obvioulsly I'm doing something wrong but I can't see what it is.
Here's my code :
procedure TForm1.FormCreate(Sender: TObject);
var
ADTField : TADTField;
Field : TField;
begin
// at this point, the clientDataSet has no TFields or TFieldDefs
Field := TIntegerField.Create(nil);
Field.FieldName := 'ID';
Field.DataSet := ClientDataset1;
ADTField := TADTField.Create(nil);
ADTField.FieldName := 'Address';
ADTField.DataSet := ClientDataset1;
Field := TStringField.Create(nil);
Field.FieldName := 'Line1';
Field.Size := 20;
Field.DataSet := ClientDataset1;
ADTField.Fields.Add(Field);
Field := TStringField.Create(nil);
Field.FieldName := 'City';
Field.Size := 20;
Field.DataSet := ClientDataset1;
ADTField.Fields.Add(Field);
ClientDataset1.CreateDataSet;
ClientDataset1.Insert;
ClientDataset1.FieldByName('ID').AsInteger := 1;
try
ADTField.Fields.FieldByName('Line1').AsString := '1, Railway Cuttings';
ADTField.Fields.FieldByName('City').AsString := 'London';
except
end;
ClientDataset1.Post;
end;
That's the entire code of the project. I'm using D7.
I remember being foxed by something similar when I first tried out ADT fields: although the TFieldDefs editor in the IDE has an obvious way to add child FieldDefs to an TADTField, there's no counterpart in the IDE's TFields editor.
Anyway, I think you're not quite "parenting" the two fields that you want to be children of the ADT one correctly. Instead of calling ADTField.Fields.Add, you need to do it via the field itself, by setting its ParentField property:
Field := TStringField.Create(ClientDataset1);
Field.FieldName := 'Line1';
Field.Size := 20;
Field.DataSet := ClientDataset1;
// ADTField.Fields.Add(Field);
Field.ParentField := ADTField;
And that btw is how you would do it in the IDE if you were setting up TFields instead of TFieldDefs. You'd create your Line1 and City fields in the ordinary way using the TFields editor, then select them in turn on the OI and set their ParentField property. I think you'll find that they and the Address field will then display in your grid correctly and the AV on shutdown will go away.

Change Memo1.Font of all Dynamically Created Forms on Delphi

I create many forms on runtime using
Application.CreateForm(TForm2, Form2);
Form2.Show;
Now I need to change the Memo1.Font of them at once.
Form2.Memo1.Font:=newfont;
But only the latest created form's Memo1.Font changes. How can I change all?
I guess you're calling Application.CreateForm(TForm2, Form2); multiple times which reassigns the newly created form to your global Form2 variable, so later when you refer to Form2 you're referring to the last-created instance.
To access all instances of TForm2 in your application, you can use Screen.Forms property:
for I := 0 to Screen.FormCount - 1 do
if Screen.Forms[I] is TForm2 then
TForm2(Screen.Forms[I]).Memo1.Font := ...
The reason for this behaviour is, that component names should be unique,
You can not address multiple components by just one name!
In this case You will have to iterate through all components to find all TMemos.
This could look something like
var i,j: integer;
begin
// first find all Forms in Application
for i:=0 to Application.ComponentCount - 1 do
begin
if Application.Components[i] is TForm then
begin
with (Application.Components[i] as TForm) do
begin
// now find all TMemos and change the font
for j:=0 to ComponentCount-1 do
begin
if (Components[j] is TMemo) and (Components[j].Name = 'Memo1') then (Components[j] as TMemo).Font.Name := 'Arial';
end;
end;
end;
end;
This is a very general approach and You can easily adapt it to other components within Your application.
Another approach would be to memorize all created TMemos in an Objectlist when they are created the first time (then You could easily iterate the object list and change TMemos properties) but without knowing more about Your implementation it is hard to give a good advice.

Adding a checkbox to cxGridDBColumn (DateEdit)

I using Delphi BDS 2006 and have a DevExpress cxGridDBColumn with properties set to DateEdit and was wondering whether it is possible to add a checkbox to the displayed date time picker popup?
I am not sure that I understand what you wish to achieve. Anyway, it is impossible without creating a custom cxEditor which supports this look&feel and desired functionality.
Here is a quick hack which should help you implement this feature. However, you should handle the checkBox yourself. I have done this for the standalone editor, however, the same approach will work with the inplace editor:
procedure TForm1.cxDateEdit1PropertiesPopup(Sender: TObject);
var
AEdit: TcxDateEdit;
ACalendar: TcxPopupCalendar;
ACheckBox: TcxCheckBox;
begin
AEdit := TcxDateEdit(Sender);
if AEdit.Tag <> 1 then
begin
AEdit.Tag := 1;
ACalendar := TcxPopupCalendar(AEdit.Properties.PopupControl);
ACheckBox := TcxCheckBox.Create(Self);
ACheckBox.Parent := ACalendar.Parent;
ACheckBox.Align := alBottom;
ACheckBox.Transparent := True;
ACalendar.Parent.Height := ACalendar.Parent.Height + ACheckBox.Height;
end;
end;

Resources