I would like to program the following situation:
I have 2 different ListViews in a form. I would like to attach specific items from ListView2 into a ListView1 item. After the "Parent" Item gets deleted, it should also delete all the attached items from ListView2.
I tried this so far:
type
TITEMS = record
A_Items : array of TListItem;
end;
A Button that adds an item to ListView1 (ParentItems)
var
item : TListItem;
begin
item := ListView1.Items.Add;
item.Caption := 'ParentTestItem';
item.SubItems.Add('TestSubItem');
A button that adds an item to ListView2 (ChildItems)
var
item : TlistItem;
items : TITEMS;
begin
if ListView1.Selected = NIL then exit; // Make sure an item is selected.
item := ListView2.Items.Add;
item.Caption := 'ChildTestItem';
item.SubItems.Add('TestSubItem');
SetLength (items.item, Length(items.item) + 1); // wrong?
items.item[Length(items.item)-1] := item;
ListView1.Selected.SubItems.Objects[0] := #items;
A button that removes a ParentItem (and it should delete ChildItems as well...)
var
items : TItems;
i : Integer;
item : TlistItem;
begin
if ListView1.Selected = NIL then exit; // Make sure an item is selected.
items := TItems(ListView1.Selected.SubItems.Objects[0]); // Cast
for i := 0 to Length (items.item) - 1 do begin
item := items.item[i];
item.Delete;
end;
ListView1.Selected.Free;
Any Idea how I could realize this?
You need to allocate the list of items dynamically on the heap, not locally on the stack, so it stays valid in memory while you are using it.
I would suggest using a TList instead of an array, it is easier to allocate dynmically. I would also suggest using the TListItem.Data property instead of the TListItem.SubItems.Objects[] property (unless you are already using the Data property for something else).
procedure TForm1.AddParentBtnClick(Sender: TObject);
var
item : TListItem;
begin
item := ListView1.Items.Add;
item.Caption := 'ParentTestItem';
item.SubItems.Add('TestSubItem');
end;
procedure TForm1.AddChildBtnClick(Sender: TObject);
var
Selected, item : TListItem;
items : TList;
begin
Selected := ListView1.Selected;
if Selected = nil then Exit; // Make sure an item is selected.
items := TList(Selected.Data);
if items = nil then begin
items := TList.Create;
Selected.Data := items;
end;
item := ListView2.Items.Add;
try
item.Caption := 'ChildTestItem';
item.SubItems.Add('TestSubItem');
items.Add(item);
except
item.Delete;
raise;
end;
end;
procedure TForm1.DeleteParentBtnClick(Sender: TObject);
var
Selected : TListItem;
begin
Selected := ListView1.Selected;
if Selected <> nil then Selected.Delete;
end;
procedure TForm1.ListView1Deletion(Sender: TObject; Item: TListItem);
var
items : TList;
i : Integer;
begin
items := TList(Item.Data); // Cast
if items <> nil then begin
for i := 0 to items.Count - 1 do begin
TListItem(items[i]).Delete;
end;
items.Free;
Item.Data := nil;
end;
end;
Related
I have a TTreeView on my form which gets populated from a DB table. The list currently has 22 items and all of them have checkboxes that can be checked.
The TTreeView is on a TForm that has a TPageControl with a pre-made TTabSheet and all other TTabSheets are created dynamically and assigned TFrames to them.
My current code to create a new TTabSheet at runtime looks like this:
procedure TForm1.Button2Click(Sender: TObject);
var
aTab: TTabSheet;
begin
aTab := TTabSheet.create(self);
aTab.Name := 'tabProduct_' + IntToStr(PageControl1.PageCount+1);
aTab.PageControl := PageControl1;
aTab.Caption := 'Product ' + IntToStr(aTab.PageIndex);
LoadFrame(aTab.PageIndex);
end;
The code for the LoadFrame() procedure is:
procedure TForm1.LoadFrame(const index: integer);
var
aClassName: string;
aFrameClass : TFrameClass;
I: Integer;
begin
if index >= 50 then
raise Exception.create('Max product count reached');
if index >= Length(frames) then
SetLength(frames, index+1);
if assigned(frames[curIndex]) then frames[curIndex].hide;
if not assigned(frames[index]) then
begin
if index = 0 then
aClassname := 'TframeClient' // client
else
aClassname := 'TframeProdus'; // anything over pageindex 0 is a product
aFrameClass := TFrameClass(GetClass(aClassname));
if not assigned(aFrameClass) then
raise exception.createfmt('Could not find class %s', [aClassname]);
frames[index] := aFrameClass.Create(self);
frames[index].name := 'myframe' + IntToStr(index); // unique name
frames[index].parent := PageControl1.pages[index];
frames[index].align := alClient;
end;
frames[index].show;
curIndex := Index;
end;
Other relevant code:
type
TFrameArray = array of TFrame;
<...>
private
{ Private declarations }
curIndex: integer;
frames: TFrameArray;
procedure LoadFrame(const index: integer);
public
{ Public declarations }
end;
TFrameClass = class of TFrame;
<...>
Let's say I check the box next to items 1, 5 and 13 in the TTreeView.
How can I determine that and modify the Button2 code to create TTabSheets and their TFrames only for the items I have checked in the TTreeView?
Meanwhile, managed to figure out this approach and it seems to work well
procedure TForm1.Button2Click(Sender: TObject);
var
aTab: TTabSheet;
i: Integer;
begin
for i:=1 to TreeView1.Items.Count do
begin
if TreeView1.Items[i-1].Checked then
begin
aTab := TTabSheet.create(self);
aTab.Name := 'tabProduct_' + IntToStr(PageControl1.PageCount+1);
aTab.PageControl := PageControl1;
aTab.Caption := TreeView1.Items.Item[i-1].Text;
LoadFrame(aTab.PageIndex);
end;
end;
end;
I have two StringList that are loaded (from a file) with users and users + password respectivally. I'm comparing these lists to determine what user (of first list) already have a password (in second list) and then insert on ListView who have and also who still not have.
But exists a problem here that from second ListItem.Caption (user) is repeting two times.
How i can solve this?
My files that are loaded on lists are:
users.dat
User01
User02
User03
logins.dat
User01|test01
User01|test01
And this was my last attempt of code:
type
TForm1 = class(TForm)
Button1: TButton;
ListView1: TListView;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
L1, L2, LSplit: TStringList;
L: TListItem;
I, J: Integer;
begin
L1 := TStringList.Create;
L2 := TStringList.Create;
LSplit := TStringList.Create;
L1.LoadFromFile('users.dat');
L2.LoadFromFile('logins.dat');
for I := 0 to L1.Count - 1 do
begin
for J := 0 to L2.Count - 1 do
begin
LSplit.Clear;
ExtractStrings(['|'], [], PChar(L2[J]), LSplit);
if L1[I] = LSplit[0] then
begin
L := ListView1.Items.Add;
L.Caption := LSplit[0];
L.SubItems.Add(LSplit[1]);
Break;
end;
L := ListView1.Items.Add;
L.Caption := L1[I];
end;
end;
L1.Free;
L2.Free;
LSplit.Free;
end;
end.
Your inner loop is broken. It is adding items to the ListView even when the 2 StringList items being compared don't match each other. For each user in the first list, you are adding it to the ListView as many times as there are entries in the second list.
Your code should look more like this instead:
procedure TForm1.Button1Click(Sender: TObject);
var
L1, L2, LSplit: TStringList;
L: TListItem;
I, J: Integer;
begin
L1 := TStringList.Create;
try
L2 := TStringList.Create;
try
LSplit := TStringList.Create;
try
L1.LoadFromFile('users.dat');
L2.LoadFromFile('logins.dat');
for I := 0 to L1.Count - 1 do
begin
L := ListView1.Items.Add;
L.Caption := L1[I];
for J := 0 to L2.Count - 1 do
begin
LSplit.Clear;
ExtractStrings(['|'], [], PChar(L2[J]), LSplit);
if L1[I] = LSplit[0] then
begin
L.SubItems.Add(LSplit[1]);
Break;
end;
end;
end;
finally
LSplit.Free;
end;
finally
L2.Free;
end;
finally
L1.Free;
end;
end;
That being said, you don't need 3 TStringList objects and 2 loops. 2 TStringList objects and 1 loop will suffice:
procedure TForm1.Button1Click(Sender: TObject);
var
L1, L2: TStringList;
L: TListItem;
I: Integer;
begin
L1 := TStringList.Create;
try
L2 := TStringList.Create;
try
L1.LoadFromFile('users.dat');
L2.LoadFromFile('logins.dat');
L2.NameValueSeparator := '|';
for I := 0 to L1.Count - 1 do
begin
L := ListView1.Items.Add;
L.Caption := L1[I];
L.SubItems.Add(L2.Values[L1[I]]);
end;
finally
L2.Free;
end;
finally
L1.Free;
end;
end;
for I := 0 to L1.Count - 1 do
begin
found := false;
L := ListView1.Items.Add;
for J := 0 to L2.Count - 1 do
begin
LSplit.Clear;
ExtractStrings(['|'], [], PChar(L2[J]), LSplit);
if L1[I] = LSplit[0] then
begin
L.Caption := LSplit[0];
L.SubItems.Add(LSplit[1]);
found := true;
Break;
end;
end;
if not found then
L.Caption := L1[I];
end;
Also note that dictionary approach is much faster for large lists
The problem with your code is that you are always adding the username from your first list to the result regardles of whether you aready added it from the second list with included password or not.
To avoid this you need to write your code in a way that adding username from first list happen only if it hasn't been added from the second one.
//If password exist in second list add username and password from second list
if L1[I] = LSplit[0] then
begin
L := ListView1.Items.Add;
L.Caption := LSplit[0];
L.SubItems.Add(LSplit[1]);
Break;
end
//Else add username from first list
else
begin
L := ListView1.Items.Add;
L.Caption := L1[I];
end;
Also note that your approqach Will fail in case if your second list contains username that is not present in the first list.
For instance let us check next scenario:
users.dat
User01
User02
User03
logins.dat
User01|test01
User02|test01
User04|test04
In the above scenario your final result won't include any data from User04 becouse it doesn't exist in your first list.
So I would recomend you to use different approach where you iterate the second list and for each entry search the first list to see if the username exists in it.
If it does you edit that entry on the first list to add the pasword infromation from the second list. And in case if you can't find username on the first list then you add both username and password to it.
This will guarantee you that final result will unclude all usernames from both lists no matter on which they were found.
i have a problem if i getting my defined type object.
I need to get my variables-defined object from a ListBox.
My data types:
type
TObjectData = class
Id: Integer;
DataType: String;
end;
TProjektInfo = record
Id: Integer;
Nazev: String;
end;
TReportSelect = record
Count: Integer;
Zakazka_Id: Integer;
Singles: Array of TProjektInfo;
Multies: Array of TProjektInfo;
end;
My procedure for fill listbox:
procedure TReportMain.VykresyFillProjectsList();
var
I,Id: Integer;
Nazev: String;
ItemData: TObjectData;
begin
VykresyProjectsListSections.Items.BeginUpdate;
VykresyProjectsListSections.Items.Clear;
for I := Low(ReportSelect.Singles) to High(ReportSelect.Singles) do
begin
Id := ReportSelect.Singles[I].Id;
Nazev := ReportSelect.Singles[I].Nazev;
ItemData := TObjectData.Create;
ItemData.Id := Id;
ItemData.DataType := 'single';
VykresyProjectsListSections.Items.AddObject(Nazev, TObject(ItemData));
ItemData.Free;
end;
for I := Low(ReportSelect.Multies) to High(ReportSelect.Multies) do
begin
Id := ReportSelect.Multies[I].Id;
Nazev := ReportSelect.Multies[I].Nazev;
ItemData := TObjectData.Create;
ItemData.Id := Id;
ItemData.DataType := 'multi';
VykresyProjectsListSections.Items.AddObject(Nazev, TObject(ItemData));
ItemData.Free;
end;
VykresyProjectsListSections.Items.EndUpdate;
end;
My button event on click for getting my datatype object (this is wrong where is commented):
procedure TReportMain.BtnExportProjectsClick(Sender: TObject);
var
ItemData: TObjectData;
Nazev: String;
I: Integer;
begin
ItemData := TObjectData.Create;
for I := 0 to VykresyProjectsListSections.Count - 1 do
begin
if VykresyProjectsListSections.Checked[I] then
begin
ItemData := TObjectData(VykresyProjectsListSections.Items.Objects[I]); // <--- This is wrong, why ?
Nazev := VykresyProjectsListSections.Items.Strings[I];
showMessage(Format('Nazev: %s ID: %d Type: %s', [Nazev, ItemData.Id, ItemData.DataType]));
end;
end;
end;
What happens to you is probably an access violation.
The variable you're trying to access is undefined because you have already freed the object the variable is pointing to.
In the code above, the ItemData object is always freed after it's added to the list.
You have to write some code to free the object when the list is cleared or freed.
This can be done in the OnDestroy event of your form:
procedure TReportMain.FormDestroy(Sender: TObject);
var
i: Integer;
begin
for i := VykresyProjectsListSections.Items.Count-1 downto 0 do begin
VykresyProjectsListSections.Items.Objects[i].Free;
VykresyProjectsListSections.Delete(i);
end;
VykresyProjectsListSections.Free;//free the list if not owned by the application
end;
As a side note, you can test if a TCheckListBox element is checked like this:
var
i: Integer;
begin
for i := 0 to VykresyProjectsListSections.Items.Count-1 do begin
if VykresyProjectsListSections.State[i] = cbChecked then
//do your stuff
end;
end;
You are calling ItemData.Free after AddObject(). This is wrong, since the object will not be valid anymore.
This will cause the error when later accessing the object in the CheckListBox.
i dont know how to deal with stringrids, i want to fill it with data, I've succeeded to do it with a listview
this is my code to fill the listview..
var
LJSONArray : TJSONArray;
LItem: TListViewItem;
I: Integer;
begin
LJSONArray := TJSONArray.Create;
try
BackendStorage1.Storage.QueryObjects('ShoppingList', [], LJSONArray);
ListView1.ClearItems;
for I := 0 to LJSONArray.Count-1 do
begin
LItem := ListView1.Items.Add;
LItem.Text := (LJSonArray.Items[I].GetValue<string>('item'));
end;
finally
LJSONArray.Free;
end;
end;
To add items to a TStringGrid, you have to set its RowCount property first and then use its Cells property to fill them in, eg:
var
LJSONArray : TJSONArray;
I: Integer;
begin
LJSONArray := TJSONArray.Create;
try
BackendStorage1.Storage.QueryObjects('ShoppingList', [], LJSONArray);
StringGrid1.RowCount := LJSONArray.Count;
for I := 0 to LJSONArray.Count-1 do
begin
StringGrid1.Cells[0, I] := LJSonArray.Items[I].GetValue<string>('item');
end;
finally
LJSONArray.Free;
end;
end;
Make sure you have set the grid's ColumnCount property to at least 1 beforehand, such as at design-time.
How to copy multiple items from TListView to another. Right now im doing it like this:
procedure TForm1.CopyToRightClick(Sender: TObject);
var
selected: TListItem;
addItems: TListItem;
begin
saveChanges.Visible := false;
selected := deviceList.Selected;
addItems := selectedDevicesList.Items.Add;
addItems.Assign(selected);
end;
But this way only one selected item get copied. Is there a way to copy all selected items?
You can do
procedure TForm1.Button1Click(Sender: TObject);
var
i: Integer;
begin
ListView2.Items.BeginUpdate;
try
for i := 0 to ListView1.Items.Count - 1 do
if ListView1.Items[i].Selected then
ListView2.Items.Add.Assign(ListView1.Items[i]);
finally
ListView2.Items.EndUpdate;
end;
end;
to copy every selected list view item in ListView1 to ListView2.
You can do
procedure TForm1.Button2Click(Sender: TObject);
var
i: Integer;
begin
ListView1.Items.BeginUpdate;
try
ListView2.Items.BeginUpdate;
try
for i := ListView1.Items.Count - 1 downto 0 do
if ListView1.Items[i].Selected then
begin
ListView2.Items.Add.Assign(ListView1.Items[i]);
ListView1.Items[i].Delete;
end;
finally
ListView2.Items.EndUpdate;
end;
finally
ListView1.Items.EndUpdate;
end;
end;
to move every selected list view item in ListView1 to ListView2.
(This works well in moderately-sized lists. In larger lists, when you need to do something for every selected item, iterating over all items and checking the Selected property is way too slow. Instead, you should use a while loop with GetNextItem with isSelected.)