Jump to content

Get associated icon for a file in any size


Recommended Posts

This whole thing was prompted by Yashied asking for help with SHGetFileInfo(). I've delved the depths of the API etc. and hopefully managed to come up with a function that will retrieve the icon for a file in any size that you ask for. The function in question is _GetIconForFile() which you can find in the code below along with an example of how to use it. The following code should be saved in to GetIconForFile.au3:

#include-once

Func _GetIconForFile($sFile, $nSize = 32)
    Local $tSHFILEINFO, $aDllRet, $sIconFile = "", $nIconNumber = 0
    Local $nDot, $sExtension, $sType, $sDefaultIcon
    If FileExists($sFile) And (StringInStr(FileGetAttrib($sFile), "D") > 0) Then
        $tSHFILEINFO = DllStructCreate("ptr;int;dword;char[260];char[80]")
        $aDllRet = DllCall("shell32.dll", "ptr", "SHGetFileInfo", "str", $sFile, "dword", 0x80, "ptr", DllStructGetPtr($tSHFILEINFO), "int", DllStructGetSize($tSHFILEINFO), "int", 0x1000)
        $sIconFile = DllStructGetData($tSHFILEINFO, 4)
        $nIconNumber = DllStructGetData($tSHFILEINFO, 2)
    Else
        $nDot = StringInStr($sFile, ".", 0, -1)
        If $nDot = 0 Then Return SetError(1, 0, 0)
        $sExtension = StringMid($sFile, $nDot)
        $sType = RegRead("HKEY_CLASSES_ROOT\" & $sExtension, "")
        If $sType = "" Then Return SetError(2, 0, 0)
        $sDefaultIcon = RegRead("HKEY_CLASSES_ROOT\" & $sType & "\DefaultIcon", "")
        If $sDefaultIcon = "" Then Return SetError(2, 0, 0)
        If $sDefaultIcon = "%1" Or $sDefaultIcon = """%1""" Then $sDefaultIcon = $sFile & ",0"
        If StringInStr($sDefaultIcon, ",") > 0 Then
            $sIconFile = StringLeft($sDefaultIcon, StringInStr($sDefaultIcon, ",") - 1)
            $nIconNumber = Number(StringMid($sDefaultIcon, StringInStr($sDefaultIcon, ",") + 1))
        Else
            $sIconFile = $sDefaultIcon
        EndIf
    EndIf
    If StringLeft($sIconFile, 1) = """" Then $sIconFile = StringMid($sIconFile, 2, StringLen($sIconFile) - 2)
    Local $nOldSetting = Opt("ExpandEnvStrings", 1)
    $aDllRet = DllCall("kernel32.dll", "long", "LoadLibraryEx", "str", $sIconFile, "long", 0, "long", 2)
    Opt("ExpandEnvStrings", $nOldSetting)
    If $aDllRet[0] = 0 Then Return SetError(3, 0, 0)
    Local $hModule = $aDllRet[0], $hIcon
    If $nIconNumber < 0 Then
        $nIconNumber = Abs($nIconNumber)
        $hIcon = _GetAssociatedIconHandle($hModule, $nIconNumber, 14, $nSize)
    Else
        $hIcon = _GetAssociatedIconBySize($hModule, $nIconNumber, $nSize)
    EndIf
    DllCall("kernel32.dll", "long", "FreeLibrary", "long", $hModule)
    If $hIcon = 0 Then Return SetError(4, 0, 0)
    Return SetError(0, 0, $hIcon)
EndFunc ;==>_GetIconForFile

Func _GetAssociatedIconHandle($hModule, $vNameOrID, $nType, $nSize)
    Local $aDllRet, $hResInfo, $hResHandle, $hResPtr, $nResLen, $pResBits, $nResID
    Local $lngResInfo, $lngResHandle
    Local $lngResPtr, $lngResLen, $lngResBuff
    Local $lngResID, $retval
    If IsString($vNameOrID) Then
        $aDllRet = DllCall("kernel32.dll", "long", "FindResource", "long", $hModule, "str", $vNameOrID, "long", $nType)
    Else
        $aDllRet = DllCall("kernel32.dll", "long", "FindResource", "long", $hModule, "long", $vNameOrID, "long", $nType)
    EndIf
    $hResInfo = $aDllRet[0]
    If $hResInfo = 0 Then Return 0
    $aDllRet = DllCall("kernel32.dll", "long", "LoadResource", "long", $hModule, "long", $hResInfo)
    $hResHandle = $aDllRet[0]
    If $hResHandle = 0 Then Return 0
    $aDllRet = DllCall("kernel32.dll", "long", "LockResource", "long", $hResHandle)
    $hResPtr = $aDllRet[0]
    If $hResPtr = 0 Then Return 0
    $aDllRet = DllCall("kernel32.dll", "long", "SizeofResource", "long", $hModule, "long", $hResInfo)
    $nResLen = $aDllRet[0]
    If $nResLen = 0 Then Return 0
    $pResBits = DllStructCreate("byte[" & $nResLen & "]", $hResPtr)
    If $nType = 14 Then
        $aDllRet = DllCall("user32.dll", "long", "LookupIconIdFromDirectoryEx", "ptr", DllStructGetPtr($pResBits), "int", 1, "long", $nSize, "long", $nSize, "long", 0)
        $nResID = $aDllRet[0]
        If $nResID = 0 Then Return 0
        Return _GetAssociatedIconHandle($hModule, $nResID, 3, $nSize)
    EndIf
    $aDllRet = DllCall("user32.dll", "long", "CreateIconFromResourceEx", "ptr", DllStructGetPtr($pResBits), "long", $nResLen, "int", 1, "long", 0x30000, "long", $nSize, "long", $nSize, "long", 0)
    Return $aDllRet[0]
EndFunc ;==>_GetAssociatedIconHandle

Func _GetAssociatedIconBySize($hModule, $nIcon, $nSize)
    Local $tHandle = DllStructCreate("long;long;long")
    DllStructSetData($tHandle, 1, 0); Handle
    DllStructSetData($tHandle, 2, $nIcon); Icon Number
    DllStructSetData($tHandle, 3, $nSize); Icon Desired Size
    Local $hCallback = DllCallbackRegister("_EnumResNamesProc", "long", "long;long;long;long")
    DllCall("kernel32.dll", "long", "EnumResourceNames", "long", $hModule, "long", 14, "long", DllCallbackGetPtr($hCallback), "long", DllStructGetPtr($tHandle))
    Return DllStructGetData($tHandle, 1)
EndFunc ;==>_GetAssociatedIconBySize

Func _EnumResNamesProc($hModule, $lpszType, $lpszName, $lParam)
    Local $sName, $nName, $bytData, $lngLen, $hStruct
    Local $tHandle = DllStructCreate("long;long;long", $lParam)
    If DllStructGetData($tHandle, 2) = 0 Then
        If BitAND($lpszName, 0xffff0000) = 0 Then
            $nName = BitAND($lpszName, 0xffff)
            DllStructSetData($tHandle, 1, _GetAssociatedIconHandle($hModule, $nName, 14, DllStructGetData($tHandle, 3)))
        Else
            $hStruct = DllStructCreate("char[256]", $lpszName)
            $sName = DllStructGetData($hStruct, 1)
            DllStructSetData($tHandle, 1, _GetAssociatedIconHandle($hModule, $sName, 14, DllStructGetData($tHandle, 3)))
        EndIf
        Return 0
    EndIf
    DllStructSetData($tHandle, 2, DllStructGetData($tHandle, 2) - 1)
    Return 1
EndFunc ;==>_EnumResNamesProc

Edited by WideBoyDixon
Link to comment
Share on other sites

With _SetHIcon().

#include "GetIconForFile.au3"
#Include <Constants.au3>
#Include <SendMessage.au3>
#Include <StaticConstants.au3>
#Include <WinAPI.au3>


_Main()

Exit

Func _Main()
    Local $nSize = 48
    Local $hGUI = GUICreate("Icon", 128, 192)
    Local $picIcon = GUICtrlCreateIcon("", 0, 40, 40, $nSize, $nSize)
    Local $btnNext = GUICtrlCreateButton("Draw Next", 16, 128, 96, 24)
    GUISetState()
    Local $nIcon = 0

    Local $hDC = DllCall("user32.dll", "hwnd", "GetDC", "hwnd", GUICtrlGetHandle($picIcon))

    Local $nMsg, $sFile, $hIcon
    Do
        $nMsg = GUIGetMsg()
        If $nMsg = $btnNext Then
            DllCall("user32.dll", "int", "InvalidateRect", "hwnd", $hGUI, "ptr", 0, "int", 1)
            Switch $nIcon
                Case 0
                    $sFile = @WindowsDir & "\Clock.avi"
                Case 1
                    $sFile = @AutoItExe
                Case 2
                    $sFile = @WindowsDir
                Case 3
                    $sFile = @MyDocumentsDir & "\My Pictures"
                Case 4
                    $sFile = "a.txt"
            EndSwitch
            $hIcon = _GetIconForFile($sFile, $nSize)
            If $hIcon <> 0 Then _SetHIcon($picIcon, $hIcon)
            $nIcon = Mod($nIcon + 1, 5)
        EndIf
        Sleep(10)
    Until $nMsg = -3

    GUIDelete()
EndFunc  ;==>_Main

func _SetHIcon($controlID, $hIcon)
    
    const $STM_SETIMAGE = 0x0172
    
    local $hWnd, $Style

    $hWnd = GUICtrlGetHandle($controlID)
    if $hWnd = 0 then
        return SetError(1, 0, 0)
    endif
    $Style = _WinAPI_GetWindowLong($hWnd, $GWL_STYLE)
    if @error then
        return SetError(1, 0, 0)
    endif
    _WinAPI_SetWindowLong($hWnd, $GWL_STYLE, BitOR($Style, Hex($SS_ICON)))
    if @error then
        return SetError(1, 0, 0)
    endif
    _WinAPI_DeleteObject(_SendMessage($hWnd, $STM_SETIMAGE, $IMAGE_ICON, 0))
    _SendMessage($hWnd, $STM_SETIMAGE, $IMAGE_ICON, $hIcon)
    if @error then
        return SetError(1, 0, 0)
    endif
    return 1
endfunc; _SetHIcon
Link to comment
Share on other sites

Yes ... definitely better since now it doesn't lose the picture when the window loses focus ^_^

I've tweaked the first post to reflect this and also removed some redundant calls (i.e. InvalidateRect)

WBD

Link to comment
Share on other sites

WideBoyDixon, see my version on this topic. Here I get the icons from the registry only.

#Include <Constants.au3>
#Include <GDIPlus.au3>
#Include <SendMessage.au3>
#Include <StaticConstants.au3>
#Include <WinAPI.au3>

dim $Ext[1] = [0]

$i = 2

while 1
    $Key = RegEnumKey('HKCR', $i)
    if (@error) or (StringLeft($Key, 1) <> '.') then
        exitloop
    endif
    $Ext[0] += 1
    redim $Ext[$Ext[0] + 1]
    $Ext[$Ext[0]] = $Key
    $i += 1
wend

GUICreate('Test', 120, 140)

$Reg = _GetRegDefIcon($Ext[1])
$Label = GUICtrlCreateLabel($Ext[1], 10, 16, 100, 20, 0x01)
$Icon = GUICtrlCreateIcon('', 0, 36, 44, 48, 48)
_SetIcon($Icon, $Reg[0], $Reg[1], 48, 48)
$Button = GUICtrlCreateButton('Next', 25, 106, 70, 23)
GUISetState()

$i = 1

while 1
    $Msg = GUIGetMsg()
    switch $Msg
        case -3
            exit
        case $Button
            $i += 1
            if $i > $Ext[0] then
                $i = 1
            endif
            GUICtrlSetData($Label, $Ext[$i])
            $Reg = _GetRegDefIcon($Ext[$i])
            _SetIcon($Icon, $Reg[0], $Reg[1], 48, 48)
    endswitch
wend

func _GetRegDefIcon($Path)
    
    const $DF_NAME  = @SystemDir & '\shell32.dll'
    const $DF_INDEX = 0
    
    local $filename, $name, $ext, $count, $curver, $defaulticon, $ret[2] = [$DF_NAME, $DF_INDEX]
    
    $filename = StringTrimLeft($Path, StringInStr($Path, '\', 0, -1))
    $count = StringInStr($filename, '.', 0, -1)
    if $count > 0 then
        $count = StringLen($filename) - $count + 1
    endif
    $name = StringStripWS(StringTrimRight($filename, $count), 3)
    $ext = StringStripWS(StringRight($filename, $count - 1), 8)
    if StringLen($ext) = 0 then
        return $ret
    endif
    $curver = StringStripWS(RegRead('HKCR\' & RegRead('HKCR\' & '.' & $ext, '') & '\CurVer', ''), 3)
    if (@error) or (StringLen($curver) = 0) then
        $defaulticon =  _WinAPI_ExpandEnvironmentStrings(StringReplace(RegRead('HKCR\' & RegRead('HKCR\' & '.' & $ext, '') & '\DefaultIcon', ''), '''', ''))
    else
        $defaulticon =  _WinAPI_ExpandEnvironmentStrings(StringReplace(RegRead('HKCR\' & $curver & '\DefaultIcon', ''), '''', ''))
    endif
    $count = StringInStr($defaulticon, ',', 0, -1)
    if $count > 0 then
        $count = StringLen($defaulticon) - $count
        $ret[0] = StringStripWS(StringTrimRight($defaulticon, $count + 1), 3)
        if $count > 0 then
            $ret[1] = StringStripWS(StringRight($defaulticon, $count), 8)
        endif
    else
        $ret[0] = StringStripWS(StringTrimRight($defaulticon, $count), 3)
    endif
    if StringLeft($ret[0], 1) = '%' then
        $count = DllCall('shell32.dll', 'int', 'ExtractIcon', 'int', 0, 'str', $Path, 'int', -1)
        if $count[0] = 0 then
            $ret[0] = $DF_NAME
            if StringLower($ext) = 'exe' then
                $ret[1] = 2
            else
                $ret[1] = 0
            endif
        else
            $ret[0] = StringStripWS($Path, 3)
            $ret[1] = 0
        endif
    else
        if (StringLen($ret[0]) > 0) and (StringInStr($ret[0], '\', 0) = 0) then
            $ret[0] = @SystemDir & '\' & $ret[0]
        endif
    endif
    if not FileExists($ret[0]) then
        $ret[0] = $DF_NAME
        $ret[1] = $DF_INDEX
    endif
;   if $ret[1] < 0 then
;       $ret[1] = - $ret[1]
;   else
;       $ret[1] = - $ret[1] - 1
;   endif
    return $ret
endfunc; _GetRegDefIcon

func _SetIcon($controlID, $sIcon, $iIndex, $iWidth, $iHeight)
    
    const $STM_SETIMAGE = 0x0172
    
    local $hWnd, $hIcon, $Style, $Error = false

    $hWnd = GUICtrlGetHandle($controlID)
    if $hWnd = 0 then
        return SetError(1, 0, 0)
    endif
    
    $hIcon = _WinAPI_PrivateExtractIcon($sIcon, $iIndex, $iWidth, $iHeight)
    if @error then
        return SetError(1, 0, 0)
    endif
    
    $Style = _WinAPI_GetWindowLong($hWnd, $GWL_STYLE)
    if @error then
        $Error = 1
    else
        _WinAPI_SetWindowLong($hWnd, $GWL_STYLE, BitOR($Style, Hex($SS_ICON)))
        if @error then
            $Error = 1
        else
            _WinAPI_DeleteObject(_SendMessage($hWnd, $STM_SETIMAGE, $IMAGE_ICON, 0))
            _SendMessage($hWnd, $STM_SETIMAGE, $IMAGE_ICON, _WinAPI_CopyIcon($hIcon))
            if @error then
                $Error = 1
            endif
        endif
    endif
    
    _WinAPI_DeleteObject($hIcon)
    
    return SetError($Error, 0, not $Error)
endfunc; _SetIcon

func _WinAPI_PrivateExtractIcon($sIcon, $iIndex, $iWidth, $iHeight)
    
    local $hIcon, $tIcon = DllStructCreate('hwnd'), $tID = DllStructCreate('hwnd')
    local $ret
    
    $ret = DllCall('user32.dll', 'int', 'PrivateExtractIcons', 'str', $sIcon, 'int', $iIndex, 'int', $iWidth, 'int', $iHeight, 'ptr', DllStructGetPtr($tIcon), 'ptr', DllStructGetPtr($tID), 'int', 1, 'int', 0)
    if (@error) or ($ret[0] = 0)then
        return SetError(1, 0, 0)
    endif
    
    $hIcon = DllStructGetData($tIcon, 1)
    
    if ($hIcon = Ptr(0)) or (not IsPtr($hIcon)) then
        return SetError(1, 0, 0)
    endif
    
    return SetError(0, 0, $hIcon)
endfunc; _WinAPI_PrivateExtractIcon
Link to comment
Share on other sites

Nice work! Are you interested too in getting the right icons for folders? I suspect you need to go back to SHGetFileInfo() for that information because it depends on the PIDL of the folder (I think).

WBD

Link to comment
Share on other sites

WideBoyDixon, see my version on this topic. Here I get the icons from the registry only.

Nice, really nice m8s ^_^. Had an error in your example, because the first key in my reg is "!ut_auto_file" and thus no ext was added! Also I guess it's much faster to redim the array in larger steps. Reduces array build time from ~400ms to ~50ms.

dim $Ext[500] = [0]
$Ext[0] = 0

$i = 1
$bFirstExtFound = False
while 1
    $Key = RegEnumKey('HKCR\', $i)
    if @error then exitloop
    if StringLeft($Key, 1) <> '.' And $bFirstExtFound = True then ExitLoop
    if StringLeft($Key, 1) = '.' then
        $bFirstExtFound = True
        $Ext[0] += 1
        $Ext[$Ext[0]] = $Key
        if $Ext[0] = UBound($ext)-1 then redim $Ext[ubound($Ext)+500]
    endif
    $i += 1
wend
ReDim $Ext[$Ext[0]+1]
Link to comment
Share on other sites

Nice, really nice m8s. Had an error in your example, because the first key in my reg is "!ut_auto_file" and thus no ext was added! Also I guess it's much faster to redim the array in larger steps. Reduces array build time from ~400ms to ~50ms.

Good point. Thanks.
Link to comment
Share on other sites

Nice work! Are you interested too in getting the right icons for folders? I suspect you need to go back to SHGetFileInfo() for that information because it depends on the PIDL of the folder (I think).

WBD

You are right, SHGetFileInfo() need to be added. Edited by Yashied
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...