How to free ObjectList without freeing contents - delphi

I have an ObjectList that is populated. Then details changed in the objects. Now I need to free the ObjectList but when I do it also frees the objects in the list. How can I free this list without freeing the objects themselfs?
Example code:
{Gets starting cards and put them into the correct rows}
//***************************************************************************
procedure TFGame.GetStartingCards;
//***************************************************************************
const
ManaTypes : array [0..3] of string = ('Lava','Water','Dark','Nature');
var
i: integer;
z:integer;
Cards: TObjectList<Tcard>;
begin
Cards := TObjectList<TCard>.Create;
z:=0;
{add all tcards (Desgin ) to this list in order Lava,water,dark,nature }
cards.Add(cardLava1);
cards.Add(cardlava2);
cards.Add(cardlava3);
cards.Add(cardlava4);
cards.Add(cardwater1);
cards.Add(cardwater2);
cards.Add(cardwater3);
cards.Add(cardwater4);
cards.Add(carddark1);
cards.Add(carddark2);
cards.Add(carddark3);
cards.Add(carddark4);
cards.Add(cardnature1);
cards.Add(cardnature2);
cards.Add(cardnature3);
cards.Add(cardnature4);
//get data from DB
for i := 0 to Length(ManaTypes) - 1 do
begin
with adoquery1 do
begin
close;
sql.Clear;
sql.Add('SELECT TOP 4 * FROM Cards WHERE Color = "'+ManaTypes[i]+'" ORDER BY Rnd(-(1000*ID)*Time())');
open;
end;
{return the result of everything for giving mana type.. }
if adoquery1.RecordCount = 0 then
Showmessage('Error no cards in db');
adoquery1.First;
while not adoquery1.Eof do
begin
cards[z].Cname := adoquery1.FieldByName('Name').AsString;
cards[z].Ccost := adoquery1.Fieldbyname('Cost').AsInteger;
cards[z].Ctext := adoquery1.FieldByName('Text').AsString;
cards[z].Ccolor := adoquery1.FieldByName('Color').AsString;
cards[z].Cinplay := false; //in the play area
if adoquery1.fieldbyname('Power').asstring <> '' then
cards[z].Cpower := adoquery1.FieldByName('Power').AsInteger;
if adoquery1.fieldbyname('Def').asstring <> '' then
cards[z].Cdef := adoquery1.FieldByName('Def').AsInteger;
if adoquery1.FieldByName('Type').AsString = 'Spell' then
cards[z].Cspell := true
else
cards[z].Cspell := false;
if adoquery1.FieldByName('Target').AsString = 'yes' then
cards[z].SetTargetTrue
else
cards[z].settargetfalse;
//based on color change background
cards[z].Background.LoadFromFile(format('%s\pics\%s.png',[maindir,cards[z].Ccolor]));
adoquery1.Next;
cards[z].repaint;
z:=z+1;
end;
end;
//cards.Free; if i free this it removes all the cards added to it..
end;

The constructor for TObjectList<T> receives a parameter named AOwnsObjects which specifies who is to be responsible for freeing the objects. If you pass True then the list has that responsibility. Otherwise, the responsibility remains with you.
constructor Create(AOwnsObjects: Boolean = True); overload;
The parameter has a default value of True and I presume that you are calling the constructor without specifying that parameter. Hence you get the default behaviour – the list owns its members.
The documentation says it like this:
The AOwnsObjects parameter is a boolean that indicates whether object entries are owned by the list. If the object is owned, when the entry is removed from the list, the object is freed. The OwnsObjects property is set from the value of this parameter. The default is true.
Now, if you want this particular list instance not to free its members, then there is little to be gained from using TObjectList<T>. You may as well use plain old TList<T>.

Related

delete all elements from objectlist except one selected element

What is best coding practise in deleting all elements from a list except one element which I would like to keep inside the list?
TMyCLass = Class()
....
end;
MyObjectList = TObjectList <TMyClass>;
var MySaveClass : TMyCLass;
begin
MySaveClass = MyObjectList.items[saveindex];
for i = 1... MyObjectList.count-1 do
if i <> saveindex then
MyObjectList.delete (i); ?? // not working :-)
end;
As TLama said in a comment, Extract the item, Clear the list (if OwnsObjects is false, loop through and free each item first), and then Add the item back in.
var
SavedItem: TMyClass;
...
SavedItem := MyObjectList.Extract(MyObjectList.Items[i]);
// Loop here to free if needed because list doesn't own objects
MyObjectList.Clear;
MyObjectList.Add(SavedItem);
If the Objectlist has OwnsObjects := True then you can just delete the elements.
But care to make it backwards otherwise you might get an error.
for i := MyObjectlist.count -1 downto 0 do
if i = Saveindex then
continue
else
MyObjectList.Delete(i);

