File Not Found in Delphi (FMX) - delphi

I am trying to load a bunch of lines from a text file located in same directory as the .dproj and Delphi can't find the file I'm trying to read from.
the code is as follows:
procedure TFoPrincipale.Button2Click(Sender: TObject);
var monFich : TextFile;
Sligne : string;
begin
try
AssignFile(monFich, 'docText.txt');
Reset(monFich);
except
showmessage('Le fichier est introuvable');
exit;
end;
while not Eof (monFich) do
begin
Readln(monFich, Sligne);
Memo1.Lines.Add(Sligne);
end;
CloseFile(monFich);
end;

If you are using default project options then your application executable isn't compiled into the project directory but instead into the Win32\Debug or Win32\Release subfolder of your project folder.
So you should take into account the relative path to your file. In your case, the desired file is in second parent folder of the folder in which your executable resides.
I recommend you first get the path location of your executable file using ExtractFilePath(Application.ExeName).
Then you can make use of TDirectory.GetParent() in order to move up the directory chain until you reach the desired directory.

Related

How to share an application and associated datafile

I have written a program (firemonkey) using delphi community edition. I would like to share the program, but the .exe file that my friends will be downloading has to access a text file from time to time to retrieve strings. When writing the program I used an event handler, to load the text file at startup:
tform3.formCreate (Sender:Tobject);
...
assignfile(myfile,('C:**********.txt));
...
Worked just fine during the
design stage.
As a hobbyist, I now find myself stuck. If I use INNO setup compiler to create an installation program, which I plan to do, I can't have this same hardwired reference ('C:*****) to the data file's location. What I need is to change the above code such that the .exe file can locate the supporting datafile irrespective of where that .exe file (and datafile) ends up on someone else's PC.
How can I do this? i.e. What code do I need (in place of the above) to ensure that the installation program I hand out will install an .exe file that can locate the data file it references?
Any help, much appreciated. Still learning.
Read-only access
If the data file should always be opened in read-only mode, the simplest solution is to place it next to the *.exe file. Then, at runtime, you dynamically find the path to the *.exe file and modify it to find the path to the data file. For instance,
uses
IOUtils;
procedure TForm1.FormCreate(Sender: TObject);
var
FileName: string;
begin
FileName := TPath.Combine(ExtractFilePath(ParamStr(0)), 'data.txt');
ShowMessage(TFile.ReadAllText(FileName, TEncoding.UTF8));
end;
ParamStr(0) contains the path to the *.exe file, such as
'C:\Users\Andreas Rejbrand\Documents\Embarcadero\Studio\Projects\test\Win32\Debug\Project1.exe'
Then ExtractFilePath(ParamStr(0)) is
'C:\Users\Andreas Rejbrand\Documents\Embarcadero\Studio\Projects\test\Win32\Debug\'
and, finally, TPath.Combine(ExtractFilePath(ParamStr(0)), 'data.txt') is
'C:\Users\Andreas Rejbrand\Documents\Embarcadero\Studio\Projects\test\Win32\Debug\data.txt'
Make sure the installer puts the data file next to the *.exe file.
Read and write access
If we are talking about a settings file or some other file that each user needs to change (via the software), you cannot place it next to the *.exe file, because the *.exe file typically resides in the Program Files folder, which is read only. Also, there is only one Program Files folder, but possibly many users on the PC, and each user should have his or her own copy.
The solution is to save the file in the user's own folders, specifically, the AppData folder:
FileName := TPath.GetHomePath + '\Mariner\My Word Processor App\Settings\settings.ini';
(using a different approach to path building).
On my system, this becomes
'C:\Users\Andreas Rejbrand\AppData\Roaming\Mariner\My Word Processor App\Settings\settings.ini'
Your installer (Inno Setup) has built-in support for placing files in this location.
If it is only accessed read only, you could also consider adding it as a resource to the executable. Which would then allow you to simply distribute this executable without the need for an installer.
Delphi Dabbler has an example, but I found it a bit confusing. I'll link to it (PDF) anyway.
You can let the user select the place where the file has to be saved. Proposing AppData folder if the file is for each individual user or CommonAppData if the file has to be shared between different users.
When the use selected the data file destination, you can save it to an INI file. The INI file can be stored, without asking the user, either to the registry or to an INI file saved in the AppData folder or ProgramData folder.
Here is a snipped of source code to get hand on those special folders:
const
SectionWindow = 'Window';
SectionData = 'Data';
CompanyFolder = 'YourCompanyName';
constructor TForm1.Create(AOwner: TComponent);
var
CommonPath : array [0..MAX_PATH] of Char;
LocalPath : array [0..MAX_PATH] of Char;
LangFileName : String;
begin
SHGetFolderPath(0, CSIDL_COMMON_APPDATA, 0, SHGFP_TYPE_CURRENT, #CommonPath[0]);
SHGetFolderPath(0, CSIDL_LOCAL_APPDATA, 0, SHGFP_TYPE_CURRENT, #LocalPath[0]);
FIniSection := SectionWindow;
FIniSectionData := SectionData;
FAppName := ChangeFileExt(ExtractFileName(Application.ExeName), '');
FCommonAppData := IncludeTrailingPathDelimiter(CommonPath) +
CompanyFolder + '\' + FAppName + '\';
FLocalAppData := IncludeTrailingPathDelimiter(LocalPath) +
CompanyFolder + '\' + FAppName + '\';
FIniFileName := FLocalAppData + FAppName + '.ini';
ForceDirectories(FCommonAppData);
ForceDirectories(FLocalAppData);
inherited Create(AOwner);
end;

Error when changing TImage picture on click event

I have the following basic code :
procedure TForm4.shrek1Click(Sender: TObject);
begin
shrek1.Picture.LoadFromFile('donkey.jpeg');
end;
Where shrek1 is a TImage, and donkey.jpeg is the image I want shrek1 to load when clicked.
donkey.jpeg is located in the same directory of literally every other related project file, yet when I attempt to run the code I get an error:
Exception class EFOpenError with message 'Cannot open file "\(removed directory for privacy)\donkey.jpeg". The system cannot find the file specified
What am I doing wrong?
Always use absolute paths. Relative paths are relative to the calling processe's Current Working Directory, which can (and usually does) change value during the process's lifetime, and is not always what you expect.
If the JPG file is in the same folder as the your EXE, you can do this instead:
var
AppPath: string;
procedure TForm4.shrek1Click(Sender: TObject);
var
FileName: string;
begin
FileName := AppPath+'donkey.jpeg'; // <-- make sure this path is accurate!
shrek1.Picture.LoadFromFile(FileName);
end;
initialization
AppPath := IncludeTrailingPathDelimiter(ExtractFilePath(Application.ExeName));

Delphi : How to set sound/image locations?

How do I effectively set sound/image locations for example :
procedure TForm1.Button1Click(Sender: TObject); begin
begin
PlaySound('C:\Users\username\Desktop\project\sfx\Sounds\ding.wav', 0, SND_ASYNC);
end;
To something like :
procedure TForm1.Button1Click(Sender: TObject); begin
begin
PlaySound('ding.wav', 0, SND_ASYNC);
end;
Everytime I move The folder that contains the the project the sounds seem to not work anymore because the directory changes.
You should set / state the locations relative to the project file. For example:
var lApplicationExecutablePath: String;
lApplicationExecutablePath := ExtractFilePath(ParamStr(0));
...
var lSoundFile: String;
lSoundFile := TPath.Combine(FolderName, 'sfx\ding.wav');
This way you will have all needed files side by side no matter where the application is installed.
If you supply a relative path it is interpreted as relative to the process working directory. As a general rule, in a GUI application you should always supply absolute paths since the process working directory may be ill-defined.
You don't need to hard code the file name. If you know the name of the file, and the folder that it is in, then you can simply combine the name of file folder with the name of the file, and obtain the full path to the file. Pass that to PlaySound.
uses
IOUtils;
....
PlaySound(PChar(TPath.Combine(FolderName, FileName)), 0, SND_ASYNC);

Delphi Tinifile.ReadString and PromptDataSource

The first, click button1,can get the [section].[Key].Value
but if change the path via the second page's [...] of PromptDataSource()
Then the next time, click button1, can not get the [section].[Key].Value
Why???
Awe.dat
[Options]
DBConnection=Provider=Microsoft.Jet.OLEDB.4.0;User ID=Admin;Data Source=D:\2000.mdb;
the Button1Click's code
procedure TForm1.Button1Click(Sender: TObject);
var
Filename:string;
DatFile:TiniFile;
str:WideString;
ConnectStr:WideString;
begin
DatFile:=Tinifile.Create('.\Awe.dat');
str:=DatFile.ReadString('Options','DBConnection','');
ShowMessage(str+'-----------------');
ConnectStr:=PromptDataSource(handle,str);
ShowMessage(str+'-----------------'+ConnectStr);
DatFile.Free;
end;
It seems that the problem is related to your use of a relative path. Your path is relative to the process working directory.
If the working directory is in your complete control then your code would be fine. But often the working directory is not in your complete control. The process may start with an unexpected working directory. File dialogs may change the working directory.
You are better being explicit and providing a full path to the file. It looks like your file is in the same directory as the executable. So you could use:
ExtractFilePath(Application.ExeName) + FileName
or similar.

Get My Documents folder path in delphi

i use the following code to get special directories
uses
ActiveX, ShlObj;
{...}
procedure TForm1.Button1Click(Sender: TObject);
// Replace CSIDL_HISTORY with the constants below
var
Allocator: IMalloc;
SpecialDir: PItemIdList;
FBuf: array[0..MAX_PATH] of Char;
PerDir: string;
begin
if SHGetMalloc(Allocator) = NOERROR then
begin
SHGetSpecialFolderLocation(Form1.Handle, CSIDL_PERSONAL, SpecialDir);
SHGetPathFromIDList(SpecialDir, #FBuf[0]);
Allocator.Free(SpecialDir);
ShowMessage(string(FBuf));
end;
end;
And now i want to get the my documents path
so i use
mydocfolderpath := string(FBuf) + '\Documents' and i think it works well
but my doubt is this the mydocuments path on all windows PCs (personalfolder/documents) can the user change this stucture and make my documents folder anywhare else (eg: c:\documents)
if the user an change the path give me a proper way and i like to know what is the name of mydocuments folder (My Documents or Documents)
If you are using a recent version of Delphi (XE5 or greater) then you can use the new platform agnostic classes. Basically include System.IOUtils in your uses then use TPath.GetDocumentsPath to get the documents folder.
Check out the Doc Wiki
CSIDL_PERSONAL is the My Documents folder:
CSIDL_PERSONAL FOLDERID_Documents
Version 6.0. The virtual folder that
represents the My Documents desktop
item. This is equivalent to
CSIDL_MYDOCUMENTS.
Previous to Version 6.0. The file
system directory used to physically
store a user's common repository of
documents. A typical path is
C:\Documents and Settings\username\My
Documents. This should be
distinguished from the virtual My
Documents folder in the namespace. To
access that virtual folder, use
SHGetFolderLocation, which returns the
ITEMIDLIST for the virtual location,
or refer to the technique described in
Managing the File System.Managing the File System.
See: http://msdn.microsoft.com/en-us/library/bb762494(VS.85).aspx for a list and description of all CSIDL constants available

Resources