Cary Bondoc Cary Bondoc - 6 months ago 31
Vb.net Question

Iterate through timer then get their names

Yes, by spending time on research I am aware that


A Timer is a Component not a Control so it will not be located in the
Control Collection. This is a case where it is probably better to not
use a common button click handler since it is not simplifying
anything. (Source)


But, is there anything that I can do to pass my current situation?

What I want is whenever a user clicks a button (I have many buttons) it will determine if the status of that button is
Start
or
Stop
then if it is
stop
it should
start
.

Stop and Start correspond to the timer, the number of buttons I have corresponds to the number of timers I have.

I can easily linked them by creating a function like this

Function isBTNStatusEnabled(sender As Object) As Boolean
Dim result As Boolean = False
Dim btnStatus As Button = DirectCast(sender, Button)
Dim btnStatusNumber As String = btnStatus.Name.Substring(btnStatus.Name.Length - 1)

Console.WriteLine("Found the " & btnStatus.Name)
If btnStatus.Text = "Start" Then
btnStatus.Text = "Stop"
result = True
btnStatus.BackColor = Color.Red
Else
btnStatus.Text = "Start"
result = False
btnStatus.BackColor = SystemColors.Control
End If

For Each frmTesterObjects As Object In Me.components.Components
If TypeOf frmTesterObjects Is Timer And DirectCast(frmTesterObjects, Timer).Tag.ToString = "tmrString" & btnStatusNumber Then
'what to do
Console.WriteLine("Timer name: " & DirectCast(frmTesterObjects, Timer).Tag.ToString)
End If
Next frmTesterObjects

Return result
End Function


My problem is this part of code

If TypeOf frmTesterObjects Is Timer And DirectCast(frmTesterObjects, Timer).Tag.ToString = "tmrString" & btnStatusNumber Then
Console.WriteLine("Timer name: " & DirectCast(frmTesterObjects, Timer).Tag.ToString)
End If


I am trying to get the name of the timer by iterating through all objects in my forms. I can easily group all of the objects by properly naming them, for example in
set 1
I have
btnStatus1
and
tmrString1
then in
set 2
I have
btnStatus2
and
tmrString2
, only the last string which is a number changes.

Answer

I've almost always found that searching for components by name at run-time will eventually fail if you continue to maintain/modify the form. It is far better, in my opinion, to make sure you have a compile-time check.

I would do this code instead:

Function isBTNStatusEnabled(sender As Object) As Boolean

    Dim button2Timer = New Dictionary(Of Button, Timer) From
        {{Button1, Timer1}, {Button2, Timer2}, {Button3, Timer3}}

    Dim result As Boolean = False
    Dim btnStatus As Button = DirectCast(sender, Button)

    Console.WriteLine("Found the " & btnStatus.Name)
    If btnStatus.Text = "Start" Then
        btnStatus.Text = "Stop"
        result = True
        btnStatus.BackColor = Color.Red
    Else
        btnStatus.Text = "Start"
        result = False
        btnStatus.BackColor = SystemColors.Control
    End If

    Console.WriteLine("Timer name: " & button2Timer(btnStatus).Tag.ToString())

    Return result

End Function

The Dictionary(Of Button, Timer) hard-codes the mapping in so that there is no need to search for the Timer. This also alleviates the need to actually name and tag the buttons and timers.


Just for the fun of it I have had a go at implementing your full solution based on your answer to my comment on the question. Here it is:

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

    Dim parts =
    {
        New With {.Button = Button1, .NumericUpDown = NumericUpDown1, .TextBox = TextBox1, .Port = 1},
        New With {.Button = Button2, .NumericUpDown = NumericUpDown2, .TextBox = TextBox2, .Port = 2},
        New With {.Button = Button3, .NumericUpDown = NumericUpDown3, .TextBox = TextBox3, .Port = 3}
    }

    Dim query =
        From p In parts
        Select
            Observable _
                .FromEventPattern(Sub(h) AddHandler p.Button.Click, h, Sub(h) RemoveHandler p.Button.Click, h) _
                .ObserveOn(Me) _
                .Do(Sub(ep)
                        Dim start = p.Button.Text = "Start"
                        p.Button.Text = If(start, "Stop", "Start")
                        p.Button.BackColor = If(start, Color.Red, SystemColors.Control)
                        p.NumericUpDown.Enabled = Not start
                        p.TextBox.Enabled = Not start
                    End Sub) _
                .Select(Function(ep) _
                    Observable _
                        .Interval(TimeSpan.FromSeconds(p.NumericUpDown.Value)) _
                        .Select(Function(n) New With {p.TextBox.Text, p.Port}) _
                        .TakeWhile(Function(x) p.Button.Text = "Stop")) _
                .Switch()

    query _
        .Merge() _
        .ObserveOn(Me) _
        .Subscribe(
            Sub(x)
                TextBox4.Text = TextBox4.Text + String.Format("Text ""{0}"" on Port ""{1}""{2}", x.Text, x.Port, Environment.NewLine)
            End Sub)

End Sub

I've used Microsoft's Reactive Framework for all of the event handling and timers. You just need to NuGet "Rx-WinForms" into your app to use it.

You'll see that parts contains a list of the button, numeric up/down, text box and a port number.

query takes these parts and creates handlers for the button clicks. Based on the text within the button it starts timers based on the numeric up/down controls and text boxes. Query simply produces a stream of values in the form of { .Text = "Foo", .Port = 1 }. query also uses a .Do(...) operator to update the UI while the timers are running.

Finally the .Subscribe(...) code takes these values, and, in my app, adds them to a text box on the form so that I can see all the output.

Here's an example of the form as I was using it:

form

I hope this is of interest.

Comments