【问题标题】:VBA Raising event on another class另一个班级的 VBA 提高活动
【发布时间】:2023-04-06 05:40:01
【问题描述】:

我正在尝试执行this post in codereview的建议

目标:

管理用户与 Excel 表格 (ListObjects) 交互时发生的情况

最后的想法是为不同的表格设置自定义事件。例如当您向 table1 添加一行时,会引发自定义 AddEvent1,当您对 table2 执行相同操作时,会引发 AddEvent2。

只有一个类来管理事件,一个类来保存表格及其信息。


所以建议的过程是:

  1. 将列表对象添加到名为Table 的类中
  2. 该类将监听父工作表上的事件(ChangeSelectionChange
  3. 触发更改事件时,从处理这些事件的类 TableManager 触发自定义事件(如 addingupdatingdeleting 行之类的事件)

编辑#1:

调整代码:

  • Create 函数现在返回Table 的实例
  • 属性Set SourceTable 现在将listObjectParentSheet 字段设置为相应的值

Table Manager 仍然没有监听listObjectParentSheet_Change 中引发的事件


组件:

1) 带有 Excel 表格 (ListObject) 的工作表和后面的代码:

Private Sub Worksheet_Activate()

    Dim myTable As Table
    Dim myTableManager As TableManager

    Set myTable = Table.Create(Me.ListObjects(1))

    Set myTableManager = New TableManager

    Set myTableManager.TableInstance = myTable

End Sub

2) 类 Table(使用 rubberduck 将预先声明的 id 设置为 true)

'@Folder("VBAProject")

Option Explicit
'@PredeclaredId

Private Type TTable
    SourceTable As ListObject
End Type

Private this As TTable

Private WithEvents listObjectParentSheet As Excel.Worksheet

Public Event AddEvent()

Public Property Get SourceTable() As ListObject
    Set SourceTable = this.SourceTable
End Property

Public Property Set SourceTable(ByVal value As ListObject)
    Set this.SourceTable = value
    Set listObjectParentSheet = value.Parent
End Property

Public Property Get Self() As Table
    Set Self = Me
End Property

Public Function Create(ByVal EvalSourceTable As ListObject) As Table
    With New Table
        Set .SourceTable = EvalSourceTable
        Set Create = .Self
    End With
End Function

Private Sub listObjectParentSheet_Change(ByVal Target As Range)
    If Not Intersect(Target, SourceTable.DataBodyRange) Is Nothing Then
        MsgBox listObjectParentSheet.Name & " " & Target.Address
        RaiseEvent AddEvent
    End If
End Sub

3) 类TableManager

Option Explicit

Private WithEvents m_table As Table

Public Property Get TableInstance() As Table
    Set TableInstance = m_table
End Property

Public Property Set TableInstance(ByRef tableObject As Table)
    Set m_table = tableObject
End Property

Private Sub m_table_AddEvent()
    MsgBox "Adding something"
End Sub

问题/问题:

我还没有弄清楚如何在TableManager 类中触发“AddEvent”。我知道我搞砸了一些实例化类的概念,但我不知道我做错了什么。


预期结果:

当用户更改列表对象的任何单元格时,在引发AddEvent 时显示消息框“添加内容”


任何帮助将不胜感激。

编辑#2

感谢 Mat 回答的最终代码:

表:Sheet1

Private Sub Worksheet_Activate()
    With TableManager
        Set .TableEvents = Table.Create(Sheet1.ListObjects(1))
    End With
End Sub

模块:ListObjectUtilities

Option Explicit

Public Function GetCellRow(ByVal EvalTable As ListObject, ByVal EvalCell As Range) As Long

    If Intersect(EvalCell, EvalTable.DataBodyRange) Is Nothing Then Exit Function

    GetCellRow = EvalCell.Row - EvalTable.HeaderRowRange.Row

End Function

Public Function GetCellColumn(ByVal EvalTable As ListObject, ByVal EvalCell As Range) As Long

    If Intersect(EvalCell, EvalTable.DataBodyRange) Is Nothing Then Exit Function

    GetCellColumn = EvalCell.Column - EvalTable.HeaderRowRange.Column + 1

End Function

班级:ITable

Option Explicit

Public Property Get SourceTable() As ListObject
End Property

班级:Table

'@Folder("VBAProject")
'@PredeclaredId
Option Explicit

Private WithEvents TableSheet As Excel.Worksheet

Private Type TTable
    SourceTable As ListObject
    LastRowCount As Long
    LastColumnCount As Long
End Type

Private this As TTable

Public Event Changed(ByVal cell As Range)
Public Event AddedNewRow(ByVal newRow As ListRow)
Public Event AddedNewColumn(ByVal newColumn As ListColumn)

