I have a procedure to save a file, which is an ini file. The procedure below is just where the user chooses the directory and name of the file:
procedure TForm1.SaveFile(Sender: TObject);
var
Dialog : TSaveDialog;
begin
Dialog := TSaveDialog.Create(self);
try
//dialog properties go here
Dialog.Filter := 'Title (*.ini)|*.ini';
Dialog.Options := Dialog.Options + [ofOverwritePrompt];
if Dialog.Execute then
begin
//any saving procedures go here if required
ShowMessage('File saved: ' + Dialog.FileName);
end
else
ShowMessage('Save file was cancelled');
finally
Dialog.Free;
end;
end;
As you can see I set the filter to ini but no files show at all (it still saves)
The procedure below is where the user can select a previously saved file
procedure TForm1.LoadFileBtnClick(Sender: TObject);
Var
FileName, FileExtension : String;
SelectedFile : TOpenDialog;
begin
SelectedFile := TOpenDialog.Create(nil);
Try
if SelectedFile.Execute() then
FileName := SelectedFile.FileName
else
//Do whatever here if user doesn't select file
Finally
SelectedFile.Free; //Free dialog from memory
End;
FileExtension := ExtractFileExt(FileName);
if not (FileExtension = 'ini') then
exit
else
//Do whatever here if file extension matches specified type
end;
I get the extension of the filename and check if its ini. The ShowMessage is there to see what value is returned for testing purposes. When i click an ini file nothing is returned but when i click a text file '.txt' is returned. Why do my ini files save with the type as 'File' when they are ini files.
How could i change my load file procedure to detect if its an ini file and if not then exit?
Set the property DefaultExt in the SaveDialog to '.ini'
These will solve your problem
I think you are assuming that the fact that you are specifying the SaveDialog's Filter as you do forces the SaveDialog's FileName to have an extension of 'Ini'. It does not. The dialog's Filter property determines what files are listed in it, not the extension that the saved file has.
Try the following code:
if SaveDialog.Execute then
begin
SaveSeatingPlan(SaveDialog.FileName);
Assert(SameText(ExtractFileExt(SaveDialog.FileName), '.Ini'));
ShowMessage('File saved: ' + SaveDialog.FileName);
end else
ShowMessage('Save file was cancelled');
Instead of the Assert, you can of course append the '.Ini' extension in your code or, better, do what the other answer suggests.
Related
I'm trying to code a function that creates a TADOQuery dynamically. With this function, I'm able to change its SQL.Text property in the parameter of the function. How my function call should work:
procedure TDlgMain.FormCreate(Sender: TObject);
var
Q: TADOQuery;
begin
Q := NewQuery('select * from Utenti');
Q.Open;
end;
Here the code of the function and a screenshot of the access violation error, is there a way to solve this?
function NewQuery(Conn: TADOConnection; SQL: String): TADOQuery; overload;
function NewQuery(SQL: String): TADOQuery; overload;
function TDMDB.NewQuery(Conn: TADOConnection; SQL: String): TADOQuery;
begin
Result := TADOQuery.Create(nil);
Result.Connection := Conn;
Result.SQL.Text := SQL;
end;
function TDMDB.NewQuery(SQL: String): TADOQuery;
begin
Result := NewQuery(DBConn, SQL);
end;
The error indicates that the TDataModule is not yet created at the time you attempt to use it to create the query via DMDB.NewQuery().
The reason for the error is two folded.
First, when using the IDE to first create a form (DlgMain: TDlgMain in your case) and then a data module (TDMDB: TDataModule). This places the module creation after the form creation in the project file (.dpr) as folllows: (to see the .dpr file, select menu Project - View source)
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TDlgMain, DlgMain);
Application.CreateForm(TDMDB, DMDB);
Application.Run;
end.
Secondly, as you attempt to create the connection already in the main forms OnCreate() event, when the data module is not yet created, the result is the AV you see.
You can correct the error by moving the creation of your data module before the creation of your main form:
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TDMDB, DMDB); // Create this before the form
Application.CreateForm(TDlgMain, DlgMain);
Application.Run;
end.
A side note, in case you wonder:
You may be aware that the first form created via Application.CreateForm() becomes the main form, and that is still valid. The data module is not a form, and thus your DlgMain is still the first created form and becomes the main form.
I would like to save a file as a "read-only" file. Is it possible?
This is a two stage process, that is only available on the Windows platform.
Save the file.
Add the read-only attribute to the file's metadata.
Assuming that you already know how to do stage 1, let's consider stage 2. Using the System.IOUtils unit you set this attribute like so:
uses
System.IOUtils;
....
var
attributes: TFileAttributes;
....
attributes := TFile.GetAttributes(FileName);
Include(attributes, faReadOnly);
TFile.SetAttributes(FileName, attributes);
If you wish to remove the read-only attribute then you use exactly the same code but replace Include with Exclude.
For platforms other than Windows, you can still use TFile.GetAttributes and TFile.SetAttributes, but the available attributes are very different, reflecting the different filesystem models of Windows and the POSIX platforms.
Unfortunately the RTL fails to provide any way to check for errors in this code. So if you want to check for errors (you should) then you are actually best calling the Windows API function SetFileAttributes directly. You might do that like this:
function FileSetAttribute(const FileName: string; const Attr: DWORD; const Value: Boolean): Boolean;
var
Flags, NewFlags: DWORD;
begin
Flags := GetFileAttributes(PChar(FileName));
if Flags=INVALID_FILE_ATTRIBUTES then begin
Result := False;
end else begin
if Value then begin
NewFlags := Flags or Attr
end else begin
NewFlags := Flags and not Attr;
end;
Result := (NewFlags=Flags) or SetFileAttributes(PChar(FileName), NewFlags);
end;
end;
function FileSetReadOnly(const FileName: string; ReadOnly: Boolean): Boolean;
begin
Result := FileSetAttribute(FileName, faReadOnly, ReadOnly);
end;
As discussed previously this code is for Windows only. The FileSetReadOnly function returns a boolean indicating whether or not it succeeded. In case of failure, you can then call GetLastError for extended error information.
Save it as normal, f.ex. via
VAR S : TFileStream;
.
S := TFileStream.Create(FileName,fmCreate);
<Write to stream>
S.Free;
Then
USES SysUtils;
// Set R/O
IF FileSetAttr(FileName,FileGetAttr(FileName) or faReadOnly)<>NO_ERROR THEN
RaiseLastOSError;
afterwards to mark it as "Read/Only".
If you want to update it later on, you need to remove the Read/Only flag first:
// Set R/W
IF FileSetAttr(FileName,FileGetAttr(FileName) AND NOT faReadOnly)<>NO_ERROR THEN
RaiseLastOSError;
then update the file, then mark it as Read/Only again.
Im trying to create an mp3 player that later I may used as base to create a karaoke the only problem so far is that I want to show the *.mp3 files of a directory that the users select on a ListBox without having to add then manually 1 by 1 and that it only shows the song name not the current path. I have find some ways like using large functions or instead of a ListBox using a ComboBox but it isn't a easier way or transfer the files from the ComboBox to the Listbox?
Try this code it will add file name to ListBox and save its path to TStringList with same Index so you can use get both everywhere in your code
var
Form1: TForm1;
FilePath: TStringList;
implementation
{$R *.dfm}
procedure FindFiles(FilesList: TStrings; FilesPath: TStrings;
StartDir, FileMask: string);
var
SR: TSearchRec;
DirList: TStringList;
IsFound: Boolean;
i: integer;
begin
if StartDir[length(StartDir)] <> '\' then
StartDir := StartDir + '\';
IsFound := FindFirst(StartDir + FileMask, faAnyFile - faDirectory, SR) = 0;
while IsFound do
begin
FilesPath.Add(StartDir + SR.Name);
FilesList.Add(SR.Name);
IsFound := FindNext(SR) = 0;
end;
FindClose(SR);
DirList := TStringList.Create;
IsFound := FindFirst(StartDir + '*.*', faAnyFile, SR) = 0;
while IsFound do
begin
if ((SR.Attr and faDirectory) <> 0) and (SR.Name[1] <> '.') then
DirList.Add(StartDir + SR.Name);
IsFound := FindNext(SR) = 0;
end;
FindClose(SR);
for i := 0 to DirList.Count - 1 do
FindFiles(FilesList, FilePath, DirList[i], FileMask);
DirList.Free;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
FilePath.Free;
end;
procedure TForm1.sButton1Click(Sender: TObject);
begin
FilePath := TStringList.Create;
FindFiles(sListBox1.Items, FilePath, EditStartDir.Text, '*.mp3');
{FilePath is where full file path saved, EditStartDir is where to search}
end;
procedure TForm1.sListBox1Click(Sender: TObject);
begin
If sListBox1.ItemIndex > -1 then
sEdit2.Text := FilePath.Strings[sListBox1.ItemIndex];
end;
end.
If you are interested only in listing all the files from specific folder without their icons then you might want to use FileListBox component from Win 3.1 tab.
You can easily define contents from which directory are shown in it by setting the Directory property. Note this property is not exposed to Object inspector to be used at design time but it is perfectly accessible at runtime.
The mentioned component also allows defining filters and even file attributes to show only files that you desire.
The only limitation of mentioned component is that it won't allow you to show files from subfolders which is something a lot of end users would probably like.
For that you would have to search for the files on your own and add them to your desired component one by one.
If you want to only display the filename instead of the whole file path you can use ExtractFileName procedure but keep in mind that you will still have to somehow keep the full path somewhere so that you know which file to open when certain file is selected on your desired component.
There are also some components available which mimic the file browsing behavior similar to Windows Explorer like TShellTreeView or TShellListView that shipped with older versions of Delphi.
And quick search on Google shows that there are even more similar components available.
My program allows the user to change the location of the main set of files. Currently I have a text file with a fixed location that contains the folder location of the other files. However, this seems to almost defeat the purpose. Is there a better way of storing this file path?
Delphi contains the TRegistry class, which makes it very simple to save and retrieve your settings information from the Windows Registry.
uses
Registry;
const
RegKey = 'Software\Your Company\Your App\';
procedure TForm1.SaveSettingsClick(Sender: TObject);
var
Reg: TRegistry;
DataDir: string;
begin
// Use the result of SelectDirectory() or whatever means you use
// to get the desired location from the user here. ExtractFilePath()
// is only used as an example - it just gets the location of the
// application itself and then appends a subdirectory name to it.
DataDir:= ExtractFilePath(Application.ExeName) + 'Data\';
Reg := TRegistry.Create;
try
if Reg.OpenKey(RegKey, True) then
Reg.WriteString('DataDir', DataDir);
finally
Reg.Free;
end;
end;
procedure TForm1.GetSettingsClick(Sender: TObject);
var
Reg: TRegistry;
begin
Reg := TRegistry.Create;
try
if Reg.OpenKey(RegKey, False) then
Label1.Caption := Reg.ReadString('DataDir');
finally
Reg.Free;
end;
end;
As #SirRufo mentions in the comments, Delphi also has the TRegistryIniFile, which serves as a wrapper around the registry functions, allowing you to use it without any knowledge of the registry structure. (It's use is similar to that of TIniFile/TMemIniFile, which I'm describing next.)
If you prefer not to use the Registry, you can also use INI (text files) pretty easily. Delphi has support for standard (WinAPI based) INI files in TIniFile, as well as it's own (and IMO better implemented) TMemIniFile. Both are fairly compatible, so I'll demonstrate using just the TMemIniFile here.
(Locations used for reading/writing are for simplicity. In reality, you should use the appropriate subfolder of the %APPDATA% directory, obtained by the appropriate call to SHGetKnownFolderPath with the FOLDERID_RoamingAppData or FOLDERID_LocalAppData constant.)
uses
IniFiles;
// Writing
var
Ini: TMemIniFile;
RootDir: string;
begin
RootDir := ExtractFilePath(Application.ExeName);
Ini := TMemIniFile.Create(TheFile);
try
Ini.WriteString('Settings', 'DataDir', RootDir + 'Data\');
Ini.UpdateFile;
finally
Ini.Free;
end;
end;
// Reading
var
Ini: TMemIniFile;
RootDir: string;
begin
RootDir := ExtractFilePath(Application.ExeName);
Ini := TMemIniFile.Create(TheFile); // The file is your ini file name with path
try
DataDir := Ini.ReadString('Settings', 'DataDir', RootDir);
if DataDir = '' then
DataDir := RootDir; // No user specified location. Use app's dir
finally
Ini.Free;
end;
end;
I very much prefer inifiles. Your users can easily read and edit them if needed, you can keep all information neatly together, and you can easily copy or install configurations to other machines. Inifiles are so simple that you can easily parse them on platforms other than windows, and with any language.
Nowadays many people start using sqlite as some sort of filesystem replacement. They store settings, data files, history, logging, translations, etc all in an sqlite database. That happens on all platforms, even on mobile and embedded devices.
It takes a bit more to set it up, but after that it's pretty flexible.
This is a follow-up of this question: Ada: reading from a file .
I would like to add an exception that checks if the file that I'm opening actually exists or not. I have made a separate procedure to avoid code clutter.
Here is the main code test_read.adb:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Long_Float_Text_IO;
with Ada.Float_Text_IO;
procedure Test_Read is
Input_File : File_Type;
Value : Long_Float;
procedure Open_Data (File : in Ada.Text_IO.File_Type; Name : in String) is separate;
begin
Ada.Text_IO.Open (File => Input_File, Mode => Ada.Text_IO.In_File, Name => "fx.txt");
while not End_Of_File (Input_File) loop
Ada.Long_Float_Text_IO.Get (File => Input_File, Item => Value);
Ada.Long_Float_Text_IO.Put (Item => value, Fore => 3, Aft => 5, Exp => 0);
Ada.Text_IO.New_Line;
end loop;
Ada.Text_IO.Close (File => Input_File);
end Test_Read;
And here is the separate body test_read-open_data.adb of the procedure Open_Data:
separate(test_read)
procedure Open_Data (File : in out Ada.Text_IO.File_Type;
Name : in String) is
--this procedure prepares a file for reading
begin
begin
Ada.Text_IO.Open
(File => File,
Mode => Ada.Text_IO.In_File,
Name => Name);
exception
when Ada.Text_IO.Name_Error =>
Ada.Text_IO.Put(File => Standard_Error, Item => "File not found.");
end;
end Open_Data;
On compilation I get an error message in the separate body test_read-open_data.adb:
actual for "File" must be a variable
How to fix this?
Thanks a lot...
Update:
I have now made the following corrections.
In test_read.adb, I now have procedure Open_Data (File : in out Ada.Text_IO.File_Type; Name : in String) is separate;
Updated the definition of the same Open_Data procedure in test_read-open_data.adb.
The program compiles well though I do not see it catch the exception say if I renamed the file fx.txt to fy.txt. The error message I get is just
raised ADA.IO_EXCEPTIONS.NAME_ERROR : fx.txt: No such file or directory
So I do not get my own error message :File not found.
What is still wrong?
The File parameter of Open_Data needs to be an in out parameter (as in, for example, Ada.Text_IO.Create), because you want the opened file to be accessible within Test_Read.
You are getting actual for "File" must be a variable because an in parameter is read-only.
procedure Open_Data (File : in out Ada.Text_IO.File_Type;
Name : in String) is
(Personally I rarely type the in mode, because it’s the default).
But in any case, it looks as though the reason for the observed behaviour is that Test_Read doesn’t actually call Open_Data!
(edited to make the recommended mode in out & to suggest calling Open_Data)
if your goal is to simply check if the file exists, consider using Ada.Directories.Exists
IIRC: Standard_Error is not a file, but a Stream.
I suspect that the reason you are not seeing your error message is that you are using Put rather than Put_Line. Different implementations/platforms treat output to the user's display differently. To be extra sure you will see the message, follow the Put_Line with a Get_Line. The Get_Line generally forces the output of the Put_Line.