How to save file as "read-only" from Delphi? - file

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.

Related

Inno Setup - How to bind a checkbox with a registry key, to check if the key may be written? [duplicate]

This question already has answers here:
How to make work the new added checkboxes with the tasks?
(2 answers)
Closed 10 months ago.
I need to create an installer with Inno Setup (version 6.2.1) in which I need to let the user select which file extension he want to associate with the installed app.
To achieve that I needed to create a custom page showing all the checkboxes I needed. This page respects a custom theme required by my company, for that reason I could not use the checkboxes automatically generated by the [Tasks] section.
However I cannot figure out how to associate the checkbox state with the registry key I want to install (or not).
For example, I have the following code:
[Code]
var
AssocABCCB: TNewCheckBox;
...
procedure InitializeWizard;
var
SelectAssocPage: TWizardPage;
begin
// select association custom page
SelectAssocPage := CreateCustomPage(wpLicense, ExpandConstant('{cm:SelectAssocTitle}'), '');
...
// .abc checkbox
AssocABCCB := TNewCheckBox.Create(SelectAssocPage);
AssocABCCB.Align := alTop;
AssocABCCB.Caption := ExpandConstant('{cm:AssocABC}');
AssocABCCB.Parent := SelectAssocPage.Surface;
AssocABCCB.OnClick := #AssocABCCBClick;
...
end;
Now I want to use the above checkbox to optionally write a key in the registry, let say something like that:
[Registry]
Root: HKCR; Subkey: "{#KeyAppName}.abc"; Flags: uninsdeletekeyifempty; Check: AssocABCCB.Checked
Of course the above code is not working, it raises a compilation error.
But is there a way to bind the above checkbox with the registry key, in order to determine if the key may be written or not? If yes, how may I achieve that?
So I finally found what was the issue.
In fact it was an if statement was not closed correctly in one of my functions. This forced the following end statement to be closed with a . instead of a ;
Here was my wrong code:
[Code]
...
procedure CurStepChanged(CurStep: TSetupStep);
begin
Log('CurStepChanged(' + IntToStr(Ord(CurStep)) + ') called');
if CurStep = ssPostInstall then
FinishedInstall := True;
end; // <= here was the issue
end.
function DoAssocABC: Boolean;
begin
Result := AssocABCCB.Checked;
end;
...
[Registry]
Root: HKCR; Subkey: "{#KeyAppName}.abc"; Flags: uninsdeletekeyifempty; Check: DoAssocABC
Because my function was located AFTER the dotted end, it WAS NOT VISIBLE from ANY other statements, for that reason my [Registry] statement didn't find the function I tried to bind.
Unfortunately the compiler didn't complain about that. I don't know if it's a bug from Inno Setup itself.
Correcting that, the Martin Prikryl comment became right, and this fixed my issue:
You cannot access a variable in the Check parameter directly. You have to wrap it to a function, as my answer shows. It does not matter if it is a [Files] or [Registry] section. –
Martin Prikryl

Delphi setting up AdoConnection in runtime gives access violation

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.

Executing a stored procedure twice using TAdoStoredProc in Delphi

I'm using Delphi 10.1 Berlin and I needed to use TAdoStoredProc in my project. I don't use any non-visual component in my forms or data modules. All my connection and db components are creating at run-time.
My Question: When I'm trying to execute TAdoStoredProc, it executes twice and my insert, update processes have worked twice.
var
mSp: TADOStoredProc;
i: Integer;
begin
mSp := TADOStoredProc.Create(nil);
mSp.Connection := conn;
mSp.ProcedureName := spname;
mSp.CommandTimeout := 600;
mSp.parameters.Refresh;
for i := 0 to parameters.Count - 1 do
begin
mSp.parameters.ParamByName(parameters[i].Name).Value := parameters[i].Value;
end;
mSp.ExecProc;
mSp.Open;
Result := mSp;
What should I do or change? Thanks.
As I see that your code required some return values. So you should use TAdoStoredProc like TAdoQuery. You don't need to use ExecProc function for execute, try to use only Open function.
You can just check out TADOStoredProc.ExecProc Method documentation from here.
If an application is only interested in the result set returned by a stored procedure, call Open method for the TADOStoredProc component or set its Active property to true.

Show Files in a ListBox Delphi

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.

How do I store say a file path that can be changed at runtime in Delphi

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.

Resources