Threading System.NullReferenceException - vb.net

I am trying to start and stop a autochecking function when checking or unchecking a checkbox.
Private Sub CheckBoxautorefresh_CheckedChanged(sender As Object, e As EventArgs) Handles CheckBoxautorefresh.CheckedChanged
Dim AutoRefreshThread As Thread = Nothing
If CheckBoxautorefresh.Checked Then
AutoRefreshThread = New Threading.Thread(AddressOf Main.AutoRefresh)
AutoRefreshThread.SetApartmentState(Threading.ApartmentState.STA)
AutoRefreshThread.Start()
Else
AutoRefreshThread.Abort()
End If
End Sub
When I check the Checkbox it starts AutoRefresh-Sub fine, and it works. When I unselect it after that, I get a System.NullReferenceException in this line:
AutoRefreshThread.Abort()
The Autorefresh function just downloads a string every 30 seconds.
And I like to check this autorefresh on/off with a checkbox.
But for some reason it doesn't work.
Can someone help me out? :)

You're defining the thread inside the CheckedChanged event:
Dim AutoRefreshThread As Thread = Nothing
When the checkbox is unchecked, you're referencing a variable that has not actually been instantiated (that only happens when the checkbox is checked). You're no longer referencing the original thread you created when the checkbox was checked.
Try defining AutoRefreshThread outside of the event.

I am not a big fan of .Abort, a lot of gotchas. See http://msdn.microsoft.com/en-us/library/5b50fdsz%28v=vs.110%29.aspx
Try this pattern.
Dim thrd As Threading.Thread
Dim thrdStopped As New Threading.ManualResetEvent(False)
Private Sub CheckBox1_CheckedChanged(sender As Object, e As EventArgs) Handles CheckBox1.CheckedChanged
If CheckBox1.Checked Then
If IsNothing(thrd) OrElse thrd.ThreadState <> Threading.ThreadState.Background Then
thrdStopped.Reset() '=false
thrd = New Threading.Thread(AddressOf someThread)
thrd.IsBackground = True
thrd.Start()
End If
ElseIf Not IsNothing(thrd) AndAlso thrd.ThreadState = Threading.ThreadState.Background Then
thrdStopped.Set() '=true
thrd.Join()
End If
End Sub
Private Sub someThread()
Do
Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss.ff"))
Loop While Not thrdStopped.WaitOne(100) 'loop while false, sleep 100 ms.
End Sub

Related

VB.NET: Add\remove row indexes of datagridview checkboxes rows

I am working with forms in VB.NET
There is a DatagridView table with a checkbox column.
See the picture below:
I am interested in the question: how to add the line index to the list when clicking in the checkbox (when we activate the checked status), and remove it from the list when we uncheck the checkbox?
Tried the following but this is not the correct solution:
If e.ColumnIndex = chk_column.Index Then
If e.RowIndex >= 0 Then
Try
For Each row As DataGridViewRow In dataGridNames.Rows
Dim cell As DataGridViewCheckBoxCell = TryCast(row.Cells(5), DataGridViewCheckBoxCell)
If cell.Value Is cell.FalseValue Then
bList_indexes.Add(DataGridnames.CurrentCell.RowIndex)
Exit For
Else 'If cell.Value Is cell.TrueValue Then
bList_indexes.RemoveAt(DataGridnames.CurrentCell.RowIndex)
End If
Next
Catch ex As Exception
'Show the exception's message.
'MessageBox.Show(ex.Message)
'Throw New Exception("Something happened.")
End try
End If
End If
Using DataSources allows you to take the logic out of mucking around in DataGridView events. You shouldn't perform [much] business logic on the UI anyways.
Here is the class I used to represent your data.
Public Class ClassWithSelect
Public Property [Select] As Boolean
Public Property Name As String
Public Sub New(s As Boolean, n As String)
Me.Select = s
Me.Name = n
End Sub
End Class
And all the code to set DataSources
Private myDataSource As List(Of ClassWithSelect)
Private selectedIndices As List(Of Integer)
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
myDataSource = Enumerable.Range(65, 10).Select(Function(i) New ClassWithSelect(False, Chr(i).ToString())).ToList()
DataGridView1.DataSource = myDataSource
updateSelectedIndices()
End Sub
Private Sub DataGridView1_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellValueChanged
updateSelectedIndices()
End Sub
Private Sub DataGridView1_CellContentClick(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellContentClick
DataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit)
End Sub
Private Sub updateSelectedIndices()
selectedIndices = New List(Of Integer)()
For i = 0 To myDataSource.Count - 1
If myDataSource(i).Select Then selectedIndices.Add(i)
Next
ListBox1.DataSource = selectedIndices
End Sub
And the end result
Now you don't need to access the UI to get the indices for further processing as they are in the class-level variable selectedIndices. The UI is meant for user I/O, NOT for storing state.
Note: The event handler was taken from this answer but this answer is also linked as an improvement to the check change handler, but I felt the complexity would distract from my answer. If you find you need to click fast, look into the latter.
Also Note: The method updateSelectedIndices() should have inside it an InvokeRequired check if you plan to perform work off the UI thread