Implements ITable

Public Function Create(ByVal Source As ListObject) As ITable
    With New Table
        Set .SourceTable = Source
        Set Create = .Self
    End With
End Function

Public Property Get Self() As Table
    Set Self = Me
End Property

Public Property Get SourceTable() As ListObject
    Set SourceTable = this.SourceTable
End Property

Public Property Set SourceTable(ByVal value As ListObject)
    ThrowIfSet this.SourceTable
    ThrowIfNothing value
    Set TableSheet = value.Parent
    Set this.SourceTable = value
    Resize
End Property

Friend Sub OnChanged(ByVal Target As Range)
    RaiseEvent Changed(Target)
End Sub

Friend Sub OnAddedNewRow(ByVal newRow As ListRow)
    RaiseEvent AddedNewRow(newRow)
End Sub

Friend Sub OnAddedNewColumn(ByVal newColumn As ListColumn)
    RaiseEvent AddedNewColumn(newColumn)
End Sub

Private Sub ThrowIfNothing(ByVal Target As Object)
    If Target Is Nothing Then Err.Raise 5, TypeName(Me), "Argument cannot be a null reference."
End Sub

Private Sub ThrowIfSet(ByVal Target As Object)
    If Not Target Is Nothing Then Err.Raise 5, TypeName(Me), "This reference is already set."
End Sub

Private Sub Resize()
    With this.SourceTable
        this.LastRowCount = .ListRows.Count
        this.LastColumnCount = .ListColumns.Count
    End With
End Sub

Private Sub TableSheet_Change(ByVal Target As Range)

    If Intersect(Target, SourceTable.DataBodyRange) Is Nothing Then Exit Sub

    Select Case True
    Case this.SourceTable.DataBodyRange.Columns.Count > this.LastColumnCount
        OnAddedNewColumn SourceTable.ListColumns(ListObjectUtilities.GetCellColumn(this.SourceTable, Target))
    Case this.SourceTable.DataBodyRange.Rows.Count > this.LastRowCount
        OnAddedNewRow SourceTable.ListRows(ListObjectUtilities.GetCellRow(this.SourceTable, Target))
    Case Else
        OnChanged Target
    End Select
    Resize
End Sub

Private Property Get ITable_SourceTable() As ListObject
    Set ITable_SourceTable = this.SourceTable
End Property

班级:TableManager

'@Folder("VBAProject")
'@PredeclaredId
Option Explicit
Private WithEvents MyTable As Table

Public Property Get TableEvents() As Table
    Set TableEvents = MyTable
End Property

Public Property Set TableEvents(ByVal value As Table)
    Set MyTable = value
End Property

Private Sub MyTable_AddedNewColumn(ByVal newColumn As ListColumn)
    MsgBox "Added new column " & newColumn.Range.Column
End Sub

Private Sub MyTable_AddedNewRow(ByVal newRow As ListRow)
    MsgBox "Added new row " & newRow.Range.Row
End Sub

Private Sub MyTable_Changed(ByVal cell As Range)
    MsgBox "Changed " & cell.Address
End Sub

Sample file

【问题讨论】:

  • 您只能从定义它的类中触发事件。您是否试图从TableManager 内部解雇Table.AddEvent?我不明白你为什么要这样做......
  • 部分问题是Create 工厂方法没有创建任何东西......它正在改变默认实例,使其成为有状态......
  • @MathieuGuindon 感谢您的回复。是的,我正在尝试触发 Table.AddEvent。我尝试使用With New 创建一个实例,但是我无法将listObjectParentSheet 变量设置为父工作表。
  • 那就是Set .SourceTable = EvalSourceTable ...您不能分配给私有字段。而那个 setter 应该分配 WithEvents 字段。
  • @MathieuGuindon 我以前有过这种方式,但我如何设置 listObjectParentSheet 变量只监听持有 ListObject 的工作表?您同意here 建议的方法吗?我认为它简化了一些东西,但我还没有弄清楚如何实现它。

标签: excel vba events rubberduck


【解决方案1】:

我尝试重现,但后来发现依靠Worksheet.Activate 注册处理程序往往会出现行为不端:有时您需要“摆动”工作表以使其跟上,尤其是在您编辑代码时。可能就是这样:)

请注意,为了能够触发 AddedNewRowAddedNewColumn,甚至是 RemovedRowRemovedColumn,您需要不断跟踪表的大小,同时混合使用 Worksheet.ChangeWorksheet.SelectionChange 处理程序。

表格类模块:

'@Folder("VBAProject")
'@PredeclaredId
Option Explicit

Private WithEvents TableSheet As Excel.Worksheet

