Control event not firing in a VBA Class Module - vba

I have a problem getting any event firing from controls embedded in Class Modules in VBA Access. Despite many (many!) conversations in various forums related to this one, I haven't found any that help me. As far as I can tell I am following all their advices (mainly, I always use "Access.Control", "Access.Label" and so on so it is always the same library that is used, and the reference still exists at the moment the event should fire...), but it still doesn't fire!
In the same project I have the same problem in several classes; I will take the example of a group of labels on a form.
The load event of the form creates a first, big group like that:
Private Sub Form_Load()
...
set mGroups = new CDocTree
mGroups.Make Me
End Sub
In CDocTree there are three member variables declared like that:
Private WithEvents mVVPlan As CDocGroup
Private WithEvents mTestPlan As CDocGroup
Private mFrm As Form_FrmAlphaDocuments
and the method Make essentially does this:
Public Sub Make(frm As Access.Form)
Set mFrm = frm
With mFrm
Set mVVPlan = New CDocGroup
mVVPlan .Make .LblVVPlanBox, .LblVVPlanRef
Set mTestPlan = New CDocGroup
mTestPlan .Make .LblTestPlanBox, .LblTestPlanRef
End With
End Sub
Finally the class CDocGroup is defined like that:
Private WithEvents mBox As Access.Label
Private WithEvents mRef As Access.Label
Public Event Clicked(grp As CDocGroup)
Public Sub Make(box As Access.Label, ref As Access.Label)
...
Set mBox = box
Set mRef = ref
End Sub
Private Sub mBox_Click()
Debug.Print "Event Click fired in CDocGroup on " & mBox.Name
RaiseEvent Clicked(Me)
End Sub
Private Sub mRef_Click()
Debug.Print "Event Click fired in CDocGroup on " & mRef.Name
RaiseEvent Clicked(Me)
End Sub
That's it: the code never gets in mBox_Click or mRef_Click. What do I do wrong?

Thanks VBlades, it does the trick.
Although it is infuriating that something like this should be necessary, I can indeed verify that if I put a declaration for some of the controls and not for others, my events fire only for the controls that have the empty event declaration in the form's module.
So to be clear, in the form's module, for each control that I need to react, I add an empty declaration:
Private Sub LblVVPlanBox_Click()
End Sub
No code at all in it, but now the event in my class fires and the code in the class is executed. It is kind of ugly, but still better than copying 20 times the same instructions in the form's module.

If anybody comes across this post like me, trying to figure out why the events aren't firing, there is a far simpler solution on another forum.
Basically when you set your mBox / mRef in the class module do the following:
mBox.OnClick = "[Event Procedure]"
mRef.OnClick = "[Event Procedure]"
This will let the class module know, that there is an event it has to execute. So there is no need to declare all the subs in your forms code.

Related

Raise an event on another form

I have two separated form(usercontrol A & B) in VB.Net. When a button clicked on formB, I want to an event raised on formA. How can do that? I searched stackoverflow.com and check several related solutions but they didn't work for me. I don't know what is the problem! Please help me.
For Example I tried this code:
FormA:
Dim MyEditForm As New ucEmployeeTimeEdit
'some code for showing and preparing MyEditForm
Private Sub GetSomeClick() Handles MyEditForm.MyClick
System.Console.WriteLine("test")
End Sub
FormB:
Public Event MyClick()
Private Sub BtnDelet_Click(sender As Object, e As EventArgs) Handles btnDelet.Click
RaiseEvent MyClick()
End Sub
While Idle_Mind answer is great, here's an alternative method with different advantages. (EDIT: I just noticed that he mentions this too, so you can consider this an elaboration on this subject)
If you keep FormB as a modal variable inside FormA, you can tell VB that you want to use it's events. Here's some skeleton code to show you:
Public Class FormA
Private WithEvents myFormB As FormB
'rest of the code for FormA
'you can use FormB's events like this
Private Sub GetSomeClicks() Handles myFormB.MyClick
'code code code
End Sub
End Class
Public Class FormB
Public Event MyClick()
End Class
Have fun!
Somewhere in the some code portion, you need to use AddHandler to "wire up" that event:
Public Sub Foo()
Dim MyEditForm As New ucEmployeeTimeEdit
'some code for showing and preparing MyEditForm
AddHandler MyEditForm.MyClick, AddressOf GetSomeClick
End Sub
Private Sub GetSomeClick()
System.Console.WriteLine("test")
End Sub
Note that GetSomeClick() does not have a "Handles" clause on the end. This is because your instance of ucEmployeeTimeEdit was a local variable.
If you had declared it as WithEvents at class level, then you could use the "Handles" clause (and consequently would no longer need the AddHandler call).

