首页 > 解决方案 > 等同于 Windows 窗体和 Access 窗体中的 Microsoft Forms 2.0 ComboBox .TopIndex 属性?

问题描述

(已弃用)Microsoft Forms 2.0 控件包括一个组合框,该组合框提供了一个宝贵的属性:.TopIndex(此处的文档)。

似乎此属性不适用于 Microsoft Access 2019 中的表单中的标准组合框(根据文档),并且不适用于 Windows 窗体 (.NET) 中的标准组合框(此处,ComboBox 继承自 ListControl 并且不提供此属性属性,而 ListBox 也继承自 ListControl,但提供了它)。

我有很多旧代码严重依赖 .TopIndex 属性,是时候将该代码移至其他技术了。

所以我想知道我是否遗漏了文档中的某些内容,以及是否有一个具有另一个名称的等效属性,我可以使用它来确定哪些项目在组合框的列表部分中可见。我想知道 Access 2019 中的组合框(我并不​​像这里的许多其他应用程序那样敌视此应用程序)以及 Windows 窗体中的组合框。

我知道有很多免费和商业控件(包括组合框)具有增强的 Windows 窗体功能。除非我遗漏了文档中的某些内容,否则我肯定会这样做(或自己编写)。

但是,对于 Access 2019 表单,情况就完全不同了。我找不到一个可以在 Access 表单上使用并提供此功能的免费第三方 ActiveX / COM 组合框。从理论上讲,我可能可以使用 .NET 编写一个 ActiveX/COM 控件,然后在 Access 2019 表单上使用它,但这似乎很痛苦。

标签: winformsms-accesscomboboxms-access-formsms-forms

解决方案


就 .Net WinForm ComboBox 而言,您没有错过任何内容,因为 TopIndex 属性的功能没有实现。也就是说,扩展基本 ComboBox 控件以添加此属性非常简单。以下示例控件应该可以帮助您入门。

此控件将侦听器附加到本机 ListBox 下拉列表,并更新 WM_VSCROLL 和LB_SETCARETINDEX上的 TopIndex 属性(这将捕获打开时的初始位置)消息。此外,基本 SelectedIndexChange 事件用于捕获由于键盘操作(pgUp/pgDn、向上/向下箭头)引起的更改。TopIndex 属性在下拉列表关闭后保留,并在打开下拉列表时重置。该控件还公开一个 TopIndexChanged 事件。

Imports System.Runtime.InteropServices

Public Class ComboBoxEx : Inherits ComboBox
  Private listBoxListener As ListBoxNativeWindow
  Public Event TopIndexChanged As EventHandler(Of ComboBoxTopIndexArg)

  Private _TopIndex As Int32 = -1

  Public Sub New()
    MyBase.New
    listBoxListener = New ListBoxNativeWindow(Me)
  End Sub

  Public Property TopIndex As Int32
    Get
      Return _TopIndex
    End Get
    Private Set(value As Int32)
      If value <> _TopIndex Then
        _TopIndex = value
        RaiseEvent TopIndexChanged(Me, New ComboBoxTopIndexArg(value))
      End If
    End Set
  End Property

  Protected Overrides Sub OnDropDown(e As EventArgs)
    _TopIndex = -1 ' reset on opening the listbox
    MyBase.OnDropDown(e)
  End Sub

  Private Class ListBoxNativeWindow : Inherits NativeWindow
    Private listBoxHandle As IntPtr
    Private TopIndex As Int32
    Private parent As ComboBoxEx

    Public Sub New(ByVal parent As ComboBoxEx)
      Me.parent = parent
      WireParent()
      If parent.IsHandleCreated Then
        GetListBoxHandle()
        AssignHandle(listBoxHandle)
      End If
    End Sub

    Private Sub WireParent()
      AddHandler parent.HandleCreated, AddressOf Me.OnHandleCreated
      AddHandler parent.HandleDestroyed, AddressOf Me.OnHandleDestroyed
      AddHandler parent.SelectedIndexChanged, AddressOf UpdateTopIndexOnIndexChanged
    End Sub

    Private Sub OnHandleCreated(ByVal sender As Object, ByVal e As EventArgs)
      GetListBoxHandle()
      AssignHandle(listBoxHandle)
    End Sub

    Private Sub OnHandleDestroyed(ByVal sender As Object, ByVal e As EventArgs)
      ReleaseHandle()
    End Sub

    Private Sub UpdateTopIndexOnIndexChanged(sender As Object, e As EventArgs)
      SetParentTopIndex()
    End Sub

    Private Sub GetListBoxHandle()
      Const CB_GETCOMBOBOXINFO As Int32 = &H164
      Dim info As New ComboBoxInfo
      info.cbSize = Marshal.SizeOf(info)
      Dim res As Boolean = SendMessage(Me.parent.Handle, CB_GETCOMBOBOXINFO, Nothing, info)
      listBoxHandle = info.hwndList
    End Sub

    Protected Overrides Sub WndProc(ByRef m As Message)
      Const WM_VSCROLL As Int32 = &H115
      Const LB_SETCARETINDEX As Int32 = &H19E

      MyBase.WndProc(m)
      If m.Msg = WM_VSCROLL OrElse m.Msg = LB_SETCARETINDEX Then
        SetParentTopIndex()
      End If
    End Sub

    Private Sub SetParentTopIndex()
      Const LB_GETTOPINDEX As Int32 = &H18E
      parent.TopIndex = SendMessage(listBoxHandle, LB_GETTOPINDEX, IntPtr.Zero, IntPtr.Zero)
    End Sub
  End Class

  Public Class ComboBoxTopIndexArg : Inherits EventArgs
    Public Sub New(topIndex As Int32)
      Me.TopIndex = topIndex
    End Sub

    Public ReadOnly Property TopIndex As Int32
  End Class

#Region "NativeMethods"
  <StructLayout(LayoutKind.Sequential)>
  Private Structure ComboBoxInfo
    Public cbSize As Int32
    Public rcItem As RECT
    Public rcButton As RECT
    Public stateButton As IntPtr
    Public hwndCombo As IntPtr
    Public hwndEdit As IntPtr
    Public hwndList As IntPtr
  End Structure

  <StructLayout(LayoutKind.Sequential)>
  Private Structure RECT
    Public Left, Top, Right, Bottom As Int32
  End Structure

  <DllImport("user32.dll")>
  Private Shared Function SendMessage(hWnd As IntPtr, Msg As Int32, wParam As IntPtr, <Out()> ByRef lParam As ComboBoxInfo) As Boolean
  End Function

  <DllImport("user32.dll")>
  Private Shared Function SendMessage(hWnd As IntPtr, Msg As Int32, wParam As IntPtr, lParam As IntPtr) As Int32
  End Function
#End Region

End Class

我让你把它包装在一个 ActiveX 暴露的包装器中,以便在 Access 中使用。使用Microsoft InteropForms Toolkit 2.1中的模板很容易做到这一点。请注意,这些模板是使用“Any CPU”平台设置的,您需要将其更改为“x86”。


推荐阅读