use Delphi to read in a text file to a TStringList but bottom to top - delphi

I was looking to read a text file in reverse so it would read in from the bottom of the text file first. I did find how to reverse it but it doesn't make sense to me. Can someone explain this to me, how it's working? Also if there is a better/quicker way? It seems to do all the work after the file is read in, like it would be quicker to just read it in from the bottom.
var
datalist : TStringList;
lines,i : Integer;
saveLine : String;
begin
datalist := TStringList.Create;
datalist.LoadFromFile(filename); //loads file
lines := datalist.Count;
for i := lines-1 downto (lines div 2) do
begin
saveLine := datalist[lines-i-1];
datalist[lines-i-1] := datalist[i];
datalist[i] := saveLine;
end;

(At least in Delphi 7, but more recent versions should act similarily)
.LoadFromFile() calls
.LoadFromStream(), which reads the whole stream/file into memory and then calls
.SetTextStr(), which just calls per line
.Add()
Knowing this helps us to avoiding to reinvent the whole wheel and instead using an own class with one subtle change in the .Add() method:
type
TStringListReverse= class( TStringList )
function Add( const S: String ): Integer; override;
end;
function TStringListReverse.Add( const S: String ): Integer;
begin
Result:= {GetCount} 0; // Our change: always in front
Insert( Result, S );
end;
And now we just use our own class:
var
l: TStringListReverse;
begin
l:= TStringListReverse.Create;
l.LoadFromFile( 'C:\Windows\win.ini' );
Memo1.Lines.Assign( l );
l.Free;

As I mentioned in a comment, it might be useful to create an adapter class that accepts a TStrings instance, and exposes it as another TStrings, but reversed.
This might look like this:
type
TReversedStrings = class(TStrings)
private
FSource: TStrings;
FOwnsSource: Boolean;
function ReversedIndex(Index: Integer): Integer;
protected
procedure Put(Index: Integer; const S: string); override;
function Get(Index: Integer): string; override;
function GetCount: Integer; override;
function GetObject(Index: Integer): TObject; override;
procedure PutObject(Index: Integer; AObject: TObject); override;
public
constructor Create(Source: TStrings; AssumeOwnership: Boolean);
destructor Destroy; override;
procedure Clear; override;
procedure Delete(Index: Integer); override;
procedure Exchange(Index1, Index2: Integer); override;
function IndexOf(const S: string): Integer; override;
procedure Insert(Index: Integer; const S: string); override;
procedure Move(CurIndex, NewIndex: Integer); override;
end;
{ TReversedStrings }
constructor TReversedStrings.Create(Source: TStrings; AssumeOwnership: Boolean);
begin
inherited Create;
FSource := Source;
FOwnsSource := AssumeOwnership;
end;
destructor TReversedStrings.Destroy;
begin
if FOwnsSource then
FSource.Free;
inherited;
end;
function TReversedStrings.ReversedIndex(Index: Integer): Integer;
begin
Result := FSource.Count - Index - 1;
end;
procedure TReversedStrings.Put(Index: Integer; const S: string);
begin
FSource[ReversedIndex(Index)] := S;
end;
function TReversedStrings.Get(Index: Integer): string;
begin
Result := FSource[ReversedIndex(Index)];
end;
function TReversedStrings.GetCount: Integer;
begin
Result := FSource.Count;
end;
function TReversedStrings.GetObject(Index: Integer): TObject;
begin
Result := FSource.Objects[ReversedIndex(Index)];
end;
procedure TReversedStrings.PutObject(Index: Integer; AObject: TObject);
begin
FSource.Objects[ReversedIndex(Index)] := AObject;
end;
procedure TReversedStrings.Clear;
begin
FSource.Clear;
end;
procedure TReversedStrings.Delete(Index: Integer);
begin
FSource.Delete(ReversedIndex(Index));
end;
procedure TReversedStrings.Exchange(Index1, Index2: Integer);
begin
FSource.Exchange(ReversedIndex(Index1), ReversedIndex(Index2));
end;
function TReversedStrings.IndexOf(const S: string): Integer;
begin
Result := FSource.IndexOf(S);
if Result > -1 then
Result := ReversedIndex(Result);
end;
procedure TReversedStrings.Insert(Index: Integer; const S: string);
begin
FSource.Insert(ReversedIndex(Index), S);
end;
procedure TReversedStrings.Move(CurIndex, NewIndex: Integer);
begin
FSource.Move(ReversedIndex(CurIndex), ReversedIndex(NewIndex));
end;
It should be obvious how to use this, and I've not tested the code, or even executed it. Consider it a sketch of an idea.