Is it possible to create and handle a custom Event in a Customized UserForm?

I have a UserForm to which I added a property with the adequate Let and Get statements. I would like to have an event fire when the property changes. Event, RaiseEvent and Routine have been stated, all in the UserForm module. However, I can't find a way to assign the routine to the event.
For what I know, this is different from the usual Custom Events in Class Modules situation, because it is not declared in a Class Module whose Class I can instantiate in a regular module. All my searches provided examples to Custom Events in Class Modules or Built-In Events in UserForms, but no material on Custom Events in UserForms.
This actually made me wonder if it is at all possible to create Custom Events in UserForms and Private Subs to handle those Events. So is it possible to do it? And if so, what am I missing? How can I make Private Sub UFStatus_StatusChange() handle the event?
Any help would be apreciated!
So far, the code goes, all in the UserForm module:
Public Event StatusChange (old_status As Long, current_status As Long)
Dim old_status As Long
Private current_status As Long
Public Property Let UFStatus (RHS As Long)
old_status = current_status
current_status = RHS
RaiseEvent StatusChange(old_status, current_status)
End Property
Private Sub UFStatus_StatusChange()
MsgBox("Status changed from " & old_status & "to " & current_status)
End Sub
Yes, but you need to understand how VBA/COM events work first.
A bit of background...
Notice the dropdowns/comboboxes at the top of the VBE's code panes? The leftmost one is listing all available interfaces and event providers - the rightmost one is listing the available members and events exposed by whatever is selected in the leftmost dropdown.
So when you handle the Click event of some OkButton in the code-behind of UserForm1, the handler stub might look like this:
Private Sub OkButton_Click()
End Sub
The signature is constructed in a very particular way, always in the same manner, regardless of whether you're implementing an interface or handling an event:
Private Sub [Provider]_[MemberName]([Args])
That underscore matters. Whatever you do, DO NOT name an event (or interface member) with an identifier that contains an underscore. In the case of an event, you'll hit a compile error:
Compile error: Invalid event name
In the case of an interface, you'll also get a compile error, with the VBE complaining that interface members aren't implemented.
This is why everything is PascalCase and not Upper_Snake_Case in VB. Stick to the convention, avoid underscores in public member names.
If you're not sure what interfaces are and why I'm mentioning them in a post about events, lookup the Implements keyword, know that interfaces and events are very intimately related and work in a very similar fashion, and keep reading ;-)
The Provider
Any class can define events. A UserForm being a class, it can absolutely define events, yes. You define events exactly how you've done, using the Event keyword:
Public Event SomethingHappened(ByVal SomeArg As Long)
The class that defines the event is an event provider - it is the only class that is allowed to raise the events it defines.
You raise events using the RaiseEvent keyword, and provide the arguments:
Private Sub OnSomethingHappened()
RaiseEvent SomethingHappened(42)
End Sub
When and why you raise events is entirely up to your imagination.
The Client
Consider the Click event of a CommandButton on a UserForm: the CommandButton class probably has a method that listens for Win32 messages involving the mouse, and when it decides to handle a left-button click, it raises its Click event, and something something and poof the OkButton_Click procedure runs. Right?
The part MSForms is automagically doing for you, is that when you add a CommandButton on the form, and name it OkButton, well this OkButton identifier essentially becomes a public field on the form, as if you had added a public, module-level variable:
Public OkButton As MSForms.CommandButton
Except, it actually looks like this:
Public WithEvents OkButton As MSForms.CommandButton
That WithEvents keyword makes the OkButton available in the left-side dropdown - OkButton becomes an event provider, and its Click event can be handled... in the form's code-behind.
The CommandButton class doesn't know or care about a handler for its Click event: the event provider is the OkButton object, and the client is the UserForm1 class you're implementing the handlers in.
In other words, the event provider, and the client, are two completely separate classes.
The catch is that WithEvents is only legal in a class module.
You can make your UserForm1 an event provider, but it cannot handle its own events.
Declare the events in your UserForm1 code-behind, and make sure you specify ByVal parameters for parameters that are meant to be read-only at the handler site - use ByRef when the handler can modify the value, e.g. for some Cancel As Boolean parameter that you can read when the handler returns:
Public Event StatusChange(ByVal oldStatus As Long, ByVal newStatus As Long)
Now add a class module, call it MyPresenter:
Option Explicit
Private WithEvents MyView As UserForm1
Private Sub Class_Initialize()
Set MyView = New UserForm1
End Sub
Private Sub Class_Terminate()
Set MyView = Nothing
End Sub
Public Sub ShowDialog()
MyView.Show
End Sub
Select MyView from the leftmost dropdown; the rightmost dropdown should contain the StatusChange event - and selecting it should create a handler stub:
Private Sub MyView_StatusChange(ByVal oldStatus As Long, ByVal newStatus As Long)
MsgBox "Status changed from " & oldStatus & " to " & newStatus & "!"
End Sub
Now, in the standard/procedural module where you were normally showing your form, instantiate that presenter class instead:
Public Sub Macro1()
With New MyPresenter
.ShowDialog
End With
End Sub