how do i prevent adding duplicates items in listview in delphi

i have some list inside a list view i want to prevent Adding items that already exist and allow only items that's not exist i search about that before i post my question i find some codes that removes the duplicated items but thats not my point , a little example of what aim trying to achieve , example
listview1.Items.Add.caption := 'item1'
listview1.Items.Add.subitems.add:= 'content'
listview1.Items.Add.caption := 'item2'
listview1.Items.Add.subitems.add:= 'content2'
listview1.Items.Add.caption := 'item3'
listview1.Items.Add.subitems.add:= 'content3'
//duplicated line
listview1.Items.Add.caption := 'item1'// here what i want to ignore if exist and add any other items comes below
listview1.Items.Add.subitems.add:= 'content'
listview1.Items.Add.caption := 'item4'
listview1.Items.Add.subitems.add:= 'content4'
any idea on how to achieve that ignore exist items and add what ever other items ?
Current code
if Command = 'CallThis' then
begin
if Assigned(MS) then
begin
SL := TStringList.Create;
try
SL.LoadFromStream(MS);
for I := 0 to SL.Count -1 do
begin
Line := SL.Strings[I];
ExplodeLine(Line, item, content, num);
with vieform.list.Items.Add do
begin
Caption := StripHTML(item);
Subitems.Add(content);
Subitems.Add(num)
end;
end;
finally
SL.Free;
end;
MS.Free;
end;
end;
You should not use the visual controls to store and manage your data. Have a list for all the data and present the data in the listview or any other control you like.
// class to store data (shortend)
TMyData = class
constructor Create( const Item, Content : string );
property Item : string;
property Content : string;
end;
// list to organize the data
MyList := TObjectList<TMyData>.Create(
// comparer, tell the list, when are items equal
TComparer<TMyData>.Construct(
function ( const L, R : TMyData ) : integer
begin
Result := CompareStr( L.Item, R.Item );
end ) );
// create an item
MyData := TMyData.Create( 'item1', 'content1' );
// check for duplicate in list
if not MyList.Contains( MyData ) then
MyList.Add( MyData )
else
MyData.Free;
// present the list in a ListView
ListView1.Clear;
for MyData in MyList do
begin
ListItem := ListView1.Items.Add;
ListItem.Data := MyData; // store a reference to the data item
ListItem.Caption := MyData.Item;
ListItem.SubItems.Add( MyData.Content );
end;
Thats all
Just write your own procedure which does all the work for you. Also helps with your sub items, except I'm not sure what you were trying to do in your code (This is what I'm assuming you were attempting to do)...
procedure TForm1.Add(const Caption, Sub: String);
var
I: TListItem;
X: Integer;
begin
for X := 0 to ListView1.Items.Count-1 do
if SameText(ListView1.Items[X].Caption, Caption) then Exit;
I:= ListView1.Items.Add;
I.Caption:= Caption;
I.SubItems.Add(Sub);
end;
Then, you simply call it like this:
Add('Item1', 'Content');
Add('Item2', 'Content2');
Add('Item3', 'Content3');
Add('Item1', 'Content1');
That would result in 3 items in the list, because the 4th already exists.
Please note however that this may not actually solve your real underlying issue. If you feel the need to perform this check, then it's probably a good time to re-think your design. The approach you're using makes me believe you're using the TListView to store data. UI controls should never be the container of actual data, it should only provide the interface to the user.

Accessing values from pointer problem

I have a class of type TObject called CModelItem
I want to have a list of these objects and be able to modify the values of each one
So, I created a class
CQueueList = class(TList)
private
public
end;
and I make
QueueList : CQueueList;
in var
Now, I can add the CModelItem to this list, like so:
QueueList := CQueueList.Create;
for idx := 0 to ndx - 1 do
begin
MyItem := CModelItem.Create;
MyItem.CopyHead(CModelItem(RunList.Objects[idx]));
MyItem.ReadData;
MyItem.NumOfIterations := NumRepEdit.Value;
MyItem.IsInQueue := True;
MyItem.LogEvents := EventsCheckBox.Checked;
MyItem.LogMatch := MatchCheckBox.Checked;
MyItem.LogUpdates := UpdatesCheckBox.Checked;
QueueList.Add(MyItem);
end;
I can also use it, so I can do:
DefForm := TRunDefForm.Create(Self, QueueList.Items[idx]);
with DefForm taking in a component and a CModelItem
But I'm running into problems trying to modify the values of an object in QueueL
First, I can't access something like MyItem.IsInQueue by doing
QueueList.Items[idx].IsInQueue := blah;
because it tells me IsInQueue is 'an undeclared identifier'
I've also tried making a new CModelItem and copying the information over, like this:
idx := QueueListBox.ItemIndex;
MyItem := QueueList.Items[idx];
and this compiles fine, but throws up an 'access violation error' when it goes into that function
I noticed that QueueList.Items[idx] is a pointer, but I'm really not sure how I should be accessing it
The compiler complains because TList.Items returns an untyped pointer.
You can use a typecast:
CModelItem(QueueL.Items[idx]).IsInQueue := blah;
You can also reimplement the Items property in your CQueueList class:
private
function GetItems(Index: Integer): CModelItem;
public
property Items[Index: Integer]: CModelItem read GetItems; default;
end;
function CQueueList.GetItems(Index: Integer): CModelItem;
begin
Result := inherited Items[Index];
end;
As you've seen, using a local variable works; although the access violation is a bug probably somewhere else in your code.

