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.
Related
I've encountered a very strange DBComboBox problem in a master/detail app using Access via ADO. If you have a DBComboBox (.Style=csDropDown) containing a list of items and you enter some text that doesn't exist in the list, the value in the table's DBComboBox field won't appear when navigating back to that record. I've used the DBNavigator.OnClick code below to attempt to resolve this problem but it only works if the first record in the table contains a value not in the list. When you change the value of the DBComboBox in the first record to one that is in the list, no nonconforming items will appear in the DBComboBox text. Has anyone found a solution to this?
procedure TForm1.DBNavigator1Click(Sender: TObject; Button: TNavigateBtn);
var
SavePlace : TBookmark;
begin
if (DBComboBox1.Text='') then begin
SavePlace := TADODataSet(DBNavigator1.DataSource.DataSet).GetBookmark;
TADODataSet(DBNavigator1.DataSource.DataSet).Requery;
TADODataSet(DBNavigator1.DataSource.DataSet).GotoBookMark(SavePlace);
TADODataSet(DBNavigator1.DataSource.DataSet).FreeBookMark(SavePlace);
end;
end;
Unfortunately I don't have XE installed, but I have made a sample project which
reproduces your problem in D7 and Seattle. The code is shown below and I think
you will find that if you follow the exact steps below, it shows that there is something
rather strange going on. Update See the bottom of the answer for a possible work-around, which I think is preferable to the code you quote in your q.
As you'll see, except for Form1 itself, all the components are created at runtime
entirely in code. This is to remove any doubt whether the behaviour is caused
by some obscure property setting (it isn't) and in case you wish to submit it
to EMBA as a bug report. For a similar reason I've used a TClientDataSet so that
the app does not depend on any external data.
Steps (please follow steps 4-7 exactly the first time you try them)
Restart the IDE and create a new project and edit the .Pas file for the main form as shown below. The reason for restarting the IDE is that I discovered that if it has been running for a long time (two days in my case) the details of the misbehaviour
of the app change slightly).
Compile and run.
The app will start with the first from in the DBGrid selected.
Type anything (an 'X' will do) into the DBComboBox, then click the Save toolbutton
on the DBNavigator.
Click the Next (>) toolbutton on the DBNavigator once only. The DBComboBox now displays
'Two'.
Click the Prior (<) toolbutton on the DBNavigator once only. The DBComboBox is now empty.
Click the Prior (<) toolbutton on the DBNavigator once only. The DBComboBox now displays
what you typed in step 4.
Close the app. Most likely the IDE debugger will catch a fault and open the CPU window.
This fault occurs on the line
DestroyWindow(FHandle);
in TApplication.Destroy. I am no Windows internals expert but I think it's likely that this is because of some corruption being caused by whatever causes the blank result in step 6. The fact that
step 7 causes the DBComboBox to correctly display what you typed makes me suspect that cause is actually
in the way the DBComboBox interacts with its FieldDataLink which connects it to the dataset.
Btw, the fact that the fault does not occur if you call DBComboBox1.Free in TForm1's FormDestroy
seems to me to confirm that the fault is related to whatever is causing your problem.
All this, and the fact that it has apparently passed unnoticed in the 25 years of Delphi, seems very strange
to me. This demo app can show up another quirk that's been lurking in the DBGrid for a similar length ot time. To see
it:
Comment out all the references to the DBComboBox and reinstate dgMultiSelect amongst the grid options in the
line that sets them. Compile and run the app.
Click in the cell in the Name column for the first row, type something and save it.
Click the Next toolbutton once. The first row does not de-select itself as it should.
AFAICT (by displaying the DBGrid's count of Bookmarks on the form's caption) this is not
because it has saved a bookmark on the first row.
While I've been writing this, a possible work-around has occurred to me, which I'll updated
this to include if I can get it to work.
Code
type
TForm1 = class(TForm)
procedure FormCreate(Sender : TObject);
private
procedure SetUpDataSet;
procedure SetUpGUI;
protected
public
ClientDataSet1 : TClientDataSet;
DBGrid1: TDBGrid;
DataSource1: TDataSource;
DBNavigator1: TDBNavigator;
DBComboBox1: TDBComboBox;
end;
[...]
procedure TForm1.SetUpGUI;
begin
ClientDataset1 := TClientDataSet.Create(Self);
DataSource1 := TDataSource.Create(Self);
DataSource1.DataSet := ClientDataSet1;
DBGrid1 := TDBGrid.Create(Self);
DBGrid1.Top := 8;
DBGrid1.Left := 8;
DBGrid1.Width := 425;
DBGrid1.Options := [dgEditing, dgTitles, dgColumnResize, dgColLines, dgRowLines, dgTabs, dgConfirmDelete, dgCancelOnExit{, dgMultiSelect}];
DBGrid1.DataSource := DataSource1;
DBGrid1.Parent := Self;
DBNavigator1 := TDBNavigator.Create(Self);
DBNavigator1.DataSource := DataSource1;
DBNavigator1.Top := 144;
DBNavigator1.Left := 16;
DBNavigator1.Parent := Self;
DBComboBox1 := TDBComboBox.Create(Self);
DBComboBox1.DataField := 'Name';
DBComboBox1.DataSource := DataSource1;
DBComboBox1.Top := 240;
DBComboBox1.Left := 16;
DBComboBox1.Parent := Self;
end;
procedure TForm1.SetUpDataSet;
var
Field : TField;
begin
// Create 2 fields in the CDS
Field := TIntegerField.Create(Self);
Field.FieldName := 'ID';
Field.FieldKind := fkData;
Field.DataSet := ClientDataSet1;
Field := TStringField.Create(Self);
Field.FieldName := 'Name';
Field.Size := 40;
Field.FieldKind := fkData;
Field.DataSet := ClientDataSet1;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
SetUpGUI;
SetUpDataSet;
// Set up DBComboBox
DBComboBox1.Style := csDropDown;
DBComboBox1.Items.Add('One');
DBComboBox1.Items.Add('Two');
DBComboBox1.Items.Add('Three');
// Next, set up the CDS
ClientDataSet1.CreateDataSet;
ClientDataSet1.InsertRecord([1, '']);
ClientDataSet1.InsertRecord([2, 'Two']);
ClientDataSet1.InsertRecord([3, '']);
ClientDataSet1.First;
end;
Possible work-around Add the following method to Form1:
procedure TForm1.ClientDataSet1AfterScroll(DataSet: TDataSet);
var
S : String;
begin
S := DataSet.FieldByName('Name').AsString;
if S <> DbComboBox1.Text then
DbComboBox1.Text := S;
Caption := IntToStr(DBGrid1.SelectedRows.Count);
end;
Then, in the SetUpGUI method, add the following immediately after the line where ClientDataSet1 is created:
ClientDataset1.AfterScroll := ClientDataSet1AfterScroll;
I have not tested this thoroughly, but it seems to work in the test conditions of the steps I've described above.
I created a report using FastReport, but the only way I know to get data to that report is from a database, I want to get data from a TEdit and I don't want to store anything, just writing in TEdit + click on the button (fastreport.preview) + print and Done.
How can I do that ?
Please explain am new with Delphi and FastReport.
You can use the OnGetValue event of your TfrxReport component as follows:
procedure TForm1.frxReport1GetValue(const VarName: string; var Value: Variant);
begin
if(VarName = 'MyVariable') then
begin
Value := Edit1.Text;
end;
end;
Then you just need to add a memo item to the report and set its value to [MyVariable].
One possible approach is to access the TfrxReport and TfrxMemoView components at runtime. Note, that when you don't have a dataset, the Master Data band won't be printed, so you should use another band.
You may use the following code as a basic example. Just place one TfrxReportTitle band (named 'ReportTitle1') and one TfrxMemoView text object (named 'Memo1') on your TfrxReport component.
procedure TfrmMain.btnReportClick(Sender: TObject);
var
memo: TfrxMemoView;
band: TfrxReportTitle;
begin
// Get the band
band := (rptDemo.Report.FindObject('ReportTitle1') as TfrxReportTitle);
// Create a memo
memo := TfrxMemoView.Create(band);
memo.CreateUniqueName;
memo.ParentFont := True;
memo.Text := edtReport.Text;
memo.SetBounds(100, 1, 100, 16);
memo.HAlign := haLeft;
memo.AutoWidth := False;
// Use existing memo
memo := (rptDemo.Report.FindObject('Memo1') as TfrxMemoView);
memo.Text := edtReport.Text;
// Preview report
rptDemo.ShowReport(False);
end;
Notes: This is a working example, tested with FastReport 4.7.
if Length(idStrArray)>0 then
begin
with DataModule4.ADQueryTemp do
begin
Close;
SQL.Clear;
SQL.Add('SELECT id, pato, ftest, res FROM tbl ');
SQL.Add('WHERE id IN ('+idStrArray+')');
Open;
(rprMasterDataFish as Tfrxmasterdata).DataSet := frxDst_Multi;
(rprMasterDataFish as Tfrxmasterdata).DataSetName := 'Multi';
end;
end;
Hello,
I have TfrxDBDataset component. I can add fields from table like above. But i also want to add fields and values manually at runtime.
I have text file like this :
id note
1 sample
2 sample
I want to read this text file and insert note to frxDst_Multi. Is this possible ?
I dont want to create a new column as note in tbl. Because, i have too many mysql server.
Thanks in advice,
You can't add fields to a dataset while it is open, so you have to do it before
it is opened, either in code or using the TDataSet fields editor. If you are
doing it in code, you can add the field in the dataset's BeforeOpen
event.
The next problem is that is you don't want to field to be bound to the table the
dataset accesses, you need to add it as a calculated field and set its value
in the dataset's`OnCalcFields' event - see example below.
Ideally, the added field would be a TMemoField, but unfortunately a TMemoField
can't be a calculated field (FieldKind = ftMemo). So probably the best thing you can do is to make it a String field, but then you will need to
give it a fixed maximum size and truncate the field's value at that size when you calculate its value.
Btw, I don't know whether your TfrxDBDataset supports the fkInternalCalc fieldkind, but if it does, then you could try adding the note field as a TMemoField instead of a TStringField one.
The one thing I haven't been able to do is to load the field's value from
an external file because you haven't said in your q how to determine the
name of the file which is to be read.
Obviously, the IsNull check in the frxDst_MultiCalcFields event is to avoid the overhead of reloading the file if its contents have already been read.
const
NoteFieldSize = 4096;
procedure TForm1.AddNoteField;
var
NoteField : TField;
begin
if frxDst_Multi.FindField('Note') = Nil then begin
NoteField := TStringField.Create(frxDst_Multi);
NoteField.FieldName := 'Note';
NoteField.Size := NoteFieldSize;
NoteField.FieldKind := fkCalculated;
NoteField.DataSet := frxDst_Multi;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
frxDst_Multi.Open;
end;
procedure TForm1.frxDst_MultiCalcFields(DataSet: TDataSet);
var
S : String;
begin
if DataSet.FieldByName('Note').IsNull then begin
S := 'a note'; // replace by code to set the field value
DataSet.FieldByName('Note').AsString := Copy(S, 1, NoteFieldSize);
end;
end;
procedure TForm1.frxDst_MultiBeforeOpen(DataSet: TDataSet);
begin
AddNoteField;
end;
I've noticed that it's possible to set the TextHint property on a TDBEdit in code (it's not visible in the Object Inspector), however it doesn't display, is there an easy way of making this work?
The following setup works in XE2. Drop a TClientDataSet, TDataSource, and 2 TDBEdit controls on a Form, and make the OnCreate event handler of the Form look like this:
procedure TForm1.FormCreate(Sender: TObject);
begin
DataSource1.DataSet := ClientDataSet1;
DBEdit1.DataSource := DataSource1;
DBEdit2.DataSource := DataSource1;
ClientDataSet1.FieldDefs.Add('First', ftString, 20);
ClientDataSet1.FieldDefs.Add('Last', ftString, 20);
ClientDataSet1.CreateDataSet;
ClientDataSet1.Open;
DBEdit1.DataField := ClientDataSet1.Fields[0].FieldName;
DBEdit1.TextHint := 'first name';
DBEdit2.DataField := ClientDataSet1.Fields[1].FieldName;
DBEdit2.TextHint := 'last name';
ClientDataSet1.Insert;
end;
One potential problem is the TDBEdits being read-only. For instance, remove the Insert() call from the snippet and the edits will remain empty. This behavior is similar with regular edits, which is reasonable - when an edit control does not allow editing, there's no point in showing a hint about what the user should enter.
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?