SelectDirectory does not include drives on some machines - windows

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.

Related

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

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.

Inno Setup: Overwrite existing installation or show dir prompt

It would be nice if I have this in my setup:
If there is no previous installation, then an edit field for the destination directory should be shown.
If there is a previous installation, than the user should should be asked if he wants to overwrite the existing installation (no directory
prompt should be visible) or if he wants to install this version in a
different directory as a separate installation (two entries in the uninstall list). If this option is chosen, than the edit field
for the destination directory should be shown.
Is it possible to use existing Inno Setup options to achieve this? Or do I have to build a custom dialog page?
At the beginning (InitializeSetup event function), check if the application is installed already (see GetUninstallString in the code below). If it is, ask user, what to do (see MsgBox use in the code and the first screenshot). If user chooses to update the existing installation, proceed normally. Inno Setup by default does not allow changing installation path of an existing installation (see DisableDirPage).
If uses chooses to install another copy, set AppId to a new unique value (GetAppId function in the code). This will make Inno Setup treat the installation as new, so it will prompt for the installation path. Update also UninstallDisplayName, so that the user can distinguish the installations when choosing which copy to uninstall (see GetAppIdentification and the third screenshot). Also update DefaultDirName to a new unique path (see GetAppIdentification and the third screenshot).
#define AppName "My Program"
#define AppVersion "1.5"
[Setup]
AppId={code:GetAppId}
AppName={#AppName}
AppVersion={#AppVersion}
UninstallDisplayName={#AppName} {#AppVersion}{code:GetAppIdentification}
UsePreviousLanguage=no # Needed when AppId is dynamic
DefaultDirName={autopf}\My Program{code:GetAppIdentification}
[Code]
var
Instance: string;
function GetAppId(Param: string): string;
begin
Result := '{#AppName}' + Instance;
end;
function GetAppIdentification(Param: string): string;
begin
if Instance <> '' then Result := ' (' + Instance + ')';
end;
function GetUninstallString(): string;
var
UninstallKey: string;
begin
UninstallKey :=
'Software\Microsoft\Windows\CurrentVersion\Uninstall\' + GetAppId('') + '_is1';
RegQueryStringValue(HKA, UninstallKey, 'UninstallString', Result);
Log(Result)
end;
function InitializeSetup(): Boolean;
var
Message: string;
Answer: Integer;
begin
Result := True;
if GetUninstallString() = '' then
begin
Log('Application is not installed yed, installing the first copy');
end
else
begin
Log('Application is installed already, asking what to do');
Message :=
'This program is installed already, ' +
'do you want to update the existing installation? ' +
'Press No to install another copy of the program';
Answer := MsgBox(Message, mbConfirmation, MB_YESNOCANCEL);
if Answer = IDYES then
begin
Log('User chose to update the installation');
end
else
if Answer = IDNO then
begin
Log('User chose to install another copy');
Instance := '2';
end
else
begin
Log('User chose to abort the installation');
Result := False;
end;
end;
end;
Now the question is what to do if there are already two installations. To make a third (or more), it's easy, just loop, increasing the value in Instance, until GetUninstallString returns an empty string. But had you wanted the user to be able to choose what copy to update, it would be more difficult. That's too much for one question.
What you want to do is quite complicated. If you want to keep the flexibility, I think that the easiest solution is to treat every new version as a separate software. In addition, when starting the installation, as a courtesy to those who want to keep the latest version only, offer to uninstall the previous (latest) installation automatically. If the user already has multiple installations, do nothing specific (or just inform the user).
I solved this problem by putting the responsibility on the user (the one installing the application) to explicitly specify that they want to install a separate instance of the application by specifying a /instancename parameter on the installer's command line (the AppId directive uses a scripted constant).

Open file in OS X

In Delphi, I would like to open a file in OS X. My approach is as follows:
const
Filename = 'test.bmp';
procedure SaveAndOpen;
begin
Chart.SaveToBitmapFile(Filename);
{$IFDEF MSWINDOWS}
ShellExecute(0, 'open', Filename, '', '', SW_Normal);
{$ELSE}
_System(Filename);
{$ENDIF}
end;
But nothing happens. What am I doing wrong?
This article from Embarcadero's Malcolm Groves covers this topic: Opening files and URLs in default applications in OS X.
In summary, all you need is this:
uses
Macapi.Appkit, // for NSWorkspace
Macapi.Foundation; // for NSSTR
....
var
Workspace: NSWorkspace; // interface, no need for explicit destruction
....
Workspace := TNSWorkspace.Create;
Workspace.openFile(NSSTR(FileName));
For sake of completeness, should you wish to open a URL rather than a file, then you call openURL instead:
Workspace.openURL(NSSTR(URL));
Regarding your Windows code, I would recommend not using ShellExecute. That function does not have reasonable error reporting. Use ShellExecuteEx in its place.
And finally, you should probably abstract this functionality away so that it can be re-used by other parts of your program. You want to write that IFDEF as few times as possible.
You must add the open verb like so
_System(PAnsiChar('open ' + Filename));

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.

how to internationalize a delphi application [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Translate application
What is the best way to internationalize my application written in delphi xe2 ?
I've seen the stringtable resource but I'm worried because I've got the feeling that the implementation could be time consuming and laborious.
Are there other equally valid methods to do this?
Maybe not the best tool for translations, but I'm using GNU Gettext for many years.
The process is quite simple:
You run dxgettext to extract strings
You translate or give for translation the files
I personally love poEdit tool to translate and manage translation repository
Optional : You merge your translation files into the final EXE
OR you put the translation files in subdirectories and that's it !
http://dxgettext.po.dk/
Update:
1/ GNU Gettext is included in JCL/JVCL library, you just need to activate this option at startup.
2/ Gnu Gettext can translate everything in the library, as VCL, JCL/JVCL also ! It's not just limited to your code !
One option is to use the Integrated Translation Environment in Delphi:
http://docwiki.embarcadero.com/RADStudio/XE3/en/Localizing_Applications_by_Using_Translation_Manager_Overview
Here you can find two articles about this theme:
Multilingua application with GNU gettext
Multilingua application with standard method (IDE)
You can find other methods and commencial components (i have used TsiLang components -excellent library-)
A Greeting.
I don't know is this the best way of internationalize an application, but for me it worked. It's a kind of home made.
I created an xml file, which is the dictionary containing the translations, but you can use any other format, from json, to xls (maybe this is the best). Than implemented a class which read the translations from this file, and a way to register procedures in case of the language is changed runtime, which is I think a good feature.
TDictionary = class
private
fOnChanged: TSignal;
fLanguage: String;
procedure setLanguage( const Value: String );
public
procedure loadFromFile( filename: string );
function getTranslation( id: string; lang: string = '' ): string;
property language: String read fLanguage write setLanguage;
property onChanged: TSignal read fonChanged;
end;
...
function TDictionary.getTranslation( id: string; lang: string = '' ): string;
begin
if lang = '' then
lang := Language; // use the default lang.
// read the translation from the file.
end;
procedure TDictionary.setLanguage( const Value: String );
begin
fLanguage := Value;
onChanged.Execute;
end;
TSignal is a class which register methods, and if you call Execute executes all the registered methods, Maybe in xe2 you have something built in for this, in delphi7 I had to create this class myself, but it's fun to implement.
in a form's createForm:
procedure TMyClass.doTranslate( dictionary: TObject );
begin
with dictionary as TDictionary do
begin
caption := dictionary.getTranslation( 'myclass.caption' );
button.caption := dictionary.getTranslation( 'some id' );
end;
// or you can go through Controls array, and automate the assignment.
end;
procedure TMyClass.FormCreate(Sender: TObject);
begin
Dictionary.onChanged.register( doTranslate );
doTranslate( dictionary );
end;
procedure TMyClass.FormDestroy(Sender: TObject);
begin
Dictionary.onChanged.deregister( doTranslate );
end;
As you can see, this is not a working example what you can copy and paste, I just wanted to show you the idea behind. if something is not clear, comment it, and I can extend my answer.
Some notes:
I think it's important to have the translations in utf8 format.
using xls makes the localizers live easier, and your too, if they ruin your xml file (If the translator is not prof., It can happen that you get back your xml file in microsoft word format)
you can put your dictionary file into resource, and load from there.
Pros
This way you can change the language runtime
if you need another language, you just need to edit the dictionary file.
Cons
If you have many forms, it's a nightmare to connect all the labels, buttons, etc, but you can automate it in smart ways.
It slows down your app a little, but not much, if changing your app language is not happening so often.
There is a product called sisulizer, it works after you have build the executable fies I think. Haven't tried it but I've read a lot about it.
Have a look at this

Resources