Custom procedure for sorting an array - delphi

Im going through one of my old exam papers, preparing for my finals, and for the love of life, I cant figure out how to do this!
The program was working earlier, but it wasn't sorting the array. Now Im getting an error saying EAccess violation with message: access violation at address 00404BDE
Here's my code (its kind of long, maybe you can help me spot my error) :
private
{ Private declarations }
iCount : Integer;
arrDams : array [1..200] of string;
Procedure List;
procedure Display;
procedure Sort;
procedure Search (sDam : String);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Display; //Display with Numbers
var
k : Integer;
begin
for K := 1 to 200 do
begin
RedOut.Lines.Add (IntToStr(k) + '.) ' + (arrDams[k]));
end;
end;
procedure TForm1.FormCreate(Sender: TObject); // Create
begin
//
end;
procedure TForm1.List; // TextFile to array
var
MyFile : TextFile;
k : Integer;
begin
If FileExists('Dams.txt') <> True
then Application.Terminate;
AssignFile (MyFile, 'Dams.txt');
Reset(MyFile);
For K := 1 to 200 do
begin
Readln(MyFile, arrDams[k])
end;
end;
procedure TForm1.Search(sDam: String); // Search
begin
end;
procedure TForm1.Sort; // Sort;
var
K,L : byte;
sKeep : string;
begin
for k := 1 to iCount -1 do
begin
for l := k + 1 to iCount do
begin
if arrDams[k] > arrDams[L] then
begin
sKeep := arrDams[k];
arrDams[k] := arrDams[L];
arrDams[L] := sKeep
end;
end;
end;
end;
procedure TForm1.btnListClick(Sender: TObject);
begin
List;
Display;
end;
procedure TForm1.btnDisplayClick(Sender: TObject);
begin
display;
sort;
end; //<---------- ERROR OVER HERE!
end.
Theres 3 buttons at the top of the form, namely Show list, Make new textfile with list and Sort list alphabetically. The button Im working on is to sort the list. This question paper says I must make a sort procedure and must be called when the Sort Button is clicked.
Thanks for any advice/help
P.S.
Can you please point me to a link where they explain Selection Sorting in depth - the logic isn't with me on this..

You don't initialize iCount. So it is 0. Therefore iCount-1 is -1. However, you use Byte, an unsigned type, for your loop variable. Now, -1 when interpreted as an unsigned Byte is 255. If you follow this all through it means that you access the array out of bounds. In fact what happens is that the inner loop executes exactly once with a value of l equal to 0 and k equal to 255.
Were you to enable the range checking compiler option, you would have encountered a runtime error as soon as you run off the end of the array.
Presumably you want to initialize iCount to some value. I cannot tell what, but you will know.
Beyond that, stop using unsigned types for loop variables. Replace Byte with Integer.

Related

Is there a way to solve an I/O error 6 in Delphi?

