How do I read value from INI file without using sections?
So instead of normal file:
[section]
name=value
it would result in this:
name=value
I wouldn't call it an INI file, then. Anyhow, for this the TStringList class fits perfectly.
Consider the file animals.txt:
dog=Sally
rat=Fiona
cat=Linus
And consider this code:
procedure TForm1.Button1Click(Sender: TObject);
begin
with TStringList.Create do
try
LoadFromFile('C:\Users\Andreas Rejbrand\Desktop\animals.txt');
ShowMessage(Values['dog']);
finally
Free;
end;
end;
There's a nice tutorial over here. For example, if iniFile is an instance of TIniFile, you can call the iniFile.ReadString method with an empty section specifier.
This is a late answer but here is some code I wrote for my project:
function GetPropertyValue(aFile, Key: string): string;
var
properties: TStringList;
begin
properties := TStringList.Create;
try
properties.LoadFromFile(aFile);
Result := properties.Values[key];
finally
properties.free;
end;
end;
procedure SetPropertyValue(aFile, Key, Value: string);
var
I: Integer;
properties: TStringList;
found: Boolean;
begin
found := False;
properties := TStringList.Create;
try
properties.LoadFromFile(aFile);
for I := 0 to properties.Count -1 do
begin
if properties.Names[I] = Key then
begin
properties[I] := Key + '=' + Value;
found := True;
Break
end;
end;
if not found then
begin
properties.Add(Key + '=' + Value);
end;
finally
properties.SaveToFile(aFile);
properties.free;
end;
end;
I think the question really needs more information. Often people will ask questions relating to what they think they need to do instead of asking questions related to what they are actually trying to accomplish.
Why do you need to do this instead of using the normal methods of reading the ini entries?
If these are existing ini files, then you should use the Tinifile.ReadSections to read the section names into a stringlist and then iterate through that list using Tinifile.ReadSectionValues to read all the section name/values pairs.
Are you reading existing INI files, or reading and writing your own files?
If these are your own files, then Andreas has a good answer above.
Related
Im stuck and asking for your help te get a solution for reading my ini file back and placing that in my memo1 form with a button.
This is in my text file:
[Filename]
Work Time=03-10-2018 15:11
Here is some of the code im working with.
var
aWorkTime: string;
procedure TForm1.button2(Sender: TObject):
begin
Memo1.Lines.Clear;
IniFile := TIniFile.Create(GetCurrentDir+'\Filename.ini');
try
aWorkTime := IniFile.ReadString('Filename', 'Work Time', <'none'>);
finally
IniFile.Free;
end;
end
I hope that this is enough information if not please tell me what you are missing from me
Your use of GetCurrentDir is problematic. The current dir may change and doesn't have to be the same directory as where your .exe file resides. Rather use ExtractFilePath(Application.ExeName)
Also, instead of reading the items one by one, to read the entire .ini file into your memo, do something like:
procedure TForm1.Button1Click(Sender: TObject);
begin
Memo1.Lines.LoadFromFile(ExtractFilePath(Application.ExeName) + 'FileName.ini');
end;
If you only need the work time, then your code is almost there:
var
aWorkTime: string;
IniFile: TIniFile;
begin
Memo1.Lines.Clear;
IniFile := TIniFile.Create(ExtractFilePath(Application.ExeName) + 'FileName.ini');
try
aWorkTime := IniFile.ReadString('Filename', 'Work Time', '<none>');
Memo1.Lines.Add('Work Time=' + aWorkTime);
finally
IniFile.Free;
end;
end;
I have a multi-line string and I want to remove some lines from it. The TMemo component contains the necessary code to do this.
MyMemo:=TMemo.Create(nil);
try
MyMemo.Text:=MyString;
MyMemo.Lines.Delete(x); // lines I want to delete
MyMemo.Lines.Delete(y);
MyString:=MyMemo.Text;
finally
MyMemo.Free;
end;
But it seems wrong to use a visual component to do basic conversions. Is there a different, but equally simple, way to do this?
Thanks
You have the answer right in the question title - use a TStringList:
procedure MyProcedure(var MyString: string);
var
sl: TStringList;
begin
sl := TStringList.Create;
try
sl.Text := MyString;
sl.Delete(x); // lines I want to delete
sl.Delete(y);
MyString := sl.Text;
finally
sl.Free;
end;
end;
I've just realised that TStringlist itself has a Text property. So that answers my own question.
I am trying to copy a file to the clipboard. All examples in Internet are the same. I am using one from, http://embarcadero.newsgroups.archived.at/public.delphi.nativeapi/200909/0909212186.html but it does not work.
I use Rad Studio XE and I pass the complete path. In mode debug, I get some warnings like:
Debug Output:
Invalid address specified to RtlSizeHeap( 006E0000, 007196D8 )
Invalid address specified to RtlSizeHeap( 006E0000, 007196D8 )
I am not sure is my environment is related: Windows 8.1 64 bits, Rad Studio XE.
When I try to paste the clipboard, nothing happens. Also, seeing the clipboard with a monitor tool, this tool shows me error.
The code is:
procedure TfrmDoc2.CopyFilesToClipboard(FileList: string);
var
DropFiles: PDropFiles;
hGlobal: THandle;
iLen: Integer;
begin
iLen := Length(FileList) + 2;
FileList := FileList + #0#0;
hGlobal := GlobalAlloc(GMEM_SHARE or GMEM_MOVEABLE or GMEM_ZEROINIT,
SizeOf(TDropFiles) + iLen);
if (hGlobal = 0) then raise Exception.Create('Could not allocate memory.');
begin
DropFiles := GlobalLock(hGlobal);
DropFiles^.pFiles := SizeOf(TDropFiles);
Move(FileList[1], (PChar(DropFiles) + SizeOf(TDropFiles))^, iLen);
GlobalUnlock(hGlobal);
Clipboard.SetAsHandle(CF_HDROP, hGlobal);
end;
end;
UPDATE:
I am sorry, I feel stupid. I used the code that did not work, the original question that somebody asked, in my project, while I used the Remy's code, the correct solution, here in Stackoverflow. I thought that I used the Remy's code in my project. So, now, using the Remy's code, everything works great. Sorry for the mistake.
The forum post you link to contains the code in your question and asks why it doesn't work. Not surprisingly the code doesn't work for you any more than it did for the asker.
The answer that Remy gives is that there is a mismatch between ANSI and Unicode. The code is for ANSI but the compiler is Unicode.
So click on Remy's reply and do what it says: http://embarcadero.newsgroups.archived.at/public.delphi.nativeapi/200909/0909212187.html
Essentially you need to adapt the code to account for characters being 2 bytes wide in Unicode Delphi, but I see no real purpose repeating Remy's code here.
However, I'd say that you can do better than this code. The problem with this code is that it mixes every aspect all into one big function that does it all. What's more, the function is a method of a form in your GUI which is really the wrong place for it. There are aspects of the code that you might be able to re-use, but not factored like that.
I'd start with a function that puts an known block of memory into the clipboard.
procedure ClipboardError;
begin
raise Exception.Create('Could not complete clipboard operation.');
// substitute something more specific that Exception in your code
end;
procedure CheckClipboardHandle(Handle: HGLOBAL);
begin
if Handle=0 then begin
ClipboardError;
end;
end;
procedure CheckClipboardPtr(Ptr: Pointer);
begin
if not Assigned(Ptr) then begin
ClipboardError;
end;
end;
procedure PutInClipboard(ClipboardFormat: UINT; Buffer: Pointer; Count: Integer);
var
Handle: HGLOBAL;
Ptr: Pointer;
begin
Clipboard.Open;
Try
Handle := GlobalAlloc(GMEM_MOVEABLE, Count);
Try
CheckClipboardHandle(Handle);
Ptr := GlobalLock(Handle);
CheckClipboardPtr(Ptr);
Move(Buffer^, Ptr^, Count);
GlobalUnlock(Handle);
Clipboard.SetAsHandle(ClipboardFormat, Handle);
Except
GlobalFree(Handle);
raise;
End;
Finally
Clipboard.Close;
End;
end;
We're also going to need to be able to make double-null terminated lists of strings. Like this:
function DoubleNullTerminatedString(const Values: array of string): string;
var
Value: string;
begin
Result := '';
for Value in Values do
Result := Result + Value + #0;
Result := Result + #0;
end;
Perhaps you might add an overload that accepted a TStrings instance.
Now that we have all this we can concentrate on making the structure needed for the CF_HDROP format.
procedure CopyFileNamesToClipboard(const FileNames: array of string);
var
Size: Integer;
FileList: string;
DropFiles: PDropFiles;
begin
FileList := DoubleNullTerminatedString(FileNames);
Size := SizeOf(TDropFiles) + ByteLength(FileList);
DropFiles := AllocMem(Size);
try
DropFiles.pFiles := SizeOf(TDropFiles);
DropFiles.fWide := True;
Move(Pointer(FileList)^, (PByte(DropFiles) + SizeOf(TDropFiles))^,
ByteLength(FileList));
PutInClipboard(CF_HDROP, DropFiles, Size);
finally
FreeMem(DropFiles);
end;
end;
Since you use Delphi XE, strings are Unicode, but you are not taking the size of character into count when you allocate and move memory.
Change the line allocating memory to
hGlobal := GlobalAlloc(GMEM_SHARE or GMEM_MOVEABLE or GMEM_ZEROINIT,
SizeOf(TDropFiles) + iLen * SizeOf(Char));
and the line copying memory, to
Move(FileList[1], (PByte(DropFiles) + SizeOf(TDropFiles))^, iLen * SizeOf(Char));
Note the inclusion of *SizeOf(Char) in both lines and change of PChar to PByte on second line.
Then, also set the fWide member of DropFiles to True
DropFiles^.fWide := True;
All of these changes are already in the code from Remy, referred to by David.
I have a .txt file and I want to replace a line with a new one. These are the steps:
Read in the .txt file
Save Source to a TStringList
Modify some data in a particular line
Save the new data back to the original file.
How do I do this?
Like this:
var
Strings: TStringList;
....
Strings := TStringList.Create;
try
Strings.LoadFromFile(FileName);
Strings[LineIndex] := NewValue;
Strings.SaveToFile(FileName);
finally
Strings.Free;
end;
With newer Delphi's you can get the contents of a file as an array of strings in a single call TFile.ReadAllLines().
program TestModifyLine; {$APPTYPE CONSOLE}
uses Types,IoUtils;
procedure ModifyLine(fn:string;Index:integer;NewText:String);
var lines:TStringDynArray;
begin
lines := TFile.ReadAllLines(fn);
lines[Index] := NewText;
TFile.WriteAllLines(fn,lines);
end;
begin
ModifyLine('test.txt',12,'hello');
end.
If you don't want to waste memory loading the entire source file at one time, you can use TStreamReader and TStreamWriter to read/write the files one line at a time, modifying the desired line after reading it and before writing it.
Var
Reader: TStreamReader;
Writer: TStreamWriter:
Line: String;
LineNum: Integer;
Begin
Reader := TStreamReader.Create(...);
Writer := TStreamWriter.Create(...);
While not Reader.EndOfStream do
Begin
Line := Reader.ReadLine;
Inc(LineNum);
If LineNum = ... Then
Begin
...
End;
Writer.WriteLine(Line);
End;
Writer.Free;
Reader.Free;
End;
I don't know exactly how to explain my question. Here is my try to explain: the function FindNext(SearchRec) will get me the next file from a directory. In my application I am looking sometimes go to backward a few files from my current SearchRec index. So how can I do that?
So I am looking for the oppose of FindNext(SearchRec) a function like FindBackward(SearchRec)
There's no such function. You'll need to keep track of previous hits in a list, say, and do the back tracking using that list.
I suggest to place them in an array of TSearchRec
SearchRecArr:array of TSearchRec;
Then when you reach a certain file, get the SearchRec that you need from the Array.
for example, this is an example where I placed in some folder 3 text files of names (z, z1, & z2).
then If I reached 'z2.txt' I will read SearchRec 2 steps backwards:
procedure TForm2.Button1Click(Sender: TObject);
var
SearchRec:TSearchRec;
SearchRecArr:array of TSearchRec;
i:integer;
begin
i:=-1;
if FindFirst('C:\Users\zeina.shehab\Desktop\New folder\z*.txt',faAnyFile,SearchRec)=0 then
begin
repeat
SetLength(SearchRecArr,length(SearchRecArr)+1);
SearchRecArr[high(SearchRecArr)]:=SearchRec;
inc(i);
if SearchRec.Name='z2.txt' then
caption:=SearchRecArr[i-2].Name;
until (FindNext(SearchRec)<>0);
end;
end;
I wrote my own function. Here is the code which works very well for me and it is very efficient for thousands of files(because it doesn't slow my playback algorithm).
Procedure GetBackward(var SRInitial:TSearchRec; iForwardSpeed:integer);
var SR:TSearchRec;
iIndex:integer;
vLastFiles:Array of String;
begin
SetLength(vLastFiles,Trunc(iForwardSpeed));
FindFirst(sPath+'*.txt',faAnyFile,SR);
while (FindNext(SR) = 0)and(SR.Name <> SRInitial.Name) do
begin
for iIndex := 0 to high(vLastFiles)-1 do
vLastFiles[iIndex]:=vLast[iIndex+1];
vLastFiles[high(vLastFiles)]:=SR.Name;
end;
//Fewer than ForwardSpeed
if vLastFiles[0] = '' then
begin
Exit;
end;
FindClose(SR);
FindClose(SRInitial);
FindFirst(sPath+'*.'+cbType.Text,faAnyFile,SRInitial);
while (FindNext(SRInitial) = 0)and(SRInitial.Name <> vLastFiles[0]) do
;
end;
The function was modified.