Using button.enabled outside the Button_Click sub

I am just starting out with Visual basic .Net. I am designing an application where each button should disable itself after a registering a single click on it.
I am trying to add an additional sub, which when called, disables all of the buttons. (Disable_all())
Public Sub disable_all()
MsgBox("Testing disable_all")
Button_eight.Enabled = False
End Sub
One of the subs is calling it from the main module.
Private Sub disable()
Dim variab As Form1 = New Form1
variab.disable_all()
End Sub
The disable_all() sub however does not disable Button_eight when called. I placed a message box to check if the sub (disable_all) is getting executed, which it is. I get a message box having the text "Testing disable all" but the Button_eight is not disabled for some weird reason.
I'd really appreciate some help, thanks.
It's simple, just do Form1.disable_all().
That is, instead of variab.disable_all(), call: form1.disable_all() from the main module. Because as mentioned in the comments, new form1 creates a new instance of Form1.
You can also create a variable that point to the form dim var = form1 or dim variab as form = form1, but since you can access form1 directly from your module, it is unnecessary.

Delegates not working in another module

Running into an odd issue with tasks and delegates. Code in question is running under dotNET 4.5.1, VS2013. On the form's code I have a sub that updates a grid, it checks to see if an invoke is required, and if it is it calls a delegate. When a task runs that's called in the same module, it works as expected, no problems. Threaded or not, the grid updates properly.
However, if the same thing is called from another module, the delegate never gets called and the visual component doesn't get updated. Just a watered down bit of pseudocode to clarify..
In the form's module:
Private Delegate Sub DoWhateverDelegate(ByVal _____)
Public Sub DoWhatever(ByVal _____)
If MyComponent.InvokeReqired
Dim Delegated As New DoWhateverDelegate(AddressOf DoWhatever)
Debug.Print("The delegate fired")
Invoke(Delegated, _____)
Else
' .. carry on as usual ..
End If
End Sub
Elsewhere....
Task.Run(Sub()
' .. various things I'd rather not block the UI thread with ..
DoWhatever()
End Sub)
Works fine. I can do Task.Run__ that calls DoWhatever and it's all happy and good. However if I create a task in another module and call DoWhatever, it doesn't fire the delegate and that visual component doesn't update. The code is identical, in the same module it works, in another module it does not.
I'm probably missing something blatantly obvious.. anyone care to point out my mistake? Thanks.
Edit -- just to clarify, that other module is just code, there's only one form in the entire solution. It's created at program startup automatically, there is no other form creation going on.
Should be a thread-specific issue. Check this:
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
foo.DoSomething()
End Sub
End Class
The class with the delegate:
Public Class foo
Public Shared Sub DoSomething()
Task.Run(Sub() UpdateText())
End Sub
Public Delegate Sub UpdateTextDelegate()
Public Shared Sub UpdateText()
Dim f = Form1
'Dim f As Form1 = Application.OpenForms("Form1")
If f.InvokeRequired Then
Dim d As UpdateTextDelegate = AddressOf UpdateText
f.Invoke(d)
Else
f.TextBox1.Text = "Hi"
End If
End Sub
End Class
Run the code and the textbox will not be updated. Use the second f=.... (that one that take a reference from OpenForms) and it will be updated.
If you just try to access the default instance and you are outside the UI-thread, a new instance of the form will be created. That means, the content IS updated, but because that form is not shown, you will not see it.
NOTE I do NOT advise to solve your problem, by using OpenForms. I'd advise to correctly instantiate forms!
Add a new module/class to your code:
Module Startup
Public MyForm1 As Form1
Public Sub main()
MyForm1 = New Form1
Application.Run(MyForm1)
End Sub
End Module
Go to project properties -> application. Disable application framework and choose Sub Main as your start object. In the app, access your form via MyForm1 - or whatever you want to name it. Problem should be gone then.