Private Type TTable
    SourceTable As ListObject
    LastRowCount As Long
    LastColumnCount As Long
End Type

Private this As TTable

Public Event Changed(ByVal cell As Range)
Public Event AddedNewRow(ByVal newRow As ListRow)
Public Event AddedNewColumn(ByVal newColumn As ListColumn)

Public Function Create(ByVal Source As ListObject) As Table
    With New Table
        Set .SourceTable = Source
        Set Create = .Self
    End With
End Function

Public Property Get Self() As Table
    Set Self = Me
End Property

Public Property Get SourceTable() As ListObject
    Set SourceTable = this.SourceTable
End Property

Public Property Set SourceTable(ByVal Value As ListObject)
    ThrowIfSet this.SourceTable
    ThrowIfNothing Value
    Set TableSheet = Value.Parent
    Set this.SourceTable = Value
    Resize
End Property

Friend Sub OnChanged(ByVal Target As Range)
    RaiseEvent Changed(Target)
End Sub

Friend Sub OnAddedNewRow(ByVal newRow As ListRow)
    RaiseEvent AddedNewRow(newRow)
End Sub

Friend Sub OnAddedNewColumn(ByVal newColumn As ListColumn)
    RaiseEvent AddedNewColumn(newColumn)
End Sub

Private Sub ThrowIfNothing(ByVal Target As Object)
    If Target Is Nothing Then Err.Raise 5, TypeName(Me), "Argument cannot be a null reference."
End Sub

Private Sub ThrowIfSet(ByVal Target As Object)
    If Not Target Is Nothing Then Err.Raise 5, TypeName(Me), "This reference is already set."
End Sub

Private Sub Resize()
    With this.SourceTable
        this.LastRowCount = .ListRows.Count
        this.LastColumnCount = .ListColumns.Count
    End With
End Sub

Private Sub TableSheet_Change(ByVal Target As Range)
    If Not (Target.ListObject Is SourceTable) Then Exit Sub
    OnChanged Target
    Resize
End Sub

请注意,您可以使用Is 运算符来确定Target.ListObject 是否与SourceTable 引用相同的对象,而不是使用Application.Intersect 和范围:

If Not (Target.ListObject Is SourceTable) Then Exit Sub

从那里我们只需要一个类来处理这个Changed 事件——我已经把它放在Sheet1 代码隐藏这里,但是任何类模块都可以(包括UserForm 模块):

Sheet1 工作表模块:

'@Folder("VBAProject")
Option Explicit
Private WithEvents MyTable As Table

Public Property Get TableEvents() As Table
    Set TableEvents = MyTable
End Property

Public Property Set TableEvents(ByVal value As Table)
    Set MyTable = value
End Property

Private Sub MyTable_Changed(ByVal cell As Range)
    MsgBox "Changed " & cell.Address
End Sub

Table 引用仍需要在某处为 Set - 在主机工作簿的 Open 处理程序中:

ThisWorkbook 工作簿模块:

'@Folder("VBAProject")
Option Explicit

Private Sub Workbook_Open()
    With Sheet1
        Set .TableEvents = Table.Create(.ListObjects(1))
    End With
End Sub

下一步是清理Table.Create 返回的公共接口 - 就目前而言,事情相当混乱,Table 接口有点臃肿:

所有这些成员都将提供给Sheet1.TableEvents,除非我们采取行动。如果我们只能像这样暴露客户端代码真正需要的成员会怎样?

使用Rubberduck,您可以提取接口,方法是右键单击Table 类中的任意位置并从“重构”菜单中选择“提取接口”,然后选择要提取的成员- 这里是SourceTable getter(我们不会暴露setter!):

这会创建一个新的私有类(这将在未来的版本中更改) - 如果接口是从公共类中提取的,请在 properties 工具窗口 (F4) 中将其设为 PublicNotCreatable。 p>

重构会在Table类的顶部添加Implements ITable(假设你没有重命名接口),并且会添加这个成员:

Private Property Get ITable_SourceTable() As ListObject
    Err.Raise 5 'TODO implement interface member
End Property

您需要做的就是提供实现:

Private Property Get ITable_SourceTable() As ListObject
    Set ITable_SourceTable = this.SourceTable
End Property

现在Table.Create 可以返回ITable 抽象:

Public Function Create(ByVal Source As ListObject) As ITable

【讨论】:

  • 这太棒了。你真的知道如何解决问题。谢谢@Mathieu!我将在我的问题中发布最终代码。
猜你喜欢
  • 2013-01-31
  • 2012-05-20
  • 1970-01-01
  • 2016-06-06
  • 1970-01-01
  • 1970-01-01
  • 2011-10-10
  • 2017-11-05
  • 1970-01-01
相关资源
最近更新 更多