Gre*_*edo 6 excel events vba class userform
我有一个用户窗体,它在运行时自行组装,通过查看文件夹并将其中的所有图片提取到我的窗体上的图像控件中。让这个过程稍微复杂一点的是,我还使用图像控件的事件来运行一些代码。
作为一个简化的例子 - 我有一个在运行时创建图片的表单,该图片有一个点击事件来清除其内容。为此,我有一个自定义类来表示图像对象
在名为“imgForm”的空白用户表单中
Dim oneImg As New clsImg 'our custom class
Private Sub UserForm_Initialize()
Set oneImg.myPic = Me.Controls.Add("Forms.Image.1") 'set some property of the class
oneImg.Init 'run some setup macro of the class
End Sub
Run Code Online (Sandbox Code Playgroud)
在名为“clsImg”的类模块中
Public WithEvents myPic As MSForms.Image
Public Sub Init() 'can't put in Class_Initialise as it is called before the set statement - so myPic is still empty at that point
myPic.Picture = LoadPicture(path/image)
End Sub
Public Sub myPic_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
onePic.Picture = Nothing
End Sub
Run Code Online (Sandbox Code Playgroud)
问题是,这并没有显示更改,我意识到我需要imgForm.Repaint在某个地方有一个-问题是,在哪里?
第一种选择是把它放在Init()子clsImg。(即imgForm.Repaint在点击事件的末尾有一行)这可行,但并不理想,因为该类只能与正确名称的用户表单一起使用。
第二个想法是将用户表单作为参数传递给 Init()
Public Sub Init(uf As UserForm) 'can't put in Class_Initialise as it is called before the set statement - so myPic is still empty at that point
myPic.Picture = LoadPicture(path/image)
uf.Repaint
End Sub
Run Code Online (Sandbox Code Playgroud)
并调用
oneImg.Init Me
Run Code Online (Sandbox Code Playgroud)
这也有效,但意味着在我需要重绘的任何地方,我都必须传递也不理想的参数 - 代码实际上比此处显示的复杂得多,所以我不想必须除非必要,添加这个额外的参数
我目前使用的第三个选项是将用户表单对象传递给类并将其保存在那里。
所以Public myForm As UserForm在我的类模块的顶部,我可以传递用户表单Init(uf As UserForm)并有一个
Set myForm = uf 'Works with a private "myForm"/ class Property
Run Code Online (Sandbox Code Playgroud)
或者我可以直接从用户表单代码中设置它
Set clsImg.myForm = Me 'only if "myForm" is Public
Run Code Online (Sandbox Code Playgroud)
但这对内存有什么作用 - 将用户表单保存为我的类中的变量会占用大量内存吗?请记住,在我的实际代码中,我声明了一个clsImgs数组,该数组可以是 >100 个实例的数量级,因此如果这就是此方法所做的,我真的不想在每个类中制作 UF 的副本。还有就是丑
... 是一种告诉用户窗体它需要重绘的方式,而不是直接从类内部重绘。对我来说,这表示我需要在我的班级中发生一个事件,用户表单可以通过一些自定义事件处理程序听到该事件。Worksheet_Change 的工作原理是,工作表对象引发更改事件,工作表类代码处理它。
这样的事情是可能的(我想我必须声明clsImg WithEvents- 你能对数组这样做吗?),或者有更好的选择。我正在寻找一种方法,它不会在声明了大量类的情况下影响性能,并且是一种可移植且易于阅读的方法。这是我第一次使用类,所以我可能会遗漏一些非常明显的东西!
由于良好的做法是类是自包含的(正如您显然知道的),clsImg因此确实不必知道,UserForm因此不应该告诉用户窗体重新绘制。
这确实需要 clsImg 引发用户窗体挂钩的事件,因此它会根据该事件重新绘制,或者用您自己的话来说:“一种告诉用户表单它需要重新绘制的方法。”
我clsImg按如下方式复制了您的自定义类 ( ) (想要使用适当的 Setter / Getter,功能并没有真正改变)
clsImg 代码:
Private WithEvents myPic As MSForms.Image 'Because we need the click event.
Public Event NeedToRepaint() 'Because we need to raise an event that the UserForm can hook into.
Public Property Let picture(value As MSForms.Image)
Set myPic = value
End Property
Public Property Get picture() As MSForms.Image
Set picture = myPic
End Property
Public Sub myPic_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
myPic.picture = Nothing
RaiseEvent NeedToRepaint
End Sub
Run Code Online (Sandbox Code Playgroud)
接下来,在 UserForm 中,我们挂钩到在图片NeedToRepaint的事件处理程序期间引发的此事件MouseDown。
UserForm1 代码:
Private WithEvents oneImg As clsImg 'Our custom class
Private Sub oneImg_NeedToRepaint() 'Handling the event of our custom class
Me.Repaint
End Sub
Private Sub UserForm_Initialize()
Dim tmpCtrl As MSForms.Image
Set oneImg = New clsImg
Set tmpCtrl = Me.Controls.Add("Forms.Image.1")
tmpCtrl.picture = LoadPicture("C:\Path\image.jpg")
oneImg.picture = tmpCtrl
End Sub
Run Code Online (Sandbox Code Playgroud)
您问题的第二部分是您是否可以在数组中使用它。简短的回答是“不”——每个对象都必须有自己的事件处理程序。但是,有一些方法可以通过使用集合或一些类似的方法来解决此限制。尽管如此,这个包装器必须是“用户窗体感知”,因为那是你要重新绘制的地方。该方法将类似于本文中的内容
编辑:无法使用数组的解决方案/解决方法:
因为我真的很喜欢这个问题 - 这是另一种方法。我们可以应用一些 PubSub 模式,如下所示:我为 CommandButtons 做了一个快速构建,但当然没有理由不能为其他类创建它。
Publisher 班级:
Public Event ButtonClicked(value As cButton)
Public Sub RegisterButtonClickEvent(value As cButton)
RaiseEvent ButtonClicked(value)
End Sub
'Add any other events + RegisterSubs.
Run Code Online (Sandbox Code Playgroud)
在一个常规类中,我设置了一个工厂例程来保持这个特定的 Publisher 是一个单例(例如:它在你指向的内存对象中总是完全相同的):
Private pub As Publisher
Public Function GetPublisher() As Publisher
If pub Is Nothing Then
Set pub = New Publisher
End If
Set GetPublisher = pub
End Function
Run Code Online (Sandbox Code Playgroud)
接下来,我们有用户窗体(我只是用 4 个按钮制作了一个)和按钮类来使用这个发布者。Userform 只会订阅它引发的事件:
Userform代码:
Private WithEvents pPub As Publisher 'Use the Publishers events.
Private button() As cButton 'Custom button array
Private Sub pPub_ButtonClicked(value As cButton) 'Hook into Published event.
MsgBox value.button.Caption
End Sub
Private Sub UserForm_Initialize()
Set pPub = GetPublisher 'Private publisher for getting it's event. Will be always the same object as long as you use "GetPublisher"
Dim i As Integer
Dim btn As MSForms.CommandButton
'Create an array of the buttons:
i = -1
For Each btn In Me.Controls
i = i + 1
ReDim Preserve button(0 To i)
Set button(i) = New cButton
button(i).button = btn
Next btn
End Sub
Run Code Online (Sandbox Code Playgroud)
最后我们有一个cButton类,它集中了按钮事件(通过数组)。我们没有单独处理每个事件,我们只是告诉发布者已经引发了一个事件。:
Private WithEvents btn As MSForms.CommandButton
Private pPub As Publisher
Public Event btnClicked()
Private Sub btn_Click()
pPub.RegisterButtonClickEvent Me 'Pass the events to the publisher.
End Sub
Public Property Let button(value As MSForms.CommandButton)
Set btn = value
End Property
Public Property Get button() As MSForms.CommandButton
Set button = btn
End Property
Private Sub Class_Initialize()
Set pPub = GetPublisher
End Sub
Run Code Online (Sandbox Code Playgroud)
通过这种方法,我们有一个“发布者”可以处理来自向其注册正确事件的特定类的任何事件。您还可以添加图像事件、工作簿事件等。发布者本身根据传递给它的内容引发我们需要的事件。通过这种方式,用户窗体可以与按钮类无关,反之亦然。根据 VBA 支持的内容,我非常有信心这是最适合您的场景的方法。如果有人有更好的主意,我很乐意看到另一个答案。