VB.NET Multithreading. Calling invoke on a UI Control from a class in a separate class file

I've been trying to understand this for a few days now and wonder if it's something simple I'm missing or doing completely wrong.
Example:
Two files - TestClass.vb, myForm.vb
TestClass.vb looks as follows:
Imports System.Threading
Public Class TestClass
Private myClassThread As New Thread(AddressOf StartMyClassThread)
Public Sub Start()
myClassThread.Start()
End Sub
Private Sub StartMyClassThread()
myForm.Msg("Testing Message")
End Sub
End Class
myForm.vb is a basic form with a listbox control and a button control named respectively Output and StartButton.
The code behind the form is as follows:
Public Class myForm
Private classEntity As New TestClass
Private Sub StartButton_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles StartButton.Click
Msg("Start Button Pressed")
classEntity.Start()
End Sub
Delegate Sub MsgCallBack(ByVal mesg As String)
Public Sub Msg(ByVal mesg As String)
If Output.InvokeRequired Then
MsgBox("Invoked")
Dim d As New MsgCallBack(AddressOf Msg)
Invoke(d, New Object() {mesg})
Else
MsgBox("Not Invoked")
mesg.Trim()
Output.Items.Add(mesg)
End If
End Sub
End Class
The result:
Application runs, no errors or exceptions.
Displayed is the listbox and the Start button.
I press the start button and a msgbox says "Not Invoked" as expected and upon clicking OK to that msgbox "Start Button Pressed" is added to the Output listbox control.
Immediately following that the msgbox pops up again and says "Not Invoked". I was expecting "Invoked" as a separate thread is trying to use the Output listbox control.
Of course this results in the Output.Items.Add being attempted which results in no visible result as the thread is not allowed to directly update the UI control.
I must've read a small books worth of different pages trying to thoroughly understand pit falls and methods but I feel I may have fallen into a trap alot of people may do.
With my current understanding and knowledge I'm unable to get out of that trap and would appreciate any input or advice.
Kind regards,
Lex
The problem here is that you are calling the function Msg not on an instance of myForm, but as a shared function of the class myForm.
Change your code in TestClass to add
Public FormInstance as myForm
and then replace
myForm.Msg("Testing Message")
with
FormInstance.Msg("Testing Message")
Then in StartButton_Click add the line
classEntity.FormInstance = Me
and you will get the expected result.