Reputation: 3
I'm using VB .NET to create a planning, and I got a little problem with events. In the main form, I put a panel in which I add programatically rows and boxes in those rows. I have inside the form a TextBox and the panel that contains all the boxes. I want to change a the text of the TextBox when I click on a box, so I use the AddHandler statement but it doesn't work. I tried to debug it and I realised that it actually calls the sub and inside it, I can see the changes it makes (TextBox.Text becomes what I want), but when it exits the sub, it is like nothing has changed. I don't know if I was clear enough. Thanks
Here is a simplified code (I removed all the graphics functions to resize the controls...)
Public Class frmPrinc
Public actEditing As Object
Private Class boxAct
Inherits Label
Public act As Integer
Public Sub New(ByVal a As Integer)
act = a
AddHandler Me.Click, AddressOf clickBox
End Sub
Private Sub clickBox(sender As Object, e As EventArgs)
Dim boxact As boxAct = DirectCast(sender, boxAct)
frmPrinc.actEditing = boxact
boxact.Text = "Clicked"
End Sub
End Class
Private Sub showPlanning()
pan_plan.Controls.Clear()
Dim plan As New Control ' Control that will be used as a row
For i As Integer = 0 To 10
plan.Controls.Add(New boxAct(i))
Next
Panel1.Controls.Add(plan)
End Sub
End Class
When I run that, the text of the box changes but actEditing is still Nothing...
Upvotes: 0
Views: 2316
Reputation: 39152
Instead of boxAct trying to directly update frmPrinc of the current "box" being clicked, it should instead raise a Custom Event that frmPrinc subscribes to. frmPrinc can use that information as it then sees fit. Below I've added the custom event and raise it in class boxAct. The form subscribes to that event using AddHandler when each instance of boxAct is created. All together, this looks something like:
Public Class frmPrinc
Public actEditing As boxAct
Public Class boxAct
Inherits Label
Public act As Integer
Public Event BoxClicked(ByVal box As boxAct)
Public Sub New(ByVal a As Integer)
act = a
End Sub
Private Sub boxAct_Click(sender As Object, e As EventArgs) Handles Me.Click
Me.Text = "Clicked"
RaiseEvent BoxClicked(Me)
End Sub
End Class
Private Sub showPlanning()
pan_plan.Controls.Clear()
Dim plan As New Control ' Control that will be used as a row
For i As Integer = 0 To 10
Dim box As New boxAct(i)
AddHandler box.BoxClicked, AddressOf box_BoxClicked
plan.Controls.Add(box)
Next
Panel1.Controls.Add(plan)
End Sub
Private Sub box_BoxClicked(box As boxAct)
actEditing = box
Debug.Print("Box Clicked: " & actEditing.act)
End Sub
End Class
From the comments:
Thanks man, it worked! I'd like to know though why I need to make such a structure to raise a simple event that modifies the main form... Just to not do the same mistake again – Algor Frile
This a design decision everyone must make: "Loosely Coupled" vs. "Tightly Coupled". The approach I gave above falls into the Loosely Coupled category. The main benefit to a loosely coupled solution is re-usability. In your specific case, we have Class boxAct being reused multiple times, albeit all within the same form. But what if that wasn't the case? What if you wanted to use boxAct on multiple forms (or even have multiple "groups" of them)? With your original approach, you had this line:
frmPrinc.actEditing = boxact
which means that if wanted to use Class boxAct with a different form you'd have to make a copy of Class boxAct, give it a new name, and then manually change that one line to reference the new form:
Public Class boxAct2
Private Sub clickBox(sender As Object, e As EventArgs)
Dim boxact As boxAct = DirectCast(sender, boxAct)
frmSomeOtherForm.actEditing = boxact
boxact.Text = "Clicked"
End Sub
End Class
This shows the disadvantage of the Tightly Coupled approach, which uses references to specifics types (the Form in this case) to communicate. The Tightly coupled approach might be initially easier to implement when you're coding fast and furious, but then it suffers from re-usability down the line. There are scenarios in which a tightly coupled solution make sense, but only you can make that decision; those scenarios usually involve some kind of "sub-control" that will only ever get used within some kind of custom container/control and will never be used on its own somewhere else.
Conversely, with the loosely coupled approach, if we wanted to re-use Class boxAct in a different form, then no changes to it would be required at all (though at that point you'd probably not want it declared within your original Form!). In the new form you'd simply add a handler for the BoxClicked() event and then do what you need to do. Each form would receive the events for its respective instances of boxAct.
Final thoughts...your original approach could actually work, but most likely was failing at this line (same as above):
frmPrinc.actEditing = boxact
Here you were referencing to frmPrinc using what is known as the Default Instance of that form. This would have worked if frmPrinc was the "Startup Object" for your application. I'm guessing it wasn't, however, and you were creating an instance of frmPrinc from somewhere else. To make the original approach work you would have had to pass a reference to your ACTUAL instance of frmPrinc into Class boxAct (usually via the Constructor in tightly coupled solutions).
Upvotes: 1