procedure TfrmSongs.Display;
var
i: Integer;
begin
redOutput.Clear;
redOutput.Lines.Add('The TOP 10');
for i := 1 to iCount-1 do
begin
redOutput.Lines.Add(IntToStr(i)+arrSongs[i]);
end;
end;
procedure TfrmSongs.FormActivate(Sender: TObject);
var
tSongList: TextFile;
sSong: string;
begin
iCount := 0;
AssignFile(tSongList, ExtractFilePath(Application.ExeName)+'Songs.txt');
Reset(tSongList);
while not EOF do
begin
Readln(tSongList, sSong);
arrSongs[iCount] := sSong;
Inc(iCount);
end;
CloseFile(tSongList);
Display;
end;
I'm trying to display the array I tried to create via a text file in a rich edit. But every time I run the app, it gives me an 'I/O error 6' error and nothing displays. I don't know if it's something with the text file or if it's something with the display procedure.
There are a few problems with your code, but regarding the I/O error specifically, error 6 means "invalid file handle".
Since you are getting a popup error notification, you clearly have I/O checking enabled, which it is by default.
I/O error 6 is not typical for a failure on System.Reset(), and you are not seeing any other kind of error related to a failure in opening a file, so we can safely assume that the file is being opened successfully, and that System.Readln() and System.CloseFile() are not being passed an invalid I/O handle.
So that leaves just one line that could be receiving an invalid I/O handle:
while not EOF do
System.Eof() has an optional parameter to tell it which file to check. Since you are omitting that parameter, Eof() will use System.Input instead. And a GUI process does not have a STDIN handle assigned by default. So that is likely where error 6 is coming from.
That line needs to be changed to this instead:
while not EOF(tSongFile) do
UPDATE: given the declaration of arrSongs you have shown in comments (arrSongs: array[1..MAX] of string;), there are additional problems with your code. You need to make sure the reading loop does not try to store more than MAX strings in the array. Also, your reading loop is trying to store a string at index 0, which is not a valid index since the array starts at index 1. Also, Display() is skipping the last string in the array. See what happens when you omit important details?
Try this instead:
private
arrSongs: array[1..MAX] of string;
...
procedure TfrmSongs.Display;
var
i: Integer;
begin
redOutput.Clear;
redOutput.Lines.Add('The TOP 10');
for i := 1 to iCount do
begin
redOutput.Lines.Add(IntToStr(i) + arrSongs[i]);
end;
end;
procedure TfrmSongs.FormActivate(Sender: TObject);
var
tSongList: TextFile;
sSong: string;
begin
iCount := 0;
AssignFile(tSongList, ExtractFilePath(Application.ExeName) + 'Songs.txt');
Reset(tSongList);
try
while (not EOF(tSongList)) and (iCount < MAX) do
begin
Readln(tSongList, sSong);
arrSongs[1+iCount] := sSong;
Inc(iCount);
end;
finally
CloseFile(tSongList);
end;
Display;
end;
That being said, I would suggest getting rid of the reading loop completely. You can use a TStringList instead:
uses
..., System.Classes;
...
private
lstSongs: TStringList;
...
procedure TfrmSongs.Display;
var
i: Integer;
begin
redOutput.Clear;
redOutput.Lines.Add('The TOP 10');
for i := 0 to lstSongs.Count-1 do
begin
redOutput.Lines.Add(IntToStr(i+1) + lstSongs[i]);
end;
end;
procedure TfrmSongs.FormCreate(Sender: TObject);
begin
lstSongs := TStringList.Create;
end;
procedure TfrmSongs.FormDestroy(Sender: TObject);
begin
lstSongs.Free;
end;
procedure TfrmSongs.FormActivate(Sender: TObject);
begin
lstSongs.LoadFromFile(ExtractFilePath(Application.ExeName) + 'Songs.txt');
Display;
end;
Or, you can use TFile.ReadAllLines() instead:
uses
..., System.IOUtils;
...
private
arrSongs: TStringDynArray;
...
procedure TfrmSongs.Display;
var
i: Integer;
begin
redOutput.Clear;
redOutput.Lines.Add('The TOP 10');
for i := 0 to High(arrSongs) do
begin
redOutput.Lines.Add(IntToStr(i+1) + arrSongs[i]);
end;
end;
procedure TfrmSongs.FormActivate(Sender: TObject);
begin
arrSongs := TFile.ReadAllLines(ExtractFilePath(Application.ExeName) + 'Songs.txt');
Display;
end;

Is this economical?

