I need to extract a list of unique items from 12 years' worth of consistent computer-generated one-per day text files. The filenames vary only by the included date, so it is easy to generate the required name in code. They consist of a list of all the aircraft movements at my local airport during the given day, in time order. Naturally, the same aircraft come and go many times, and the objective is to loop through the files, pick out the first instance of when each individual aircraft appears (the first visit or FV) copy it to a list and then ignore it from then on. The result should be a list of all the first visits in date order. Should be simple, but... My program is small so I am including the entire implementation code.
procedure TForm1.FormCreate(Sender: TObject);
begin
FileDate := StrToDate('01/01/2007');
FName := 'E:LGW Reports/SBSLGW2007-01-01.txt'; //1st file to be read
FDStr := copy(FName, 21, 10);
TempList := TStringList.Create; //temp holder for file contents
FVCheckList := TStringList.Create; //holds unique identifier (UID)
FVCheckList.Sorted := TRUE;
FVCheckList.Duplicates := dupIgnore;
FVList:= TStringList.Create; //the main output
end;
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
begin
Memo1.Lines.Append('Started');
Repeat
TempList.Clear;
TempList.LoadFromFile(FName);
for i := 1 to TempList.Count-1 do
begin
Line := TempList.Strings[i];
//create a //create a Unique identifier (UID) from elements in Line
Serial := Trim(Copy(Line, 22, 9));
MsnPos1 := Pos('[', Line) + 1;
MsnPos2 := Pos(']', Line);
Msn := copy(Line, MsnPos1, (MsnPos2 - MsnPos1));
UID := Serial + '/' + Msn;
//
if (FVCheckList.IndexOf(UID) < 0) then
begin
FVCheckList.Append(UID);
//Add date of file to Line, otherwise it gives no clue when FV was
FVList.Append(FormatDateTime('YYYY-MM-DD', FileDate) + ' ' + Line);
FileDate := IncDay(FileDate, 1);
FName := 'E:LGW Reports/SBSLGW' + FormatDateTime('YYYY-MM-DD', FileDate) + '.txt';
end;
end;
Until FileExists(FName) = FALSE;
FVCheckList.SaveToFile('E:LGW Reports/First Visit Checklist.txt');
FVList.SaveToFile('E:LGW Reports/First Visits.txt');
Memo1.Lines.Append('Finished');
Memo1.Lines.SaveToFile('E:LGW Reports/Files parsed.txt');
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
TempList.Free;
FVCheckList.Free;
FVList.Free;
end;
There are no compiler errors, it runs to completion in seconds and produces the two text files specified, correctly formatted. The big problem is that the lines actually listed in FVList are not always the very first visit of the aircraft, they can be the first, the most recent or somewhere in between. I cannot see any obvious clue as to why the wrong instance is appearing: if my code is right, then something is wrong with the functioning of TStringList FVCheckList. The fault is far more likely to be something I have overlooked, or my understanding of how .dupIgnore works, or maybe my looping isn't working as it should.
I should be very grateful for any practical help. Many thanks in advance.
Repeat
...
Until FileExists(FName) = FALSE;
Should be
While FileExists(FName) = TRUE do
Begin
End;
If the first 2007-01-01 file does not exist, your code will crash on the first LoadFromFile() since you don't check for the file's existence before loading it, unlike with the subsequent files.
Otherwise, I would suggest sticking with repeat but assign FName at the top of each loop iteration instead of initializing it outside the loop and then reassigning at the bottom of each iteration. No need to duplicate efforts.
If you check IndexOf() manually, you don't need to use Sorted or dupIgnore at all. This is what you should be doing in this situation. When dupIgnore ignores a new string, Append() doesn't tell you that the string was ignored. To do that, you would have to check whether the Count was actually increased or not.
Inside the outer loop, the reassignment of FileDate and FName should be outside of the inner for loop,not inside the for loop at all.
Try this instead:
procedure TForm1.FormCreate(Sender: TObject);
begin
FileDate := EncodeDate(2007,1,1);
FDStr := FormatDateTime('YYYY-MM-DD', FileDate);
TempList := TStringList.Create; //temp holder for file contents
FVCheckList := TStringList.Create; //holds unique identifier (UID)
FVList := TStringList.Create; //the main output
end;
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
begin
Memo1.Lines.Append('Started');
Repeat
FName := 'E:LGW Reports/SBSLGW' + FormatDateTime('YYYY-MM-DD', FileDate) + '.txt';
if not FileExists(FName) then Break;
Memo1.Lines.Append(FName)
TempList.LoadFromFile(FName);
for i := 1 to TempList.Count-1 do
begin
Line := TempList.Strings[i];
//create a Unique identifier (UID) from elements in Line
Serial := Trim(Copy(Line, 22, 9));
MsnPos1 := Pos('[', Line) + 1;
MsnPos2 := PosEx(']', Line, MsnPos1);
Msn := copy(Line, MsnPos1, (MsnPos2 - MsnPos1));
UID := Serial + '/' + Msn;
if FVCheckList.IndexOf(UID) = -1 then
begin
FVCheckList.Append(UID);
//Add date of file to Line, otherwise it gives no clue when FV was
FVList.Append(FormatDateTime('YYYY-MM-DD', FileDate) + ' ' + Line);
end;
end;
FileDate := IncDay(FileDate, 1);
end;
FVCheckList.SaveToFile('E:LGW Reports/First Visit Checklist.txt');
FVList.SaveToFile('E:LGW Reports/First Visits.txt');
Memo1.Lines.Append('Finished');
Memo1.Lines.SaveToFile('E:LGW Reports/Files parsed.txt');
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
TempList.Free;
FVCheckList.Free;
FVList.Free;
end;
Related
how to print all data/records contain in Recordset to the text file in Delphi 10 ?
I could not able to find any Method or property for it.Please guide, i am newbie in delphi.
I have done following:
Var:
CurrField : Field;
RecSet:_RecordSet ;
Begin:
RecSet:= command.Execute(records,Params,-1);
CurrField := RecSet.Fields[0];
end;
but i want to print complete records/data contain in RecSet(_RecordSet type) in text file.
You can write it yourself. If the recordset is relatively small, the easiest way is to use a TStringList.
var
i: Integer;
s: string;
SL: TStringList;
begin
SL := TStringList.Create;
try
while not RecSet.Eof do
begin
// Clear string for the next row
s := '';
// Loop through the fields in this row, creating a comma-separated list
for i := 0 to RecSet.FieldCount - 1 do
s := s + RecSet.Fields[i].Value + ',';
// Remove unnecessary final comma at end
SetLength(s, Length(s) - 1);
// Add to the stringlist
SL.Add(s);
end;
// Save the stringlist content to disk
SL.SaveToFile('YourFileName.txt');
finally
SL.Free;
end;
end;
I have Inherited form and a Ehlib dbgrid on it for selecting-listing records... The form is ready made for a lot of buttons and im using this form with different queries.
Like this...
If Dm.QrTmp.Active then Dm.QrTmp.Active:=False;
Dm.QrTmp.SQL.Clear;
Dm.QrTmp.SQL.Add(' SELECT ');
Dm.QrTmp.SQL.Add(' ch.cari_RECno AS KayitNo ');
Dm.QrTmp.SQL.Add(' FROM CARI_HESAPLAR ch ');
if FrmTmp=nil then FrmTmp:=TFrmTmp.Create(Self);
FrmTmp.StatusBar.Hide;
Dm.QrTmp.Open;
FrmTmp.DbGrid.DataSource:=Dm.DsQrTmp;
This query is cutted down but i have of course use a lot of fields. And Queries changes alot of time in the application.
The problem is column width. Manager wants to set column widths and restore them again. Actually my grid component supports save - restore column properties but as you can see my usage i m not using static columns. also i dont want to use xgrid.columns[0].width percent by percent.
Im using a ini in may app.
I want to add new section on it and named "Gridwidth"...
[Gridname]
Colwidths=x,y,z (where they are width values)
I'm now coding this line by line.
My write procedure is like this.
With dbgridx do
begin
For i:=0 to columns.count-1
begin
widthstr:=widthstr+Column[i].width+',';
end;
end;
Widthstr will be "15,23,45,67" etc...
But i want to know if this is good solution and if somebody know a better way and has some good code.
This should do it:
uses
IniFiles;
const
SETTINGS_FILE = 'Edijus\Settings.ini';
procedure TForm1.LoadDBGridColumnsWidth(const ADBGrid: TDBGrid);
var
_MemIniU: TMemIniFile;
_SettingsPath: string;
i, j: integer;
_ParentClass: TWinControl;
begin
_SettingsPath := GetHomePath + PathDelim + SETTINGS_FILE;
if (not Assigned(ADBGrid)) or (not Assigned(ADBGrid.DataSource)) or
(not Assigned(ADBGrid.DataSource.DataSet)) then
Exit;
_MemIniU := TMemIniFile.Create(_SettingsPath, TEncoding.UTF8);
try
_ParentClass := ADBGrid.Parent;
while not(_ParentClass is TForm) do
_ParentClass := _ParentClass.Parent;
for i := 0 to Pred(ADBGrid.DataSource.DataSet.Fields.Count) do
for j := 0 to Pred(ADBGrid.Columns.Count) do
begin
if (ADBGrid.DataSource.DataSet.Fields[i].FieldName = ADBGrid.Columns[j]
.FieldName) then
ADBGrid.Columns[j].Width :=
_MemIniU.ReadInteger(_ParentClass.Name + '_' + ADBGrid.Name,
ADBGrid.Columns[j].FieldName, 64);
end;
finally
FreeAndNil(_MemIniU);
end;
end;
procedure TForm1.SaveDBGridColumnsWidth(const ADBGrid: TDBGrid);
var
_MemIniU: TMemIniFile;
_SettingsPath: string;
i: integer;
_ParentClass: TWinControl;
begin
_SettingsPath := GetHomePath + PathDelim + SETTINGS_FILE;
if (not Assigned(ADBGrid)) or
(not ForceDirectories(ExtractFilePath(_SettingsPath))) then
Exit;
_MemIniU := TMemIniFile.Create(_SettingsPath, TEncoding.UTF8);
try
_ParentClass := ADBGrid.Parent;
while not(_ParentClass is TForm) do
_ParentClass := _ParentClass.Parent;
for i := 0 to Pred(ADBGrid.Columns.Count) do
if (ADBGrid.Columns[i].FieldName <> '') then
_MemIniU.WriteInteger(_ParentClass.Name + '_' + ADBGrid.Name,
ADBGrid.Columns[i].FieldName, ADBGrid.Columns[i].Width);
_MemIniU.UpdateFile;
finally
FreeAndNil(_MemIniU);
end;
end;
I Have a TStringList I create on FormCreate
ScriptList := TStringList.Create;
In another function in my program after I have loaded strings into the list I have the following code
ScriptList.Sorted := True;
ScriptList.Sort;
for i := 0 to ScriptList.Count - 1 do
ShowMessage(ScriptList[i]);
But the list is not sorted
Why is that?
Edited:
Filling the list is done by the following code
function TfrmMain.ScriptsLocate(const aComputer: boolean = False): integer;
var
ScriptPath: string;
TempList: TStringList;
begin
TempList := TStringList.Create;
try
if aComputer = True then
begin
ScriptPath := Folders.DirScripts;
Files.Search(TempList, ScriptPath, '*.logon', False);
ScriptList.AddStrings(TempList);
end
else
begin
if ServerCheck then
begin
ScriptPath := ServerPath + 'scripts_' + Network.ComputerName + '\';
Folders.Validate(ScriptPath);
TempList.Clear;
Files.Search(TempList, ScriptPath, '*.logon', False);
ScriptList.AddStrings(TempList);
Application.ProcessMessages;
ScriptPath := ServerPath + 'scripts_' + 'SHARED\';
Folders.Validate(ScriptPath);
TempList.Clear;
Files.Search(TempList, ScriptPath, '*.logon', False);
ScriptList.AddStrings(TempList);
end;
end;
finally
TempList.Free;
end;
ScriptList.Sort;
Result := ScriptList.Count;
end;
The filesearch function:
function TFiles.Search(aList: TstringList; aPathname: string; const aFile: string = '*.*'; const aSubdirs: boolean = True): integer;
var
Rec: TSearchRec;
begin
Folders.Validate(aPathName, False);
if FindFirst(aPathname + aFile, faAnyFile - faDirectory, Rec) = 0 then
try
repeat
aList.Add(aPathname + Rec.Name);
until FindNext(Rec) <> 0;
finally
FindClose(Rec);
end;
Result := aList.Count;
if not aSubdirs then Exit;
if FindFirst(aPathname + '*.*', faDirectory, Rec) = 0 then
try
repeat
if ((Rec.Attr and faDirectory) <> 0) and (Rec.Name<>'.') and (Rec.Name<>'..') then
Files.Search(aList, aPathname + Rec.Name, aFile, True);
until FindNext(Rec) <> 0;
finally
FindClose(Rec);
end;
Result := aList.Count;
end;
The main problem is that the list is filled OK with the items I want, but it never gets sorted.
When you set Sorted to True you are saying that you want the list to be maintained in order. When new items are added, they will be inserted in order. When Sorted is True, the Sort method does nothing because the code is built on the assumption that the list is already order.
So, in your code calling Sort does nothing and could be removed. However, I would take the alternative approach, remove the setting of Sorted and call Sort explicitly:
ScriptList.LoadFromFile(...);
ScriptList.Sort;
for i := 0 to ScriptList.Count - 1 do
...
Now, in fact I think that your code is not quite as you have claimed. You claim that you load the file, and then set Sorted to True. That cannot be the case. Here is the SetSorted implementation:
procedure TStringList.SetSorted(Value: Boolean);
begin
if FSorted <> Value then
begin
if Value then Sort;
FSorted := Value;
end;
end;
So, if Sorted is False when you set it to True, the list will be sorted.
But even that does not explain what you report. Because if Sorted is True when you call LoadFromFile, each new line will be inserted in order. So, what you report in the question cannot be the whole story.
Unless you are making subsequent additions to the list, it is cleaner, in my view, to ignore the Sorted property. Leave Sorted as its default value of False. And call Sort when you want to enforce an ordering to the list. All the same, it might be worth digging a bit deeper to understand why your assertions in the question don't tally with the implementation of TStringList.
I have dynamically created TValueListEditor VCL component on a TForm. The code is located in nested procedure of one of the main form's methods. I have set:
ValueListEditor.KeyOptions := [keyEdit, keyAdd, keyUnique];
It looks like this:
TMainForm.Method();
Method has a nested procedure that contains code that creates the components mentioned above.
Then, I have helper function:
function GetMenuListData(XMLNode: TXMLNode; const XNMLDoc: string = '') : string;
In this helper I use this code to load an XML file and then retrieve its nodes and insert them into ValueListEditor.
XMLDoc := TXMLDocument.Create(Self);
XMLDoc.ParseOptions := [poPreserveWhiteSpace];
try
XMLDoc.LoadFromFile(XNMLDoc);
try
Control := FindControl(FindWindow('TForm',PChar('(' + ExtractFileExt(Form1.Edit1.Text) + ')')));
if Control <> nil then
begin
TValuelistEditor(Control).Keys[TValuelistEditor(Control).RowCount-1] := XMLDoc.DocumentElement.NodeName;
if XMLDoc.DocumentElement.ChildNodes.First.AttributeNodes.Count > 0 then
TValuelistEditor(Control).Values[TValuelistEditor(Control).Keys[TValuelistEditor(Control).RowCount-1]] := String(XMLDoc.DocumentElement.Attributes['id'])
else
TValuelistEditor(Control).Values[TValuelistEditor(Control).Keys[TValuelistEditor(Control).RowCount-1]] := '<Empty>';
end else begin
MessageBeep(0);
FlashWindow(Application.Handle, True);
ShowMessagePos('...');
end;
finally
XMLDoc.Active := False; Result := 'Forced ' + Form1.RAWInputBtn.Caption + ' in ' + DateTimeToStr(Now);
end;
except
on E : EXMLDocError do
begin
Result := 'Forced ' + Form1.RAWInputBtn.Caption + ' in ' + DateTimeToStr(Now);
end;
end;
The problem is that I get access violations every time code goes into the line:
TValuelistEditor(Control).Keys[TValuelistEditor(Control).RowCount-1] := XMLDoc.DocumentElement.NodeName;
I have tried various typecasts, values, parameters .. nothing does the trick.
What is my mistake?
I'm using Delphi XE.
As Ken commented your problem is, instead of finding the value list editor, you are finding your form and then typecasting it to a value list editor, hence the AV.
First, you're passing 'TForm' as 'lpClassName' to FindWindow. Assuming 'TForm' is the class name of your form, it will of course find the form - not a child window on it. Second, you cannot use FindWindow to find a child window, see its documentation, it searches top-level windows.
If you had tested the return of FindControl, the code raising the AV would never run:
if (Control <> nil) and (Control is TValueListEditor) then
You can use FindWindowEx to search in child windows, if you don't know the handle of your form find it first as you've done already:
FormHandle := FindWindow('TForm',PChar('(' + ExtractFileExt(Form1.Edit1.Text) + ')'));
if FormHandle <> 0 then
begin
Control := FindControl(FindWindowEx(FormHandle, 0, 'TValueListEditor', nil));
or better yet, test the return of FindWindowEx first to avoid passing '0' to FindControl:
ValueListEditorHandle := FindWindowEx(FormHandle, 0, 'TValueListEditor', nil);
if Win32Check(ValueListEditorHandle <> 0) then
begin
Control := FindControl(ValueListEditorHandle);
if Assigned(Control) then
begin
...
If your dynamically created form is part of the same application, you don't need all the noise of the incorrect FindControl(FindWindow()). Just create your form, giving it a name, and making Application the owner:
MyForm := TMyForm.Create(Application);
MyForm.Name := 'MyDynamicForm';
When you want to get a new reference to it:
var
TheForm: TMyForm;
i: Integer;
begin
TheForm := nil;
for i := 0 to Screen.FormCount - 1 do
if Screen.Forms[i] is TMyForm then
// Could also use Screen.Forms[i].Caption
if Screen.Forms[i].Name = 'MyDynamicForm' then
TheForm := TMyForm(Screen.Forms[i]);
if Assigned(TheForm) then
TheForm.MethodThatLoadsXML(XMLFileName); // or whatever
end;
TheForm.MethodThatLoadsXML can now access the TValueListEditor directly:
procedure TMyForm.MethodThatLoadsXML(const XMLFileName: string);
begin
// Load xml as before, using XMLFileName
with TValueListEditor.Create(Self) do
begin
Options := [Whatever];
Parent := Self;
Left := SomeNumber;
Top := SomeNumber;
// Create items for value list from XML and other stuff
end;
end;
I want to know how I can write an Inifile so that I can read from it in order.
First of all writing it: Let say I have 2 strings I want to save (Name and Lastname).
This is a button. So it will be clicked everytime:
Ini.WriteString(person1, 'Name', Name.text);
Ini.WriteString(person1, 'Lastname', Lastname.text);
How can I make this person1, alter everytime a new person is added. so next time person2, person3, etc. How is it possible? Do I have to look at what the previous one said? or can it remember? Maybe if I understand this, reading from the file will go in same maner.
kind regards and thank you
I suppose you want to store n data items in an INI file. I do that all the time -- it's easy. First, saving is trivial, but of course, the exact procedure depends on where you get the data from. If you have two arrays of strings, for example (let's call them FirstNames and LastNames), then you just do
for i := 0 to high(FirstNames) do
begin
IniFile.WriteString('Names', 'FirstName' + IntToStr(i), FirstNames[i]);
IniFile.WriteString('Names', 'LastName' + IntToStr(i), LastNames[i]);
end;
To read the (unknown number of items), do something like
for i := 0 to MaxInt do
if ValueExists('Names', 'FirstName' + IntToStr(i)) then
// Do something with ReadString('Names', 'FirstName' + IntToStr(i))
// and ReadString('Names', 'LastName' + IntToStr(i))
else
break;
Notice that 'Names' is the arbitrary name of the section in the INI file under which you store the data.
Update
If you just want to save items when clicking a button, why not do
private
{ Private declarations }
n: integer;
and
procedure TForm1.FormCreate(Sender: TObject);
begin
n := 0;
end;
and then
procedure TForm1.Button1Click(Sender: TObject);
begin
with TIniFile.Create('myfile.ini') do
try
WriteString('Names', 'FirstName' + IntToStr(n), Edit1.Text);
WriteString('Names', 'LastName' + IntToStr(n), Edit2.Text);
inc(n);
finally
Free;
end;
end;
If you instead would prefer to have a section per item, do
procedure TForm1.Button1Click(Sender: TObject);
begin
with TIniFile.Create('myfile.ini') do
try
WriteString('Name' + IntToStr(n), 'FirstName', Edit1.Text);
WriteString('Name' + IntToStr(n), 'LastName', Edit2.Text);
inc(n);
finally
Free;
end;
end;
instead.
To get sequential numbering of sections each time you save a person you would have to read all the section names, determine the highest number, increment that and then use it to write the a new section with that name and the new person's values.
Something like:
var
IniFile: TIniFile;
SL: TStringList;
i: Integer;
Highest: Integer;
begin
IniFile := TIniFile.Create('MyIni.ini');
try
SL := TStringList.Create;
try
IniFile.ReadSections(SL);
Highest := 0;
for i := 0 to SL.Count - 1 do begin
Highest := Max(Highest, StrToIntDef(Copy(SL[i], Length('Person'), MAXINT), 0));
end;
IniFile.WriteString('Person' + IntToStr(Highest), 'Name', Name.Text);
IniFile.WriteString('Person' + IntToStr(Highest), 'LastName', LastName.Text);
finally
SL.Free;
end;
finally
IniFile.Free;
end;
end;
I suspect that using an ini file to do this is the wrong way to go about it. Ini files are not designed to store things in order. The simplest way of achievng this is to use a TStringList and save and load it to disk.