Infinite loop is causing the form to not show up vb

I have an infinite loop in this sub because I want the program to keep testing this process to see if the variable has changed. When I run the program in the debugger, nothing shows up, including the form however when I removed the infinite loop from the program, the form showed up again. Does anyone know why this is happening? I should also mention I've tried a DO LOOP as well. Can anyone help?
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim greenCount As Integer
Dim looptest As Boolean = True
While looptest = True
For Each control In Me.Controls.OfType(Of Button)
If control.BackColor = Color.Lime Then
greenCount += 1
End If
Next
txtFielder.Text = greenCount.ToString
End While
End Sub
You need to get rid of all that code regardless. Depending on how you're changing the BackColor of those Buttons in the first place, updating the lime count might be best done there. Otherwise, you should be handling the appropriate event, e.g.
Private limeButtonCount As Integer = 0
Private Sub Buttons_BackColorChanged(sender As Object, e As EventArgs) Handles Button3.BackColorChanged,
Button2.BackColorChanged,
Button1.BackColorChanged
If DirectCast(sender, Button).BackColor = Color.Lime Then
limeButtonCount += 1
Else
limeButtonCount -= 1
End If
TextBox1.Text = limeButtonCount.ToString()
End Sub
Note that this code assumes that there are only two possible BackColor values and that all Buttons are not lime by default. If your scenario is a bit more complex than that then you may need to change a code a little, e.g.
Private limeButtonCount As Integer = 0
Private Sub Buttons_BackColorChanged(sender As Object, e As EventArgs) Handles Button3.BackColorChanged,
Button2.BackColorChanged,
Button1.BackColorChanged
limeButtonCount = Controls.OfType(Of Button)().Count(Function(b) b.BackColor = Color.Lime)
TextBox1.Text = limeButtonCount.ToString()
End Sub
Form.Load occurs before a form is displayed for the first time.
This means that you'll never see your form as long as you loop in this event. You probably want to use the Shown event instead.

Replacement for thread.start() and thread.abort()

I need to display a form for some amount of time - basically a "please wait, loading" form with progress bar. When certain operation completes, I want this window to disappear. Here's my try at it:
If IsNothing(mlLabels) Or mblnIsLoading Then Exit Sub
If mstrPrinterA.Equals(Me.cmbPrinters.Text, StringComparison.OrdinalIgnoreCase) Then
Exit Sub
End If
Dim th As New Threading.Thread(AddressOf WaitPrinter)
th.Start()
If mlLabels.IsPrinterOnLine(Me.cmbPrinters.Text) Then
Me.cmbPrinters.BackColor = Drawing.Color.Green
Else
Me.cmbPrinters.BackColor = Drawing.Color.Red
End If
th.Abort()
Do While th.IsAlive
Loop
th = Nothing
mstrPrinterA = Me.cmbPrinters.Text
Private Sub WaitPrinter()
Dim fw As New FormWaiting
fw.ShowDialog()
fw = Nothing
End Sub
However, I then read that using Thread.Start() and Thread.Abort() is not considered a good practice. Is there another way I can do that?
Here is a simple example of what I described in my comment above. Create a WinForms project with two forms, adding a Button to Form1 and a BackgroundWorker to Form2. Add this code to Form1:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'Display a dialogue while the specified method is executed on a secondary thread.
Dim dialogue As New Form2(New Action(Of Integer)(AddressOf Pause), New Object() {5})
dialogue.ShowDialog()
MessageBox.Show("Work complete!")
End Sub
Private Sub Pause(period As Integer)
'Pause for the specified number of seconds.
Threading.Thread.Sleep(period * 1000)
End Sub
and this code to Form2:
Private ReadOnly method As [Delegate]
Private ReadOnly args As Object()
Public Sub New(method As [Delegate], args As Object())
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.method = method
Me.args = args
End Sub
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
'Execute the specified method with the specified arguments.
method.DynamicInvoke(args)
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
'Close the dialogue when the work is complete.
Close()
End Sub
Run the project and click the Button on the startup form. You'll see the dialogue displayed while the work is executed and then disappear when it's done. The dialogue is written in such a way that it can be used to invoke any method with any arguments. It's the caller that gets to define what the work to be performed is.
In this particular case, the "work" is a simple sleep but you can put anything you like in there. Just note that it is executed on a secondary thread so no direct interaction with the UI is allowed. If you need UI interaction then that could be accomplished but you'd need slightly more complex code. Note that the code as it is also does not allow for returning a result from the executed method, but you could support that fairly easily too.