Just wanting to see if there is a better way to do the following(there is always a better way for everything) because it does delay the application when loading due the amount of data.
I want to fill an array of records with data I have stored in csv file, I currently have it fixed length for the array but will later make it dynamic so I can add to the csv file.
type
TStarCoords = Packed record
szSystem: String[40];
fCoordX: Single;
fCoordY: Single;
fCoordZ: Single;
end;
SystemCoords: Array [0 .. 22379] of TStarCoords;
Const
SYSTEMS = 'Data\Systems.csv';
I then fill the array on the oncreate event
procedure TForm1.FormCreate(Sender: TObject);
var
szFile, sRecord: string;
Row, Index, i: Integer;
slList: TStringList;
begin
szFile := ExtractFilePath(ParamStr(0)) + SYSTEMS;
if FileExists(szFile) then
try
slList := TStringList.Create;
slList.LoadFromFile(szFile);
for Row := 0 to slList.Count - 1 do
begin
sRecord := slList[Row];
index := Pos(',', sRecord);
if index > 0 then
begin
SystemCoords[Row].szSystem := Copy(sRecord, 1, index - 1);
Delete(sRecord, 1, index);
end;
index := Pos(',', sRecord);
if index > 0 then
begin
SystemCoords[Row].fCoordX := StrToFloat(Copy(sRecord, 1, index - 1));
Delete(sRecord, 1, index);
end;
index := Pos(',', sRecord);
if index > 0 then
begin
SystemCoords[Row].fCoordY := StrToFloat(Copy(sRecord, 1, index - 1));
Delete(sRecord, 1, index);
end;
SystemCoords[Row].fCoordZ := StrToFloat(sRecord);
end;
finally
slList.Free;
end;
for i := Low(SystemCoords) to High(SystemCoords) do
begin
cbSystem.Items.Add(SystemCoords[i].szSystem);
end;
end;
As you can see I am using "Pos" function to parse the csv file and also loop the array at the end to add the Star name to a combobox, Is there a more economical way of doing this?
Any suggestions are welcomed
It doesn't look very efficient.
Allocating a fixed length global array looks poor. Use a dynamic array of length determined at runtime.
Short strings are not recommended. Don't use them in modern programming. They are legacy and don't handle Unicode.
Don't pack records. That results in misaligned data.
There seems to be far more heap allocations that are needed. Avoid Delete if you can.
Loading into a string list won't be efficient. Use a line reader based approach for speed. Delphi's built in class though is rubbish. If you want speed and effective use of memory, roll your own.
Probably the bulk of the time is spent populating the combo! Adding 22380 items to a combo will take a very long time. Don't do that. If the data set is smaller, only add as many items as there are in the data. Otherwise, use the virtual paradigm in your UI control.
Your next step though is to work out where the bottleneck is. We can only guess because we are missing so much information. We don't know if the data is static, how big it is, and so on.
Like others said, probably the majority of the time is spent populating the combo.
In my opinion, when dealing with big updates of a TStrings the BeginUpdate / EndUpdate technique proposed by the Jens Borrisholt's answer constitutes a valid approach.
As a minor issue, if your application is the only which writes and reads the data and neither machines nor humans care about the CSV format, you might consider to store the records adopting a different file format, using the BlockRead and BlockWrite functions.
type
TStarCoords = record
szSystem: string[40];
fCoordX,
fCoordY,
fCoordZ: Single;
end;
. . .
const
CFILENAME = '<your path to some file .dat>';
Reading the data:
procedure TForm1.FormCreate(Sender: TObject);
var
lstStarCoords: TList<TStarCoords>;
f: File;
starCoords: TStarCoords;
begin
lstStarCoords := TList<TStarCoords>.Create;
try
AssignFile(f, CFILENAME);
Reset(f, SizeOf(TStarCoords));
try
while not Eof(f) do begin
BlockRead(f, starCoords, 1);
lstStarCoords.Add(starCoords);
end;
finally
CloseFile(f);
end;
cbSystem.Items.BeginUpdate;
for starCoords in lstStarCoords do
cbSystem.Items.Add(starCoords.szSystem);
cbSystem.Items.EndUpdate;
finally
lstStarCoords.Free;
end;
end;
Writing the data:
procedure TForm1.WriteStarCoords;
var
lstStarCoords: TList<TStarCoords>;
f: File;
starCoords: TStarCoords;
i: Integer;
begin
lstStarCoords := TList<TStarCoords>.Create;
try
//let's insert 5k new items
for i:=1 to 5000 do begin
with starCoords do begin
szSystem := 'HYEL YE';
fCoordX := 122;
fCoordY := 12.375;
fCoordZ := 45.75;
end;
lstStarCoords.Add(starCoords);
end;
AssignFile(f, CFILENAME);
Rewrite(f, SizeOf(TStarCoords));
try
for starCoords in lstStarCoords do
BlockWrite(f, starCoords, 1);
finally
CloseFile(f);
end;
finally
lstStarCoords.Free;
end;
end;
EDIT: example using pointers to store the record information directly in the cbSystem component.
This approach is a little more "dangerous" since it allocates memory which has to be manually freed but allows to avoid the usage of a TDictionary to pair the TStarCoords.szSystem with the corresponding record.
Declare a new type which points to the TStarCoords record:
type
PStarCoords = ^TStarCoords;
Reading the data:
procedure TForm1.FormCreate(Sender: TObject);
var
lstStarCoords: TStringList;
f: File;
starCoords: PStarCoords;
begin
ClearCbSystem;
lstStarCoords := TStringList.Create(False);
{another minor enhancement:
since lstStarCoords does not own any TObject which needs to be freed
the OwnsObjects property of the TStringList can be set to False
in order to avoid some code to be execute in some method like Clear and Delete}
try
lstStarCoords.BeginUpdate;
AssignFile(f, CFILENAME);
Reset(f, SizeOf(TStarCoords));
try
while not Eof(f) do begin
New(starCoords);
BlockRead(f, starCoords^, 1);
lstStarCoords.AddObject(starCoords^.szSystem, TObject(starCoords));
end;
finally
CloseFile(f);
end;
lstStarCoords.EndUpdate;
cbSystem.Items.Assign(lstStarCoords);
finally
lstStarCoords.Free;
end;
end;
Clearing the list with cbSystem.Clear does not automatically dispose the underlying pointers which have to be manually freed. Use the ClearCbSystem procedure everytime the cbSystem list has to be cleared:
procedure TForm1.ClearCbSystem;
var
i: Integer;
begin
cbSystem.Items.BeginUpdate;
for i := cbSystem.Items.Count-1 downto 0 do
Dispose(PStarCoords(cbSystem.Items.Objects[i]));
cbSystem.Clear;
cbSystem.Items.EndUpdate;
end;
When the form is destroyed, a call to the ClearCbSystem procedure ensures the pointers are disposed before the cbSystem component is freed by the application itself:
procedure TForm1.FormDestroy(Sender: TObject);
begin
ClearCbSystem;
end;
You can use TStringlist for the parsing of the line. In the following I assume that you have you elements seperated by a comma.
Since you are putting the string representation of you records into a combobox I assunme you later on in your program needs to go the other way: Find a TStarCoords from string. Given that I woyls recoment you putting your elements in a TDictionary instread og a Array.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Generics.Collections, StdCtrls;
type
TStarCoords = packed record
szSystem: string[40];
fCoordX: Single;
fCoordY: Single;
fCoordZ: Single;
end;
const
SYSTEMS = 'Data\Systems.csv';
type
TForm1 = class(TForm)
ComboBox1: TComboBox;
procedure FormCreate(Sender: TObject);
procedure ComboBox1Change(Sender: TObject);
private
SystemCoords: TDictionary<string, TStarCoords>;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.ComboBox1Change(Sender: TObject);
var
StarCoord: TStarCoords;
begin
if not SystemCoords.TryGetValue(ComboBox1.Text, StarCoord) then
exit; //todo : Make some error handling
Caption := FloatToStr(StarCoord.fCoordX);
end;
procedure TForm1.FormCreate(Sender: TObject);
var
Lines, Elements: TStringlist;
Line: string;
SystemCoord: TPair<string, TStarCoords>;
begin
if not FileExists(ExtractFilePath(ParamStr(0)) + SYSTEMS) then
exit; //todo: Some error handling
SystemCoords := TDictionary<string, TStarCoords > .Create;
Lines := TStringlist.Create;
Elements := TStringlist.Create;
Elements.LineBreak := ',';
try
for Line in Lines do
begin
Elements.Text := Line;
SystemCoord.Key := Elements[0];
with SystemCoord.Value do
begin
szSystem := string(Elements[0]);
fCoordX := StrToFloat(Elements[1]);
fCoordY := StrToFloat(Elements[2]);
fCoordZ := StrToFloat(Elements[3]);
end;
SystemCoords.Add(SystemCoord.Key, SystemCoord.Value);
end;
finally
Lines.Free;
Elements.Free;
end;
try
ComboBox1.Items.BeginUpdate;
for SystemCoord in SystemCoords do
ComboBox1.Items.Add(SystemCoord.Key);
finally
ComboBox1.Items.EndUpdate;
end;
end;
end.

