Delphi : How to set sound/image locations? - delphi

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);

Related

Why does Delphi TOpenDialog fail to open in the Initial Directory?

I am using TOpenDialog (in Delphi 10.4) to show the user the PDF files I have installed for them in their Documents folder. In that folder, I have created a folder MyFolder10.2 and copied the PDF files there.
The code is simple and has worked in the past, and even now it still works on my older slower Win10 machine. But on my newer faster Win10 computer, it only works SOME of the time. When it does not work, a file dialog is opened but in some other directory (not sure where that comes from), and it does not filter the file type (.pdf) that was set in the TOpenDialog component.
Any way to track down this mystery?
docPath:= GetEnvironmentVariable('USERPROFILE') + '\Documents\MyFolder10.2\';
OpenDocsDlg.InitialDir := docPath;
OpenDocsDlg.Execute;
Prior to Vista, TOpenDialog is a wrapper for the GetOpenFileName() API, where TOpenDialog.InitialDir maps to the OPENFILENAME.lpstrInitialDir field.
On Vista and later, TOpenDialog (usually, depending on configuration) wraps the IFileDialog/IFileOpenDialog API instead, where TOpenDialog.InitialDir maps to the IFileDialog.SetFolder() method (not to IFolderDialog.SetDefaultFolder(), as one would expect).
Per the OPENFILENAME documentation:
lpstrInitialDir
Type: LPCTSTR
The initial directory. The algorithm for selecting the initial directory varies on different platforms.
Windows 7:
If lpstrInitialDir [TOpenDialog.InitialDir] has the same value as was passed the first time the application used an Open or Save As dialog box, the path most recently selected by the user is used as the initial directory.
Otherwise, if lpstrFile [TOpenDialog.FileName] contains a path, that path is the initial directory.
Otherwise, if lpstrInitialDir is not NULL [TOpenDialog.InitialDir is not empty], it specifies the initial directory.
If lpstrInitialDir is NULL [TOpenDialog.InitialDir is empty] and the current directory contains any files of the specified filter types, the initial directory is the current directory.
Otherwise, the initial directory is the personal files directory of the current user.
Otherwise, the initial directory is the Desktop folder.
Windows 2000/XP/Vista:
If lpstrFile [TOpenDialog.FileName] contains a path, that path is the initial directory.
Otherwise, lpstrInitialDir [TOpenDialog.InitialDir] specifies the initial directory.
Otherwise, if the application has used an Open or Save As dialog box in the past, the path most recently used is selected as the initial directory. However, if an application is not run for a long time, its saved selected path is discarded.
If lpstrInitialDir is NULL [TOpenDialog.InitialDir is empty] and the current directory contains any files of the specified filter types, the initial directory is the current directory.
Otherwise, the initial directory is the personal files directory of the current user.
Otherwise, the initial directory is the Desktop folder.
And per the Common Item Dialog documentation:
Controlling the Default Folder
Almost any folder in the Shell namespace can be used as the default folder for the dialog (the folder presented when the user chooses to open or save a file). Call IFileDialog::SetDefaultFolder prior to calling Show [TOpenDialog.Execute()] to do so.
The default folder is the folder in which the dialog starts the first time a user opens it from your application. After that, the dialog will open in the last folder a user opened or the last folder they used to save an item. See State Persistence for more details.
You can force the dialog to always show the same folder when it opens, regardless of previous user action, by calling IFileDialog::SetFolder [TOpenDialog.InitialDir]. However, we do not recommended doing this. If you call SetFolder before you display the dialog box, the most recent location that the user saved to or opened from is not shown. Unless there is a very specific reason for this behavior, it is not a good or expected user experience and should be avoided. In almost all instances, IFileDialog::SetDefaultFolder is the better method.
When saving a document for the first time in the Save dialog, you should follow the same guidelines in determining the initial folder as you did in the Open dialog. If the user is editing a previously existing document, open the dialog in the folder where that document is stored, and populate the edit box with that document's name. Call IFileSaveDialog::SetSaveAsItem() with the current item prior to calling Show [TOpenDialog.Execute()].
Neither TOpenDialog nor TFileOpenDialog have properties that map to the IFileDialog.SetDefaultFolder() or IFileDialog.SetSaveAsItem() methods. However, the TFileOpenDialog.Dialog property does give you access to the underlying IFileDialog, so you can manually call these methods, such as in the TFileOpenDialog.OnExecute event.
The detailed answer by #Remy Lebeau triggered me to try again to fix a problematic common scenario I've not managed to fix before, despite numerous attempts:
I have an image edit VCL application with a TFileOpenDialog and a TFileSaveDialog that is often used for editing a bunch of screenshots one by one on a connected Android device which are saved on a desktop computer.
The problem was that the DefaultFolder for the FileOpenDialog didn't stick to the opened Android folder after a save on the computer. That is now fixed to my liking, and the solution might be of interest to someone who needs to play around with the dialogs.
The test code:
unit Main;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
Winapi.ShlObj, Winapi.KnownFolders, Winapi.ActiveX;
type
TMainForm = class(TForm)
FileOpenDialog1: TFileOpenDialog;
FileSaveDialog1: TFileSaveDialog;
OpenButton: TButton;
SaveButton: TButton;
Memo1: TMemo;
procedure FileOpenDialog1Execute(Sender: TObject);
procedure FileSaveDialog1Execute(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure OpenButtonClick(Sender: TObject);
procedure SaveButtonClick(Sender: TObject);
private
FOpenShellItem: IShellItem;
FSaveShellItem: IShellItem;
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
function GetItemName(ShellItem: IShellItem; const Flags: Cardinal): string;
var
pszItemName: LPCWSTR;
begin
Result := '';
if ShellItem.GetDisplayName(Flags, pszItemName) = S_OK then
begin
Result := pszItemName;
CoTaskMemFree(pszItemName);
end;
end;
procedure TMainForm.FormCreate(Sender: TObject);
var
pch: PChar;
begin
DesktopFont := True;
if SHGetKnownFolderPath(FOLDERID_Pictures, 0, 0, pch) = S_OK then
begin
FileOpenDialog1.DefaultFolder := pch;
FileSaveDialog1.DefaultFolder := pch;
CoTaskMemFree(pch);
end;
end;
procedure TMainForm.OpenButtonClick(Sender: TObject);
var
ShellItem: IShellItem;
ParentItem: IShellItem;
begin
if FileOpenDialog1.Execute(Handle) then
begin
if fdoAllowMultiSelect in FileOpenDialog1.Options then
FileOpenDialog1.ShellItems.GetItemAt(0, ShellItem)
else
ShellItem := FileOpenDialog1.ShellItem;
if ShellItem.GetParent(ParentItem) = S_OK then
FOpenShellItem := ParentItem;
Memo1.Lines.Add('Opened');
Memo1.Lines.Add('ItemParsingName: ' +
GetItemName(ShellItem, SIGDN_DESKTOPABSOLUTEPARSING));
Memo1.Lines.Add('ItemNormalName: ' +
GetItemName(ShellItem, SIGDN_NORMALDISPLAY));
Memo1.Lines.Add('ParentItemParsingName: ' +
GetItemName(ParentItem, SIGDN_DESKTOPABSOLUTEPARSING));
Memo1.Lines.Add('ParentItemNormalame: ' +
GetItemName(ParentItem, SIGDN_NORMALDISPLAY));
Memo1.Lines.Add('----------');
end;
end;
procedure TMainForm.SaveButtonClick(Sender: TObject);
var
ParentItem: IShellItem;
begin
if FileSaveDialog1.Execute(Handle) then
begin
if FileSaveDialog1.ShellItem.GetParent(ParentItem) = S_OK then
FSaveShellItem := FileSaveDialog1.ShellItem;
Memo1.Lines.Add('Saved');
Memo1.Lines.Add('ItemParsingName: ' +
GetItemName(FileSaveDialog1.ShellItem, SIGDN_DESKTOPABSOLUTEPARSING));
Memo1.Lines.Add('ItemNormalName: ' +
GetItemName(FileSaveDialog1.ShellItem, SIGDN_NORMALDISPLAY));
Memo1.Lines.Add('ParentItemParsingName: ' +
GetItemName(ParentItem, SIGDN_DESKTOPABSOLUTEPARSING));
Memo1.Lines.Add('ParentItemNormalName: ' +
GetItemName(ParentItem, SIGDN_NORMALDISPLAY));
Memo1.Lines.Add('----------');
end;
end;
procedure TMainForm.FileOpenDialog1Execute(Sender: TObject);
begin
if Assigned(FOpenShellItem) then
FileOpenDialog1.Dialog.SetFolder(FOpenShellItem);
end;
procedure TMainForm.FileSaveDialog1Execute(Sender: TObject);
begin
//Note the IFileSaveDialog typecast
if Assigned(FSaveShellItem) then
IFileSaveDialog(FileSaveDialog1.Dialog).SetSaveAsItem(FSaveShellItem);
end;
end.

How to create a folder and a txt file in Delphi

How do I create a new folder in the internal storage (?) of Android (I want say, the main folder that has all the subfolders ... Whatsapp, DCIM, pictures, Ringtones, Alarms ..) and create a new .txt file inside in this folder.
I want to create a .txt file and I need the user to plug the USB cable into their computer, access the device, enter the folder my application creates, and copy this file to their desktop.
I tried this code to create the file:
procedure TF_start.Button2Click(Sender: TObject);
var
output_text: string;
arquivo: TextFile;
begin
output_text := 'test';
TFile.WriteAllText(TPath.Combine(TPath.GetDocumentsPath, 'test.txt'), 'content');
ReWrite(arquivo);
WriteLn(arquivo, output_text);
CloseFile(arquivo);
end;
But it does not work.
To get the internal storage(?) path, I found this code:
P := '/storage/';
if (FindFirst(P + '*', faAnyFile, Sr) = 0) then
repeat
Memo1.Lines.Add(Sr.Name);
until (FindNext(Sr) <> 0);
FindClose(Sr);
But I can't understand how it works, so I can't even use it.
I also found this link, but I didn't find any function that returns me the "general" directory path I want to create a folder.
The functions System.IOUtils.TPath.GetHomePath(), and System.IOUtils.TPath.GetDocumentsPath() do not return me the correct path.
System.SysUtils.GetHomePath() return -> /data/user/0/com.embarcadero.app/cache
System.IOUtils.TPath.GetDocumentsPath() return -> /data/com.embarcadero.app-1/lib/arm
#edit
Using the #Remy Lebeau code and this code I managed to get to this point.
The problem is that the code to update the directory with the files does nothing
Use System.IOUtils, Androidapi.Helpers, Androidapi.Jni.Media, Androidapi.JNI.JavaTypes, Androidapi.JNI.GraphicsContentViewText;
//Button
procedure TF_start.Button2Click(Sender: TObject);
var
path_file output_text: string;
begin
path_file := TPath.Combine(System.IOUtils.TPath.GetSharedDownloadsPath, 'Folder_app');
output_text := 'test';
if not TDirectory.Exists(path_file) then
TDirectory.CreateDirectory(path_file);
try
TFile.WriteAllText(TPath.Combine(path_file, Nome_Arquivo), Arquivo_saida);
except
ShowMessage('An error occurred while saving the file.');
end;
end;
Another button:
procedure TF_corrida.BTNfinalize_appClick(Sender: TObject);
var
c: Integer;
JMediaScannerCon: Androidapi.Jni.Media.JMediaScannerConnection;
JMediaScannerCon_Client: Androidapi.Jni.Media.JMediaScannerConnection_MediaScannerConnectionClient;
begin
JMediaScannerCon:=TJMediaScannerConnection.JavaClass.init(TAndroidHelper.Context, JMediaScannerCon_Client);
JMediaScannerCon.connect;
c:=0;
while not JMediaScannerCon.isConnected do begin
Sleep(100);
inc(c);
if (c>20) then break;
end;
if (JMediaScannerCon.isConnected) then begin
JMediaScannerCon.scanFile(StringToJString(path_file), nil);
JMediaScannerCon.disconnect;
end;
end;
PS This warning had appeared:
[DCC Warning] u_corrida.pas(682): W1000 Symbol 'SharedActivityContext'
is deprecated: 'Use TAndroidHelper.Context'
So I changed the code
Note: I also tried replacing "path_file" with "System.IOUtils.TPath.GetSharedDownloadsPath", but to no avail too
This question has already been answered, the other question (index files and folders) has been moved to: How to index a created file in Android sdcard Delphi
You don't actually want "internal storage", that is private to your app and not even the user can access it (without root access to the device). You want "external storage" instead, so the user (and other apps) can access it.
Per Save files on device storage in Android's documentation:
Internal storage is best when you want to be sure that neither the user nor other apps can access your files.
External storage is the best place for files that don't require access restrictions and for files that you want to share with other apps or allow the user to access with a computer.
Use one of the TPath.GetShared...() methods to get an "external storage" path, such as TPath.GetSharedDocumentsPath(). And make sure your app has the WRITE_EXTERNAL_STORAGE permission enabled.
Also, note that TFile.WriteAllText() will not create a missing folder (in fact, it will raise an EDirectoryNotFoundException). You have to create the folder yourself first, such as with TDirectory.CreateDirectory() or SysUtils.ForceDirectories(). TPath.Combine() simply concatenates the input strings together, it does not create the actual folder.
Try this:
procedure TF_start.Button2Click(Sender: TObject);
var
path, output_text: string;
begin
output_text := 'test';
path := TPath.Combine(TPath.GetSharedDocumentsPath, 'myfolder');
if not TDirectory.Exists(path) then
TDirectory.CreateDirectory(path);
TFile.WriteAllText(TPath.Combine(path, 'test.txt'), output_text);
end;

How to solve [FireDAC][Phys][SQLite] ERROR: unable to open database file, When app is installed in programFiles?

I developed a tool using Firedac with the database as SQLite.
after finishing the project and making an installer (InnoSetup) I get an error
[FireDAC][Phys][SQLite] ERROR: unable to open database file
when I launch the app (double click).
This is the connection parameters I use
constructor TDbInteract.Create(const aDatabasePath: string; const aOnNeedCredentials: TOnNeedCredentials);
var
aParams: array of string;
begin
if not TFile.Exists(aDatabasePath) then
raise Exception.Create('Database file not found');
aParams := ['DriverID=SQLite',
'Database=' + aDatabasePath,
'OpenMode=CreateUTF16',
'LockingMode=Normal',
'JournalMode=WAL',
'StringFormat=Unicode',
'Synchronous=Full',
'UpdateOptions.LockWait=True',
'BusyTimeout=30000',
'SQLiteAdvanced=temp_store=MEMORY;page_size=4096;auto_vacuum=FULL'];
InitiateResource(aParams, aOnNeedCredentials);
end;
procedure TDbInteract.InitiateResource(const aParams: array of string; const aOnNeedCredentials: TOnNeedCredentials);
var
I: Integer;
Credentials: TStringDynArray;
begin
FRowsAffected := 0;
FIsForeignKeyHonored := True;
FOwnsResultDataSets := True;
FDataSetContainer := TDataSetContainer.Create(nil);
FConnection := TFDConnection.Create(nil);
try
for I := Low(aParams) to High(aParams) do
begin
FConnection.Params.Add(aParams[I]);
end;
if Assigned(aOnNeedCredentials) then
begin
aOnNeedCredentials(Self, Credentials);
for I := Low(Credentials) to High(Credentials) do
begin
FConnection.Params.Add(Credentials[I]);
end;
end;
FConnection.Open;
except
raise;
end;
end;
**Identified problems:
I read somewhere (do not remember the page I was in) that SQLite engine requires full lock on the directory that it wants to write to. and this is the problem. How ever I run the tool as invoker and my account is an admin so that is not a problem. Also I have the same tool written in c# and this problem never occurs.
Solutions I found:
Run the tool as administrator
Do not Install the tool in ProgramFiles directory
I really don't like these solutions. and would like to run my tool from program Files directory as it is part of a bigger project.
Note: The database file is in programdata directory. It is created by the tool (this works).
Edit: I just tried putting the DB file in C:\Users\Nacereddine\AppData\Roaming\MyTool And I still have the same problem when the tool is installed in C:\Program Files (x86)\MyTool
This how I create the DB file
class procedure TDbInteract.CreateSQLiteDb(const aDatabasePath: string; const aTables: TStringDynArray);
var
I: Integer;
aParams: array of string;
aConnection: TFDConnection;
begin
aParams := ['DriverID=SQLite',
'Database=' + aDatabasePath,
'OpenMode=CreateUTF16',
'LockingMode=Normal',
'JournalMode=WAL',
'StringFormat=Unicode',
'Synchronous=Full',
'UpdateOptions.LockWait=True',
'BusyTimeout=30000',
'SQLiteAdvanced=temp_store=MEMORY;page_size=4096;auto_vacuum=FULL'];
aConnection := TFDConnection.Create(nil);
try
for I := Low(aParams) to High(aParams) do
begin
aConnection.Params.Add(aParams[I]);
end;
aConnection.Open();
for I := Low(aTables) to High(aTables) do
begin
aConnection.ExecSQL(aTables[I]);
end;
finally
aConnection.Close;
aConnection.Free;
end;
end;
Note: I do not know if this makes any difference but the Db file is encrypted.
Sorry for the trouble folks.
The problem was that we had a Localization db file installed with the tool in ProgramFiles.
What made me exclude that from my investigation is that, when opening this file I set the OpenMode to ReadOnly
FConnection.Params.Add('OpenMode=ReadOnly');
but as I said before in my question SQLite engine requires full access to the folder containing the db file so it preforms a lock on it (still did not find the page I read this on).
I checked this by playing around with open modes and debugging the tool each time.
once I changed the permissions of both the file and the directory the error was gone.
at the end I decided to move the localization file to the programData directory with the main db file and all is well.
I realized (Thank you for this #Ken and #David) that the programData directory also requires admin permissions to write to, and therefore I will move the db files to a more appropriate dir (i.e Users).
What is useful from this problem is that even if you connect to the Sqlite db file with OpenMode=ReadOnly, you still need write access for the path to that file.

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 get a list of all files of directory

I am working with delphi, I want a list of all files of a directory when I execute openpicturedialog.
i.e., When open dialog is executed and
i select one file from it, I want the
list of all files from the directory
of selected file.
You can even suggest me for getting directory name from FileName property of TOpenDialog
Thank You.
if you use delphi 2010 then you can use tdirectory.getfiles
first add ioutils.pas to uses clause then write the following line of code in the event handler(in addition to code you already have in that event handler)
uses IOUtils;
var
path : string;
begin
for Path in TDirectory.GetFiles(OpenPictureDialog1.filename) do
Listbox1.Items.Add(Path);{assuming OpenPictureDialog1 is the name you gave to your OpenPictureDialog control}
end;
#Himadri, the primary objective of the OpenPictureDialog is not select an directory, anyway if you are using this dialog with another purpose you can try this code.
Var
Path : String;
SR : TSearchRec;
DirList : TStrings;
begin
if OpenPictureDialog1.Execute then
begin
Path:=ExtractFileDir(OpenPictureDialog1.FileName); //Get the path of the selected file
DirList:=TStringList.Create;
try
if FindFirst(Path + '*.*', faArchive, SR) = 0 then
begin
repeat
DirList.Add(SR.Name); //Fill the list
until FindNext(SR) <> 0;
FindClose(SR);
end;
//do your stuff
finally
DirList.Free;
end;
end;
end;
Change the filter property in your OpenPictureDialog to include all files:
All (*.*)
Edit: I don't think you can select a directory in a Open(Picture)Dialog, it surely isn't the purpose of an OpenPictureDialog anyway.
Then use FindFirst and FindNext to get the files in this dir.
You can use extractFilePath function to get the directory name:
myPath := extractFilePath(FileName);
where FileName is name of file you choose by OpenDialog.
if OpenPictureDialog1.Execute then
FileListBox1.Directory := extractFilePath(OpenPictureDialog1.FileName);
You can also use a FilterComboBox linked to FileListBox to filter the file type.
TFileListBox and TFilterComboBox are in the tool palette under "Win 3.1". From Delphi 4 there are these objects.
Uses System.IOUtils;
var List : TStringlist;
var File : String := '';enter code here
var Path : string := IncludeTrailingPathDelimiter(Edit1.Text);
Lista := TStringList.Create;
try
for File in TDirectory.GetFiles(Path) do
List.Add(File); // Add all file names to list
finally
FreeAndNil(Lista);
end;

Resources