Iterate through timer then get their names - vb.net

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.

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:
I hope this is of interest.

I've just simplified your code. Here it is:
For Each x In Me.components.Components.OfType(Of Timer)()
If x.Tag = "tmrString" & btnStatusNumber Then
Console.WriteLine("Timer name: " & x.Tag)
End If
Next
All you have to do now is match the Tag of each Timer corresponding to its Name.

Related

Adding Multiple TreeNode Levels with Multi threading

i'm having alot of trouble trying to get a background worker properly functioning to populate a couple of TreeView Nodes. In said TreeView i have multiple levels of Nodes. For example
FileName
Model
Test & Result.
Model refers to a MicroStation Model (CAD Drawing Program), easiest way to explain it is a Sheet within a Spreadsheet.
I'm using a FileDialog to select files, once the files are selected each filename is added to the TreeView with its own Node.
The idea is that the program will then open each file, scan each model and add a sub TreeNode under the files Node wih the type of test and the result.
The DoWork function for the Background worker is below. I have removed alot of the code to simply my post. But there are 7 "tests" that the program does, i have included 2.
In the below example, "CheckFonts" is a function that just counts the text elements in a file and returns a number.
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
For i As Integer = 0 To m_CountTo
If BackgroundWorker1.CancellationPending Then
e.Cancel = True ' Set Cancel to True
Exit For
End If
Dim sError As String = ""
Dim bBorderFound As Boolean = False
Dim oDesignFile As DesignFile
Dim oModel As ModelReference
Dim oFontNode As New TreeNode
Dim oFontResultNode As New TreeNode
For Each oNode As TreeNode In trvItems.Nodes
Dim ustn As New MicroStationDGN.Application
oDesignFile = ustn.OpenDesignFileForProgram(oNode.Text, True)
For Each oModel In oDesignFile.Models
'####### Checks for Items on Default Level #######
If bDefaultPass = True Then
Dim iDefaultItems As Long
iDefaultItems = DefaultItems(oModel)
If iDefaultItems > 0 Then
sDefaultMessage = "There are " & iDefaultItems & " items on the Default Level"
bDefaultPass = False
Else
sDefaultMessage = "There are no items on the Default Level"
bDefaultPass = True
End If
End If
'####### Checks for Non Standard Fonts #######
If bFontPass = True Then
Dim iFontCheck As Long
iFontCheck = CheckFonts(oModel)
If iFontCheck > 0 Then
sFontMessage = "There are " & iFontCheck & " Text Elements that use a Non Standard Font."
bFontPass = False
ElseIf iFontCheck = -99999 Then
sFontMessage = "There are some corrupt or invalid Fonts used in the Design File"
bFontPass = False
Else
sFontMessage = "All Text Elements use the Correct Font"
bFontPass = True
End If
End If
Next ' End Model
oFontNode = oNode.Nodes.Add("Font Check")
oFontResultNode = oFontNode.Nodes.Add("")
If bFontPass = True Then
oFontResultNode.Text = "PASS - " & sFontMessage
oFontResultNode.ImageIndex = 0
oNode.Collapse()
Else
oFontResultNode.Text = "FAIL - " & sFontMessage
bPass = False
oFontResultNode.ImageIndex = 1
oFontNode.ImageIndex = 1
oNode.Expand()
oFontNode.Expand()
End If
oDefaultItemsNode = oNode.Nodes.Add("Default Items Check")
oDefaultItemsResultNode = oDefaultItemsNode.Nodes.Add("")
If bDefaultPass = True Then
oDefaultItemsResultNode.Text = "PASS - " & sDefaultMessage
oDefaultItemsResultNode.ImageIndex = 0
oNode.Collapse()
Else
oDefaultItemsResultNode.Text = "FAIL - " & sDefaultMessage
oDefaultItemsResultNode.ImageIndex = 1
oDefaultItemsResultNode.ImageIndex = 1
oNode.Expand()
bPass = False
End If
Next ' End File
Next
End Sub
I have worked with Background Workers before but this is a bit more complex to what i have done, i understand that you cant update controls from a different thread and that Invoke is used to pass it information. But i'm confused how to do it with multiple nodes. The best example I saw was here
Adding nodes to treeview with Begin Invoke / Invoke
But with multiple nodes the code became quite confusing and didn't work. The error message keeps coming up regarding the Invoke / BeginInvoke being called.
So, i guess my main question is where i would call the Invoke command to best make use of the Background worker?
Thanks in advance!!

Why does Windows.Media.VisualTreeHelper.GetChild(...) exit the sub?

