首页 > 解决方案 > VBA 中的 UTF-8 字符 Shell_NotifyIconW

问题描述

Shell_NotifyIcon Windows API 将消息发送到任务栏的状态区域。

我正在寻找一种使 Windows 通知 API 与 UTF-8 字符一起工作的方法。

下面的代码运行良好,但仅适用于 ANSI 字符。

UTF-8 字符显示为“??”。

Option Explicit
Private Declare PtrSafe Function Shell_NotifyIconA Lib "shell32.dll" (ByVal dwMessage As Long, ByRef nfIconData As NOTIFYICONDATA) As LongPtr
Public nfIconData As NOTIFYICONDATA
Private Type NOTIFYICONDATA
    cbSize As Long
    hwnd As LongPtr
    uID As Long
    uFlags As Long
    uCallbackMessage As Long
    hIcon As LongPtr
    szTip As String * 128
    dwState As Long
    dwStateMask As Long
    szInfo As String * 256
    uTimeout As Long
    szInfoTitle As String * 64
    dwInfoFlags As Long
End Type
Public Function toast(Optional ByVal title As String, Optional ByVal info As String, Optional ByVal flag As Long)
With nfIconData
    .dwInfoFlags = flag
    .uFlags = &H10
    .szInfoTitle = title
    .szInfo = info
    .cbSize = &H1F8
End With
Shell_NotifyIconW &H0, nfIconData
Shell_NotifyIconW &H1, nfIconData
End Function
'Flags for the balloon message..
'None = 0
'Information = 1
'Exclamation = 2
'Critical = 3
Sub TestANSI()
toast "Hi"    
End Sub

Sub testUTF8()
toast ChrW(55357) & ChrW(56397)
End Sub

只需将 Windows API 声明中的 A 更改为 W 并不能解决此问题。

有人有使用 Shell_NotifyIconW 处理 UTF-8 字符的经验吗?

我已经尝试使用下面的代码,但它不起作用。

Option Explicit
Private Declare PtrSafe Function Shell_NotifyIconW Lib "shell32.dll" (ByVal dwMessage As Long, ByRef nfIconData As NOTIFYICONDATA) As LongPtr
Public nfIconData As NOTIFYICONDATA
Private Type NOTIFYICONDATA
    cbSize As Long
    hwnd As LongPtr
    uID As Long
    uFlags As Long
    uCallbackMessage As Long
    hIcon As LongPtr
    szTip As String * 128
    dwState As Long
    dwStateMask As Long
    szInfo As String * 256
    uTimeout As Long
    szInfoTitle As String * 64
    dwInfoFlags As Long
End Type
Public Function toast(Optional ByVal title As String, Optional ByVal info As String, Optional ByVal flag As Long)
With nfIconData
    .dwInfoFlags = flag
    .uFlags = &H10
    .szInfoTitle = title
    .szInfo = info
    .cbSize = &H1F8
End With
Shell_NotifyIconW &H0, nfIconData
Shell_NotifyIconW &H1, nfIconData
End Function
'Flags for the balloon message..
'None = 0
'Information = 1
'Exclamation = 2
'Critical = 3

Sub TestANSI()
toast "Hi"    
End Sub
Sub testUTF8()
toast ChrW(55357) & ChrW(56397)
End Sub

标签: excelvbawinapi

解决方案


UnicodeUTF-8不是可互换的术语(请参阅https://www.joelonsoftware.com/articles/Unicode.html)。一个是一个概念,另一个是坚持这个概念的一种方式。

WWindows 中的函数使用 UTF-16 形式的 Unicode,因此您希望以这种方式为它们提供 Unicode。

你的AW版本都没有正确声明,你想先解决这个问题。
然后,对于W函数,您必须避免 VB 执行的隐式字符串转换,为此您需要将固定长度的字符串声明为字节数组。因为 VB 字符串已经是 UTF-16 中的 Unicode,这是W函数所期望的,所以您只需将这些字符串中的字节复制到这些字节数组中。不需要转换,但您需要小心将每个字符串中的最后两个字节保留为零。

请注意,与A函数不同的是,该W函数要求您同时提供标题和信息(文档中已提及)。如果您只提供一个,它将不会显示。

另请注意,不幸的是,VBA没有为64 位结构成员提供正确的填充,这严重破坏了 的有用性LongPtr,因此您需要自己提供。

Option Explicit

Private Declare PtrSafe Function Shell_NotifyIconW Lib "shell32.dll" (ByVal dwMessage As Long, ByRef nfIconData As NOTIFYICONDATAW) As Long
Private Declare PtrSafe Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)

Private Type NOTIFYICONDATAW
  cbSize As Long
#If Win64 Then
  padding1 As Long
#End If
  hwnd As LongPtr
  uID As Long
  uFlags As Long
  uCallbackMessage As Long
#If Win64 Then
  padding2 As Long
#End If
  hIcon As LongPtr
  szTip(1 To 128 * 2) As Byte
  dwState As Long
  dwStateMask As Long
  szInfo(1 To 256 * 2) As Byte
  uTimeout As Long
  szInfoTitle(1 To 64 * 2) As Byte
  dwInfoFlags As Long
End Type

Private Const NIM_ADD As Long = &H0&
Private Const NIM_MODIFY As Long = &H1&
Private Const NIF_INFO As Long = &H10&

Private Function Min(ByVal a As Long, ByVal b As Long) As Long
  If a < b Then Min = a Else Min = b
End Function

Public Sub Toast(Optional ByVal title As String, Optional ByVal info As String, Optional ByVal flag As Long)
  Dim nfIconData As NOTIFYICONDATAW
  
  With nfIconData
    .cbSize = Len(nfIconData)
    
    .uFlags = NIF_INFO
    .dwInfoFlags = flag
    
    If Len(title) > 0 Then
      CopyMemory ByVal VarPtr(.szInfoTitle(LBound(.szInfoTitle))), ByVal StrPtr(title), Min(Len(title) * 2, UBound(.szInfoTitle) - LBound(.szInfoTitle) + 1 - 2)
    End If
    
    If Len(info) > 0 Then
      CopyMemory ByVal VarPtr(.szInfo(LBound(.szInfo))), ByVal StrPtr(info), Min(Len(info) * 2, UBound(.szInfo) - LBound(.szInfo) + 1 - 2)
    End If
  End With
  
  Shell_NotifyIconW NIM_ADD, nfIconData
  Shell_NotifyIconW NIM_MODIFY, nfIconData
End Sub
Toast ChrW$(55357) & ChrW$(56397), ChrW$(55357) & ChrW$(56397)

推荐阅读