Algor Frile
Algor Frile

Reputation: 3

VB .NET event handler of controls

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

Answers (1)

Idle_Mind
Idle_Mind

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

Related Questions