I have a vb application with a listview.
I was testing and ended up with this sub for my dragover event:
Private Sub ListView1_DragOver(sender As Object, e As DragEventArgs) Handles ListView1.DragOver
XToolStripLabel.Text = "X:" & e.X
YToolStripLabel.Text = "Y:" & e.Y
Dim Insertion As Integer = GetInsertion(MousePosition, ListView1)
SelectionTestTSlabel.Text = "SEL " & Insertion
If CurrentlyDragging Then
Dim gr As Graphics = ListView1.CreateGraphics
Windows.Media.VisualTreeHelper.GetChild(New Windows.DependencyObject(), 0) 'I know this seems like it doesn't do much
MsgBox("") 'I actually added this later because the rest of the code did not run normally.
Dim InsertionY As Integer = 24 + (Insertion * TestItemHeight)
If MyLastInsPoint = Insertion Then
Else
ListView1.Refresh()
gr.DrawLine(New Pen(Color.Gray, 3), New Point(0, InsertionY), New Point(ListView1.Width, InsertionY))
End If
MyLastInsPoint = Insertion
End If
End Sub
I realized that the MsgBox("") doesn't popup a message box then i spammed breakpoints on the whole sub.
The sub ran as normal but after this line,
Windows.Media.VisualTreeHelper.GetChild(New Windows.DependencyObject(), 0)
the program returned to the form and did not execute the MsgBox("") line.
What's wrong with the line? The program did not even stop at the End Sub breakpoint.
New Windows.DependencyObject() has no children, so index 0 is not available
See the remaks on MSDN
Call the GetChildrenCount method to determine the total number of
child elements of a parent visual.
The value of reference can
represent either a Visual or Visual3D object, which is why the common
base type DependencyObject is used here as a parameter type.

Force datagridview call row validating after finish input