Multithreading doesn't work

I'm making a simple multithreading program to explain the working of threading. I want two counters counting on the same time but it doesn't work.
It only works if I use: CheckForIllegalCrossThreadCalls = False. But, I want to program in a proper way.
Code:
Dim Thread1 As System.Threading.Thread
Dim Thread2 As System.Threading.Thread
Private Delegate Sub SetTeller1()
Private Sub teller1()
If teller1Label.InvokeRequired Then
Invoke(New SetTeller1(AddressOf teller1))
Else
For i As Integer = 0 To 1000
teller1Label.Text = i
Refresh()
Next
End If
End Sub
Delegate Sub SetTeller2()
Private Sub teller2()
If teller2Label.InvokeRequired Then
Invoke(New SetTeller2(AddressOf teller2))
Else
For i As Integer = 0 To 1000
teller2Label.Text = i
Refresh()
Next
End If
End Sub
Private Sub teller1Button_Click(sender As Object, e As EventArgs) Handles teller1Button.Click
Thread1 = New Threading.Thread(AddressOf teller1)
Thread1.Start()
End Sub
Private Sub teller2Button_Click(sender As Object, e As EventArgs) Handles teller2Button.Click
Thread2 = New Threading.Thread(AddressOf teller2)
Thread2.Start()
End Sub
The multithreading works perfectly, but you are not utilizing it. The only thing you're currently doing in the background thread is calling Invoke, which means that your thread will exit within a few milliseconds and then be discarded.
Once you call Invoke the execution of the teller1 or teller2 method is moved to the UI thread, meaning it will block the UI until its execution is finished. You should only invoke when you are to update the UI, and perform all iterations in the background thread.
Here's an example of how you can do it more properly:
Delegate Sub SetTeller1(ByVal Text As String)
Private Sub teller1()
For i As Integer = 0 To 1000
SetTeller1Text(i)
Next
End Sub
Private Sub SetTeller1Text(ByVal Text As String)
If Me.InvokeRequired Then
Me.Invoke(New SetTeller1(AddressOf SetTeller1Text), Text)
Else
teller1Label.Text = Text
Me.Refresh()
End If
End Sub
For improved readability I changed for example Invoke(...) to Me.Invoke(...).
Also I'm not sure why you're calling Refresh() as it isn't necessary and will just cause extra redrawing of the entire container (guessing this is a form).

Vb.net button still allows click while disabled

My button is responding to clicks while disabled.
Private Sub btnGenerate_Click(sender As Object, e As EventArgs) Handles btnGenerate.Click
btnGenerate.Enabled = False
Me.Cursor = Cursors.WaitCursor
'Do a bunch of operations
Me.Cursor = Cursors.Default
btnGenerate.Enabled = True
End Sub
It takes about 5-10 seconds to process the stuff I'm doing in the background. During that 5-10 seconds the button is greyed out, but if I click it a second time, then it performs the operational stuff a second time after finishing the first.
I'm missing something here. How can I prevent button from allowing interaction until operations are finished?
Dim Working as boolean=false
Private Sub btnGenerate_Click(sender As Object, e As EventArgs) Handles btnGenerate.Click
if Working=true then exit sub
' Your Work Process
Work()
End Sub
sub Work()
Working=True
' Work code
Working=False
end sub
this should prevent the double click
With VS2012, Async Work is very easy to use (compared to previous versions...).
The problem is the UI thread is not letting go. Without seeing what 'work' is actually going on, I can not explain why. I am hoping nothing that re enables the button...
However, Async will allow release of the UI thread and the enabled = false should take effect. Try something like this:
Private Async Sub btnGenerate_Click(sender As Object, e As EventArgs) Handles btnGenerate.Click
btnGenerate.Enabled = False
Dim t As New Task(Sub() MyWorkLoad())
t.Start()
Await t
btnGenerate.Enabled = True
End Sub
Private Sub MyWorkLoad()
'do your work here
'for testing
Dim time As Date = Now
Do While True
If DateAdd(DateInterval.Second, -5, Now) > time Then Exit Do
Loop
End Sub
This did work for me...