How to bulk copy the values from a TSQLQuery record to a TdxMemData record? - sql

Just to make it clear, I do not want to copy the entire TSQLQuery into the TdxMemData, as I would use memds.CopyFromDataSet(qry) for that.
I am interating through each record from the TSQLQuery, and I may or may not be adding a record(s) to the TdxMemData. Generally the record in memds matches that in qry, but sometimes the values are altered and sometimes additional records are added to memds. My example did not make this clear since all it seemed to do was copy over each record.
So given an active record in the TSQLQuery, I want to copy over the values into an active editable record in the TdxMemData.
The following code works in so far as it creates a copy of the record:
qry := TSQLQuery.Create(nil);
memds := TdxMemData.Create(nil);
try
qry.SQLConnection := cn;
qry.Text := 'SELECT Field1, Field2, Field3 FROM Table1';
qry.Open
memds.CreateFieldsFromDataSet(qry);
memds.Open;
while not qry.Eof do
begin
if {some condition} then
begin
memds.Append;
for i := 0 to qry.FieldCount-1 do
memds.Fields[i+1].Value := qry.Fields[i].Value; //First field is RecID
//Do something with the current memds record
end
else if {some other condition} then
begin
memds.Append;
//change values
memds.Append;
//change values
memds.Append;
//change values
end
else if {a third condition} then
; //Skip any work on memds
qry.next;
end;
qry.Close;
//Do something with memds
memds.Close;
finally
memds.Free;
qry.Free;
end;
Is there a better way? I had looked at AppendRecord but creating the array of TVarRec doesn't seem to be straightforward.
EDIT:
Let's use these examples with very simplified criteria. Note that the actual conditions that determine how many records to append and the changes to the field values in the destination are complex and not in any database.
Method 1:
While not tblSource.Eof do
Begin
If (iCondition = 1) Then
Begin
// Add one record
tblDestination.Append;
tblDestination.FieldByName('Field1').Value := tblSource.FieldByName('Field1').Value;
tblDestination.FieldByName('Field2').Value := tblSource.FieldByName('Field2').Value;
tblDestination.FieldByName('Field3').Value := tblSource.FieldByName('Field3').Value;
tblDestination.FieldByName('Field4').Value := tblSource.FieldByName('Field4').Value;
tblDestination.FieldByName('Field5').Value := tblSource.FieldByName('Field5').Value;
if bSomethingCondition then
tblDestination.FieldByName('Field4').Value := 'Something';
End
Else If (iCondition = 2) Then
Begin
// Add two records
tblDestination.Append;
tblDestination.FieldByName('Field1').Value := tblSource.FieldByName('Field1').Value;
tblDestination.FieldByName('Field2').Value := tblSource.FieldByName('Field2').Value;
tblDestination.FieldByName('Field3').Value := tblSource.FieldByName('Field3').Value;
tblDestination.FieldByName('Field4').Value := tblSource.FieldByName('Field4').Value;
tblDestination.FieldByName('Field5').Value := tblSource.FieldByName('Field5').Value;
if bAnotherThingCondition then
tblDestination.FieldByName('Field4').Value := 'Another thing';
tblDestination.Append;
tblDestination.FieldByName('Field1').Value := tblSource.FieldByName('Field1').Value;
tblDestination.FieldByName('Field2').Value := tblSource.FieldByName('Field2').Value;
tblDestination.FieldByName('Field3').Value := tblSource.FieldByName('Field3').Value;
tblDestination.FieldByName('Field4').Value := tblSource.FieldByName('Field4').Value;
tblDestination.FieldByName('Field5').Value := tblSource.FieldByName('Field5').Value;
if bSomethingElseCondition then
tblDestination.FieldByName('Field4').Value := 'Something else';
End
Else If (iCondition = 0) Then
Begin
// Add no records
End;
tblSource.Next;
End;
Since the number of fields in the source and destination tables can vary, hard-coding field names as in Method 1 is not suitable.
Method 2:
While not tblSource.Eof do
Begin
If (iCondition = 1) Then
Begin
// Add one record
tblDestination.Append;
for i := 0 to tblSource.FieldCount-1 do
tblDestination.Fields[i+1].Value := tblSource.Fields[i].Value;
if bSomethingCondition then
tblDestination.Fields(iSomethingConditionFieldIndex).Value := 'Something';
End
Else If (iCondition = 2) Then
Begin
// Add two records
tblDestination.Append;
for i := 0 to tblSource.FieldCount-1 do
tblDestination.Fields[i+1].Value := tblSource.Fields[i].Value;
if bAnotherThingCondition then
tblDestination.Fields(iAnotherThingConditionFieldINdex).Value := 'Another thing';
tblDestination.Append;
for i := 0 to tblSource.FieldCount-1 do
tblDestination.Fields[i+1].Value := tblSource.Fields[i].Value;
if bSomethingElseCondition then
tblDestination.Fields(iSomethingElseConditionFieldIndex).Value := 'Something else';
End
Else If (iCondition = 0) Then
Begin
// Add no records
End;
tblSource.Next;
End;
While method 2 above does work, and is the way it is currently done, this question is whether there is a way to pass the variant array of field values from tblSource to tblDestination using AppendRecord.
Instead of this:
// Add one record
tblDestination.Append;
for i := 0 to tblSource.FieldCount-1 do
tblDestination.Fields[i+1].Value := tblSource.Fields[i].Value;
if bSomethingCondition then
tblDestination.Fields(iSomethingConditionFieldIndex).Value := 'Something';
Do this:
tblDestination.AppendRecord({tblSource fields var array);
if bSomethingCondition then
tblDestination.Fields(iSomethingConditionFieldIndex).Value := 'Something';
Of course, it might be that there is no answer, and that the method I currently employ is the best solution.

Try the following
Query1.Open();
dxMemData1.AddFieldsFromDataSet(Query1);
dxMemData1.Open;
dxMemData1.LoadFromDataSet(Query1);

Creating an array of TVarRec isn't difficult, and AppendRecord may indeed help. The following code adds a record to a TClientDataSet (named CDS for brevity) that has 4 fields of type string, float, boolean, and string in that order:
CDS.AppendRecord(['Smith', 123.45, False, 'Test text']);
Note that you have to create a value for every single field (column) in the dataset, in the order that they exist in the FieldDefs collection, or you'll get an exception.
(Of course, the real question is why you're returning extra rows and iterating through them, instead of testing the conditions in your SQL statement WHERE clause and only returning the rows you actually need. This can almost always be done using parameters.)

If you don't want to use AddFieldsFromDataSet -> LoadFromDataSet , and want to load fields and records manually, you also can do it. You can create fields programmatically and append records by iterations. Look example:
MD.Fields.Clear;
MD.FieldDefs.Clear;
MD.Close;
with MD.FieldDefs.AddFieldDef do
begin
Name := 'UserID';
DataType := TFieldType.ftInteger;
CreateField(MD);
Name := 'GridName';
DataType := TFieldType.ftString;
Size := 255;
CreateField(MD);
Name := 'TemplateGrid';
DataType := TFieldType.ftBlob;
CreateField(MD);
end;
MD.Close;
MD.Open;
MD.Append;
MD.FieldByName('UserID').AsInteger := 1;
MD.FieldByName('GridName').AsString := Self.Name
TBlobField(MD.FieldByName('TemplateGrid')).LoadFromStream(LStream);
MD.Post;
You can easily modify it to load fields with names and types from your dataset as they are.

Related

Error while writing to array plsql how to fix? Extend doesn't work also

so I am trying to write to an array in PL/SQL, and I always get the subscript outside of limit error. I've seen similar posts and implemented everything based on those answers, I can't seem to find what I'm doing wrong. The line giving the error is "arr_quartosLivres(counter) := q.id;" I've tried to extend the array and it still doesn't work, however, either way, the look only runs 21 times (because there are only 21 values in the table quarto) so it shouldn't even need to be extended. Any help would be highly appreciated! Thank you
SET SERVEROUTPUT ON;
DECLARE
p_idReserva reserva.id%type := 408;
v_dataEntradaReserva reserva.data_entrada%type;
counter integer := 0;
type arr_aux IS varray(21) of quarto.id%type;
arr_quartosLivres arr_aux := arr_aux();
BEGIN
SELECT data_entrada INTO v_dataEntradaReserva FROM reserva WHERE id = p_idreserva;
FOR q IN (SELECT * FROM quarto)
LOOP
BEGIN
IF isQuartoIndisponivel(q.id, v_dataEntradaReserva)
THEN DBMS_OUTPUT.PUT_LINE('nao disponivel' || counter);
arr_quartosLivres(counter) := q.id;
ELSE DBMS_OUTPUT.PUT_LINE('disponivel' || counter);
END IF;
counter := counter + 1;
END;
END LOOP;
END;
The index values for varray begin with 1. Your logic is trying to use index value 0. Thus index out of range. BTW extend does not apply to varray, when declared a varray has a fixed size. You have 3 solutions: initialize counter to 1 instead of 0, or move incrementing it prior to its use as an index. Since as it stands you increment every time through the loop, even when the IF condition returns false and you do not use the counter as an index, leaving a NULL value in the array.But you use counter for 2 different purposes: Counting rows processed and index into the array. Since the row value may not be put into the array then your 3rd option is to introduce another variable for the index. Further there is no need for the BEGIN ... End block in the loop.
declare
p_idreserva reserva.id%type := 408;
v_dataentradareserva reserva.data_entrada%type;
counter integer := 0;
type arr_aux is varray(21) of quarto.id%type;
arr_quartoslivres arr_aux := arr_aux();
varray_index integer := 1 ; -- index varaibal for varray.
begin
select data_entrada into v_dataentradareserva from reserva where id = p_idreserva;
for q in (select * from quarto)
loop
if isquartoindisponivel(q.id, v_dataentradareserva)
then
dbms_output.put_line('nao disponivel' || counter || ' at index ' || varray_index);
arr_quartoslivres(varray_index) := q.id;
varray_index := varray_index + 1;
else
dbms_output.put_line('disponivel' || counter);
end if;
counter := counter + 1;
end loop;
end;

Commit vs CommitRetaining

I have made a Delphi application, which inserts a row into Firebird database.
There was a problem with a query which a solved via CommitRetaining, but I read that it is not right thing to use, because it may affect the server being more slow. Strange thing happens when I use Commit only, query runs ok, but when I want to see if the row is inserted, Retainingit isn't. It only gets inserted on application terminate. But when using CommitRetaining, the row is inserted instantly.
What may cause the problem?
EDIT: Code using CommitRetaining
adqPom := TADQuery.Create(nil);
adqPom.Connection := form1.ADOConnection1;
adTransakcija := TADTransaction.Create(nil);
adTransakcija.Connection:=form1.ADOConnection1;
adqPom.Transaction:=adTransakcija;
adTransakcija.StartTransaction;
try
with adqPom do
begin
close;
sql.Clear;
sql.Add('insert into uplate(sifra,b_prijema,magacin,datum,iznos,b_uplate,b_izvoda,banka,godina,tr_rac,datum_dokument)');
sql.Add('values(:S,:BP,:M,:D,:I,:BU,:BI,:B,:G,:TR,:DD)');
ParamByName('S').Value := strtoint(edit1.Text);
if Form15.adoqDostavn.FieldValues['A1'] = 3 then
edit3.Text := '99999';
ParamByName('BP').Value := edit3.Text;
ParamByName('M').Value := edit2.Text;
ParamByName('D').Value := strtodate(edit4.Text);
ParamByName('I').Value := StrToFloat(edit5.Text);
ParamByName('BU').Value := Br_Uplate+1;
ParamByName('BI').Value := strtoint(Edit6.Text);
ParamByName('B').Value := Edit8.Text;
ParamByName('G').Value := 2006;
if Form15.adoqDostavn.FieldValues['A1'] = 3 then
ParamByName('TR').Value:= form15.adoqDostavn.FieldValues['B_PRIJEMA']
else
ParamByName('TR').Value:= Form15.adoqDostavn.FieldValues['B_DOST'];
ParamByName('DD').Value:=StrToDate(edit9.Text);
ExecSQL;
end;
adTransakcija.CommitRetaining;
except
adTransakcija.RollbackRetaining;
raise;
end;
FreeAndNil(adTransakcija);
FreeAndNil(adqPom);
EDIT: Code using Commit (actually property of a query is set to autocommit)
adqPom := TADQuery.Create(nil);
adqPom.Connection := form1.ADOConnection1;
with adqPom do
begin
close;
sql.Clear;
sql.Add('insert into uplate(sifra,b_prijema,magacin,datum,iznos,b_uplate,b_izvoda,banka,godina,tr_rac,datum_dokument)');
sql.Add('values(:S,:BP,:M,:D,:I,:BU,:BI,:B,:G,:TR,:DD)');
ParamByName('S').Value := strtoint(edit1.Text);
if Form15.adoqDostavn.FieldValues['A1'] = 3 then
edit3.Text := '99999';
ParamByName('BP').Value := edit3.Text;
ParamByName('M').Value := edit2.Text;
ParamByName('D').Value := strtodate(edit4.Text);
ParamByName('I').Value := StrToFloat(edit5.Text);
ParamByName('BU').Value := Br_Uplate+1;
ParamByName('BI').Value := strtoint(Edit6.Text);
ParamByName('B').Value := Edit8.Text;
ParamByName('G').Value := 2006;
if Form15.adoqDostavn.FieldValues['A1'] = 3 then
ParamByName('TR').Value:= form15.adoqDostavn.FieldValues['B_PRIJEMA']
else
ParamByName('TR').Value:= Form15.adoqDostavn.FieldValues['B_DOST'];
ParamByName('DD').Value:=StrToDate(edit9.Text);
ExecSQL;
end;
FreeAndNil(adqPom);
Commit free the transaction environment and CommitRetaining is a Commit that not free the transaction environment (cursors still open). You can use CommitRetaining in a process but at the end you must use Commit to release the memory.
Usually CommitRetainning is used to optimize a process (that include a big number of Begin/Commit), but at the end of you must this process use Commit to clear memory.

How to get insert values from a table into an array using SQL?

with dmHospital do
begin
qryHospital.SQL.Clear;
qryHospital.SQL.Add('SELECT * FROM Patients ') ;
qryHospital.SQL.Add('WHERE DoctorID = :DoctorID');
qryHospital.Parameters.ParamByName('DoctorID').Value := StrToInt(sID);
qryHospital.Open;
iCount := qryHospital.RecordCount
end;
This code displays the values I want to put into the array. However I'm not sure how to cycle through each record and to get each value from the record into the correct array. For example: I want the names from 'PatientName' and the surnames from 'PatientSurname'.
iCount is the array size.
You must loop from TDataset using While.
Some sample code:
...
var
fieldCod:TField;
Str1:String;
i, Cod:Integer;
begin
...
qryHospital.Open;
iCount := qryHospital.RecordCount;
// Create pointer to field
fieldCod := qryHospital.FieldByName('PatientCode');
// loop the recordset (while not arrive at end)
While (not qryHospital.eof) do begin
// Different modes to access table fields content
Str1 := qryHospital.FieldByName('PatientName').AsString;
i := qryHospital.Fields[1].AsInteger;
Cod := fieldCod.AsInteger;
// Add the values to your array
//...
// Next Record
qryHospital.Next;
end;
NOTE: For better performance, don't use FieldByName inside the loop (for big number of records). You can use Fields[index] or create a variable out of the loop and reference to fields.

delphi - Save multiple records at once

Dont know how to formulate this exactly so bear with me please... I am saving text from a memo to a database with date selected in the PlannerCalendar1. Since I can select multiple dates in the PlannerCalendar1, how can I post the value of the memo to all dates selected in the PlannerCalendar1?So when I click 'save' the contents of the memo gets saved to all selected dates.Database is SQLite. The table also has an ID field which is autoinc (primary).PlannerCalendar is from the set of TMS components.
procedure TForm1.cxButton1Click(Sender: TObject);
var i:integer;
begin
with UniQuery1 do
begin
UniQuery1.SQL.Text:='INSERT INTO LOG (DATE,PERSON,DONE,TIME) VALUES (:a1,:a2,:a3,:a4)';
UniQuery1.PARAMS.ParamByName('A1').VALUE := PlannerCalendar1.Date;
UniQuery1.PARAMS.ParamByName('A2').VALUE := cxmemo1.Lines.text ;
UniQuery1.PARAMS.ParamByName('A3').VALUE := (0);
UniQuery1.PARAMS.ParamByName('A4').Value := AdvOfficeStatusBar1.Panels[0].Text;
UniQuery1.ExecSQL;
cxmemo1.Clear;
UniTable1.Refresh;
Tried this at the end but it wont work :
with plannercalendar1.Dates do
begin
for i := 0 to -1 do
begin
UniQuery1.PARAMS.ParamByName('A1').VALUE :=plannercalendar1.dates.Add + i ;
UniQuery1.ExecSQL;
end;
I have no idea what a PlannerCalendar is, but presumably there's some way to get at the list of dates that are selected. You want to do something like this:
UniQuery1.SQL.Text:='INSERT INTO LOG (DATE,PERSON,DONE,TIME) VALUES (:a1,:a2,:a3,:a4)';
UniQuery1.PARAMS.ParamByName('A2').VALUE := cxmemo1.Lines.text ;
UniQuery1.PARAMS.ParamByName('A3').VALUE := (0);
UniQuery1.PARAMS.ParamByName('A4').Value := AdvOfficeStatusBar1.Panels[0].Text;
for i := 0 to PlannerCalendar1.NumberOfDatesSelected-1 do begin
UniQuery1.PARAMS.ParamByName('A1').VALUE := PlannerCalendar1.SelectedDate[i];
UniQuery1.ExecSQL;
end;
Of course, NumberOfDatesSelected and SelectedDate are wild guesses. You'll need to find out what they're really called.
You need to use the Planner's SelectionToAbsTime method :-
Var
lStart, lEnd : TDateTime;
Begin
Planner1.SelectionToAbsTime(lStart, lEnd);
For I := Trunc(lStart) To Trunc(lEnd) Do
SaveMemosForDate(I);
End;

dynamically create popup menu tree from sql server table in Delphi

I have a table like this:
id parent_id name
1 1 Root
2 1 Car
3 1 Plane
4 2 BMW
5 4 CLK
How can I dynamically create popup menu with all subitems in Delphi?
This is how it should look like:
Assuming root element has NULL as Parent_ID you can issue the request
Select ID, Parent_ID, Name from all_my_menus
order by Parent_ID nulls first, ID
where Menu_ID = :MenuIDParameter
1 <NULL> Root
8 <NULL> another root
2 1 Car
4 1 Plane
3 2 BMW
5 4 CLK
You would also cache in-memory created menu items: var MI_by_id: TDictionary<integer, TMenuItem>;
The traversing through the results would look like
var MI: TMenuItem;
MI_by_id: TDictionary<integer, TMenuItem>;
begin
MI_by_id := TDictionary<integer, TMenuItem>.Create;
try
While not Query.EOF do begin
MI := TMenuItem.Create(Self);
MI.Caption := Query.Fields[2].AsString;
MI.Tag := Query.Fields[0].AsInteger; // ID, would be helpful for OnClick event
MI.OnClick := ...some click handler
if Query.Fields[1].IsNull {no parent}
then MainMenu.Items.Add(MI)
else MI_by_id.Items[Query.Fields[1].AsInteger].Add(MI);
MI_by_id.Add(MI.Tag, MI); //save shortcut to potential parent for future searching
Query.Next;
end;
finally
MI_by_id.Free;
end;
end;
Actually, since we made sort upon Parent_ID on the query, all the children for given parent make single continuous list, so could be better to remove populated parents from the dictionary after we populated last child (i.e. after parent_ID got new value) and caching previously found parent otherwise in another local variable (instead of making yet another search through the dictionary).
However reasonable size for human-targeted menu should be much less to worth this. But you have to understand this approach most probably scales as O(n*n) thus would start loose speed very fast as the table grows.
http://docwiki.embarcadero.com/Libraries/XE3/en/Vcl.Menus.TMenuItem.Add
http://docwiki.embarcadero.com/CodeExamples/XE2/en/Generics.Collections.TDictionary_(Delphi)
Note: this also requires that for every non-root element ID > ParentID (put CHECK CONSTRAINT on the table)
1 <NULL> Root
8 <NULL> another root
7 1 Plane
3 4 BMW
4 7 CLK
5 8 Car
This would lead to BMW tied to create before its parent CLK created.
Violation for that conditions can be overcome by few means:
recursive load: select <items> where Parent_id is null, then for each of the added menu items do select <items> where Parent_id = :current_memuitem_id and so on that. This is like VirtualTreeView would work
ask SQL server to sort and flatten the tree - this is usually called self-recursive SQL selection and is server-dependant.
introduce one more collection variable - menu items w/o parent. After each new item added to the menu this collection should be searched if there are pending children to extract from it and move into the newly created parent.
Too many solutions for such a simple problem. Too bad you got ordered ID's because without ordered ID's things would have been more fun. Here's my own solution. On an empty form drop a button, a TClientDataSet and a TPopupMenu. Make the form's PopupMenu = PopupMenu1 so you can see the result. Add this to Button1.OnClick:
Note: I'm intentionally using TClientDataSet and not a real Query. This question is not about the query and this solution works with whatever TDataSet descendant you throw at it. Just make sure the result set is ordered on id, or else you could see the child nodes before the parents. Also note, half the code is used to fill up the ClientDataSet with the sample data in the question!
procedure TForm16.Button1Click(Sender: TObject);
var Prev: TDictionary<Integer, TMenuItem>; // We will use this to keep track of previously generated nodes so we do not need to search for them
CurrentItem, ParentItem: TMenuItem;
begin
if not ClientDataSet1.Active then
begin
// Prepare the ClientDataSet1 structure
ClientDataSet1.FieldDefs.Add('id', ftInteger);
ClientDataSet1.FieldDefs.Add('parent_id', ftInteger);
ClientDataSet1.FieldDefs.Add('name', ftString, 100);
ClientDataSet1.CreateDataSet;
// Fill the dataset
ClientDataSet1.AppendRecord([1, 1, 'Root']);
ClientDataSet1.AppendRecord([2, 1, 'Car']);
ClientDataSet1.AppendRecord([3, 1, 'Plane']);
ClientDataSet1.AppendRecord([4, 2, 'BMW']);
ClientDataSet1.AppendRecord([5, 4, 'CLK']);
end;
// Clear the existing menu
PopupMenu1.Items.Clear;
// Prepare the loop
Prev := TDictionary<Integer, TMenuItem>.Create;
try
ClientDataSet1.First; // Not required for a true SQL Query, only required here for re-entry
while not ClientDataSet1.Eof do
begin
CurrentItem := TMenuItem.Create(Self);
CurrentItem.Caption := ClientDataSet1['name'];
if (not ClientDataSet1.FieldByName('parent_id').IsNull) and Prev.TryGetValue(ClientDataSet1['parent_id'], ParentItem) then
ParentItem.Add(CurrentItem)
else
PopupMenu1.Items.Add(CurrentItem);
// Put the current Item in the dictionary for future reference
Prev.Add(ClientDataSet1['id'], CurrentItem);
ClientDataSet1.Next;
end;
finally Prev.Free;
end;
end;
Try this
procedure TForm1.MyPopup(Sender: TObject);
begin
with Sender as TMenuItem do ShowMessage(Caption);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
MyItem,MySubItem1: TMenuItem;
begin
Inc(Num);
MyItem:=TMenuItem.Create(Self);
MySubItem1:=TMenuItem.Create(Self);
MyItem.Caption:='Hello'+IntToStr(Num);
MySubItem1.Caption:='Good Bye'+IntToStr(Num);
MainMenu1.Items.Add(MyItem);
MainMenu1.Items[0].Insert(num-1,MySubItem1);
MyItem.OnClick:=MyPopUp;
MySubItem1.OnClick:=MyPopUp;
end;
Taken from http://www.greatis.com/delphicb/tips/lib/components-addmenuitem.html
This solution requires parent_id of root to be 0, tested with
Select 1 as ID, 0 as Parent_ID, 'Root' as Name
union
Select 2, 1, ' Car'
union
Select 3 , 1, 'Plane'
union
Select 4, 2, 'BMW'
union
Select 5, 4, 'CLK'
should by optimized, have just a lack of time ...
Function GetMenu(pop:TPopupmenu;ID:Integer):TMenuItem;
var
i:Integer;
Function CheckItem(mi:TMenuItem):TMenuItem;
var
i:Integer;
begin
Result := nil;
if mi.Name = 'DYN_' + INtToStr(ID) then Result := mi
else for i := 0 to mi.Count-1 do
if not Assigned(Result) then Result := CheckItem(mi[i]);
end;
begin
Result := nil;
for i := 0 to pop.Items.Count-1 do
begin
if not Assigned(Result) then Result := CheckItem(pop.Items[i]);
if Assigned(Result) then Break;
end;
end;
Function InsertMenuItem(pop:TPopupMenu;mi:TMenuItem;ID:Integer;Const caption:String):TMenuItem;
begin
Result := TMenuItem.Create(pop);
Result.Caption := caption;
Result.Name := 'DYN_' + INtToStr(ID) ;
if not Assigned(mi) then pop.Items.Add(Result) else mi.Add(Result);
end;
Function AddMenuItem(pop:TPopupmenu;ID:Integer;Ads:TDataset):TMenuItem;
begin
Ads.Locate('ID',ID,[]);
Result := GetMenu(pop,id);
if (not Assigned(Result)) then
begin
if (Ads.FieldByName('parent_ID').AsInteger<>0) then
begin
result := AddMenuItem(pop,Ads.FieldByName('parent_ID').AsInteger,Ads);
Ads.Locate('ID',ID,[]);
end;
Result := InsertMenuItem(pop,Result,ID,Ads.FieldByName('Name').AsString);
end;
Ads.Locate('ID',ID,[]);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
while not ADS.Eof do
begin
AddMenuItem(Popupmenu1,ads.FieldByName('ID').AsInteger,Ads);
Ads.Next
end;
end;
Interesting conundrum ...another late night thought, a practical answer for re-use :)
Make a derived component:
type
TCascadeMenuItem = class(TMenuItem)
private
Id: Integer;
public
function AddItem(const ToId, WithId: Integer; AName: string): Boolean;
end;
with code
function TCascadeMenuItem.AddItem(const ToId, WithId: Integer; AName: string): Boolean;
var
i: Integer;
cmi: TCascadeMenuItem;
begin
if ToId = Id then
begin
cmi := TCascadeMenuItem.Create(Owner);
cmi.Caption := AName;
cmi.Id := WithId;
Add(cmi);
Result := True;
end
else begin
i := 0;
Result := False;
while (i < Count) and (not Result) do
begin
Result := TCascadeMenuItem(Items[i]).AddItem(ToId,WithId, ANAme);
inc(i);
end;
end;
end;
Main form, Assumes your data:
procedure TForm4.Button2Click(Sender: TObject);
var
mi: TCascadeMenuItem;
i: Integer;
Added: Boolean;
begin
cds1.First;
while not cds1.Eof do
begin
i := 0;
Added := False;
while (i < pup.Items.Count) and (not Added) do
begin
Added := TCascadeMenuItem(pup.Items[i]).AddItem(cds1Parent_Id.AsInteger, cds1id.AsInteger, cds1name.AsString);
inc(i);
end;
if not Added then
begin // new root
mi := TCasCadeMenuItem.Create(Self);
mi.Caption := cds1name.AsString;
mi.id := cds1Parent_Id.AsInteger;
pup.Items.Add(mi);
end;
cds1.Next;
end;
end;
You could derive a TCascasePopupMenu and put it on the palette :)