How do I save a picture from a open picture dialogue to a file - image

Hi I'm currently using Delphi 2010.
I basically have a form where a user has to enter information about themselves and upload a picture. I have an Image component on my form. I did some research and many of the websites I looked at said to use a OpenPictureDialogue to allow the user to select an image and display it in the Image component.
My question is, how can I save this image to a file on my computer? Keeping in mind I will have multiple users adding their picture and that I will have to use the picture later on again, basically I want to use the LoadFromFile procedure to display the picture in my program later on.
I also read many websites saying to use the SavePictureDialogue, but that allows the user to select the file they want the image to be saved to and I don't want that, I want it to save to a file that only I can access.
I have this so far, I know it is very limited.
if opdAcc.Execute then
begin
if opdAcc.FileName <> '' then
begin
imgAccImage.Picture.LoadFromFile(opdAcc.FileName);
end;
end;
I am a student and my knowledge is quite limited and I would appreciate any help. :)

First of all, there is no place on the hard drive that only you can access. But you can create a folder to store your files and copy users' pictures there. This reduces the likelihood that the user will have access to these files. The usual folder for storing such files is the AppData folder. It is better to create a folder with the same name as your application in AppData and store such files there.
Suppose the GetPicturesDirectoryPath function generates the address of such a folder and ensures that this folder has already been created or will be created. The next step is to generate a unique name for the file you want to store. Note that multiple users may select files with the same name. In this case, after copying the picture selected by the second user, the image file will be written over the previous user's file. If a unique identifier is assigned to each user, this identifier is the best choice for the picture file name. But you can use the GetGUIDFileName function to create a unique address. Make sure the generated address is kept with the rest of the user information, or the connection between the copied file and the user will be lost. The implementation of all these will be something like the following:
uses IOUtils;
function GetAppDataDirectoryPath: string;
begin
...
end;
function GetPicturesDirectoryPath: string;
begin
Result := TPath.Combine(GetAppDataDirectoryPath, 'MyApp');
TDirectory.CreateDirectory(Result);
end;
function GetUniqueFilePath(const Extension: string): string;
begin
Result := TPath.ChangeExtension(
TPath.Combine(GetPicturesDirectoryPath, TPath.GetGUIDFileName),
Extension);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
DestPath: string;
begin
OpenPictureDialog1.Options := OpenPictureDialog1.Options +
[ofFileMustExist]; // make sure that selected file exists
if OpenPictureDialog1.Execute then
begin
DestPath := GetUniqueFilePath(TPath.GetExtension(OpenPictureDialog1.FileName));
TFile.Copy(OpenPictureDialog1.FileName, DestPath);
if TFile.Exists(DestPath) then
Image1.Picture.LoadFromFile(DestPath)
else
ShowMessage('Well, something went wrong!');
end;
end;
Read this to implement GetAppDataDirectoryPath.

Related

Change PDF out put file name dynamically in oracle reports

