gpt4 book ai didi

vba - 从用户窗体监视类事件

转载 作者:行者123 更新时间:2023-12-04 15:47:31 25 4
gpt4 key购买 nike

我有一个在运行时自行组装的用户表单,方法是查看文件夹并将其中的所有图片提取到表单上的图像控件中。让这个过程变得更复杂的是,我还使用图像控件的事件来运行一些代码。

作为一个简化的示例 - 我有一个在运行时创建图片的表单,该图片有一个单击事件来清除其内容。为此,我有一个自定义类来表示图像对象

在一个名为“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

在名为“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

问题是,这不显示更改,我意识到我需要一个 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

并调用
oneImg.Init Me

这也有效,但意味着无论我需要重绘,我都必须传递同样不理想的参数 - 代码实际上比这里显示的要复杂得多,所以我不想除非必要,否则添加此额外参数

我目前使用的第三个选项是将用户表单对象传递给类并将其保存在那里。

所以用 Public myForm As UserForm在我的类模块的顶部,我可以使用 Init(uf As UserForm) 传递用户表单并有一个
Set myForm = uf 'Works with a private "myForm"/ class Property

或者我可以直接从用户表单代码中设置它
Set clsImg.myForm = Me 'only if "myForm" is Public

但这对内存有什么影响 - 将用户表单保存为我的类(class)中的变量会占用大量内存吗?请记住,在我的真实代码中,我声明了一个数组 clsImg s 可以是 >100 个实例的数量级,所以如果这是此方法所做的,我真的不想在每个类中复制 UF。还有就是丑

我真正想要的...

... 是一种告诉用户窗体它需要重新绘制,而不是直接从类中重新绘制的方式。对我来说,这表示我需要在我的类中发生一个事件,用户窗体通过一些自定义事件处理程序听到该事件。 Worksheet_Change 究竟是如何工作的,工作表对象引发一个更改事件,工作表类代码处理它。

这样的事情是否可能(我想我必须声明 clsImg WithEvents - 你可以为数组做这件事吗?),还是有更好的选择。我正在寻找一种在声明大量类时不会影响性能的方法,以及一种可移植且易于阅读的方法。这是我第一次使用类,所以我可能遗漏了一些非常明显的东西!

最佳答案

由于良好的做法是类是独立的(您显然知道)clsImg确实应该不必知道 UserForm因此不应该告诉用户窗体重新绘制。

这确实需要 clsImg 引发 UserForm Hook 的事件,因此它会根据该事件重新绘制,或者用您自己的话来说:“一种告诉 userform 它需要重新绘制的方式。”

我复制了您的自定义类( 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

接下来,在用户窗体中,我们连接到这个 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

您问题的第二部分是您是否可以在数组中使用它。
简短的回答是“不”——每个对象都必须有自己的事件处理程序。但是,有一些方法可以通过使用 Collection 或一些类似的方法来解决此限制。尽管如此,这个包装器必须是“用户窗体感知”的,因为那是你要重新绘制的地方。该方法类似于 this article

编辑:无法使用数组的解决方案/解决方法:

因为我真的很喜欢这个问题 - 这是另一种方法。
我们可以应用一些 PubSub 模式,如下所示:
我为 CommandButtons 做了一个快速构建,但没有理由不能为其他类制作它。
Publisher类(class):
Public Event ButtonClicked(value As cButton)
Public Sub RegisterButtonClickEvent(value As cButton)
RaiseEvent ButtonClicked(value)
End Sub
'Add any other events + RegisterSubs.

在一个普通的类中,我设置了一个工厂例程来保持这个特定的 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

接下来,我们有 UserForm(我刚做了一个有 4 个按钮的)和按钮类来使用这个 Publisher。 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

最后我们有 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

通过这种方法,我们有一个“发布者”,它可以处理来自特定类的任何事件,这些事件向它注册正确的事件。您还可以添加图像事件、工作簿事件等。
发布者本身会根据传递给它的内容引发我们需要的事件。
这样用户窗体可以与按钮类无关,反之亦然。
根据 VBA 所支持的内容,我非常有信心这是最适合您的场景的方法。如果有人有更好的主意,我很想看到另一个答案。

关于vba - 从用户窗体监视类事件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44671994/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com