delphi hashmap?

i have java-code filling a hashmap from a textfile.
HashMap<String, String[]> data = new HashMap<String, String[]>();
i use this to make key-value-pairs. the values are an array of string. i have to iterate over every possible combo of the key-value-pairs (so also have to iterate over the String[]-array). This works with java but now i have to port this to delphi. is it possible to do so? and how?
thanks!
In Delphi 2009 and higher, you can use TDictionary<string, TStringlist> using Generics.Collections.
In older versions, you can use TStringlist where every item in the TStringlist has an associated object value of type TStrings.
The Docwiki has a page to get started with TDictionary
If you have an older version of Delphi (Delphi 6 and up), you could also use a dynamic array of record, then our TDynArray or TDynArrayHashed wrappers to create a dictionary with one field of the dynamic array records. See this unit.
The TDynArrayHashed wrapper was developed to be fast.
Here is some sample code (from supplied unitary tests):
var ACities: TDynArrayHashed;
Cities: TCityDynArray;
CitiesCount: integer;
City: TCity;
added: boolean;
N: string;
i,j: integer;
const CITIES_MAX=200000;
begin
// valide generic-like features
// see http://docwiki.embarcadero.com/CodeExamples/en/Generics_Collections_TDictionary_(Delphi)
ACities.Init(TypeInfo(TCityDynArray),Cities,nil,nil,nil,#CitiesCount);
(...)
Check(ACities.FindHashed(City)>=0);
for i := 1 to 2000 do begin
City.Name := IntToStr(i);
City.Latitude := i*3.14;
City.Longitude := i*6.13;
Check(ACities.FindHashedAndUpdate(City,true)=i+2,'multiple ReHash');
Check(ACities.FindHashed(City)=i+2);
end;
ACities.Capacity := CITIES_MAX+3; // make it as fast as possible
for i := 2001 to CITIES_MAX do begin
City.Name := IntToStr(i);
City.Latitude := i*3.14;
City.Longitude := i*6.13;
Check(ACities.FindHashedAndUpdate(City,true)=i+2,'use Capacity: no ReHash');
Check(ACities.FindHashed(City.Name)=i+2);
end;
for i := 1 to CITIES_MAX do begin
N := IntToStr(i);
j := ACities.FindHashed(N);
Check(j=i+2,'hashing with string not City.Name');
Check(Cities[j].Name=N);
CheckSame(Cities[j].Latitude,i*3.14);
CheckSame(Cities[j].Longitude,i*6.13);
end;
end;
So for your problem:
type
TMyMap = record
Key: string;
Value: array of string;
end;
TMyMapDynArray = array of TMyMap;
var
Map: TMyMap;
Maps: TMyMapDynArray;
MapW: TDynArrayHashed;
key: string;
i: integer;
begin
MapW.Init(TypeInfo(TMyMapDynArray),Maps);
Map.Key := 'Some key';
SetLength(Map.Value,2);
Map.Value[0] := 'One';
Map.Value[1] := 'Two';
MapW.FindHashedAndUpdate(Map,true); // ,true for adding the Map content
key := 'Some key';
i := MapW.FindHashed(key);
// now i=0 and Maps[i].Key=key
for i := 0 to MapW.Count-1 do // or for i := 0 to high(Maps) do
with Maps[i] do
// now you're enumerating all key/value pairs
end;
Since Delphi 6, the set of predefined container classes includes TBucketList and TObjectBucketList. These two lists are associative, which means they have a key and an actual entry. The key is used to identify the items and search for them. To add an item, you call the Add method with two parameters: the key and the data. When you use the Find method, you pass the key and retrieve the data. The same effect is achieved by using the Data array property, passing the key as parameter.

Delphi: how to set the length of a RTTI-accessed dynamic array using DynArraySetLength?

I'd like to set the length of a dynamic array, as suggested in this post. I have two classes TMyClass and the related TChildClass defined as
TChildClass = class
private
FField1: string;
FField2: string;
end;
TMyClass = class
private
FField1: TChildClass;
FField2: Array of TChildClass;
end;
The array augmentation is implemented as
var
RContext: TRttiContext;
RType: TRttiType;
Val: TValue; // Contains the TMyClass instance
RField: TRttiField; // A field in the TMyClass instance
RElementType: TRttiType; // The kind of elements in the dyn array
DynArr: TRttiDynamicArrayType;
Value: TValue; // Holding an instance as referenced by an array element
ArrPointer: Pointer;
ArrValue: TValue;
ArrLength: LongInt;
i: integer;
begin
RContext := TRTTIContext.Create;
try
RType := RContext.GetType(TMyClass.ClassInfo);
Val := RType.GetMethod('Create').Invoke(RType.AsInstance.MetaclassType, []);
RField := RType.GetField('FField2');
if (RField.FieldType is TRttiDynamicArrayType) then begin
DynArr := (RField.FieldType as TRttiDynamicArrayType);
RElementType := DynArr.ElementType;
// Set the new length of the array
ArrValue := RField.GetValue(Val.AsObject);
ArrLength := 3; // Three seems like a nice number
ArrPointer := ArrValue.GetReferenceToRawData;
DynArraySetLength(ArrPointer, ArrValue.TypeInfo, 1, #ArrLength);
{ TODO : Fix 'Index out of bounds' }
WriteLn(ArrValue.IsArray, ' ', ArrValue.GetArrayLength);
if RElementType.IsInstance then begin
for i := 0 to ArrLength - 1 do begin
Value := RElementType.GetMethod('Create').Invoke(RElementType.AsInstance.MetaclassType, []);
ArrValue.SetArrayElement(i, Value);
// This is just a test, so let's clean up immediatly
Value.Free;
end;
end;
end;
ReadLn;
Val.AsObject.Free;
finally
RContext.Free;
end;
end.
Being new to D2010 RTTI, I suspected the error could depend on getting ArrValue from the class instance, but the subsequent WriteLn prints "TRUE", so I've ruled that out. Disappointingly, however, the same WriteLn reports that the size of ArrValue is 0, which is confirmed by the "Index out of bounds"-exception I get when trying to set any of the elements in the array (through ArrValue.SetArrayElement(i, Value);). Do anyone know what I'm doing wrong here? (Or perhaps there is a better way to do this?) TIA!
Dynamic arrays are kind of tricky to work with. They're reference counted, and the following comment inside DynArraySetLength should shed some light on the problem:
// If the heap object isn't shared (ref count = 1), just resize it. Otherwise, we make a copy
Your object is holding one reference to it, and so is the TValue. Also, GetReferenceToRawData gives you a pointer to the array. You need to say PPointer(GetReferenceToRawData)^ to get the actual array to pass to DynArraySetLength.
Once you've got that, you can resize it, but you're left with a copy. Then you have to set it back onto the original array.
TValue.Make(#ArrPointer, dynArr.Handle, ArrValue);
RField.SetValue(val.AsObject, arrValue);
All in all, it's probably a lot simpler to just use a list instead of an array. With D2010 you've got Generics.Collections available, which means you can make a TList<TChildClass> or TObjectList<TChildClass> and have all the benefits of a list class without losing type safety.
I think you should define the array as a separate type:
TMyArray = array of TMyClass;
and use that.
From an old RTTI based XML serializer I know the general method that you use should work (D7..2009 tested):
procedure TXMLImpl.ReadArray(const Name: string; TypeInfo: TArrayInformation; Data: Pointer; IO: TParameterInputOutput);
var
P: PChar;
L, D: Integer;
BT: TTypeInformation;
begin
FArrayType := '';
FArraySize := -1;
ComplexTypePrefix(Name, '');
try
// Get the element type info.
BT := TypeInfo.BaseType;
if not Assigned(BT) then RaiseSerializationReadError; // Not a supported datatype!
// Typecheck the array specifier.
if (FArrayType <> '') and (FArrayType <> GetTypeName(BT)) then RaiseSerializationReadError;
// Do we have a fixed size array or a dynamically sized array?
L := FArraySize;
if L >= 0 then begin
// Set the array
DynArraySetLength(PPointer(Data)^,TypeInfo.TypeInformation,1,#L);
// And restore he elements
D := TypeInfo.ElementSize;
P := PPointer(Data)^;
while L > 0 do begin
ReadElement(''{ArrayItemName},BT,P,IO); // we allow any array item name.
Inc(P,D);
Dec(L);
end;
end else begin
RaiseNotSupported;
end;
finally
ComplexTypePostfix;
end;
end;
Hope this helps..

Resources