ICS HTTPCLI Free Exception

How can I properly free the component once i make it with a loop like this? If I free it like I do now I get some GETMEM.INC exception. I am coming from Indy so i don't really know ICS too much.
Thanks
const
URLs : array[0..3] of string =
(
'http://www.example.com',
'http://www.example.com',
'http://www.example.com',
'http://www.example.com'
) ;
var
Output: array of TStringList;
S: array of TMemoryStream;
Async: array of TSslHttpCli;
implementation
procedure RequestDone(Sender: TObject; RqType: THttpRequest;
ErrCode: Word);
begin
with Sender as TSSLHTTPCLI do begin
S[Tag].Position:=0;
Output[Tag].LoadFromStream(S[Tag]);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
i:integer;
begin
for i := 0 to High(URLS) do begin
S[i]:=TMemoryStream.Create;
Output[i]:=TStringList.Create;
Async[i]:=TSslHttpCli.Create(nil);
Async[i].Tag:=i;
Async[i].FollowRelocation:=true;
Async[i].NoCache:=true;
Async[i].SocketFamily:=sfAny;
Async[i].OnRequestDone:=RequestDone;
Async[i].RcvdStream:=S[i];
Async[i].URL:= URLs[i];
Async[i].MultiThreaded:=true;
Async[i].GetASync;
end;
end;
procedure TForm1.Button4Click(Sender: TObject);
var
i:integer;
begin
for i := 0 to High(URLS) do begin
Output[i].Free;
Async[i].RcvdStream.Free;
Async[i].Free; // << -- EXCEPTION
// S[i].Free;
end;
end;
You never allocate any memory for Result, Asynch, or S. You need to SetLength on each of them before you can put anything into them (or take anything back out).
procedure TForm1.Button1Click(Sender: TObject);
var
i:integer;
begin
SetLength(Result, Length(URLS));
SetLength(S, Length(URLS));
SetLength(Asynch, Length(URLS));
for i := 0 to High(URLS) do begin
S[i]:=TMemoryStream.Create;
Result[i]:=TStringList.Create;
Async[i]:=TSslHttpCli.Create(nil);
// etc.
end;
end;
BTW, Result is a terrible name for a variable, especially one that's global in scope. It's the return value from a function that's automatically generated by the compiler, and use anywhere but in a function makes your code hard to read. See this, for instance:
var
Result: string = '';
procedure AddToReslt(CharToAdd: Char);
begin
// Many many lines of code
// go in here. Maybe a few loops
// of if statements.
Result := Result + CharToAdd;
end;
function DoSomeMath: Integer;
begin
// Some really complex numeric code, maybe
// calculating the value of `pi` to the 900th
// digit
Result := 2 * 2;
end;
Now quickly - remembering that each of them containss lots of code - which one is a function and which is a procedure?