I'm creating a Form with a Bound Datagridview inside it. On Form_Load or Row_Validating, I added new row to Datagridview by:
Private Sub PurchaseInRowAdd()
'add new row to datagridview
Dim dtRow As DataRow = CType(Me.dgvPurchaseIn.DataSource, DataTable).NewRow()
dtRow.Item("InvoiceID") = 0
dtRow.Item("KindID") = CType(CType(Me.dgvPurchaseIn.Columns("colPurchaseKind"), DataGridViewComboBoxColumn).DataSource, DataTable).Rows(0)("KindID")
dtRow.Item("InvoiceSign") = ""
dtRow.Item("InvoiceNo") = ""
dtRow.Item("InvoiceDate") = New Date(objController.ProcessYear, objController.ProcessMonth, 1)
dtRow.Item("ID") = CType(CType(Me.dgvPurchaseIn.Columns("colPurchaseCustomer"), DataGridViewComboBoxColumn).DataSource, DataTable).Rows(0)("ID")
dtRow.Item("Product") = ""
dtRow.Item("Price") = "0.00"
dtRow.Item("Note") = ""
dtRow.Item("Tax") = CType(CType(Me.dgvPurchaseIn.Columns("colPurchaseKind"), DataGridViewComboBoxColumn).DataSource, DataTable).Rows(0)("Tax")
dtRow.Item("TaxCode") = CType(CType(Me.dgvPurchaseIn.Columns("colPurchaseCustomer"), DataGridViewComboBoxColumn).DataSource, DataTable).Rows(0)("TaxCode")
dtRow.Item("VAT") = ""
CType(Me.dgvPurchaseIn.DataSource, DataTable).Rows.Add(dtRow)
End Sub
The problem here is, when user finished input in that new row and press enter, Row_Validating hasn't been fired because there's no row below it. So how can I force Row_Validating trigger when user finished input and press enter?
I have found this solution, but it doesn't suit my case because I don't want to set Enable Adding to True. I want to handle row adding by code instead.
I found the solution, I subclassed DataGridView and override ProcessDialogKey like this:
protected override bool ProcessDialogKey(Keys keyData)
{
if(keyData == Keys.Enter)
{
KeyEnterPress(this, new EventArgs());
}
return base.ProcessDialogKey(keyData);
}
(I wrote this subclass in C#)
Then handle key enter press in my form like this
Private Sub PurchaseInCellKeyDown(ByVal sender As Object, ByVal e As EventArgs)
If Me.dgvPurchaseIn.CurrentRow.Index = Me.dgvPurchaseIn.Rows.Count - 1 Then
If PurchaseInRowValidate(Me.dgvPurchaseIn.CurrentRow.Index, True) Then
Me.PurchaseInRowAdd()
Me.deselectPurchaseCell(Me.dgvPurchaseIn.CurrentRow.Index)
Me.dgvPurchaseIn.Rows(Me.dgvPurchaseIn.Rows.Count - 1).Cells("colPurchaseSign").Selected = True
End If
End If
End Sub
This line:
Me.dgvPurchaseIn.Rows(Me.dgvPurchaseIn.Rows.Count - 1).Cells("colPurchaseSign").Selected = True
will trigger row validating

Trouble with Timer_tick not stopping

I'm very new to programming and vb.net, trying to self teach more so as a hobby, as I have an idea for a program that I would find useful, but I am having trouble getting past this issue and I believe it is to do with the timer.
I have a form of size.(600,600) with one button of size.(450,150) that is set location(100,50) on the form. When clicked I want to move down it's own height, then add a new button in it's place. The code included below works as desired for the first two clicks, but on the third click the button keeps moving and the autoscroll bar extends. I initially thought it was the autoscroll function or the location property, but realised that as the button keeps moving, the timer hasn't stopped. I am aware that the code is probably very clunky in terms of achieving the outcome, and that there are a few lines/variables that are currently skipped over by the compiler (these are from older attempts to figure this out).
I have looked around and can't find the cause of my problem. Any help would be greatly appreciated. Apologies if the code block looks messy - first go.
Public Class frmOpenScreen
Dim intWButtons, intCreateButtonY, intCreateButtonX 'intTimerTick As Integer
Dim arrWNames() As String
Dim ctrlWButtons As Control
Dim blnAddingW As Boolean
Private Sub btnCreateW_Click(sender As System.Object, e As System.EventArgs) Handles btnCreateW.Click
'Creates new Button details including handler
Dim strWName, strWShort As String
Dim intCreateButtonY2 As Integer
Static intNumW As Integer
Dim B As New Button
strWName = InputBox("Please enter the name name of the button you are creating. Please ensure the spelling is correct.", "Create W")
If strWName = "" Then
MsgBox("Nothing Entered.")
Exit Sub
End If
strWShort = strWName.Replace(" ", "")
B.Text = strWName
B.Width = 400
B.Height = 150
B.Font = New System.Drawing.Font("Arial Narrow", 21.75)
B.AutoSizeMode = Windows.Forms.AutoSizeMode.GrowAndShrink
B.Anchor = AnchorStyles.Top
B.Margin = New Windows.Forms.Padding(0, 0, 0, 0)
'Updates Crucial Data (w name array, number of w buttons inc Create New)
If intNumW = 0 Then
ReDim arrWNames(0)
Else
intNumW = UBound(arrWNames) + 1
ReDim Preserve arrWNames(intNumW)
End If
arrWNames(intNumW) = strWShort
intNumW = intNumW + 1
intWButtons = WButtonCount(intWButtons) + 1
'updates form with new button and rearranges existing buttons
intCreateButtonY = btnCreateW.Location.Y
intCreateButtonX = btnCreateW.Location.X
‘intTimerTick = 0
tmrButtonMove.Enabled = True
‘Do While intTimerTick < 16
‘ 'blank to do nothing
‘Loop
'btnCreateW.Location = New Point(intCreateButtonX, intCreateButtonY + 150)
B.Location = New Point(intCreateButtonX, intCreateButtonY)
Me.Controls.Add(B)
B.Name = "btn" & strWShort
intCreateButtonY2 = btnCreateW.Location.Y
If intCreateButtonY2 > Me.Location.Y Then
Me.AutoScroll = False
Me.AutoScroll = True
Else
Me.AutoScroll = False
End If
'MsgBox(intCreateButtonY)
End Sub
Function WButtonCount(ByRef buttoncount As Integer) As Integer
buttoncount = intWButtons
If buttoncount = 0 Then
Return 1
End If
Return buttoncount
End Function
Public Sub tmrButtonMove_Tick(sender As System.Object, e As System.EventArgs) Handles tmrButtonMove.Tick
Dim intTimerTick As Integer
If intTimerTick > 14 Then
intTimerTick = 0
End If
If btnCreateW.Location.Y <= intCreateButtonY + 150 Then
btnCreateW.Top = btnCreateW.Top + 10
End If
intTimerTick += 1
If intTimerTick = 15 Then
tmrButtonMove.Enabled = False
End If
End Sub
End Class
So my current understanding is that the tick event handler should be increasing the timertick variable every time it fires, and that once it has hits 15 it should diable the timer and stop the button moving, but it is not doing so.
Thanks in advance.
IntTimerTick is initialized to 0 at the beginning of every Tick event. This won't happen if you declare it to be static:
Static Dim intTimerTick As Integer

Dynamic controls not rendering in WinForm

I'm trying to create a list of labels and textboxes. No errors but they aren't rendering on the form. I have confirmed my loop have values
Private Sub AddLabels_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'MsgBox(strNumberOfLabels + " " + strOrderNum)
TableLayoutPanel1.AutoSize = True
TableLayoutPanel1.Visible = False
TableLayoutPanel1.SuspendLayout()
For i As Integer = 0 To strNumberOfLabels
'MsgBox(i)
Dim txtBox As New TextBox
Dim txtLabel As New Label
txtLabel.Text = "Label " + i
txtBox.Name = "txt" + i
TableLayoutPanel1.Controls.Add(txtLabel)
txtLabel.Show()
txtBox.Show()
TableLayoutPanel1.ResumeLayout()
TableLayoutPanel1.Visible = True
Next
End Sub
Try using the other Add overload, which specifies which column and row the control should go into:
Dim txtLabel As New Label
txtLabel.Text = "Label" + i.ToString
'\\ TableLayoutPanel1.Controls.Add(txtLabel)
TableLayoutPanel1.Controls.Add(txtLabel, 0, 0)
This is not necessary:
'\\ txtLabel.Show()
This should be moved outside of the loop:
Next
TableLayoutPanel1.ResumeLayout()
'\\ TableLayoutPanel1.Visible = True
The txtBox control is never being added to the TableLayoutPanel control or the form.
I don't think it's necessary to make your TableLayoutPanel visible or invisible during the OnLoad procedure, the form isn't visible yet.
The only other thing to confirm is the value of strNumberOfLabels. I'm suspecting it's zero.