Please guide how to change output PDF file name at run time
I am using below code in after parameter form trigger but its not working :
:DESNAME := 'INVOICE_'||:P_CLIENT||'.PDF';
Please guide me..
You didn't mention which Reports version you use; up to 6i program that is used to create reports was called "Reports Builder", while later versions use name "Reports Developer". Doesn't really matter, but - if you open Reports Online Help System (navigate to "Help" menu) and search for DESNAME, you'll find which executables can be used to set DESNAME's value. RWBUILDER is not among them. And yes, rwbuilder is Builder's and/or Developer's EXE name.
It means that you're out of luck, at least regarding the way you're trying to set DESNAME's value. You can't set it in any of Reports' triggers. I mean, you can, but it won't take any effect.
However, if you call the report from, for example, a form developed by Oracle Forms, then you'd use RWRUN which can specify DESNAME value and yes, you can dynamically create it.
For some more info, do read Help I pointed you to previously.
Among several ways of calling Reports from Forms, i can suggest you to use adding RP2RRO.pll to your Form :
And then, add parameters related to this pll :
And you may call your reports by this code :
Pr_Print_Rp2Rro('Rep_Invoice','|Prm1|Prm2|','|'||:Prm1||'|'||:Prm2||'|','no','INVOICE_'||:P_Client||'.PDF');
assuming you have two user defined parameters namely : Prm1 & Prm2. And Pr_Print_Rp2Rro is a procedure with the following code :
Procedure Pr_Print_Rp2Rro(
i_rep_name varchar2,
i_prm_name varchar2,
i_prm_val varchar2,
i_param_frm varchar2, -- 'Yes','No'
i_desname varchar2,
i_destype varchar2 default 'FILE'
) Is
plist ParamList;
arr_prm_name owa.vc_arr;
arr_prm_val owa.vc_arr;
Begin
plist := Get_Parameter_List('REPPARAM');
if not Id_Null(plist) then
Destroy_Parameter_List('REPPARAM');
end if;
plist := Create_Parameter_List('REPPARAM');
Add_Parameter(plist, 'PARAMFORM', Text_Parameter, i_param_frm);
Rp2rro.SetDestype(i_destype);
Rp2rro.SetDesname(i_desname);
for i in 1..100
loop
arr_prm_name(i) := substr(i_prm_name,instr(i_prm_name,'|',1,i)+1,instr(i_prm_name,'|',1,1+i)-instr(i_prm_name,'|',1,i)-1);
arr_prm_val(i) := substr(i_prm_val,instr(i_prm_val,'|',1,i)+1,instr(i_prm_val,'|',1,1+i)-instr(i_prm_val,'|',1,i)-1);
if length(arr_prm_name(i)) > 0 then
Add_Parameter( plist, arr_prm_name(i) , Text_Parameter, arr_prm_val(i) );
end if;
end loop;
Rp2rro.Rp2rro_Run_Product(Reports, i_rep_name, Synchronous, Runtime,Filesystem, plist,null);
End ;
As you may have noticed this procedure contains a method Rp2rro.SetDesname(i_desname) which you could use and manage your task to create Report names spesific to your customer, unless you set your Destype parameter as CACHE.

SelectDirectory does not include drives on some machines

The following code gets different results on different machines. One machine just gives the desktop folder (not desired) the other gives the desktop folder and Computer, mapped drives (desired).
procedure TForm1.Button1Click(Sender: TObject);
var
Directory : String;
begin
FileCtrl.SelectDirectory('Caption', 'Desktop', Directory, [sdNewUI, sdShowEdit]);
end;
One one machine it gives:
On another it gives:
This feels like a windows setting, but I am not sure where to start. Using Delphi XE, Windows 10.
Any thoughts are appreciated. Thanks for your time.
Workaround
Use a TFileOpenDialog instead*.
Set FileOpenDialog1.Options:= [fdoPickFolders,fdoPathMustExist]
Now you have a dialog that:
Always works.
Allows copy paste
*) Not to be confused with the TOpenDialog, which does not allow you to only select folders.
Solution for Windows XP
Note that the new TFileOpenDialog only works for Vista and above.
Your program will not work on XP if you include this control.
If you start the dialog on XP it will generate an EPlatformVersionException.
You may want to use the following code instead if you want to be backward compatible:
uses JclSysInfo; //because you have XE use JCL.
...
var
WinMajorVer: Integer;
Directory: string;
FileDialog: TFileOpenDialog;
begin
WinMajorVer:= GetWindowsMajorVersionNumber;
if WinMajorVer < 6 then begin //pre-vista
//To show the root Desktop namespace, you should be setting the Root parameter to an empty string ('') instead of 'Desktop'
FileCtrl.SelectDirectory('Caption', '', Directory, [sdNewUI, sdShowEdit]);
end else begin
FileDialog:= TFileOpenDialog.Create(self);
try
FileDialog.Options:= [fdoPickFolders,fdoPathMustExist];
if FileDialog.Execute then Directory:= FileOpenDialog1.FileName;
finally
FileDialog.Free;
end;
end;
Result:= Directory;
end;
Recommended reading:
detect windows version
EDIT
FileCtrl.SelectDirectory('Caption', 'Desktop', Directory, [sdNewUI, sdShowEdit]);
The 'Desktop' goes into the Root parameter, which is handled like so:
...
SHGetDesktopFolder(IDesktopFolder);
IDesktopFolder.ParseDisplayName(Application.Handle, nil,
Root, Eaten, RootItemIDList, Flags);
...
Here's what MSDN for IDesktopFolder.ParseDisplayName has to say:
pszDisplayName [in]
Type: LPWSTR
A null-terminated Unicode string with the display name. Because each Shell folder defines its own parsing syntax, the form this string can take may vary. The desktop folder, for instance, accepts paths such as "C:\My Docs\My File.txt". It also will accept references to items in the namespace that have a GUID associated with them using the "::{GUID}" syntax.
Note that the documentation states that the desktop folder will accept paths and guids. It does not accept 'Desktop'. Because that's neither.
The fact that 'Desktop' as a root works on one system but not another is some undocumented fix made in an older/newer version of the IDesktopFolder interface.
Technical solution
Use '' as a 'root' as shown in my code above.
Obviously SelectDirectory is a really bad design by Microsoft that should never be used. It just sucks in so many ways. I recommend it not be used whenever possible.