How do I sort a generic list using a custom comparer?

I'm kinda a Delphi-newbie and I don't get how the Sort method of a TList of Records is called in order to sort the records by ascending integer value.
I have a record like the following:
type
TMyRecord = record
str1: string;
str2: string;
intVal: integer;
end;
And a generic list of such records:
TListMyRecord = TList<TMyRecord>;
Have tried to find a code-example in the help files and found this one:
MyList.Sort(#CompareNames);
Which I can't use, since it uses classes. So I tried to write my own compare function with a little different parameters:
function CompareIntVal(i1, i2: TMyRecord): Integer;
begin
Result := i1.intVal - i2.intVal;
end;
But the compiler always throws a 'not enough parameters' - error when I call it with open.Sort(CompareIntVal);, which seems obvious; so I tried to stay closer to the help file:
function SortKB(Item1, Item2: Pointer): Integer;
begin
Result:=PMyRecord(Item1)^.intVal - PMyRecord(Item2)^.intVal;
end;
with PMyRecord as PMyRecord = ^TMyRecord;
I have tried different ways of calling a function, always getting some error...
The Sort overload you should be using is this one:
procedure Sort(const AComparer: IComparer<TMyRecord>);
Now, you can create an IComparer<TMyRecord> by calling TComparer<TMyRecord>.Construct. Like this:
var
Comparison: TComparison<TMyRecord>;
....
Comparison :=
function(const Left, Right: TMyRecord): Integer
begin
Result := Left.intVal-Right.intVal;
end;
List.Sort(TComparer<TMyRecord>.Construct(Comparison));
I've written the Comparison function as an anonymous method, but you could also use a plain old style non-OOP function, or a method of an object.
One potential problem with your comparison function is that you may suffer from integer overflow. So you could instead use the default integer comparer.
Comparison :=
function(const Left, Right: TMyRecord): Integer
begin
Result := TComparer<Integer>.Default.Compare(Left.intVal, Right.intVal);
end;
It might be expensive to call TComparer<Integer>.Default repeatedly so you could store it away in a global variable:
var
IntegerComparer: IComparer<Integer>;
....
initialization
IntegerComparer := TComparer<Integer>.Default;
Another option to consider is to pass in the comparer when you create the list. If you only ever sort the list using this ordering then that's more convenient.
List := TList<TMyRecord>.Create(TComparer<TMyRecord>.Construct(Comparison));
And then you can sort the list with
List.Sort;
The concise answer:
uses
.. System.Generics.Defaults // Contains TComparer
myList.Sort(
TComparer<TMyRecord>.Construct(
function(const Left, Right: TMyRecord): Integer
begin
Result := Left.intVal - Right.intVal;
end
)
);
I want to share my solution (based on the input I have gathered here).
It's a standard setup. A filedata class that holds data of a single file in a generic TObjectList. The list has the two private attributes fCurrentSortedColumn and fCurrentSortAscending to control the sort order. The AsString-method is the path and filename combined.
function TFileList.SortByColumn(aColumn: TSortByColums): boolean;
var
Comparison: TComparison<TFileData>;
begin
result := false;
Comparison := nil;
case aColumn of
sbcUnsorted : ;
sbcPathAndName: begin
Comparison := function(const Left, Right: TFileData): integer
begin
Result := TComparer<string>.Default.Compare(Left.AsString,Right.AsString);
end;
end;
sbcSize : begin
Comparison := function(const Left, Right: TFileData): integer
begin
Result := TComparer<int64>.Default.Compare(Left.Size,Right.Size);
if Result = 0 then
Result := TComparer<string>.Default.Compare(Left.AsString,Right.AsString);
end;
end;
sbcDate : begin
Comparison := function(const Left, Right: TFileData): integer
begin
Result := TComparer<TDateTime>.Default.Compare(Left.Date,Right.Date);
if Result = 0 then
Result := TComparer<string>.Default.Compare(Left.AsString,Right.AsString);
end;
end;
sbcState : begin
Comparison := function(const Left, Right: TFileData): integer
begin
Result := TComparer<TFileDataTestResults>.Default.Compare(Left.FileDataResult,Right.FileDataResult);
if Result = 0 then
Result := TComparer<string>.Default.Compare(Left.AsString,Right.AsString);
end;
end;
end;
if assigned(Comparison) then
begin
Sort(TComparer<TFileData>.Construct(Comparison));
// Control the sort order
if fCurrentSortedColumn = aColumn then
fCurrentSortAscending := not fCurrentSortAscending
else begin
fCurrentSortedColumn := aColumn;
fCurrentSortAscending := true;
end;
if not fCurrentSortAscending then
Reverse;
result := true;
end;
end;
I found a much simpler modified sort function to alphabetize a TList of records or nonstandard list of items.
Example
PList = ^TContact;
TContact = record //Record for database of user contact records
firstname1 : string[20];
lastname1 : string[20];
phonemobile : Integer; //Fields in the database for contact info
phonehome : Integer;
street1 : string;
street2 : string;
type
TListSortCompare = function (Item1,
Item2: TContact): Integer;
var
Form1: TForm1;
Contact : PList; //declare record database for contacts
arecord : TContact;
Contacts : TList; //List for the Array of Contacts
function CompareNames(i1, i2: TContact): Integer;
begin
Result := CompareText(i1.lastname1, i2.lastname1) ;
end;
and the function to call to sort your list
Contacts.Sort(#CompareNames);

First chance exception at $7C81EB33

I have an application that when run at home works fine, however when ran on school computers(Windows XP) i get the following message. (This is recompiling it, not just running the .exe)- In Delphi 2005
First chance exception at $7C81EB33. Exception class EAccessViolation with message 'Access violation at address 0045E5E2 in module 'Project2.exe'. Read of address 00000198'. Process Project2.exe (440)
Code: Ignoring unneeded stuff.
Image1: TImage; // Image(all the way to 72)
Timer1: TTimer; Timer2: TTimer;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure SomeOtherProcedure(Sender: TImage);
procedure Timer1Timer(Sender: TObject);
procedure Timer2Timer(Sender: TObject);
private
{ private declarations }
public
{ public declarations }
end;
var
Form1: TForm1;
left : integer;
top : integer;
gap : integer;
type
coordinates = record
row : integer ;
col : integer;
end;
var
picarray : array[0..5,0..5] of timage;
thiscover, midcover, lastcover : timage;
imageindex : array[0..5,0..5] of integer;
picloc: array[0..3] of coordinates;
clickcount, pairsfound, attemptcount : integer;
implementation
{$R *.lfm}
procedure initialise();
var
i, j, whichcol, whichrow : integer;
begin
for i := 0 to 5 do
for j := 0 to 5 do
imageindex[i,j] := -1; // not used
randomize;
for i := 0 to 11 do
for j := 1 to 3 do
begin
repeat
begin
whichcol := random(6) ;
whichrow := random(6) ;
end;
until imageindex[whichcol, whichrow] = -1;
picarray[whichcol, whichrow].Picture.LoadFromFile('C:\Users\Hayden\Pictures\'+ inttostr(I+1) +'.jpg');
imageindex[whichcol, whichrow] := I ;
end;
clickcount := 0 ; //
pairsfound := 0 ;
attemptcount := 0 ;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
cpic : tcomponent;
whichcol: integer;
whichrow : integer;
begin
gap := image2.left - image1.left;
top := image1.Top;
left := image1.left;
for cpic in form1 do
begin
if (cpic.ClassType = timage) and (cpic.Tag = 10) then
begin
whichcol := (timage(cpic).left - left) div gap;
whichrow := (timage(cpic).Top - top) div gap;
picarray[whichcol, whichrow] := timage(cpic) ;
end;
end;
initialise;
end;
Line >>> picarray[whichcol, whichrow].Picture.LoadFromFile('C:\Users\Hayden\Pictures\'+ inttostr(I+1) +'.jpg');
seems to cause the error. And if it is a coding error, what is the correct way to do this?
First, I'm going to clean up your code a little, because as it stands, it's very difficult to figure what's going on. I highly recommend you get into the habit of taking a few minutes to keep your code clearly formatted - it will save you hours of debugging.
I've applied only the following simple changes: Indentation, Blank lines, and liberal use of begin .. end;
var
picarray : array[0..5,0..5] of timage;
thiscover, midcover, lastcover : timage;
imageindex : array[0..5,0..5] of integer;
picloc: array[0..3] of coordinates;
clickcount, pairsfound, attemptcount : integer;
implementation
{$R *.lfm}
procedure initialise();
var
i, j, whichcol, whichrow : integer;
begin
for i := 0 to 5 do
begin
for j := 0 to 5 do
begin
//It's clear you're initialising the 36 entries of imageindex to -1
imageindex[i,j] := -1; // not used
end;
end;
randomize;
for i := 0 to 11 do
begin
for j := 1 to 3 do
begin
//This loop also runs 36 times, so it fills the whole of imageindex with new values
//It also loads all 36 entries of picarray with an image specfied by the current value of i
//The approach is dangerous because it depends on the 'loop sizes' matching,
//there are much safer ways of doing this, but it works
repeat
begin //This being one of the only 2 begin..end's you provided inside this is routine is pointless because repeat..until implies it.
whichcol := random(6) ;
whichrow := random(6) ;
end;
until imageindex[whichcol, whichrow] = -1;
//This line itself will throw an access violation if picarray[whichcol, whichrow] doesn't
//contain a valid TImage instance... we have to check other code to confirm that possibility
picarray[whichcol, whichrow].Picture.LoadFromFile('C:\Users\Hayden\Pictures\' + inttostr(I+1) + '.jpg');
imageindex[whichcol, whichrow] := I ;
end;
end;
clickcount := 0 ; //
pairsfound := 0 ;
attemptcount := 0 ;
end;
Moving on to the next piece of code:
procedure TForm1.FormCreate(Sender: TObject);
var
cpic : tcomponent;
whichcol: integer;
whichrow : integer;
begin
gap := image2.left - image1.left;
top := image1.Top;
left := image1.left;
for cpic in form1 do
begin
//This loop attempts to assign existing TImage instances to picarray
//However, the way you're going about it is extremely dangerous and unreliable.
//You're trying to use the position of a component on the form to determine its
//position in the array.
//There are many things that could go wrong here, but since this seems to be a
//homework excercise, I'll just point you in the right direction - you need
//to debug this code.
if (cpic.ClassType = timage) and (cpic.Tag = 10) then
begin
whichcol := (timage(cpic).left - left) div gap;
whichrow := (timage(cpic).Top - top) div gap;
picarray[whichcol, whichrow] := timage(cpic) ;
end;
end;
//Here you call initialise, which as I said before, will
//cause an Access Violation if picarray is not correctly 'set up'
//The previous code in this method certainly has a bug which is
//preventing one or more picarray entries from being assigned a
//valid TImage instance.
//You could write a simple for I := 0 to 5, for J := 0 to 5 loop
//here to check each of picarray entries and pinpoint which is
//incorrect to aid your debugging of the pevious loop.
initialise;
end;
The critical section is the initialization of picarray. You can't be sure that every array element is assigned with a TImage component. If at least one Image has a wrong left or top you have a double assignment to one element and another is left nil. This will result in an Access Violation when you use it for the first time e.g. in picarray[whichcol, whichrow].Picture.LoadFromFile.
I would recommend to redesign the picarray initalization with for loops for every dimension. To get the correct TImage I would name them like 'Image_2_3' and get the instances in the loop by name.
you can check if the file exists and try to catch the exception to display a meaningful message
try
if FileExists('C:\Users\Hayden\Pictures\'+ inttostr(I+1) +'.jpg') then
picarray[whichcol, whichrow].Picture.LoadFromFile('C:\Users\Hayden\Pictures\'+ inttostr(I+1) +'.jpg');
else
ShowMessage("File not found");
except
on E : Exception do
ShowMessage(E.ClassName+' error raised, with message : '+E.Message);
end;

Resources