【问题标题】:ListBox created with WinAPI in VBA doesn't work在 VBA 中使用 WinAPI 创建的 ListBox 不起作用
【发布时间】:2018-02-10 14:36:41
【问题描述】:

我想用 WinAPI 在 VBA 中创建一个 ListBox。我设法创建它,但 ListBox 不响应操作 - 没有滚动,没有选择。这些都不起作用。它看起来像它被禁用了。如何让它响应动作? 以下代码用于创建和填充ListBox

WinAPI 函数

Declare Function FindWindow Lib "user32.dll" Alias "FindWindowA" ( _
        ByVal lpClassName As String, _
        ByVal lpWindowName As String) As Long

Declare Function CreateWindow Lib "user32.dll" Alias "CreateWindowExA" ( _
     ByVal dwExStyle As WindowStylesEx, _
     ByVal lpClassName As String, _
     ByVal lpWindowName As String, _
     ByVal dwStyle As Long, _
     ByVal x As Long, _
     ByVal y As Long, _
     ByVal nWidth As Long, _
     ByVal nHeight As Long, _
     ByVal hWndParent As Long, _
     ByVal hMenu As Long, _
     ByVal hInstance As Long, _
     ByVal lpParam As Long) As Long

Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA" ( _
    ByVal hwnd As Long, _
    ByVal wMsg As Long, _
    ByVal wParam As Long, _
    ByVal lParam As Any) As Long

创建列表框:

Private hlist As Long
hlist = WinAPI.CreateWindow( _
        dwExStyle:=WS_EX_CLIENTEDGE, _
        lpClassName:="LISTBOX", _
        lpWindowName:="MYLISTBOX", _
        dwStyle:=WS_CHILD Or WS_VISIBLE Or WS_VSCROLL Or WS_SIZEBOX Or LBS_NOTIFY Or LBS_HASSTRINGS, _
        x:=10, _
        y:=10, _
        nWidth:=100, _
        nHeight:=100, _
        hWndParent:=WinAPI.FindWindow("ThunderDFrame", Me.Caption), _
        hMenu:=0, _
        hInstance:=Application.hInstance, _
        lpParam:=0 _
    )

填充列表框:

Dim x As Integer
For x = 10 To 1 Step -1
    Call WinAPI.SendMessage(hlist, LB_INSERTSTRING, 0, CStr(x))
Next

结果:

【问题讨论】:

  • 您希望在这里使用通用控件和低级 API 而不是使用 MSForms 的列表框来实现什么?
  • @T.M.它 CreateWindowsEx - 看看Alias :) 虽然vbNullString 有效,但列表框仍处于禁用状态。
  • 只是一个想法:看看mrexcel.com/forum/excel-questions/… 它使用WindowFromPoint 来获得正确的句柄,因为显然“类名与相同客户区和上面列出的所有控件(框架、列表框、多页)”。
  • @T.M.没用... :(
  • 这个新的列表框窗口是否在您的进程中作为父级?在我看来不像。

标签: vba excel winapi listbox


【解决方案1】:

您的列表框不可交互,因为它不接收发送到窗口的消息。似乎所有消息都由子容器处理:

要使其工作,请调用 CreateWindow 并将 hWndParent 设置为此容器的句柄:

Private Sub UserForm_Initialize()
    Dim hWin, hClient, hList, i As Long

    ' get the top window handle '
    hWin = FindWindow(StrPtr("ThunderDFrame"), 0)
    If hWin Then Else Err.Raise 5, , "Top window not found"

    ' get first child '
    hClient = GetWindow(hWin, GW_CHILD)

    ' create the list box '
    hList = CreateWindow( _
        dwExStyle:=WS_EX_CLIENTEDGE, _
        lpClassName:=StrPtr("LISTBOX"), _
        lpWindowName:=0, _
        dwStyle:=WS_CHILD Or WS_VISIBLE Or WS_VSCROLL Or WS_SIZEBOX Or LBS_NOTIFY Or LBS_HASSTRINGS, _
        x:=10, _
        y:=10, _
        nWidth:=100, _
        nHeight:=100, _
        hWndParent:=hClient, _
        hMenu:=0, _
        hInstance:=0, _
        lpParam:=0)

    ' add some values '
    For i = 1 To 13
        SendMessage hList, LB_ADDSTRING, 0, StrPtr(CStr(i))
    Next

End Sub

对于声明:

Public Declare PtrSafe Function GetWindow Lib "user32.dll" ( _
    ByVal hWnd As LongPtr, _
    ByVal uCmd As Long) As LongPtr

Public Declare PtrSafe Function FindWindow Lib "user32.dll" Alias "FindWindowW" ( _
    ByVal lpClassName As LongPtr, _
    ByVal lpWindowName As LongPtr) As Long

Public Declare PtrSafe Function CreateWindow Lib "user32.dll" Alias "CreateWindowExW" ( _
    ByVal dwExStyle As Long, _
    ByVal lpClassName As LongPtr, _
    ByVal lpWindowName As LongPtr, _
    ByVal dwStyle As Long, _
    ByVal x As Long, _
    ByVal y As Long, _
    ByVal nWidth As Long, _
    ByVal nHeight As Long, _
    ByVal hWndParent As LongPtr, _
    ByVal hMenu As LongPtr, _
    ByVal hInstance As LongPtr, _
    ByVal lpParam As LongPtr) As LongPtr

Public Declare PtrSafe Function SendMessage Lib "user32.dll" Alias "SendMessageW" ( _
    ByVal hWnd As LongPtr, _
    ByVal wMsg As Long, _
    ByVal wParam As LongPtr, _
    ByVal lParam As LongPtr) As LongPtr

Public Const WS_EX_CLIENTEDGE = &H200&
Public Const WS_CHILD = &H40000000
Public Const WS_VISIBLE = &H10000000
Public Const WS_VSCROLL = &H200000
Public Const WS_SIZEBOX = &H40000
Public Const LBS_NOTIFY = &H1&
Public Const LBS_HASSTRINGS = &H40&
Public Const LB_ADDSTRING = &H180&
Public Const GW_CHILD = &O5&

【讨论】:

  • 这太棒了!它按预期工作!感谢您提供屏幕截图以查看发生的情况和解释!一个小问题:从ListBox收听消息的方式是什么?设置WindowsHook?谢谢! 您将获得奖励!
  • @JohnyL,要收听消息,用您的列表框或顶部窗口的SetWindowLong 或(SetWindowLondPtr,如果为 64 位)覆盖该过程,并使用自定义的,一旦消息处理调用原来的。但是由于不可能获得方法的AddressOf,因此您必须在模块中创建过程,然后将所有消息转发到表单中的方法。发布另一个问题,我将发布一个示例。
  • 我有posted question。感谢您的帮助!
【解决方案2】:

答案是致电SetParent 感谢David Hefferman 指出这一点。

所以根本不需要子类化。

用户窗体类

Option Explicit

Private Declare Function SetParent Lib "user32.dll" (ByVal hWndChild As Long, ByVal hWndNewParent As Long) As Long

Private Const GWL_WNDPROC As Long = -4

Private Declare Function FindWindow Lib "user32.dll" Alias "FindWindowA" ( _
        ByVal lpClassName As String, _
        ByVal lpWindowName As String) As Long

Private Declare Function CreateWindow Lib "user32.dll" Alias "CreateWindowExA" ( _
     ByVal dwExStyle As WindowStylesEx, _
     ByVal lpClassName As String, _
     ByVal lpWindowName As String, _
     ByVal dwStyle As Long, _
     ByVal X As Long, _
     ByVal Y As Long, _
     ByVal nWidth As Long, _
     ByVal nHeight As Long, _
     ByVal hWndParent As Long, _
     ByVal hMenu As Long, _
     ByVal hInstance As Long, _
     ByVal lpParam As Long) As Long

Private Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA" ( _
    ByVal hwnd As Long, _
    ByVal wMsg As Long, _
    ByVal wParam As Long, _
    ByVal lParam As Any) As Long