How to set folder display name with e.g. "SHGetSetFolderCustomSettings()"?

Looks like SHGetSetFolderCustomSettings allows you to set an icon, a tooltip, a web view template and stuff, but I could not find how to set the LocalizedResourceName in the associated desktop.ini (see SHFOLDERCUSTOMSETTINGS structure).
Therefore I am currently writing to desktop.ini directly, however this comes with a caveat:
Explorer does not properly update its views even when you tell it to refresh with F5 or Ctrl+R.
This is what I want to write, using Python (though non-Python code should be less of an issue):
[.ShellClassInfo]
LocalizedResourceName=My Folder Name
InfoTip=A customized folder
Any ideas how to set the folder name and have Explorer properly update it ?
I have tried with SHChangeNotify(SHCNE_ALLEVENTS, SHCNF_PATH, path, path), but this does not seem to update the display name (and also with SHCNE_RENAMEFOLDER, SHCNE_RENAMEITEM, SHCNE_UPDATEDIR, SHCNE_UPDATEITEM).
(The worst approach would probably be to edit the desktop.ini twice... once directly, then with that API function... rather not what I want).
About the why (I guess at least one of you will ask):
I am storing project data using GUIDs as folder names.
The user should however see a friendly name that can also be used for sorting (and maybe even be able to edit it without interfering with the internal name).
Furthermore, the low-level file system layout should be backwards-compatible with older versions of the software.
Use simple call of IShellFolder.SetNameOf:
procedure UpdateLocalizedResourceName(const ADirectory, ANewResourceName: UnicodeString);
var
Desktop: IShellFolder;
Eaten: DWORD;
DirIDList1, Child, NewChild: PItemIDList;
Attr: DWORD;
Folder: IShellFolder;
begin
OleCheck(SHGetDesktopFolder(Desktop));
try
Attr := 0;
OleCheck(Desktop.ParseDisplayName(0, nil, PWideChar(ADirectory), Eaten, DirIDList1, Attr));
try
OleCheck(SHBindToParent(DirIDList1, IShellFolder, Pointer(Folder), Child));
try
OleCheck(Folder.SetNameOf(0, Child, PWideChar(ANewResourceName), SHGDN_INFOLDER, NewChild));
CoTaskMemFree(NewChild);
finally
Folder := nil;
end;
finally
CoTaskMemFree(DirIDList1);
end;
finally
Desktop := nil;
end;
end;
UPDATE
Important notice! LocalizedResourceName parameter must exists in desktop.ini before you call UpdateLocalizedResourceName. Otherwise SetNameOf function fails.