If you want to use the TStringList.LoadFromFile() function, then another way to do it is to copy one TStringList to another TStringList. It would be faster than the current scheme, and is fewer lines of code.
var
datalist1, datalist2 : TStringList;
lines, i: Integer;
filename : string;
begin
datalist1 := TStringList.Create;
datalist2 := TStringList.Create;
datalist1.LoadFromFile(filename); //loads file
lines := datalist1.Count;
data2list.Capacity := lines; // so it allocates the memory once
for i := lines-1 downto 0 do
begin
datalist2.Add (datalist1[i]);
end;
end;
Personally, I would read the file in myself.

Related

How can I pass either Variant or TObject to the same method argument?

I have the two overload methods:
procedure TProps.SetProp(Value: TObject); overload;
procedure TProps.SetProp(const Value: Variant); overload;
They do pretty much the same repeating code except from minor variations depending on whether the Value is Variant or TObject.
I want to use a common method:
procedure TProps.DoSetProp(Value: <what type here?>); // <--
So I can pass both Variant or TObject from SetProp and be able to distinguish between the two types. what are my options?
Edit: for now I used:
procedure TProps.DoSetProp(Value: Pointer; IsValueObject: Boolean);
begin
// common code...
if IsValueObject then
PropValue.Obj := Value
else
PropValue.V := PVariant(Value)^;
// common code...
if IsValueObject then
PropValue.Obj := Value
else
PropValue.V := PVariant(Value)^;
// etc...
end;
and the overload methods:
procedure TProps.SetProp(const Value: Variant); overload;
begin
DoSetProp(#Value, False);
end;
procedure TProps.SetProp(Value: TObject); overload;
begin
DoSetProp(Value, True);
end;
I'm not sure I like this solution because of the IsValueObject. I would rather detect the type from a common type "container".
I could use TVarRec:
VarRec: TVarRec;
// for Variant:
VarRec.VType := vtVariant;
VarRec.VVariant := #Value;
// for TObject
VarRec.VType := vtObject;
VarRec.VObject := Value;
And pass the VarRec to the common method. but I'm not sure I like it either.
EDIT 2: What I am trying to do? I'm trying to extend properties for any TObject similar to SetProp API.
Here is the entire MCVE:
function ComparePointers(A, B: Pointer): Integer;
begin
if Cardinal(A) = Cardinal(B) then
Result := 0
else if Cardinal(A) < Cardinal(B) then
Result := -1
else
Result := 1
end;
type
TPropValue = class
private
V: Variant;
Obj: TObject;
procedure SetValue(const Value: Pointer; IsValueObject: Boolean);
end;
TPropNameValueList = class(TStringList)
public
destructor Destroy; override;
procedure Delete(Index: Integer); override;
end;
TObjectProps = class
private
BaseObject: TObject;
PropList: TPropNameValueList;
public
constructor Create(AObject: TObject);
destructor Destroy; override;
end;
TProps = class(TComponent)
private
FList: TObjectList;
protected
procedure DoSetProp(AObject: TObject; const PropName: string; const Value: Pointer; IsValueObject: Boolean);
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
public
function Find(AObject: TObject; var Index: Integer): Boolean;
procedure SetProp(AObject: TObject; const PropName: string; const Value: Variant); overload;
procedure SetProp(AObject: TObject; const PropName: string; Value: TObject); overload;
function RemoveProp(AObject: TObject; const PropName: string): Boolean;
function RemoveProps(AObject: TObject): Boolean;
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
{ TPropValue }
procedure TPropValue.SetValue(const Value: Pointer; IsValueObject: Boolean);
begin
if IsValueObject then
Obj := Value
else
V := PVariant(Value)^;
end;
{ TPropNameValueList }
destructor TPropNameValueList.Destroy;
var
I: Integer;
begin
for I := 0 to Count - 1 do
Objects[I].Free; // TPropValue
inherited;
end;
procedure TPropNameValueList.Delete(Index: Integer);
begin
Objects[Index].Free;
inherited;
end;
{ TObjectProps }
constructor TObjectProps.Create(AObject: TObject);
begin
BaseObject := AObject;
PropList := TPropNameValueList.Create;
PropList.Sorted := True;
PropList.Duplicates := dupError;
end;
destructor TObjectProps.Destroy;
begin
PropList.Free;
inherited;
end;
{ TProps }
constructor TProps.Create(AOwner: TComponent);
begin
inherited;
FList := TObjectList.Create(true);
end;
procedure TProps.Notification(AComponent: TComponent; Operation: TOperation);
begin
inherited;
if (Operation = opRemove) and (AComponent <> nil) then
begin
RemoveProps(AComponent);
end;
end;
destructor TProps.Destroy;
begin
FList.Free;
inherited;
end;
function TProps.Find(AObject: TObject; var Index: Integer): Boolean;
var
L, H, I, C: Integer;
begin
Result := False;
L := 0;
H := FList.Count - 1;
while L <= H do
begin
I := (L + H) shr 1;
C := ComparePointers(TObjectProps(FList[I]).BaseObject, AObject);
if C < 0 then L := I + 1 else
begin
H := I - 1;
if C = 0 then
begin
Result := True;
L := I;
end;
end;
end;
Index := L;
end;
procedure TProps.DoSetProp(AObject: TObject; const PropName: string; const Value: Pointer;
IsValueObject: Boolean);
var
OP: TObjectProps;
PropValue: TPropValue;
Index, NameIndex: Integer;
Found: Boolean;
I: Integer;
begin
Found := Find(AObject, Index);
if not Found then
begin
OP := TObjectProps.Create(AObject);
if AObject is TComponent then
TComponent(AObject).FreeNotification(Self);
PropValue := TPropValue.Create;
PropValue.SetValue(Value, IsValueObject);
OP.PropList.AddObject(PropName, PropValue);
FList.Insert(Index, OP);
end
else
begin
OP := TObjectProps(FList[Index]);
NameIndex := OP.PropList.IndexOf(PropName);
if NameIndex <> -1 then
begin
PropValue := TPropValue(OP.PropList.Objects[NameIndex]);
PropValue.SetValue(Value, IsValueObject);
end
else
begin
PropValue := TPropValue.Create;
PropValue.SetValue(Value, IsValueObject);
OP.PropList.AddObject(PropName, PropValue);
end;
end;
end;
procedure TProps.SetProp(AObject: TObject; const PropName: string; const Value: Variant);
begin
DoSetProp(AObject, PropName, #Value, False);
end;
procedure TProps.SetProp(AObject: TObject; const PropName: string; Value: TObject);
begin
DoSetProp(AObject, PropName, Value, True);
end;
function TProps.RemoveProp(AObject: TObject; const PropName: string): Boolean;
var
Index, NameIndex: Integer;
OP: TObjectProps;
begin
Result := False;
if not Find(AObject, Index) then Exit;
OP := TObjectProps(FList[Index]);
NameIndex := OP.PropList.IndexOf(PropName);
if NameIndex <> -1 then
begin
OP.PropList.Delete(NameIndex);
Result := True;
end;
end;
function TProps.RemoveProps(AObject: TObject): Boolean;
var
Index: Integer;
OP: TObjectProps;
begin
if not Find(AObject, Index) then
begin
Result := False;
Exit;
end;
OP := TObjectProps(FList[Index]);
Result := FList.Remove(OP) <> -1;
end;
Usage:
Props := TProps.Create(Self);
Props.SetProp(Button1, 'myprop1', Self); // TObject property
Props.SetProp(Button1, 'myprop2', 666); // variant
Props.SetProp(Button2, 'myprop', 'Hello'); // variant
Props.SetProp(MyObject, 'foo', 123.123);
Note: TProps.GetProp is not yet implemented.
You are fighting the compiler; You should continue to use overloads.
"I would rather detect the type from a common type 'container'."
Your choices are variant or untyped pointer. You are going to have to unpack the "Value" parameter. With an untyped pointer you will have to do all the work; with a variant you will have to do most of the work. Very messy.
"They do pretty much the same repeating code except from minor variations depending on whether the Value is Variant or TObject."
If that is really true then you should still continue to use overloads but add an internal "SetProp" method that takes "normalized" data that does the actual work. Your "repeating" code is the setting of the property values. But you still have specific code to write to crack the incoming "Value" parameter whether you have one method that accepts a "container" type or multiple overloaded methods that take the various types you want to accept. In the one-method-container type you will have a (complex) if-then-else block that cracks the Value. In the overloaded-methods type there is no if-testing; you just crack the Value for the type that each method accepts.
The major advantage is that your object is better documented: you can see what types are acceptable for "Value" and, better still, the compiler helps you because it "knows" what types are acceptable. With your one-method approach the compiler will not be able to help you enforce the type of "Value"; you are doing all the work.
Also, using the overloaded methods, I wouldn't have one that accepts variant (although the example below does). Have an separate overload for each of string, integer, double, etc.
type
TNormalizedPropValue = record
// ....
end;
procedure TProps.internalSetProp(Value : TNormalizedPropValue);
begin
//
// Set the property value from the "Normalized" pieces and parts.
//
end;
procedure TProps.SetProp(Value : TObject);
var
NormalizedObjectPropValue : TNormalizedPropValue;
begin
// Copy the pieces and parts from "Value" into NormalizedObjectPropValue
//
internalSetProp(NormalizedObjectPropValue);
end;
procedure TProps.SetProp(Value : variant);
var
NormalizedVariantPropValue : TNormalizedPropValue;
begin
// Crack "Value" variant and copy the pieces and parts into NormalizedVariantPropValue
//
internalSetProp(NormalizedVariantPropValue);
end;

Delphi - Drag and Drop .txt Files in TMemo [duplicate]

In Delphi XE can I allow my form to accept file 'drag and drop' but without having to handle bare windows messages?
You don't need to handle messages to implement this. You just need to implement IDropTarget and call RegisterDragDrop/RevokeDragDrop. It's really very very simple. You can actually implement IDropTarget in your form code but I prefer to do it in a helper class that looks like this:
uses
Winapi.Windows,
Winapi.ActiveX,
Winapi.ShellAPI,
System.StrUtils,
Vcl.Forms;
type
IDragDrop = interface
function DropAllowed(const FileNames: array of string): Boolean;
procedure Drop(const FileNames: array of string);
end;
TDropTarget = class(TObject, IInterface, IDropTarget)
private
// IInterface
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
private
// IDropTarget
FHandle: HWND;
FDragDrop: IDragDrop;
FDropAllowed: Boolean;
procedure GetFileNames(const dataObj: IDataObject; var FileNames: TArray<string>);
procedure SetEffect(var dwEffect: Integer);
function DragEnter(const dataObj: IDataObject; grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult; stdcall;
function DragOver(grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult; stdcall;
function DragLeave: HResult; stdcall;
function Drop(const dataObj: IDataObject; grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult; stdcall;
public
constructor Create(AHandle: HWND; const ADragDrop: IDragDrop);
destructor Destroy; override;
end;
{ TDropTarget }
constructor TDropTarget.Create(AHandle: HWND; const ADragDrop: IDragDrop);
begin
inherited Create;
FHandle := AHandle;
FDragDrop := ADragDrop;
RegisterDragDrop(FHandle, Self)
end;
destructor TDropTarget.Destroy;
begin
RevokeDragDrop(FHandle);
inherited;
end;
function TDropTarget.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then begin
Result := S_OK;
end else begin
Result := E_NOINTERFACE;
end;
end;
function TDropTarget._AddRef: Integer;
begin
Result := -1;
end;
function TDropTarget._Release: Integer;
begin
Result := -1;
end;
procedure TDropTarget.GetFileNames(const dataObj: IDataObject; var FileNames: TArray<string>);
var
i: Integer;
formatetcIn: TFormatEtc;
medium: TStgMedium;
dropHandle: HDROP;
begin
FileNames := nil;
formatetcIn.cfFormat := CF_HDROP;
formatetcIn.ptd := nil;
formatetcIn.dwAspect := DVASPECT_CONTENT;
formatetcIn.lindex := -1;
formatetcIn.tymed := TYMED_HGLOBAL;
if dataObj.GetData(formatetcIn, medium)=S_OK then begin
(* This cast needed because HDROP is incorrectly declared as Longint in ShellAPI.pas. It should be declared as THandle
which is an unsigned integer. Without this fix the routine fails in top-down memory allocation scenarios. *)
dropHandle := HDROP(medium.hGlobal);
SetLength(FileNames, DragQueryFile(dropHandle, $FFFFFFFF, nil, 0));
for i := 0 to high(FileNames) do begin
SetLength(FileNames[i], DragQueryFile(dropHandle, i, nil, 0));
DragQueryFile(dropHandle, i, #FileNames[i][1], Length(FileNames[i])+1);
end;
end;
end;
procedure TDropTarget.SetEffect(var dwEffect: Integer);
begin
if FDropAllowed then begin
dwEffect := DROPEFFECT_COPY;
end else begin
dwEffect := DROPEFFECT_NONE;
end;
end;
function TDropTarget.DragEnter(const dataObj: IDataObject; grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
var
FileNames: TArray<string>;
begin
Result := S_OK;
Try
GetFileNames(dataObj, FileNames);
FDropAllowed := (Length(FileNames)>0) and FDragDrop.DropAllowed(FileNames);
SetEffect(dwEffect);
Except
Result := E_UNEXPECTED;
End;
end;
function TDropTarget.DragLeave: HResult;
begin
Result := S_OK;
end;
function TDropTarget.DragOver(grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
begin
Result := S_OK;
Try
SetEffect(dwEffect);
Except
Result := E_UNEXPECTED;
End;
end;
function TDropTarget.Drop(const dataObj: IDataObject; grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult;
var
FileNames: TArray<string>;
begin
Result := S_OK;
Try
GetFileNames(dataObj, FileNames);
if Length(FileNames)>0 then begin
FDragDrop.Drop(FileNames);
end;
Except
Application.HandleException(Self);
End;
end;
The idea here is to wrap up the complexity of the Windows IDropTarget in TDropTarget. All you need to do is to implement IDragDrop which is much simpler. Anyway, I think this should get you going.
Create the drop target object from your control's CreateWnd. Destroy it in the DestroyWnd method. That point is important because VCL window re-creation means that a control can have its window handle destroyed and re-created during its lifetime.
Note that reference counting on TDropTarget is suppressed. That is because when RegisterDragDrop is called it increments the reference count. This creates a circular reference and this code to suppress reference counting breaks that. This means that you would use this class through a class variable rather than an interface variable, in order to avoid leaking.
The usage would look something like this:
type
TMainForm = class(TForm, IDragDrop)
....
private
FDropTarget: TDropTarget;
// implement IDragDrop
function DropAllowed(const FileNames: array of string): Boolean;
procedure Drop(const FileNames: array of string);
protected
procedure CreateWindowHandle; override;
procedure DestroyWindowHandle; override;
end;
....
procedure TMainForm.CreateWindowHandle;
begin
inherited;
FDropTarget := TDropTarget.Create(WindowHandle, Self);
end;
procedure TMainForm.DestroyWindowHandle;
begin
FreeAndNil(FDropTarget);
inherited;
end;
function TMainForm.DropAllowed(const FileNames: array of string): Boolean;
begin
Result := True;
end;
procedure TMainForm.Drop(const FileNames: array of string);
begin
; // do something with the file names
end;
Here I am using a form as the drop target. But you could use any other windowed control in a similar fashion.
If you don't like pure WinAPI, then you can use components. Drag and Drop Component Suite is free with sources.
No, unless you are about to peruse some custom TForm descendant which have this functionality built-in already.
I used David Heffernan's solution as base for my test application and got 'Invalid pointer operation' on application close.
The solution for that problem was to change the TDropTarget.Create by adding '_Release;'
constructor TDropTarget.Create(AHandle: HWND; const ADragDrop: IDragDrop);
begin
inherited Create;
FHandle := AHandle;
FDragDrop := ADragDrop;
RegisterDragDrop(FHandle, Self);
_Release;
end;
A discussion about this problem you can see on Embarcadero forum.
You have to either write code yourself, or install a 3rd party product like DropMaster, which lets you do drag and drop in much older Delphi versions as well.
--jeroen

How to fix byte ordering issue in this piece of code?

To read a index file in a specific format, I cooked the following piece of code without considering byte ordering:
unit uCBI;
interface
uses
SysUtils,
Classes,
Generics.Collections;
type
TIndexList = class
private
FIndexList:TList<Cardinal>;
FOwnedStream:Boolean;
FMemoryStream: TMemoryStream;
function GetCount: Integer;
protected
public
constructor Create(AStream:TMemoryStream; OwnedStream:Boolean=True);
destructor Destroy; override;
function Add(const Value: Cardinal): Integer;
procedure Clear;
procedure SaveToFile(AFileName:TFileName);
procedure LoadFromFile(AFileName:TFileName);
property Count: Integer read GetCount;
end;
implementation
{ TIndexList }
function TIndexList.Add(const Value: Cardinal): Integer;
begin
Result := FIndexList.Add(Value)
end;
procedure TIndexList.Clear;
begin
FIndexList.Clear;
end;
constructor TIndexList.Create(AStream: TMemoryStream; OwnedStream: Boolean);
begin
FMemoryStream := AStream;
FOwnedStream := OwnedStream;
FIndexList := TList<Cardinal>.Create;
end;
destructor TIndexList.Destroy;
begin
if (FOwnedStream and Assigned(FMemoryStream)) then
FMemoryStream.Free;
FIndexList.Free;
//
inherited;
end;
function TIndexList.GetCount: Integer;
begin
Result := FIndexList.Count;
end;
procedure TIndexList.LoadFromFile(AFileName: TFileName);
var
lMemoryStream:TMemoryStream;
lCount:Cardinal;
begin
lMemoryStream := TMemoryStream.Create;
try
lMemoryStream.LoadFromFile(AFileName);
lMemoryStream.ReadBuffer(lCount,SizeOf(Cardinal));
if (lCount = Cardinal((lMemoryStream.Size-1) div SizeOf(Cardinal))) then
begin
FMemoryStream.Clear;
lMemoryStream.Position :=0;
FMemoryStream.CopyFrom(lMemoryStream,lMemoryStream.Size)
end else
raise Exception.CreateFmt('Corrupted CBI file: %s',[ExtractFileName(AFileName)]);
finally
lMemoryStream.Free;
end;
end;
procedure TIndexList.SaveToFile(AFileName: TFileName);
var
lCount:Cardinal;
lItem:Cardinal;
begin
FMemoryStream.Clear;
lCount := FIndexList.Count;
FMemoryStream.WriteBuffer(lCount,SizeOf(Cardinal));
for lItem in FIndexList do
begin
FMemoryStream.WriteBuffer(lItem,SizeOf(Cardinal));
end;
//
FMemoryStream.SaveToFile(AFileName);
end;
end.
It tested it and seems to work well as needed. Great was my suprise when I pursue extensive tests with real sample file. In fact the legacy format was devised with Amiga computer with a different byte ordering.
My Question:
How can I fix it ?
I want to keep the code unchanged and wonder wether a decorated TMemorySream will do so that I can transparently switch between big endian and little endian.
To change 'endianness' of Cardinals you can use the following:
function EndianChange(Value: Cardinal): Cardinal;
var
A1: array [0..3] of Byte absolute Value;
A2: array [0..3] of Byte absolute Result;
I: Integer;
begin
for I:= 0 to 3 do begin
A2[I]:= A1[3 - I];
end;
end;
If you want to keep your code unchanged, you can write your own TMemoryStream descendant and override its Read and Write methods using the above function, like that:
function TMyMemoryStream.Read(var Buffer; Count: Integer): Longint;
var
P: PCardinal;
I, N: Integer;
begin
inherited;
P:= #Buffer;
Assert(Count and 3 = 0);
N:= Count shr 2;
while N > 0 do begin
P^:= EndianChange(P^);
Inc(P);
Dec(N);
end;
end;

Problem with typecast in Delphi XE

I try to do list of procedures this way:
type
TProc = procedure of object;
TMyClass=class
private
fList:Tlist;
function getItem(index:integer):TProc;
{....}
public
{....}
end;
implementation
{....}
function TMyClass.getItem(index: Integer): TProc;
begin
Result:= TProc(flist[index]);// <--- error is here!
end;
{....}
end.
and get error:
E2089 Invalid typecast
How can I fix it?
As I see, I can make a fake class with only one property Proc:TProc; and make list of it. But I feel that it's a bad way, isn't it?
PS: project have to be delphi-7-compatible.
The typecast is invalid because you can not fit a method pointer to a pointer, a method pointer is in fact two pointers first being the address of the method and the second being a reference to the object that the method belongs. See Procedural Types in the documentation. This will not work in any version of Delphi.
Sertac has explained why your code doesn't work. In order to implement a list of such things in Delphi 7 you can do something like this.
type
PProc = ^TProc;
TProc = procedure of object;
TProcList = class(TList)
private
FList: TList;
function GetCount: Integer;
function GetItem(Index: Integer): TProc;
procedure SetItem(Index: Integer; const Item: TProc);
public
constructor Create;
destructor Destroy; override;
property Count: Integer read GetCount;
property Items[Index: Integer]: TProc read GetItem write SetItem; default;
function Add(const Item: TProc): Integer;
procedure Delete(Index: Integer);
procedure Clear;
end;
type
TProcListContainer = class(TList)
protected
procedure Notify(Ptr: Pointer; Action: TListNotification); override;
end;
procedure TProcListContainer.Notify(Ptr: Pointer; Action: TListNotification);
begin
inherited;
case Action of
lnDeleted:
Dispose(Ptr);
end;
end;
constructor TProcList.Create;
begin
inherited;
FList := TProcListContainer.Create;
end;
destructor TProcList.Destroy;
begin
FList.Free;
inherited;
end;
function TProcList.GetCount: Integer;
begin
Result := FList.Count;
end;
function TProcList.GetItem(Index: Integer): TProc;
begin
Result := PProc(FList[Index])^;
end;
procedure TProcList.SetItem(Index: Integer; const Item: TProc);
var
P: PProc;
begin
New(P);
P^ := Item;
FList[Index] := P;
end;
function TProcList.Add(const Item: TProc): Integer;
var
P: PProc;
begin
New(P);
P^ := Item;
Result := FList.Add(P);
end;
procedure TProcList.Delete(Index: Integer);
begin
FList.Delete(Index);
end;
procedure TProcList.Clear;
begin
FList.Clear;
end;
Disclaimer: completely untested code, use at your own risk.

Using TOwnedCollection descendant in Delphi

I'm trying to create a custom component with a collection property. However if I try to open the collection editor during design time by clicking "..." button in object inspector, nothing happens. What I am missing?
Here's my TCollection descendant:
TMyCollection = class(TOwnedCollection)
private
function GetItem(Index: Integer): TMyCollectionItem;
procedure SetItem(Index: Integer; const Value: TMyCollectionItem);
public
function Add : TMyCollectionItem;
property Items[Index: Integer]: TMyCollectionItem read GetItem write SetItem;
end;
And the item:
TMyCollectionItem = class(TCollectionItem)
private
FValue: integer;
protected
function GetDisplayName: string; override;
public
procedure Assign(Source: TPersistent); override;
constructor Create(Collection: TCollection); override;
destructor Destroy; override;
published
property Value : integer read FValue write FValue;
end;
Your class definitions look correct so with out seeing the entire implementation I don't know what the problem is.
Here is a simple unit I've written that uses TOwnedCollection, TCollectionItem and TComponent.
I know this unit works. Use it as a basis for checking your code.
unit rmMultiStrings;
interface
uses classes, sysutils;
type
ErmMultiStringNameException = Exception;
TrmMultiStringsCollection = class;
TrmMultiStringCollectionItem = class(TCollectionItem)
private
fItemDesc: string;
fItemName: string;
fData : TStringList;
fMultiStrings : TrmMultiStringsCollection;
function GetStrings: TStringList;
function GetStringText: String;
procedure SetItemName(const Value: string);
procedure SetStrings(const Value: TStringList);
procedure SetStringText(const Value: String);
public
constructor Create(Collection: TCollection); override;
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
published
property ItemName : string read fItemName write SetItemName;
property Description : string read fItemDesc write fItemDesc;
property Strings : TStringList read GetStrings write SetStrings stored false;
property Text : String read GetStringText write SetStringText;
end;
TrmMultiStringsCollection = class(TOwnedCollection)
private
function GetItem(AIndex: integer): TrmMultiStringCollectionItem;
procedure SetItem(AIndex: integer; const Value: TrmMultiStringCollectionItem);
public
function Add: TrmMultiStringCollectionItem;
function IndexOf(ItemName:string):integer;
function ValueOf(ItemName:string):String;
function ValueOfIndex(aIndex:integer):string;
procedure Assign(Source: TPersistent); override;
property Items[AIndex: integer] : TrmMultiStringCollectionItem read GetItem write SetItem;
end;
TrmMultiStrings = class(TComponent)
private
fData : TrmMultiStringsCollection;
procedure SetData(const Value: TrmMultiStringsCollection);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function IndexOf(ItemName:string):integer;
function ValueOf(ItemName:string):String;
function ValueOfIndex(aIndex:integer):string;
published
property Data : TrmMultiStringsCollection read fData write SetData;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterClass(TrmMultiStringsCollection);
RegisterClass(TrmMultiStringCollectionItem);
RegisterComponents('rmConcordia', [TrmMultiStrings]);
end;
{ TrmMultiStringCollectionItem }
procedure TrmMultiStringCollectionItem.Assign(Source: TPersistent);
var
wSrc : TrmMultiStringCollectionItem;
begin
if Source is TrmMultiStringCollectionItem then
begin
wSrc := TrmMultiStringCollectionItem(Source);
ItemName := wSrc.ItemName;
Description := wSrc.Description;
Text := wSrc.Text;
end
else
inherited;
end;
constructor TrmMultiStringCollectionItem.Create(Collection: TCollection);
begin
inherited;
fMultiStrings := TrmMultiStringsCollection(Collection);
fData := TStringList.create;
end;
destructor TrmMultiStringCollectionItem.Destroy;
begin
fData.free;
inherited;
end;
function TrmMultiStringCollectionItem.GetStrings: TStringList;
begin
result := fData;
end;
function TrmMultiStringCollectionItem.GetStringText: String;
begin
result := fData.Text;
end;
procedure TrmMultiStringCollectionItem.SetItemName(const Value: string);
begin
if (fItemName <> Value) then
begin
if fMultiStrings.IndexOf(Value) = -1 then
fItemName := Value
else
raise ErmMultiStringNameException.Create('Item name already exists');
end;
end;
procedure TrmMultiStringCollectionItem.SetStrings(
const Value: TStringList);
begin
fData.Assign(Value);
end;
procedure TrmMultiStringCollectionItem.SetStringText(const Value: String);
begin
fData.Text := Value;
end;
{ TrmMultiStringsCollection }
function TrmMultiStringsCollection.Add: TrmMultiStringCollectionItem;
begin
result := TrmMultiStringCollectionItem(inherited Add);
result.ItemName := 'Item_'+inttostr(NextID);
end;
procedure TrmMultiStringsCollection.Assign(Source: TPersistent);
var
wSrc : TrmMultiStringsCollection;
loop : integer;
begin
if (source is TrmMultiStringsCollection) then
begin
wSrc := TrmMultiStringsCollection(Source);
Clear;
for loop := 0 to wSrc.Count - 1 do
Add.Assign(wSrc.Items[loop]);
end
else
inherited;
end;
function TrmMultiStringsCollection.GetItem(
AIndex: integer): TrmMultiStringCollectionItem;
begin
result := TrmMultiStringCollectionItem(inherited Items[AIndex]);
end;
function TrmMultiStringsCollection.IndexOf(ItemName: string): integer;
var
loop : integer;
begin
result := -1;
loop := 0;
while (result = -1) and (loop < Count) do
begin
if (CompareText(Items[loop].ItemName, ItemName) = 0) then
result := loop
else
inc(loop);
end;
end;
procedure TrmMultiStringsCollection.SetItem(AIndex: integer; const Value: TrmMultiStringCollectionItem);
begin
inherited SetItem(AIndex, Value)
end;
function TrmMultiStringsCollection.ValueOf(ItemName: string): String;
begin
result := ValueOfIndex(IndexOf(ItemName));
end;
function TrmMultiStringsCollection.ValueOfIndex(aIndex: integer): string;
begin
if (aIndex >= 0) and (aIndex < Count) then
result := Items[aIndex].Text
else
result := '';
end;
{ TrmMultiStrings }
constructor TrmMultiStrings.Create(AOwner: TComponent);
begin
inherited;
fData := TrmMultiStringsCollection.Create(self, TrmMultiStringCollectionItem);
end;
destructor TrmMultiStrings.Destroy;
begin
fData.Free;
inherited;
end;
function TrmMultiStrings.IndexOf(ItemName: string): integer;
begin
result := Data.IndexOf(ItemName);
end;
procedure TrmMultiStrings.SetData(const Value: TrmMultiStringsCollection);
begin
fData.Assign(Value);
end;
function TrmMultiStrings.ValueOf(ItemName: string): String;
begin
result := Data.ValueOf(ItemName);
end;
function TrmMultiStrings.ValueOfIndex(aIndex: integer): string;
begin
result := Data.ValueOfIndex(aIndex);
end;
end.

Resources