Form don't wait for thread finish and hangs my app - vb.net

This is my app, you can see 3 buttons, start button enabled, pause disabled and stop disabled.
The problem is I have a separated thread in my form to make a "process" (and to print the information in the black richtextbox), and my intention is to can pause it or stop it, but when I launch the thread, the pause button and stop button turns Enabled to disabled in a second.
I can tell the form to wait after launching the thread with a _WaitHandle_FirstThreadDone.WaitOne() and then i can see enabled the pause and the stop buttons, but then the problem is my app hangs until the "process" is done.. so I can't push any button.
Please, I need help to make this...
The important part of my form:
Public Class Form1
#Region "Append text function"
' Append Text
Public Sub AppendText(box As RichTextBox, color As Color, text As String)
Control.CheckForIllegalCrossThreadCalls = False
Dim start As Integer = box.TextLength
box.AppendText(text)
Dim [end] As Integer = box.TextLength
' Textbox may transform chars, so (end-start) != text.Length
box.[Select](start, [end] - start)
If True Then
box.SelectionColor = color
' could set box.SelectionBackColor, box.SelectionFont too.
End If
box.SelectionLength = 0
' clear
End Sub
#End Region
#Region "Thread"
Public _WaitHandle_FirstThreadDone As New System.Threading.AutoResetEvent(False)
Public Sub ThreadProc(ByVal aDir As DirectoryInfo)
Dim aFile As FileInfo
For Each aFile In aDir.GetFiles()
If accepted_extensions.ToLower.Contains(aFile.Extension.ToLower) Then
' print output
AppendText(consolebox, Color.Yellow, "Processing: ")
AppendText(consolebox, Color.White, aFile.ToString() + vbNewLine)
consolebox.ScrollToCaret()
processedfiles += 1
totalfiles_label.Text = "Processed " + processedfiles.ToString() + " of " + totalfiles.ToString() + " total video files"
' MEDIAINFO: (ac3, dts, wav and multitrack)
If ac3 = True Or dts = True Or wav = True Or multitrack = True Then
MI.Open(aFile.FullName)
Dim Pos As Integer = 0
To_Display = Nothing
While Pos < MI.Count_Get(StreamKind.Audio)
' AC-3
If ac3 = True Then
If MI.Get_(StreamKind.Audio, Pos, "Format").ToString() = "AC-3" Then
results_box.AppendText("AC3 Track: " + aFile.FullName.ToString() + vbNewLine)
results_box.SelectionStart = results_box.Text.Length
results_box.ScrollToCaret()
problems += 1
problems_label.Text = problems.ToString() + " problems found"
End If
End If
System.Math.Max(System.Threading.Interlocked.Increment(Pos), Pos - 1)
End While
End If
End If
Next
_WaitHandle_FirstThreadDone.Set()
End Sub
#End Region
#Region "Organize function"
Public Sub MediaInfo(Directory)
Dim MyDirectory As DirectoryInfo
MyDirectory = New DirectoryInfo(NameOfDirectory)
MediaInfoWorkWithDirectory(MyDirectory)
End Sub
Public Sub MediaInfoWorkWithDirectory(ByVal aDir As DirectoryInfo)
Dim nextDir As DirectoryInfo
Dim t As New Threading.Thread(AddressOf ThreadProc)
t.Start(aDir)
'
For Each nextDir In aDir.GetDirectories
If playlist = True Then
Using writer As StreamWriter = New StreamWriter(aDir.FullName & "\" & nextDir.Name & "\" & nextDir.Name & ".m3u", False, System.Text.Encoding.UTF8)
'overwrite existing playlist
End Using
End If
MediaInfoWorkWithDirectory(nextDir)
Next
End Sub
#End Region
#Region "Action buttons"
' start button
Public Sub Button2_Click(sender As Object, e As EventArgs) Handles start_button.Click
consolebox.Clear()
' pause / cancel button ON
start_button.Enabled = False
pause_button.Enabled = True
cancel_button.Enabled = True
' Organization process
NameOfDirectory = userSelectedFolderPath
MediaInfo(NameOfDirectory)
' _WaitHandle_FirstThreadDone.WaitOne()
consolebox.AppendText(vbNewLine + "[+] Organization finalized!" + vbNewLine)
consolebox.Refresh()
consolebox.SelectionStart = consolebox.Text.Length
consolebox.ScrollToCaret()
' pause / cancel button OFF
start_button.Enabled = True
pause_button.Enabled = False
cancel_button.Enabled = False
End Sub
#End Region
Private Sub pause_button_Click(sender As Object, e As EventArgs) Handles pause_button.Click
paused = True
End Sub
End Class

The reason the app hangs is the program is blasting through data sequentially. You should add an if statement inside the part that is looping to check for the pause condition in between processing. Its not a good idea to put the on/off controls inside the subroutine like you have because it can only enable the buttons after everything has completed.
I.E to stop the process
For i to 10 Do
If checkbox1.checked = True then Exit Sub 'check for stop condition
'process videos
Loop
To pause it you could implement a stop but make it remember where to start when resumed.
Also why do you have faces of death? That stuff kills braincells.

Related

How do Make my win and loss counter on my dice roll game output the correct numbers using visual basic

I created the craps dice game as you press play your dice rolls if you roll the winning number or losing number when you first hit play it will tell you. if you dont win or lose on first roll you have the chance to use the roll button in order to keep rolling until you win or lose against the first set of dice rolled
i tried using an if else statement to determine the win count and display to the label although it only displays wins if you win on the first roll as you hit the play button not if you win and it took you multiple rolls clicking the roll button
Imports System.IO
Public Class CrapsGame
' die roll constants
Enum DiceNames
SNAKE_EYES = 2
TREY = 3
CRAPS = 7
LUCKY_SEVEN = 7
YO_LEVEL = 11
BOX_CARS = 12
End Enum
' file name and directory constants
Const FILE_PREFIX As String = "/images/die"
Const FILE_SUFFEX As String = ".png"
' instance variables
Dim myPoint As Integer = 0
Dim randomobject As New Random()
Dim winCount As Integer
Dim lossCount As Integer
' begin new game and determine point
Private Sub PlayButton_Click(sender As Object, e As EventArgs) Handles PlayButton.Click
'intialize variables for new game
myPoint = 0
PointBox.Text = "Point"
OutputLabel.Text = " "
' remove point die images
PointDie1PictureBox.Image = Nothing
PointDie2PictureBox.Image = Nothing
Dim Sum As Integer = RollDice() ' roll dice
'check die roll
Select Case Sum
' win on first roll
Case DiceNames.LUCKY_SEVEN, DiceNames.YO_LEVEL
OutputLabel.Text = "you win!!"
' lose on first roll
Case DiceNames.SNAKE_EYES, DiceNames.TREY, DiceNames.BOX_CARS
OutputLabel.Text = "sorry you lose."
Case Else ' player must match point
myPoint = Sum
PointBox.Text = "Point is " & Sum
OutputLabel.Text = "Roll again!"
PointDie1PictureBox.Image = Die1PictureBox.Image
PointDie2PictureBox.Image = Die2PictureBox.Image
PlayButton.Enabled = False ' disable Play Button
RollButton.Enabled = True ' enable Roll Button
End Select ' sum
If (Sum = myPoint Or Sum = DiceNames.CRAPS) Then
If OutputLabel.Text = "you win!!" Then
winCount = winCount + 1
Else
lossCount = lossCount + 1
End If
Label1.Text = "Win Count - " & winCount.ToString() + Environment.NewLine + "Loss Count - " & lossCount.ToString() + Environment.NewLine
End If
End Sub ' playbutton click
' determine outcome of next roll
Private Sub rollButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles RollButton.Click
Dim sum As Integer = RollDice() ' roll dice
' determine outcome of roll
If sum = myPoint Then ' player matches point
OutputLabel.Text = "You win!!!"
RollButton.Enabled = False ' disable Roll Button
PlayButton.Enabled = True ' enable Play Button
ElseIf sum = DiceNames.CRAPS Then ' player loses
OutputLabel.Text = "Sorry, you lose."
RollButton.Enabled = False ' disable Roll Button
PlayButton.Enabled = True ' enable Play Button
End If
End Sub ' rollButton_Click
' generate random die rolls
Function RollDice() As Integer
' roll the dice
Dim die1 As Integer = randomobject.Next(1, 7)
Dim die2 As Integer = randomobject.Next(1, 7)
' display image corresponding to each die
DisplayDie(Die1PictureBox, die1)
DisplayDie(Die2PictureBox, die2)
Return (die1 + die2) ' return sum of dice values
End Function ' RollDice
' display die image
Sub DisplayDie(ByVal die As PictureBox, ByVal face As Integer)
' assign die images to PictureBox
die.Image = Image.FromFile(Directory.GetCurrentDirectory & FILE_PREFIX & face & FILE_SUFFEX)
End Sub ' DisplayDie
End Class
You need to increment your counters (winCount or lossCount) in the rollButton_Click subroutine. Currently the counters are only being updates when the Play button is clicked.

Change label text in foreach loop

i want to update a label while a foreach-loop.
The problem is: the program waits until the loop is done and then updates the label.
Is it possible to update the label during the foreach-loop?
Code:
Dim count as Integer = 0
For Each sFile as String in Files
'ftp-code here, works well
count = count+1
progressbar1.value = count
label1.text = "File " & count & " of 10 uploaded."
next
Thanks in advance
Label is not updated because UI thread is blocked while executing your foreach loop.
You can use async-await approach
Private Async Sub Button_Click(sender As Object, e As EventArgs)
Dim count as Integer = 0
For Each sFile as String in Files
'ftp-code here, works well
count = count+1
progressbar1.value = count
label1.text = "File " & count & " of 10 uploaded."
Await Task.Delay(100)
Next
End Sub
Because you will work with Ftp connections, which is perfect candidate for using async-await.
The Await line will release UI thread which will update label with new value, and continue from that line after 100 milliseconds.
If you will use asynchronous code for ftp connection , then you don't need Task.Delay
You've already accepted an answer but just as an alternative a BackgroundWorker can also be used for something like this. In my case the FTP to get the original files happens very quickly so this snippet from the DoWork event is for downloading those files to a printer.
Dim cnt As Integer = docs.Count
Dim i As Integer = 1
For Each d As String In docs
bgwTest.ReportProgress(BGW_State.S2_UpdStat, "Downloading file " & i.ToString & " of " & cnt.ToString)
Dim fs As New IO.FileStream(My.Application.Info.DirectoryPath & "\labels\" & d, IO.FileMode.Open)
Dim br As New IO.BinaryReader(fs)
Dim bytes() As Byte = br.ReadBytes(CInt(br.BaseStream.Length))
br.Close()
fs.Close()
For x = 0 To numPorts - 1
If Port(x).IsOpen = True Then
Port(x).Write(bytes, 0, bytes.Length)
End If
Next
If bytes.Length > 2400 Then
'these sleeps are because it is only 1-way comm to printer so I have no idea when printer is ready for next file
System.Threading.Thread.Sleep(20000)
Else
System.Threading.Thread.Sleep(5000)
End If
i = i + 1
Next
In the ReportProgress event... (of course, you need to set WorkerReportsProgress property to True)
Private Sub bgwTest_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles bgwTest.ProgressChanged
Select Case e.ProgressPercentage
'BGW_State is just a simple enum for the state,
'which determines which UI controls I need to use.
'Clearly I copy/pasted from a program that had 15 "states" :)
Case BGW_State.S2_UpdStat
Dim s As String = CType(e.UserState, String)
lblStatus.Text = s
lblStatus.Refresh()
Case BGW_State.S15_ShowMessage
Dim s As String = CType(e.UserState, String)
MessageBox.Show(s)
End Select
End Sub
Is it not enough to use Application.DoEvents()? This clears the build up and you should be able to see the text fields being updated very quickly.

Debugging using a timer

I'm making a Console game where a moving character has to move left and right to intercept falling 'fruit'/ASCII characters, only I'm having trouble. I'm using a timer with a 1 second interval, and every time it elapses it's supposed to check a list of fruit that's already on the board and move each fruit down by one, and then it randomly inserts a new fruit onto the board. Fruits are all kept as objects in a class.
Here's the timer code:
Sub FruitTick() Handles FruitTimer.Elapsed
Dim RandomNumber As Integer
Dim Fruit As Fruit
For i = 0 To FruitList.Count - 1
If FruitList(i).Position.Y < FruitBoard.Height - 1 Then
FruitList(i).LowerFruitByOne()
End If
Next
PeriodUntilFruitAppears -= 1
If PeriodUntilFruitAppears <= 0 Then
PeriodUntilFruitAppears = FruitFrequency
RandomNumber = New Random().Next(1, 5)
If RandomNumber = 1 Then
Fruit = New Fruit()
Fruit.AddToList()
Fruit.PlaceOnBoard()
End If
End If
End Sub
And here's the class for Fruit:
Public Class Fruit
Private FruitIcons() As Char = {"#", "ð", "ó", "ç", "%", "$"}
Public Icon As Char
Public Position As Location
Public Colour As ConsoleColor
Sub New()
Me.Icon = FruitIcons(New Random().Next(FruitIcons.Length))
Me.Position = New Location(New Random().Next(FruitBoard.Width), 0)
Me.Colour = New Random().Next(1, 16)
End Sub
Sub New(_Icon As Char, _
_Position As Location, _
_Colour As ConsoleColor)
Me.Icon = _Icon
Me.Position = New Location(_Position.X, 0)
Me.Colour = _Colour
End Sub
Sub PlaceOnBoard()
Console.SetCursorPosition(FruitBoard.Position.X + Me.Position.X, FruitBoard.Position.Y + Me.Position.Y)
Console.ForegroundColor = Me.Colour
Console.BackgroundColor = FruitBoard.BackColour
Console.Write(Me.Icon)
End Sub
Sub AddToList()
FruitList.Add(Me)
End Sub
Sub LowerFruitByOne()
Dim DrawInstruction As Instruction
DrawInstruction = New Instruction(" ", _
New Location(FruitBoard.Position.X + Me.Position.X, _
FruitBoard.Position.Y + Me.Position.Y), _
FruitBoard.BackColour, _
FruitBoard.BackColour)
DrawInstruction.Execute()
Me.Position.Y += 1
DrawInstruction = New Instruction(Me.Icon, _
New Location(FruitBoard.Position.X + Me.Position.X, _
FruitBoard.Position.Y + Me.Position.Y), _
Me.Colour, _
FruitBoard.BackColour)
DrawInstruction.Execute()
End Sub
End Class
The Instruction class referred to is simply used to redraw characters in the Console.
I'm having weird problems, such as trailing characters where they should have been drawn over by a blank space, the fruit falling two characters instead of one, fruit spawning to the left of the previous fruit and then stopping, etc... but I'm especially having a problem debugging it. When I put a breakpoint in and step into the code, the debugger seems to go from place to place erratically, as if the timer's still running while it's paused and I'm too slow.
Is there any way to debug it properly, line-by-line, or am I going to have to make intelligent guesses about what's going on?
You should stop the timer while in the elapsed method. Try to stop the timer on the beggning and enabling it on the last line.
Sub FruitTick() Handles FruitTimer.Elapsed
FruitTimer.Enabled = False
' Your actual code
FruitTimer.Enabled = True
End Sub
Probably, your code last more than a second and the code starts again before the last execution is complete. Which is more evident when debugging. It will probably be generating all your problems and it will cause memory issues on the end.

Background Worker and SaveDialog

I am very new with Background worker control. I have an existing project that builds file but throughout my project while building files I get the deadlock error.
I am trying to solve it by creating another project that will only consist out of the background worker. I will then merge them.
My problem is I don't know where it will be more effective for my background worker to be implemented and also the main problem is how can I use the SaveDialog with my background worker? I need to send a parameter to my background worker project telling it when my file is being build en when it is done.
This is where my file is being build:
srOutputFile = New System.IO.StreamWriter(strFile, False) 'Create File
For iSeqNo = 0 To iPrintSeqNo
' Loop through al the record types
For Each oRecord As stFileRecord In pFileFormat
If dsFile.Tables.Contains(oRecord.strRecordName) Then
' Loop through al the records
For Each row As DataRow In dsFile.Tables(oRecord.strRecordName).Rows
' Check record id
If oRecord.strRecordId.Length = 0 Then
bMatched = True
Else
bMatched = (CInt(oRecord.strRecordId) = CInt(row.Item(1)))
End If
' Match records
If iSeqNo = CInt(row.Item(0)) And bMatched Then
strRecord = ""
' Loop through al the fields
For iLoop = 0 To UBound(oRecord.stField)
' Format field
If oRecord.stField(iLoop).iFieldLength = -1 Then
If strRecord.Length = 0 Then
strTmp = row.Item(iLoop + 1).ToString
Else
strTmp = strDelimiter & row.Item(iLoop + 1).ToString
End If
ElseIf oRecord.stField(iLoop).eFieldType = enumFieldType.TYPE_VALUE Or _
oRecord.stField(iLoop).eFieldType = enumFieldType.TYPE_AMOUNT_CENT Then
strTmp = row.Item(iLoop + 1).ToString.Replace(".", "").PadLeft(oRecord.stField(iLoop).iFieldLength, "0")
strTmp = strTmp.Substring(strTmp.Length - oRecord.stField(iLoop).iFieldLength)
Else
strTmp = row.Item(iLoop + 1).ToString.PadRight(oRecord.stField(iLoop).iFieldLength, " ").Substring(0, oRecord.stField(iLoop).iFieldLength)
End If
If oRecord.stField(iLoop).iFieldLength > -1 And (bForceDelimiter) And strRecord.Length > 0 Then
strTmp = strDelimiter & strTmp
End If
strRecord = strRecord & strTmp
Next
' Final delimiter
If (bForceDelimiter) Then
strRecord = strRecord & strDelimiter
End If
srOutputFile.WriteLine(strRecord)
End If
Next
End If
Next
Next
You could try this:
Private locker1 As ManualResetEvent = New System.Threading.ManualResetEvent(False)
Private locker2 As ManualResetEvent = New System.Threading.ManualResetEvent(False)
Dim bOpenFileOK As Boolean
Dim myOpenFile As OpenFileDialog = New OpenFileDialog()
Private Sub FileOpener()
While Not bTerminado
If myOpenFile.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
bOpenFileOK = True
Else
bOpenFileOK = False
End If
locker2.Set()
locker1.WaitOne()
End While
End Sub
' Detonator of the action
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
Dim tFileOp As Thread = New Thread(AddressOf FileOpener)
tFileOp.SetApartmentState(ApartmentState.STA)
tFileOp.Start()
' Start BackgroundWorker
BW1.RunWorkerAsync()
End Sub
Private Sub AsyncFunctionForBW(ByVal args As ArrayList)
'[...]
'Change options dinamically for the OpenFileDialog
myOpenFile.Filter = ""
myOpenFile.MultiSelect = True
'Calling the FileDialog
locker1.Set()
locker2.WaitOne()
locker1.Reset()
locker2.Reset()
If bOpenFileOK Then
myStream = myOpenFile.OpenFile()
'[...]
End If
End Sub
It's a little bit complicated but it works.
ManualResetEvents interrupt the execution of code (if they are told to stop) when reached until you use .Set(). If you use .WaitOne() you set it in stop mode, so it will stop again when reached.
This code defines two ManualResetEvents. When you click the Button1 starts the function FileOpener() in a new Thread, and then starts the BackgroundWorker. The FileOpener() function shows a FileOpenDialog and waits in the locker1 so when you use locker1.Set() the function shows the file dialog.
As the myOpenFile is a "global" variable (as well as bOpenFileOK), once the user select the file (or not) you could detect the dialog result (bOpenFileOK) and the selected file.

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