Lazarus(Pascal) RunError(5)

My program exits with RunError(5), which would suggest that it can't access the file, which it should be able to. I have checked and the file is used as it should be, the file isn't read-only, etc. What the program does is, it creates a .dat file if one doesn't exists and uses it for saving stuff. If I run the program and the file doesn't exist, the file is created, but after that, in the same execution, the program won't access the file. This ONLY happens if the file was created in the current execution.
This is the way in which the procedures are called(the code is quite long but I am giving you the first few lines, where the error occurs):
fileName := 'labSave.dat';
CreateFile;
assign(labyrinthFile,fileName);
writeln(CheckFileSize);
and then there is each of the procedures:
procedure Initialize;
begin
fileName := 'labSave.dat';
assign(labyrinthFile,fileName);
end;
procedure CreateFile;
begin
if not FileExists(fileName) then FileCreate(fileName);
end;
function CheckFileSize: integer;
begin
reset(labyrinthFile);
CheckFileSize := FileSize(labyrinthFile);
close(labyrinthFile);
end;
According to Lazarus forum (http://forum.lazarus.freepascal.org/index.php?topic=4936.0):
Runtime Error 5 means Access denied. The file maybe readonly and you
use the wrong (default) filemode, or you try to re-open the file with
a new filehandle without having closed it before (somewhere in the
while and repeat loops possibly you assignfile more then once, then
the reset fails?).
If I recall correctly now, the workflow should be as follows for create:
AssignFile(f, filename); Rewrite(f); CloseFile(f);
and for existing file:
AssignFile(f, filename); Reset(f); CloseFile(f);
Seeing other mistakes found in your code through questions in comments, I strongly suggest you to devote more time to debugging and when such errors happen - strip out ALL of the irrelevant code and check your code design for cases like above (assigning file before creating it, etc.).

CopyFileEx and 8.3 file names

Suppose you have 2 files in the same directory:
New File Name.txt and
NewFil~1.txt
If you use CopyFileEx to copy both files to the same destination, maintaining the same names, you will end up having only ONE file (the second one replaces the first one) which can be sometimes not a good thing. Any workaround for this behavior?
This happens at file system level so you there is no much you can do if you don't want to disable SFN generation at all.
The way I use to handle this problem is to:
1) Before the file is copied, I check if the file name exists.
2) If there is a collision then I first rename the existing file top some temporary name
3) Then I copy the file
4) rename the first file back.
To detect the collision do something like this:
function IsCollition(const Source, Destination: string; var ExistingName: string): boolean;
var
DesFD: TSearchRec;
Found: boolean;
ShortSource, FoundName: string;
begin
ShortSource:= ExtractFileName(SourceName);
Found:= false;
FoundName:= WS_NIL;
if (FindFirst(DestinationName, faAnyFile, DesFD) = 0) then
begin
Found:= true;
FoundName:= DesFD.Name;
SysUtils.FindClose(DesFD);
end;
if (not Found) or (SameFileName(ShortSource, FoundName)) then
begin
Result:= false;
end else
begin
// There iis a collision:
// A file exists AND it's long name is not equal to the original name
Result:= true;
end;
ExistingName:= FoundName;
end;
There's not a great solution for the automatic generation of short filename aliases. If your application will have adequate privileges, you might be able to use the SetFileShortName() API. Another (heavy handed) alternative might be to disable short name alias generation altogether, though I'd be reluctant to require this of your users. See
http://support.microsoft.com/kb/210638/EN-US/
http://technet.microsoft.com/en-us/library/cc778996.aspx
http://blogs.msdn.com/adioltean/archive/2005/01/27/362105.aspx
for more details.

Resources