Vba 未答复的旧帖子:Excel自定义图标与多个工作簿一起丢失

Vba 未答复的旧帖子:Excel自定义图标与多个工作簿一起丢失,vba,excel,winapi,hwnd,Vba,Excel,Winapi,Hwnd,我可以使用以下代码为Excel应用程序设置自定义图标。这将更改窗口的图标以及Windows任务栏中显示的图标: Public Const strIcon As String = "%SystemRoot%\system32\SHELL32.dll" ' Icon file Public Const IconIndex As Long = 137 Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal ClassNa

我可以使用以下代码为Excel应用程序设置自定义图标。这将更改窗口的图标以及Windows任务栏中显示的图标:

Public Const strIcon As String = "%SystemRoot%\system32\SHELL32.dll" ' Icon file
Public Const IconIndex As Long = 137

Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal ClassName As String, ByVal WindowName As String) As Long
Public Declare Function SendMessageA Lib "user32" (ByVal HWnd As Long, ByVal wMsg As Long, ByVal wParam As Integer, ByVal lParam As Long) As Long
Public Declare Function ExtractIconA Lib "shell32.dll" (ByVal hInst As Long, ByVal lpszExeFileName As String, ByVal nIconIndex As Long) As Long
Public Const ICON_SMALL As Long = 0&
Public Const ICON_BIG As Long = 1&
Public Const WM_SETICON As Long = &H80



Sub SetupIcon()
    SetIcon strIcon, IconIndex
End Sub

Sub SetIcon(FileName As String, Optional index As Long = 0)
    '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    ' SetIcon
    ' This procedure sets the icon in the upper left corner of
    ' the main Excel window. FileName is the name of the file
    ' containing the icon. It may be an .ico file, an .exe file,
    ' or a .dll file. If it is an .ico file, Index must be 0
    ' or omitted. If it is an .exe or .dll file, Index is the
    ' 0-based index to the icon resource.
    '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    #If VBA7 And Win64 Then
        ' 64 bit Excel
        Dim HWnd As LongPtr
        Dim HIcon As LongPtr
    #Else
        ' 32 bit Excel
        Dim HWnd As Long
        Dim HIcon As Long
    #End If
    Dim n As Long
    Dim s As String
    If Dir(FileName, vbNormal) = vbNullString Then
        ' file not found, get out
        Exit Sub
    End If
    ' get the extension of the file.
    n = InStrRev(FileName, ".")
    s = LCase(Mid(FileName, n + 1))
    ' ensure we have a valid file type
    Select Case s
        Case "exe", "ico", "dll"
            ' OK
        Case Else
            ' invalid file type
            Err.Raise 5
    End Select
    HWnd = Application.HWnd
    If HWnd = 0 Then
        Exit Sub
    End If
    HIcon = ExtractIconA(0, FileName, index)
    If HIcon <> 0 Then
        SendMessageA HWnd, WM_SETICON, ICON_SMALL, HIcon
    End If
End Sub
Public Const strIcon As String=“%SystemRoot%\system32\SHELL32.dll”图标文件
公共常数指数,长度=137
将函数findwindowlib“user32”别名“FindWindowA”(ByVal ClassName作为字符串,ByVal WindowName作为字符串)声明为
公共声明函数sendmagesa Lib“user32”(ByVal HWnd为Long,ByVal wMsg为Long,ByVal wParam为Integer,ByVal lParam为Long)为Long
公共声明函数ExtractIconA Lib“shell32.dll”(ByVal hInst为长,ByVal lpszExeFileName为字符串,ByVal nIconIndex为长)为长
公共常数图标\u小到等于0&
公共常数图标\u大到等于1&
公共常数WM_设置图标长度=&H80
子设置图标()
设置图标strIcon,图标索引
端接头
子集合图标(文件名为字符串,可选索引为Long=0)
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'设置图标
'此过程将在屏幕左上角设置图标
'打开Excel主窗口。FileName是文件的名称
'包含图标。它可能是一个.ico文件,一个.exe文件,
'或.dll文件。如果是.ico文件,则索引必须为0
”或省略。如果是.exe或.dll文件,则索引是
'从0开始索引到图标资源。
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
#如果是VBA7和Win64,则
'64位Excel
变暗HWnd为长PTR
Dim HIcon As LongPtr
#否则
'32位Excel
暗淡的HWnd尽可能长
暗希肯一样长
#如果结束
长
像线一样变暗
如果Dir(FileName,vbNormal)=vbNullString,则
'找不到文件,请退出
出口接头
如果结束
'获取文件的扩展名。
n=InStrRev(文件名“.”)
s=LCase(Mid(文件名,n+1))
'确保我们有一个有效的文件类型
选择案例s
案例“exe”、“ico”、“dll”
”“好的
其他情况
'无效的文件类型
呃,提高5分
结束选择
HWnd=Application.HWnd
如果HWnd=0,则
出口接头
如果结束
HIcon=ExtractIconA(0,文件名,索引)
如果是0那么
SendMessageA HWnd,WM_设置图标,图标小,HIcon
如果结束
端接头
但是,我注意到,如果将新工作簿添加到应用程序中,则自定义图标将丢失(至少在任务栏中),并恢复为默认的Excel图标