Private Const WS_CHILD As Long = &H40000000
Private Const WS_VISIBLE As Long = &H10000000
Private Const WS_VSCROLL As Long = &H200000

Private Const WS_THICKFRAME As Long = &H40000
Private Const WS_SIZEBOX As Long = WS_THICKFRAME

Private Const WS_BORDER            As Long = &H800000 '* From WinUser.h

Private Const LB_INSERTSTRING      As Long = &H181

Private Enum ListboxStyle
    '* From WinUser.h
    LBS_NOTIFY = &H1
    LBS_HASSTRINGS = &H40
End Enum

Private Enum WindowStylesEx
    '* From WinUser.h
    WS_EX_CLIENTEDGE = &H200
End Enum

Private mlHwndList As Long


Sub JohnyL_Listbox()


    Dim lHwndForm As Long
    lHwndForm = FindWindow("ThunderDFrame", Me.Caption)

    mlHwndList = CreateWindow( _
            dwExStyle:=WS_EX_CLIENTEDGE, _
            lpClassName:="LISTBOX", _
            lpWindowName:="MYLISTBOX", _
            dwStyle:=WS_CHILD Or WS_VISIBLE Or WS_VSCROLL Or WS_SIZEBOX Or LBS_NOTIFY Or LBS_HASSTRINGS, _
            X:=10, _
            Y:=10, _
            nWidth:=110, _
            nHeight:=110, _
            hWndParent:=FindWindow("ThunderDFrame", Me.Caption), _
            hMenu:=0, _
            hInstance:=Application.hInstance, _
            lpParam:=0 _
        )

    SetParent mlHwndList, lHwndForm
End Sub


Private Sub UserForm_Initialize()

    JohnyL_Listbox

    Dim X As Integer
    For X = 10 To 1 Step -1
        Call SendMessage(mlHwndList, LB_INSERTSTRING, 0, CStr(X))
    Next


End Sub

【讨论】:

  • 如果它们没有窗口,那么它们如何再次使用 WinAPI 获得样式(Excel 2007 VBA Programmer's Reference 书的第 622 页)?那我如何得到他们的句柄?我不清楚。
  • 也许对谈话有帮助。 stackoverflow.com/questions/24534141/….
  • @Ryan:我只是在想“列表框的 winproc 定义在哪里?”问题是将列表框添加到 VB6 表单实际上是有效的。所以我认为它也可以在这里跳过。来自msdn.microsoft.com/en-us/library/windows/desktop/…,“每个窗口类都有一个关联的窗口过程,由同一类的所有窗口共享。”
  • @SMeaden 感谢您的回答!稍后我会看看 :) 如果一切正常,我应该使用钩子 (SetWindowsHook) 来查看我的 ListBox 的消息吗?
  • 也感谢您的回答!虽然代码有效,但我认为这有点 hack。我还不知道所有细节(因为我刚开始使用 WinAPI),但 ListBox 没有像 Florent B. 的回答那样获得白色背景。无论如何,谢谢你的回答!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-26
相关资源
最近更新 更多