I have two forms with TListview for each form (to show the data I just added).
Its okay when I try to add data from form1 and in Listview(form1), its shown. But i also want, if i add data from form1, Listview(form1) and Listview(Form2) automatically added.
Here is my code:
procedure TForm1.btnAddClick(Sender: TObject);
var
data1,data2 : TListItem;
m1,m2,m3 : String;
dat : TForm2;
begin
m1 := EMom1.Text;
m2 := EMom2.Text;
m3 := EMom3.Text;
//listview form1
data1 := ListView1.Items.Add;
data1.Caption := m1;
data1.SubItems.Add(m2);
data1.SubItems.Add(m3);
//listview form2
data2 := dat.ListView1.Items.Add; {error on this code}
data2.Caption := m1;
data2.SubItems.Add(m2);
data2.SubItems.Add(m3);
data2 := dat.ListView1.Items.Add;
The variable dat has not been initialized. I would expect the compiler to warn you of this. I hope you enabled hints and warnings.
You need to supply a valid reference to a TForm2 instance. I don't know enough about your program to know where you'll get that reference. Presumably you know the answer.
You should also extract the list view item adding code into a method:
procedure AddListItem(lv: TListView; s1, s2, s3: string);
You can then call this function twice passing the two different list views.
Finally, were you able to switch to using virtual list views you would not need two copies of the data.
Related
Can anyone help me to clone a TFDQuery in run-time? I'm coding in Delphi Tokyo , I have a Datamodule with a TFDQuery in which I defined all fields properties using Fields Editor at design time, in this way my DBGrid1 that points to this a Datamodule of this dataset, has all columns properly formated (dislay names, width, format, order). During run-time I need to create new instances of TFDQuery, TDatamodule and link these new objects with the Dbgrid1. I need this new TFDQuery be identiical to the existing one defined at design-time in order to keep DBgrid1 with same display names, display width and display formats as the design-time!
I tried the following approaches to copy dataset field definitions :
**1st Approach : Method Assign for TFDQuery (didn't work) **
type
TFormDados = class(TForm)
Edit1: TEdit;
Button1: TButton;
DBGrid1: TDBGrid;
Edit2: TEdit;
Label1: TLabel;
Label2: TLabel;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
vconnection : TFDConnection;
vdataset : TFDQuery;
vdatasource : Tdatasource;
public
{ Public declarations }
end;
var
FormDados: TFormDados;
implementation
{$R *.dfm}
Uses
unitdata;
procedure TFormDados.Button1Click(Sender: TObject);
var
i : integer;
begin
vconnection := TFDConnection.Create(nil);
vconnection.Assign(Dtmodule.FDConGrafico);
vdataset := TFDQuery.Create(nil);
vdataset.Connection := vconnection;
vdataset.Assign(Dtmodule.FDQueryDados); // Runtime Error : Cannot assign a TFDQuery to a TFDQuery
2nd Approach : Assign FieldDefs from the existing Dataset to the new one - didn't work !
...
vdataset.FieldDefs.Assign(Dtmodule.FDQueryDados.FieldDefs);
vdataset.sql := Dtmodule.FDQueryDados.sql;
vdataset.params := Dtmodule.FDQueryDados.Params;
vdataset.FieldDefs.Update;
vdataset.CreateDataSet;
vdatasource := Tdatasource.create(nil);
vdatasource.DataSet := vdataset;
dbgrid1.DataSource := vdatasource;
vdataset.close;
vdataset.Params[0].Asinteger := strtoint(edit1.Text);
vdataset.Params[1].Asinteger := strtoint(edit2.Text);
vdataset.Open;
Althought Assign method had run, vdataset didn't receive the fields definitions of the existing FDQquery . After open the vdataset , DBGrid1 did not show the columns sequence, labels and formats fro the source dataset , WHY ?
3rd Approach - Copy fields definition, one by one - didn't work
for i:=0 to Dtmodule.FDQueryDados.Fields.Count -1 do
begin
with vdataset.FieldDefs.AddFieldDef do
begin
Name := Dtmodule.FDQueryDados.FieldDefs[i].Name;
Datatype := Dtmodule.FDQueryDados.FieldDefs[i].DataType;
Displayname := Dtmodule.FDQueryDados.FieldDefs[i].Displayname;
Fieldno := Dtmodule.FDQueryDados.FieldDefs[i].FieldNo;
end;
end;
vdataset.FieldDefs.Update;
vdataset.CreateDataSet;
vdatasource := Tdatasource.create(nil);
vdatasource.DataSet := vdataset;
dbgrid1.DataSource := vdatasource;
...
This code lead to the same result as the approach 2nd, i.e., it run but after opened vdataset , DBGrid1 did not show the columns sequence, labels and formats fro the source dataset.
I appreciate your help to fix the above code OR to implement the right method to copy dataset fields definitions from one existing dataset to a new one.
Thank you all in advance !
When you use the Fields editor for queries you are creating Fields not FieldDefs. From what I can tell the FieldDefs are kept in sync with the FieldsCollection when the component is created (or maybe opened not 100% sure). The Display* properties are not available on the FieldDef object - they only exist on the Field object. When you go to copy the structure you need to iterate the fields. The the method we use is below.
Note that the loop and the items created are "Fields", but we use a temporary FieldDef object to make the code simpler. The TFieldDef.CreatField serves as a class factory method to get the correct type of field i.e. TIntegerField vs TStringField. Also if you are using calculated fields you will need to hookup the OnCalcField event. This method does not do that.
procedure CopyFieldStructure(Source: TDataSet; Target: TDataset);
{^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^}
var
Field: TField;
NewField: TField;
FieldDef: TFieldDef;
begin
Target.Fields.Clear;
Target.FieldDefs.Clear;
// Cannot perform the next operation on an opened dataset
if Target.State <> dsInactive then
Target.Close;
for Field in Source.Fields do
begin
// We are going to setup the first part in a FieldDef
// that will set us use the CreateField Call in order to
// get the correct subclass of TField created.
FieldDef := Target.FieldDefs.AddFieldDef;
FieldDef.DataType := Field.DataType;
FieldDef.Size := Field.Size;
FieldDef.Name := Field.FieldName;
NewField := FieldDef.CreateField(Target);
NewField.Visible := Field.Visible;
NewField.DisplayLabel := Field.DisplayLabel;
NewField.DisplayWidth := Field.DisplayWidth;
NewField.EditMask := Field.EditMask;
NewField.Calculated := Field.Calculated;
end;
end;
Here is a similar StackOverflow question. I think this is where I originally took my code from: Is there some better way to copy all DataSet Fields and their properties to another DataSet?
And here is one other blog post that uses a similar approach: How to: Clone TField and TDataset fields structure
Also don't get fooled by the TDataSet.CopyField method. The help makes it seem like it could copy the field structure. When really it copies the current field "values" for any matching field names.
I have two identical statusbars (AdvOfficeStatusBar) on each form. That means Form1 has the same status bar as the Form2.Now,before I close the Form1 I would like all the values from the status bar to be transfered to that one on the form2. I suppose I could do it one by one like... :
procedure TForm2.FormShow(Sender: TObject);
begin
AdvOfficeStatusBar1.Panels[0].Text := Form1.AdvOfficeStatusBar1.Panels[0].Text;
AdvOfficeStatusBar1.Panels[1].Text := Form1.AdvOfficeStatusBar1.Panels[1].Text;
AdvOfficeStatusBar1.Panels[2].Text := Form1.AdvOfficeStatusBar1.Panels[2].Text;
AdvOfficeStatusBar1.Panels[4].Text := Form1.AdvOfficeStatusBar1.Panels[4].Text;
AdvOfficeStatusBar1.Panels[5].Text := Form1.AdvOfficeStatusBar1.Panels[5].Text;
AdvOfficeStatusBar1.Panels[6].Text := Form1.AdvOfficeStatusBar1.Panels[6].Text;
end;
I was wondering if there's a more simple way?Less code...
You're suffering from an anti-pattern called copy-paste-programming.
It makes for very easy programming, but difficult maintenance.
Every time you add a line to one statusbar, you have to go back and update to code to have it be linked into the other statusbar.
It's easy to forget updating the code and ehm well it's work, which is why this is bad practice.
A better way is to use Assign or if that does not work a loop. Both are demonstrated below.
Note that the Panel is an array property.
Normally every array_property has a associated count property.
I'm not sure what it is in this instance, but I'm guessing it's called PanelCount.
As per David's suggestion it's better to store the state somewhere inside your program, because you might redesign the form and lose the StatusBar, in which case you'd also lose the storage.
type
TForm2 = class(TForm)
private
StatusStore: array of string;
.....
end;
implementation
procedure TForm2.FormCreate(Sender: TObject);
begin
//Initialisation, you cannot use a loop, unless you'd read it from a file.
SetLength(StatusStore,6);
StatusStore[0]:= 'a';
StatusStore[1]:= 'b';
StatusStore[2]:= 'c';
StatusStore[3]:= 'd';
StatusStore[4]:= 'e';
StatusStore[5]:= 'f';
end;
procedure TForm2.FormShow(Sender: TObject);
var
i,maxi: integer;
begin
StatusStore[0]:= 'Showing Form2';
Maxi:= SizeOf(StatusStore);
i:= 0;
AdvOfficeStatusBar1.PanelCount:= Maxi;
while (i < Maxi) do begin
AdvOfficeStatusBar1.Panels[i].Text:= StatusStore[i];
end; {while}
Form1.AdvOfficeStatusBar1.Panels.Assign(Form2.AdvOfficeStatusBar1.Panels);
end;
Now whatever data is to be displayed and however many items there are, the display will update.
You can even program the loop to skip an item if you want the first or last item to be different for each form.
I have ComboBox4,ComboBox1 and Button5
When I click Button5 program should remove component selected in combobox4 from the ComboBox4 and ComboBox1 components' list. But I get list out of bounds error with the following code...
procedure TForm1.Button5Click(Sender: TObject);
var
cat : Integer;
trinti: TComponent;
catT : String;
begin
catT := ComboBox4.Text;
cat := ComboBox4.Items.IndexOf(catT);
trinti := ComboBox4.Components[cat];
ComboBox1.Items.BeginUpdate;
ComboBox4.Items.BeginUpdate;
ComboBox4.RemoveComponent(trinti);
ComboBox1.RemoveComponent(trinti);
ComboBox1.Items.EndUpdate;
ComboBox4.Items.EndUpdate;
removeCat(catT);
end;
Please help :(
The Components property, and the RemoveComponent method are the wrong things to use here. These are for ownership and lifetime management. Typically the only thing on your form that owns anything is the form itself. So using Components on the combo box will always results in an error.
Instead you need to use the Items property of the combo box, and its Delete method. It might look like this:
var
Index: Integer;
....
catT := ComboBox4.Text;
Index := ComboBox4.Items.IndexOf(catT);
if Index <> -1 then
ComboBox4.Items.Delete(Index);
I need move the data stored in a array of bytes to a set of records located in a TList, but i'm getting this error
E2197 Constant object cannot be passed as var parameter
This code reproduce the issue.
uses
System.Generics.Collections,
System.SysUtils;
type
TData = record
Age : Byte;
Id : Integer;
end;
//this code is only to show the issue, for simplicity i'm filling only the first
//element of the TList but the real code needs fill N elements from a very big array.
var
List : TList<TData>;
P : array [0..1023] of byte;
begin
try
List:=TList<TData>.Create;
try
List.Count:=1;
//here i want to move the content of the P variable to the element 0
Move(P[0],List[0], SizeOf(TData));
finally
List.Free;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
How i can copy the contents of a buffer to a TList Element
In XE2, the internal storage for TList<T> is opaque and hidden. You cannot gain access to it by normal means. All access to elements of the list are copied – references to the underlying storage are not available. So you cannot blit to it using Move. If you want a structure that you can blit to, you should consider a dynamic array, TArray<T>.
You can always use the trick of implementing a class helper for TList<TData> that would expose the private variable FItems. That's pretty hacky but will do what you ask.
type
__TListTData = TList<TData>;
//defeat E2086 Type 'TList<T>' is not yet completely defined
type
TListTDataHelper = class helper for TList<TData>
procedure Blit(const Source; Count: Integer);
end;
procedure TListTDataHelper.Blit(const Source; Count: Integer);
begin
System.Move(Source, Pointer(FItems)^, Count*SizeOf(Self[0]));
end;
I guess you might want to put some parameter checking in TListTDataHelper.Blit, but I'll leave that to you.
If you were using XE3, you could access the private storage of TList<T> by using the List property.
Move(P, Pointer(List.List)^, N*SizeOf(List[0]));
If you don't need to blit and can use a for loop then do it like this:
type
PData = ^TData;
var
i: Integer;
Ptr: PData;
....
List.Count := N;
Ptr := PData(#P);
for i := 0 to List.Count-1 do
begin
List[i] := Ptr^;
inc(Ptr);
end;
But I interpret your question that you wish to avoid this option.
Instead of using Move(), try using the TList<T>.Items[] property setter instead and let the compiler and RTL handle the copying for you:
type
PData = ^TData;
...
List[0] := PData(#P[0])^;
In my app I have different forms that use the same datasource (so the queries are the same too), defined in a common datamodule. Question is, is there a way to know how many times did I open a specific query? By being able to do this, I could avoid close that query without closing it "every where else".
Edit: It's important to mention that I'm using Delphi3 and it is not a single query but several.
The idea is to use the DataLinks property of the TDataSource.
But, as it is protected, you have to gain access to it. One common trick is to create a fake descendant just for the purpose of casting:
type
TDataSourceHack = class(TDataSource);
Then you use it like:
IsUsed := TDataSourceHack(DataSource1).DataLinks.Count > 0;
You can get creative using a addref/release like approach. Just create a few functions and an integer variable in your shared datamodule to do the magic, and be sure to call them..partial code follows:
TDMShared = class(tDataModule)
private
fQueryCount : integer; // set to 0 in constructor
public
function GetQuery : tDataset;
procedure CloseQuery;
end;
function TDMShared.GetQuery : tDataset;
begin
inc(fQueryCount);
if fQueryCount = 1 then
SharedDatsetQry.open;
Result := shareddatasetqry; // your shared dataset here
end;
procedure TDMShared.CloseQuery;
begin
dec(fQueryCount);
if fQueryCount <= 0 then
shareddatasetqry.close; // close only when no refs left.
end;
EDIT: To do this with multiple queries, you need a container to hold the query references, and a way to manipulate them. a tList works well for this. You will need to make appropriate changes for your TDataset descendant, as well as create a FreeAndNil function if you are using an older version of Delphi. The concept I used for this was to maintain a list of all queries you request and manipulate them by the handle which is in effect the index of the query in the list. The method FreeUnusedQueries is there to free any objects which no longer have a reference...this can also be done as part of the close query method, but I separated it to handle the cases where a specific query would need to be reopened by another module.
Procedure TDMShared.DataModuleCreate(Sender:tObject);
begin
dsList := tList.create;
end;
Function TDMShared.CreateQuery(aSql:String):integer;
var
ds : tAdoDataset;
begin
// create your dataset here, for this example using TADODataset
ds := tAdoDataset.create(nil); // self managed
ds.connection := database;
ds.commandtext := aSql;
ds.tag := 0;
Result := dsList.add(ds);
end;
function TDMShared.GetQuery( handle : integer ) : tDataset;
begin
result := nil;
if handle > dsList.count-1 then exit;
if dsList.Items[ handle ] = nil then exit; // handle already closed
result := tAdoDataset( dsList.items[ handle ]);
Inc(Result.tag);
if Result.Tag = 1 then
Result.Open;
end;
procedure TDMShared.CloseQuery( handle : integer );
var
ds : tAdoDataset;
begin
if handle > dsLIst.count-1 then exit;
ds := tAdoDataset( dsList.items[ handle ]);
dec(ds.Tag);
if ds.Tag <= 0 then
ds.close;
end;
procedure TDMShared.FreeUnusedQueries;
var
ds : tAdoDataset;
ix : integer;
begin
for ix := 0 to dsList.Count - 1 do
begin
ds := tAdoDataset(dsLIst.Items[ ix ]);
if ds.tag <= 0 then
FreeAndNil(dsList.Items[ix]);
end;
end;
procedure TDMShared.DataModuleDestroy(Sender: TObject);
var
ix : integer;
begin
for ix := 0 to dsList.count-1 do
begin
if dsLIst.Items[ix] <> nil then
FreeAndNil(dsLIst.Items[ix]);
end;
dsList.free;
end;
Ok, a completely different solution...one that should work for Delphi 3.
Create a new "Descendant Object" from your existing dataset into a new unit, and add some behavior in the new object. Unfortunately I do not have Delphi 3 available for testing, but it should work if you can find the proper access points. For example:
TMySharedDataset = class(tOriginalDataset)
private
fOpenCount : integer;
protected
procedure Internal_Open; override;
procedure Internal_Close; override;
end;
TMySharedDataset.Internal_Open;
begin
inherited Internal_Open;
inc(fOpenCount);
end;
TMySharedDataset.Internal_Close;
begin
dec(fOpenCount);
if fOpenCount <= 0 then
Inherited Internal_Close;
end;
Then just include the unit in your data module, and change the reference to your shared dataset (you will also have to register this one and add it to the palette if your using components). Once this is done, you won't have to make changes to the other units as the dataset is still a descendant of your original one. What makes this all work is the creation of YOUR overridden object.
You could have a generic TDataSet on the shared datamodule and set it on the OnDataChange, using the DataSet property of the Field parameter
dstDataSet := Field.DataSet;
This way, when you want to close the dataset, close the dataset on the datamodule, which is a pointer to the correct DataSet on some form you don't even have to know