在网上搜索解决方案时,我发现了一个类似的问题:

当然,我通常不会发布与现有问题完全相同的新问题。然而,对于这个相关的问题,还没有(现成的)解决方案。我还注意到,这个问题是在2012年发布的,因此,从那时起,我们的社区很可能在专业知识和经验方面有所增长。他们很可能现在就在这里,他们知道如何解决这个问题,但根本没有看到这个问题。我希望社区能够原谅这个重复的问题(把它看作是对旧问题的碰撞)


有谁能提供一个解决方案吗?我的API知识几乎为零。谢谢。

当您启动Excel时,它会使用一个应用程序图标

它会一直使用它,直到您创建了Excel最初创建的工作簿之外的任何工作簿。然后它在任务栏上分解工作簿,得到两个带有工作簿图标的按钮

即使关闭第二个工作簿,第一个工作簿仍会使用工作簿图标。 当您关闭所有工作簿时,它将恢复为应用程序图标(您可以通过调用
设置图标
并关闭所有工作簿进行检查),但在创建任何工作簿后,它将切换回工作簿图标

您应该尝试枚举所有工作簿窗口,并更改它们的图标

我不确定这是否可以直接在VBA中完成,但您可以使用winapi函数
FindWindowEx
EnumChildWindows
GetWindow

Excel主窗口具有类名
XLMAIN
。它包含
XLDESK
,其中包含工作簿(
EXCEL7
)和其他子项。使用
Spy++
检查层次结构

此行为可能取决于任务栏设置和可用空间。若任务栏并没有分解按钮,它将显示应用程序图标


检查了一下,不幸的是它不工作了。它会更改工作簿窗口的图标(未最大化时),但任务栏上的图标保持不变


这是可行的,但有点粗俗。我使用的是硬编码的类名
mssdib
。这是Excel2007的实现细节,在其他版本中可能不起作用

'Doesn't work for me
'Public Const strIcon As String = "%SystemRoot%\system32\SHELL32.dll" ' Icon file
Public Const strIcon As String = "C:\Windows\system32\SHELL32.dll" ' Icon file

Public Const IconIndex As Long = 137

Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal ClassName As String, ByVal WindowName As String) As Long
Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWndParent As Long, ByVal hWndChildAfter As Long, ByVal lpszClassName As String, ByVal lpszCaption As String) As Long
' For 64 bit may need replacing with SetClassLongPtr
Declare Function SetClassLong Lib "user32" Alias "SetClassLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Const GCL_HICON As Long = -14
Const GCL_HICONSM As Long = -34
Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As Long, lpdwProcessId As Long) As Long

Public Declare Function SendMessageA Lib "user32" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Integer, ByVal lParam As Long) As Long
Public Declare Function ExtractIconA Lib "shell32.dll" (ByVal hInst As Long, ByVal lpszExeFileName As String, ByVal nIconIndex As Long) As Long
Public Const ICON_SMALL As Long = 0&
Public Const ICON_BIG As Long = 1&
Public Const WM_SETICON As Long = &H80


Sub SetupIcon()
    SetIcon strIcon, IconIndex
End Sub

