How i can get the icon of special folder for example Desktop
function GetFolderIcon( FName: string ): integer;
var
FInfo: TSHFileInfo;
begin
if SHGetFileInfo(pChar(FName), FILE_ATTRIBUTE_NORMAL, FInfo, SizeOf(FInfo),
SHGFI_SYSICONINDEX or SHGFI_SMALLICON or SHGFI_USEFILEATTRIBUTES or SHGFI_PIDL or SHGFI_ICON or SHGFI_OPENICON ) <> 0 then begin
Result := FInfo.iIcon
end
else
Result := -1;
end;
GetFolderIcon(GetSpecialFolder(CSIDL_DESKTOP)); retern -1
CSIDL_DESKTOP is the "virtual folder that represents the Windows desktop, the root of the namespace". As such, it does not have a filesystem path that you can pass to SHGetFileInfo(). You are probably thinking of CSIDL_DESKTOPDIRECTORY instead, which is "The file system directory used to physically store file objects on the desktop (not to be confused with the desktop folder itself)":
GetFolderIcon(GetSpecialFolder(CSIDL_DESKTOPDIRECTORY));
When calling SHGetFileInfo(), you can specify the SHGFI_PIDL flag so you can pass a PIDL instead of a filesystem path. That allows for querying virtual items. Your code is already using SHGFI_PIDL, but it is not using any PIDLs, which means you are using SHGetFileInfo() incorrectly to begin with.
Try this:
uses
..., ShlObj, SHFolder;
function GetSpecialFolderPath(FolderID: Integer): String;
var
Path: array[0..MAX_PATH] of Char;
begin
if SHGetFolderPath(0, FolderID, nil, SHGFP_TYPE_CURRENT, Path) = 0 then
Result := Path
else
Result := '';
end;
function GetSpecialFolderPidl(FolderID: Integer): PItemIDList;
begin
Result := nil;
SHGetSpecialFolderLocation(0, FolderID, Result);
end;
function GetFolderIcon( FName: String ): integer; overload;
var
FInfo: TSHFileInfo;
begin
ZeroMemory(#FInfo, SizeOf(FInfo));
if SHGetFileInfo(PChar(FName), FILE_ATTRIBUTE_NORMAL, FInfo, SizeOf(FInfo), SHGFI_USEFILEATTRIBUTES or SHGFI_SYSICONINDEX or SHGFI_SMALLICON or SHGFI_ICON or SHGFI_OPENICON ) <> 0 then
begin
Result := FInfo.iIcon;
if FInfo.hIcon <> 0 then DestroyIcon(FInfo.hIcon);
end else
Result := -1;
end;
function GetFolderIcon( Pidl: PItemIDList ): integer; overload;
var
FInfo: TSHFileInfo;
begin
if SHGetFileInfo(PChar(Pidl), FILE_ATTRIBUTE_NORMAL, FInfo, SizeOf(FInfo), SHGFI_PIDL or SHGFI_USEFILEATTRIBUTES or SHGFI_SYSICONINDEX or SHGFI_SMALLICON or SHGFI_ICON or SHGFI_OPENICON ) <> 0 then
begin
Result := FInfo.iIcon;
if FInfo.hIcon <> 0 then DestroyIcon(FInfo.hIcon);
end
else
Result := -1;
end;
var
Icon: Integer;
Pidl: PItemIDList;
begin
Icon := -1;
Pidl := GetSpecialFolderPidl(CSIDL_DESKTOP);
if Pidl <> nil then
try
Icon := GetFolderIcon(Pidl);
finally
CoTaskMemFree(Pidl);
end;
end;
var
Icon: Integer;
Path: string;
begin
Icon := -1;
Path := GetSpecialFolderPath(CSIDL_DESKTOPDIRECTORY);
if Path <> '' then
Icon := GetFolderIcon(Path);
end;
Related
In a 32-bit VCL Application in Windows 10 in Delphi 11 Alexandria, I am trying to get the window handle of a running main task from the task's module path:
type
TFindWindowRec = record
ModuleToFind: string;
FoundHWnd: HWND;
end;
function EnumWindowsCallBack(aHandle: HWND; var FindWindowRec: TFindWindowRec): BOOL; stdcall;
const
C_FileNameLength = 256;
var
WinFileName: string;
PID, hProcess: DWORD;
Len: Byte;
begin
Result := True;
SetLength(WinFileName, C_FileNameLength);
GetWindowThreadProcessId(aHandle, PID);
hProcess := OpenProcess(PROCESS_ALL_ACCESS, False, PID);
Len := GetModuleFileNameEx(hProcess, 0, PChar(WinFileName), C_FileNameLength);
if Len > 0 then
begin
SetLength(WinFileName, Len);
if SameText(WinFileName, FindWindowRec.ModuleToFind) then
begin
Result := False;
FindWindowRec.FoundHWnd := aHandle;
end;
end;
end;
var
FindWindowRec: TFindWindowRec;
function TformMain.GetmainWindowHandleFRomProcessPath(aProcessPath: string): HWND;
begin
Result := 0;
CodeSite.Send('TformMain.GetmainWindowHandleFRomProcessPath: aProcessPath', aProcessPath);
FindWindowRec.ModuleToFind := aProcessPath;
FindWindowRec.FoundHWnd := 0;
EnumWindows(#EnumWindowsCallback, Integer(#FindWindowRec));
if FindWindowRec.FoundHWnd <> 0 then
begin
Result := FindWindowRec.FoundHWnd;
CodeSite.Send('TformMain.GetmainWindowHandleFRomProcessPath: Result', Result);
end;
end;
When I do this with:
GetmainWindowHandleFRomProcessPath('c:\windows\system32\notepad.exe');
... then I get the correct window handle.
When I do this with:
GetmainWindowHandleFRomProcessPath('C:\Program Files (x86)\Embarcadero\Studio\22.0\bin\bds.exe');
... then I get a WRONG (non-existing) window handle!
Why is this happening? How do I get the correct window handle?
The discussion with Remy and Andreas lead me to this successful working answer:
type
TFindWindowRec = record
ModuleToFind: string;
FoundHWnd: HWND;
end;
// The `RzShellUtils` unit is from Ray Konopka's Signature Library available from GetIt:
function PathsAreSamePIDL(const Path1, Path2: string): Boolean;
begin
var AIL1: PItemIdList;
var AIL2: PItemIdList;
RzShellUtils.ShellGetIdListFromPath(Path1, AIL1);
RzShellUtils.ShellGetIdListFromPath(Path2, AIL2);
var CompResult:= RzShellUtils.CompareAbsIdLists(AIL1, AIL2);
Result := CompResult = 0;
end;
function EnumWindowsCallBack(aHandle: HWND; var FindWindowRec: TFindWindowRec): BOOL; stdcall;
const
C_FileNameLength = MAX_PATH;
PROCESS_QUERY_LIMITED_INFORMATION = $1000;
var
WinFileName: string;
PID, hProcess: DWORD;
Len: Byte;
begin
Result := True;
SetLength(WinFileName, C_FileNameLength);
GetWindowThreadProcessId(aHandle, PID);
hProcess := OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, PID);
Len := GetModuleFileNameEx(hProcess, 0, PChar(WinFileName), C_FileNameLength);
CloseHandle(hProcess);
if Len > 0 then
begin
SetLength(WinFileName, Len);
//if SameText(WinFileName, FindWindowRec.ModuleToFind) then
if PathsAreSamePIDL(WinFileName, FindWindowRec.ModuleToFind) then
begin
var IsVisible := IsWindowVisible(aHandle);
if not IsVisible then EXIT;
var IsOwned := GetWindow(aHandle, GW_OWNER) <> 0;
if IsOwned then EXIT;
var IsAppWindow := GetWindowLongPtr(aHandle, GWL_EXSTYLE) and WS_EX_APPWINDOW <> 0;
if not IsAppWindow then EXIT;
Result := False;
FindWindowRec.FoundHWnd := aHandle;
end;
end;
end;
function TformMain.GetMainWindowHandleFromProcessPath(aProcessPath: string): HWND;
var
FindWindowRec: TFindWindowRec;
begin
Result := 0;
FindWindowRec.ModuleToFind := aProcessPath;
FindWindowRec.FoundHWnd := 0;
EnumWindows(#EnumWindowsCallback, LPARAM(#FindWindowRec));
if FindWindowRec.FoundHWnd <> 0 then
begin
Result := FindWindowRec.FoundHWnd;
end;
end;
I don't understand why the person who moved the discussion to another page deleted the latest comments. Was there anything forbidden in those deleted comments?
Again: Thank you to Remy and Andreas!
I have the following code that changes path of one shortcut. Happens that when path is changed, the icon also is updated to icon of new application.
How change path wihout update icon of shortcut?
uses
ActiveX,
ComObj,
ShlObj;
...
function GetDesktopFolder: string;
var
buf: array[0..MAX_PATH] of Char;
pidList: PItemIDList;
begin
Result := '';
SHGetSpecialFolderLocation(Application.Handle, CSIDL_DESKTOP, pidList);
if (pidList <> nil) then
if (SHGetPathFromIDList(pidList, buf)) then
Result := buf;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
MyObject: IUnknown;
MySLink: IShellLink;
MyPFile: IPersistFile;
LnkPath, sExePath, sParams: string;
begin
sParams := '';
sExePath := 'C:\Program Files\Google\Chrome\Application\chrome.exe';
LnkPath := GetDesktopFolder + '\Target.lnk';
MyObject := CreateComObject(CLSID_ShellLink);
MySLink := MyObject as IShellLink;
MyPFile := MyObject as IPersistFile;
with MySLink do
begin
SetDescription('');
SetPath(PWideChar(sExePath));
SetArguments(PWideChar(sParams));
SetWorkingDirectory(PWideChar(ExtractFilePath(sExePath)));
SetIconLocation(PWideChar(''), 0);
end;
MyPFile.Save(PWChar(WideString(LnkPath)), False);
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, PWideChar(LnkPath), nil);
end;
You can't prevent the icon from being updated.
What you can do is retrieve the current icon via IShellLink.GetIconLocation() before setting the new path, and then you can restore the icon afterwards, eg:
function GetDesktopFolder(Wnd: HWND = 0): string;
var
buf: array[0..MAX_PATH] of Char;
begin
if Wnd = 0 then Wnd := Application.Handle;
if Succeeded(SHGetFolderPath(Wnd, CSIDL_DESKTOP, 0, SHGFP_TYPE_CURRENT, buf)) then
Result := IncludeTrailingPathDelimiter(buf)
else
Result := '';
end;
procedure TForm1.Button1Click(Sender: TObject);
var
MySLink: IShellLink;
MyPFile: IPersistFile;
sLnkPath, sExePath, sParams: string;
szIconPath: array[0..MAX_PATH] of Char;
iIconIndex: Integer;
bHasIcon: Boolean;
begin
sParams := '';
sExePath := 'C:\Program Files\Google\Chrome\Application\chrome.exe';
sLnkPath := GetDesktopFolder(Handle) + 'Target.lnk';
MySLink := CreateComObject(CLSID_ShellLink) as IShellLink;
MyPFile := MySLink as IPersistFile;
if Succeeded(MyPFile.Load(PChar(sLnkPath), STGM_READ)) then
begin
MySLink.Resolve(Handle, 0);
bHasIcon := Succeeded(MySLink.GetIconLocation(szIconPath, Length(szIconPath), #iIconIndex));
end;
with MySLink do
begin
SetDescription(PChar(''));
SetPath(PChar(sExePath));
SetArguments(PChar(sParams));
SetWorkingDirectory(PChar(ExtractFilePath(sExePath)));
if bHasIcon then
SetIconLocation(szIconPath, iIconIndex)
else
SetIconLocation(PChar(''), 0);
end;
MyPFile.Save(PChar(sLnkPath), False);
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, PChar(sLnkPath), nil);
end;
I want associate my .jar file to open with java.exe using Windows registry and have a doubt about how return the complete path of java.exe file ignoring java version installed on computer.
Ex:
in my case i have:
C:\Program Files\Java\jdk1.7.0_45\bin\java.exe
then how access java.exe file ignoring this part 1.7.0_45?
uses
Windows, Registry;
function GetProgramFilesDir: string;
var
reg: TRegistry;
begin
reg := TRegistry.Create;
try
reg.RootKey := HKEY_LOCAL_MACHINE;
reg.OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion', False);
Result := reg.ReadString('ProgramFilesDir');
finally
reg.Free;
end;
end;
procedure RegisterFileType(cMyExt, cMyFileType, ExeName: string);
var
reg: TRegistry;
begin
reg := TRegistry.Create;
try
reg.RootKey := HKEY_CURRENT_USER;
if reg.OpenKey('\Software\Classes\.jar', True) then
reg.WriteString('', 'MyAppDataFile');
if reg.OpenKey('\Software\Classes\MyAppDataFile', True) then
reg.WriteString('', 'myappname');
if reg.OpenKey('\Software\Classes\MyAppDataFile\DefaultIcon', True) then
reg.WriteString('', GetProgramFilesDir + '\Java\jdk1.7.0_45\bin\java.exe');
if reg.OpenKey('\Software\Classes\MyAppDataFile\shell\open\command', True)
then
reg.WriteString('', GetProgramFilesDir + '\Java\jdk1.7.0_45\bin\java.exe "%1"');
finally
reg.Free;
end;
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, 0, 0);
end;
Don't use the Registry to discover the path to system folders, like Program Files. Use SHGetFolderPath() or SHGetKnownFolderPath() instead, eg:
function GetProgramFilesDir: string;
var
path: array[MAX_PATH] of Char;
begin
if SHGetFolderPath(0, CSIDL_PROGRAM_FILES, 0, SHGFP_TYPE_CURRENT, path) = S_OK then
Result := IncludeTrailingPathDelimiter(path)
else
Result := '';
end;
function GetProgramFilesDir: string;
var
path: PChar;
begin
if SHGetKnownFolderPath(FOLDERID_ProgramFiles, 0, 0, path) = S_OK then
begin
try
Result := IncludeTrailingPathDelimiter(path);
finally
CoTaskMemFree(path);
end;
end else
Result := '';
end;
That being said, to get the current installed path of java.exe, there are a few options you can try:
check if the %JAVA_HOME% environment variable is set.
check the HKLM\SOFTWARE\JavaSoft\Java Runtime Environment\<version> Registry key for a JavaHome value (there may be multiple <version> subkeys!).
search the %PATH% environment variable for any listed folder that has java.exe in it (there may be multiple folders!). You can parse the %PATH% yourself manually, or you can use SearchPath() with its lpPath parameter set to NULL (if you only care about the first copy of java.exe found).
function GetJavaPathViaEnvironment: string;
begin
Result := GetEnvironmentVariable('JAVA_HOME');
if Result <> '' then
begin
Result := IncludeTrailingPathDelimiter(Result) + 'bin' + PathDelim + 'java.exe';
// if not FileExists(Result) then Result := '';
end;
end;
function GetJavaPathViaRegistry: string;
const
JAVA_KEY: string = '\SOFTWARE\JavaSoft\Java Runtime Environment\';
Wow64Flags: array[0..2] of DWORD = (0, KEY_WOW64_32KEY, KEY_WOW64_64KEY);
var
reg: TRegistry;
s: string;
i: integer;
begin
Result := '';
reg := TRegistry.Create;
try
reg.RootKey := HKEY_LOCAL_MACHINE;
for i := Low(Wow64Flags) to High(Wow64Flags) do
begin
reg.Access := (reg.Access and not KEY_WOW64_RES) or Wow64Flags[i];
if reg.OpenKeyReadOnly(JAVA_KEY) then
begin
s := reg.ReadString('CurrentVersion');
if s <> '' then
begin
if reg.OpenKeyReadOnly(s) then
begin
s := reg.ReadString('JavaHome');
if s <> '' then
begin
Result := IncludeTrailingPathDelimiter(s) + 'bin' + PathDelim + 'java.exe';
// if not FileExists(Result) then Result := '' else
Exit;
end;
end;
end;
reg.CloseKey;
end;
end;
finally
reg.Free;
end;
end;
function GetJavaPathViaSearchPath: string;
var
path: array[0..MAX_PATH] of Char;
s: string;
len: DWORD;
begin
Result := '';
len := SearchPath(nil, 'java.exe', nil, Length(path), path, PPChar(nil)^);
if len <= Length(path) then
SetString(Result, path, len)
else
begin
repeat
SetLength(s, len);
len := SearchPath(nil, 'java.exe', nil, len, PChar(s), PPChar(nil)^);
until len <= Length(s);
SetString(Result, PChar(s), len);
end;
end;
function GetJavaPath: string;
begin
Result := GetJavaPathViaEnvironment;
if Result = '' then
Result := GetJavaPathViaRegistry;
if Result = '' then
Result := GetJavaPathViaSearchPath;
end;
Also, don't forget that paths with spaces must be wrapped in double-quotes. You can use Delphi's AnsiQuotedStr() to help you with that, eg:
reg.WriteString('', AnsiQuotedStr(GetJavaPath, '"') + ' "%1"');
I want to get and show the name and extension of selected file in explorer by delphi7.
I use below code for show caption of active window but i need selected file name in active window.
function ActiveCaption: string;
var
Handle: THandle;
Len: LongInt;
Title: string;
begin
Result := '';
Handle := GetForegroundWindow;
if Handle <> 0 then
begin
Len := GetWindowTextLength(Handle) + 1;
SetLength(Title, Len);
GetWindowText(Handle, PChar(Title), Len);
ActiveCaption := TrimRight(Title);
end;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Label1.Caption := ActiveCaption;
end;
The only way I know of is to use the Active-X IShellWindows and IWebBrowser Interfaces to to that.
First, you have to import the "Microsoft Internet Controls" Active-X (via the Component Menu). By that you will get a unit called "SHDocVW_TLB". Put this unit and the ActiveX unit in your uses clause.
Than you can use the following two functions to retrieve the selected file from the window handle provided:
The first function does a rough test if the given handle belongs to an explorer window
function isexplorerwindow(exwnd: hwnd): boolean;
var
p: array[0..max_path] of Char;
begin
GetClassName(exwnd, p, SizeOf(p));
result := ((strcomp(p, 'CabinetWClass') = 0) or (strcomp(p, 'ExploreWClass') = 0));
end;
And the second function retrieves the name of the nth selected file:
function getexplorerselectedfile(exwnd: hwnd; nr: integer): string;
var
pvShell: IShellWindows;
pvWeb2: IWebBrowser2;
ovIE: OleVariant;
psp: IServiceProvider;
psb: IShellBrowser;
isw: IShellView;
ido: IDataObject;
FmtEtc: TFormatEtc;
Medium: TStgMedium;
dwcount: integer;
n: integer;
p: array[0..max_path] of Char;
s: string;
found: boolean;
begin
found := false;
result := '';
s :='';
try
pvShell := CoShellWindows.Create;
for dwcount := 0 to Pred(pvShell.count) do
begin
ovIE := pvShell.Item(dwcount);
if (ovIE.hwnd = exwnd) or ((exwnd = 0) and isexplorerwindow(ovIE.hwnd)) then
begin
found := true;
if (IDispatch(ovIE).QueryInterface(IWebBrowser2, pvWeb2) = S_OK) then
begin
psp := (pvWeb2 as IServiceProvider);
psp.QueryService(IID_IShellBrowser, IID_IShellBrowser, psb);
psb.QueryActiveShellView(isw);
if isw.GetItemObject(SVGIO_SELECTION, IDataObject, pointer(ido)) = S_OK then
begin
try
FmtEtc.cfFormat := CF_HDROP;
FmtEtc.ptd := nil;
FmtEtc.dwAspect := DVASPECT_CONTENT;
FmtEtc.lindex := -1;
FmtEtc.tymed := TYMED_HGLOBAL;
ido.GetData(FmtEtc, Medium);
GlobalLock(Medium.hGlobal);
try
n := DragQueryFile(Medium.hGlobal, $FFFFFFFF, nil, 0);
if nr < n then
begin
DragQueryFile(Medium.hGlobal, nr, p, max_path);
s := strpas(p);
end;
finally
DragFinish(Medium.hGlobal);
GlobalUnLock(Medium.hGlobal);
end;
finally
ReleaseStgMedium(Medium);
end;
end;
pvWeb2 := nil;
end;
end;
ovIE := Unassigned;
if found then
break;
end;
pvShell := nil;
finally
result := s;
end;
end;
To test this code create a new project and place a button and a memo on the form.
Add the following units to the uses clause:
USES SHDocVW_TLB, ShlObj, activex, shellapi;
And add this code to the button event handler:
PROCEDURE TForm2.Button1Click(Sender: TObject);
VAR
wnd, exwnd: hwnd;
n: integer;
s: STRING;
BEGIN
exwnd := 0;
wnd := getwindow(getdesktopwindow, gw_child);
REPEAT
IF isexplorerwindow(wnd) THEN
BEGIN
exwnd := wnd;
break;
END;
wnd := getwindow(wnd, gw_hwndnext);
UNTIL (wnd = 0) OR (exwnd <> 0);
IF exwnd <> 0 THEN
BEGIN
n := 0;
REPEAT
s := getexplorerselectedfile(exwnd, n);
memo1.Lines.Add(s);
inc(n);
UNTIL s = '';
END;
END;
If you press the button, the memo will contain the selected files of the first open explorer window it finds. Of course you should have an explorer window open with at least one file selected.
In Delphi XE7, I use this code to create a SHELL LINK pointing to a specific folder. This folder is displayed in Windows Explorer with a custom folder icon defined by a desktop.ini file inside this folder. The SHELL LINK should be created with the icon parameters found in the desktop.ini file, i.e. pointing to the same icon resource as the desktop.ini file. So here is the code:
function GetDesktopIniIconDataFromFolder(const APath: string; var VIconIndex: Integer): string;
var
DeskTopIniFile: string;
DesktopIni: System.IniFiles.TIniFile;
ThisIconFileStr, ThisIconIndexStr: string;
ThisIconIndexInt: Integer;
begin
Result := '';
if DirectoryExists(APath) then
begin
DeskTopIniFile := IncludeTrailingPathDelimiter(APath) + 'Desktop.ini';
if FileExists(DeskTopIniFile) then
begin
DesktopIni := System.IniFiles.TIniFile.Create(DeskTopIniFile);
try
ThisIconFileStr := DesktopIni.ReadString('.ShellClassInfo', 'IconFile', '');
if ThisIconFileStr <> '' then
begin
ThisIconIndexStr := DesktopIni.ReadString('.ShellClassInfo', 'IconIndex', '');
if ThisIconIndexStr <> '' then
begin
ThisIconIndexInt := System.SysUtils.StrToIntDef(ThisIconIndexStr, MaxInt);
if ThisIconIndexInt <> MaxInt then
begin
Result := ThisIconFileStr;
VIconIndex := ThisIconIndexInt;
end;
end;
end;
finally
DesktopIni.Free;
end;
end;
end;
end;
function MyCreateShellLink(const LinkFileName, AssocFileName, Desc, WorkDir,
Args, IconFileName: string; const IconIdx: Integer): Boolean;
var
SL: Winapi.ShlObj.IShellLink;
PF: Winapi.ActiveX.IPersistFile;
begin
Result := False;
Winapi.ActiveX.CoInitialize(nil);
try
if Winapi.ActiveX.Succeeded(
Winapi.ActiveX.CoCreateInstance(
Winapi.ShlObj.CLSID_ShellLink,
nil,
Winapi.ActiveX.CLSCTX_INPROC_SERVER,
Winapi.ShlObj.IShellLink, SL
)
) then
begin
SL.SetPath(PChar(AssocFileName));
SL.SetDescription(PChar(Desc));
SL.SetWorkingDirectory(PChar(WorkDir));
SL.SetArguments(PChar(Args));
if (IconFileName <> '') and (IconIdx >= 0) then
SL.SetIconLocation(PChar(IconFileName), IconIdx);
PF := SL as Winapi.ActiveX.IPersistFile;
Result := Winapi.ActiveX.Succeeded(
PF.Save(PWideChar(WideString(LinkFileName)), True)
);
end;
finally
Winapi.ActiveX.CoUninitialize;
end;
end;
// Usage:
var
IconFile: string;
IconIndex: Integer;
begin
IconFile := GetDesktopIniIconDataFromFolder(APath, IconIndex);
if IconFile <> '' then
MyCreateShellLink(ALinkFileName, ATargetFileName, ADescription, AWorkDir, AArgs, IconFile, IconIndex);
This works well, EXCEPT in cases where the IconIndex in the desktop.ini file is a negative value (which means the negative value indicates a resource ID rather than an ordinal value), like in this example:
[.ShellClassInfo]
InfoTip=#Shell32.dll,-12688
IconFile=%SystemRoot%\system32\mydocs.dll
IconIndex=-101
In this case the created SHELL LINK is erroneous, which means the Shell LINK does not contain the correct icon reference.
So how can I translate the negative IconIndex value -101 from the desktop.ini file to a value I can use in the MyCreateShellLink function?
If you want to use negative IconIndex then pass FULL path of icon to SetIconLocation. Use the following variant of GetDesktopIniIconDataFromFolder:
function GetDesktopIniIconDataFromFolder(const APath: string; var AIconIndex: Integer): string;
var
Setting: TSHFolderCustomSettings;
begin
ZeroMemory(#Setting, SizeOf(Setting));
Setting.dwSize := SizeOf(Setting);
Setting.dwMask := FCSM_ICONFILE;
SetLength(Result, MAX_PATH + 1);
Setting.pszIconFile := PChar(Result);
Setting.cchIconFile := MAX_PATH;
if Succeeded(SHGetSetFolderCustomSettings(#Setting, PChar(APath), FCS_READ)) then
begin
Result := PChar(Result);
AIconIndex := Setting.iIconIndex;
end
else
Result := '';
end;
It automatically expands variables of icon path. Also it supports IconResource parameter of desktop.ini.
Variant 2 (universal)
function GetObjectIconFileName(AParentWnd: HWND; const AName: UnicodeString; var AIndex: Integer): UnicodeString;
var
Desktop: IShellFolder;
Attr: DWORD;
Eaten: DWORD;
IDList: PItemIDList;
Parent: IShellFolder;
Child: PItemIDList;
ExtractIconW: IExtractIconW;
ExtractIconA: IExtractIconA;
AnsiResult: AnsiString;
Flags: DWORD;
Ext: UnicodeString;
BuffSize: DWORD;
P: Integer;
begin
OleCheck(SHGetDesktopFolder(Desktop));
try
Attr := SFGAO_STREAM;
OleCheck(Desktop.ParseDisplayName(AParentWnd, nil, PWideChar(AName), Eaten, IDList, Attr));
try
OleCheck(SHBindToParent(IDList, IShellFolder, Pointer(Parent), Child));
if Succeeded(Parent.GetUIObjectOf(AParentWnd, 1, Child, IExtractIconW, nil, ExtractIconW)) then
try
SetLength(Result, MAX_PATH + 1);
if (ExtractIconW.GetIconLocation(0, PWideChar(Result), MAX_PATH, AIndex, Flags) = S_OK) then
begin
Result := PWideChar(Result);
if // (Flags and GIL_NOTFILENAME = 0) and // Dont know why shell return GIL_NOTFILENAME flag
FileExists(Result) then
Exit
else
Result := '';
end
else
Result := '';
finally
ExtractIconW := nil;
end
else
if Succeeded(Parent.GetUIObjectOf(AParentWnd, 1, Child, IExtractIconA, nil, ExtractIconA)) then
try
SetLength(AnsiResult, MAX_PATH + 1);
if (ExtractIconA.GetIconLocation(0, PAnsiChar(AnsiResult), MAX_PATH, AIndex, Flags) = S_OK) then
begin
Result := UnicodeString(PAnsiChar(AnsiResult));
if // (Flags and GIL_NOTFILENAME = 0) and // Dont know why shell return GIL_NOTFILENAME flag
FileExists(Result) then
Exit
else
Result := '';
end
else
Result := '';
finally
ExtractIconA := nil;
end;
finally
CoTaskMemFree(IDList);
end;
finally
Desktop := nil;
end;
if Attr and SFGAO_STREAM <> 0 then
begin
Ext := ExtractFileExt(AName);
if (AssocQueryStringW(ASSOCF_NONE, ASSOCSTR_DEFAULTICON, PWideChar(Ext), nil, nil, #BuffSize) = S_FALSE) and (BuffSize > 1) then
begin
SetLength(Result, BuffSize - 1);
if Succeeded(AssocQueryStringW(ASSOCF_NONE, ASSOCSTR_DEFAULTICON, PWideChar(Ext), nil, PWideChar(Result), #BuffSize)) then
begin
AIndex := 0;
P := LastDelimiter(',', Result);
if P > 0 then
begin
AIndex := StrToIntDef(Copy(Result, P + 1, MaxInt), MaxInt);
if AIndex <> MaxInt then
Delete(Result, P, MaxInt)
else
AIndex := 0;
end;
Exit;
end;
end;
end;
Result := '';
end;