Sub SetIcon(FileName As String, Optional index As Long = 0)
    #If VBA7 And Win64 Then
        ' 64 bit Excel
        Dim hwnd As LongPtr
        Dim DeskHWnd As LongPtr
        Dim Workbook As LongPtr
        Dim HIcon As LongPtr
    #Else
        ' 32 bit Excel
        Dim hwnd As Long
        Dim DeskHWnd As Long
        Dim Workbook As Long
        Dim HIcon As Long
    #End If
    Dim ThreadId As Long
    Dim n As Long
    Dim s As String
    If Dir(FileName, vbNormal) = vbNullString Then
        ' file not found, get out
        Exit Sub
    End If
    ' get the extension of the file.
    n = InStrRev(FileName, ".")
    s = LCase(Mid(FileName, n + 1))
    ' ensure we have a valid file type
    Select Case s
        Case "exe", "ico", "dll"
            ' OK
        Case Else
            ' invalid file type
            Err.Raise 5
    End Select
    hwnd = Application.hwnd
    If hwnd = 0 Then
        Exit Sub
    End If
    ThreadId = GetWindowThreadProcessId(hwnd, ByVal 0&)
    DeskHWnd = FindWindowEx(hwnd, 0, "XLDESK", vbNullString)
    If DeskHWnd = 0 Then
        Exit Sub
    End If

    HIcon = ExtractIconA(0, FileName, index)
    If HIcon = 0 Then
        Exit Sub
    End If

    SendMessageA hwnd, WM_SETICON, ICON_SMALL, HIcon
    SendMessageA hwnd, WM_SETICON, ICON_BIG, HIcon
    ' For 64 bit may need replacing with SetClassLongPtr
    SetClassLong hwnd, GCL_HICON, HIcon
    SetClassLong hwnd, GCL_HICONSM, HIcon

    WorkbookHWnd = FindWindowEx(DeskHWnd, 0, "EXCEL7", vbNullString)
    Do While WorkbookHWnd <> 0
        SendMessageA WorkbookHWnd, WM_SETICON, ICON_SMALL, HIcon
        SendMessageA WorkbookHWnd, WM_SETICON, ICON_BIG, HIcon

        WorkbookHWnd = FindWindowEx(DeskHWnd, WorkbookHWnd, "EXCEL7", vbNullString)
    Loop
    SetClassLong WorkbookHWnd, GCL_HICON, HIcon
    SetClassLong WorkbookHWnd, GCL_HICONSM, HIcon

    WorkbookHWnd = FindWindowEx(0, 0, "MS-SDIb", vbNullString)
    Do While WorkbookHWnd <> 0
        ' Check if WorkbookHWnd was created by same thread as Application.hwnd
        If ThreadId = GetWindowThreadProcessId(WorkbookHWnd, ByVal 0&) Then
            SendMessageA WorkbookHWnd, WM_SETICON, ICON_SMALL, HIcon
            SendMessageA WorkbookHWnd, WM_SETICON, ICON_BIG, HIcon
            SetClassLong WorkbookHWnd, GCL_HICON, HIcon
            SetClassLong WorkbookHWnd, GCL_HICONSM, HIcon
        End If

        WorkbookHWnd = FindWindowEx(0, WorkbookHWnd, "MS-SDIb", vbNullString)
    Loop
End Sub
”对我不起作用
'Public Const strIcon As String=“%SystemRoot%\system32\SHELL32.dll”图标文件
Public Const strIcon As String=“C:\Windows\system32\SHELL32.dll”图标文件
公共常数指数,长度=137
将函数findwindowlib“user32”别名“FindWindowA”(ByVal ClassName作为字符串,ByVal WindowName作为字符串)声明为
将函数FindWindowEx Lib“user32”别名“FindWindowExA”(ByVal hWndParent为Long,ByVal hWndChildAfter为Long,ByVal lpszClassName为String,ByVal lpszCaption为String)声明为Long
'对于64位,可能需要替换为SetClassLongPtr
将函数SetClassLong Lib“user32”别名“SetClassLongA”(ByVal hwnd为Long,ByVal nIndex为Long,ByVal dwNewLong为Long)声明为Long
常量GCL_HICON As Long=-14
常量GCL_HICONSM As Long=-34
将函数GetWindowThreadProcessId Lib“user32”(ByVal hwnd为Long,lpdwProcessId为Long)声明为Long
公共声明函数sendmagesa Lib“user32”(ByVal hwnd为Long,ByVal wMsg为Long,ByVal wParam为Integer,ByVal lParam为Long)为Long
公共声明函数ExtractIconA Lib“shell32.dll”(ByVal hInst为长,ByVal lpszExeFileName为字符串,ByVal nIconIndex为长)为长
公共Const图标小如Lon