-
May 5th, 2022, 07:15 PM
#1
Thread Starter
Lively Member
Can't start timer.
I created my own File Copy dialog (had to) and I'm trying to use a timer to add a simple 5-frame animation.
I created a form and placed a Timer object on it. The timer is set to Enabled with an interval of 150 (I also tried 15.)
When I call the form, the timer never starts (I placed a breakpoint in the "_Tick" event to confirm.)
I call the form from another form using:
Code:
frmCopy.Activate()
frmCopy.Timer1.Enabled = True
BackupData() ' Backup settings.
BackupData() IS executed. It contains frmCopy.Show() & frmCopy.Refresh()
frmCopy's code:
Code:
Public Class frmCopy
Private Sub frmCopy_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Timer1.Enabled = True
Timer1.Start()
End Sub
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
intTick += 1
If intTick > 5 Then intTick = 1
picCopyAni.Image = imgCopyAni.Images.Item(intTick)
End Sub
End Class
A breakpoint in the form_Load confirms it loads just fine, but a breakpoint in the timer event is never tripped and I have no idea why.
Any help is appreciated.
Last edited by Mugsy323; May 5th, 2022 at 07:20 PM.
-
May 5th, 2022, 08:40 PM
#2
Re: Can't start timer.
You haven't shown us your copy code but I'm betting that it's executed on the UI thread. In that case, the UI thread is too busy to maintain the UI. That's why you perform long-running tasks on secondary threads.
-
May 6th, 2022, 05:45 AM
#3
Thread Starter
Lively Member
Re: Can't start timer.
Thx for the reply. Here is my Copy code:
Code:
For Each myFile In Directory.GetFiles(strPath & "Save", "Data*.*")
Try
My.Computer.FileSystem.CopyFile(myFile, strBackupPath & My.Computer.FileSystem.GetName(myFile), True) ' Must trim path from source file otherwise Crash. No idea why. Using Win copy dialog prompts to overwrite EVERY SINGLE FILE.
intCnt += 1
frmCopy.lblFileNum.Text = intCnt.ToString
frmCopy.lblSrcCopy.Text = myFile.ToString
frmCopy.Refresh()
Catch
' Attempting to copy "~Temp" files causes an error after the fact. Ignore.
End Try
Next
I'm not sure how to perform a "secondary" thread.
-
May 6th, 2022, 10:29 AM
#4
Re: Can't start timer.
Take a look at the BackgroundWorker component. That was created for this kind of a situation, and is likely to be well suited to this task.
The timer IS starting, it's just that it raises a tick event, which just gets queued up. The UI will get to it once it is free to do so. Normally, the UI is spending most of it's time waiting around, so it would get to the timer tick event about as quick as it happens. In this case, the UI thread is totally occupied with your copying loop, so everything else, including processing timer ticks, responding to user interaction, and so forth, waits until the loop has finished. By shifting the work onto a different thread, you are freeing up the UI to process messages and events.
I'm a little surprised that your loop is taking all that long, though. That .Refresh could prove to be costly, and if there are a LOT of files, then that could prove to be costly, as well, but I'd want to do at least SOMETHING in that Catch block. Exceptions that are thrown, even if you do nothing with them, are terribly costly in terms of time, so I'm wondering if you aren't getting some exceptions that you would find interesting. Just writing the exception message to a List(of String) would be enough for you to at least see whether anything is happening once the loop has completed.
Also, if you use the BGW, it has a ReportProgress method that raises a progress event on the UI thread. You'd want to be doing your updating of those labels in that event handler, not in the DoWork method of the BGW. Those are UI elements, so you want to update them on the UI thread. You shouldn't need the .Referesh, in that case, either, because the progress event would fire, you'd update the labels, then the event would end, which would allow the UI to process the Paint message, which would update the labels.
My usual boring signature: Nothing
-
May 6th, 2022, 11:16 AM
#5
Thread Starter
Lively Member
Re: Can't start timer.
Thx for the reply. I'm still stuck.
I scoured Google for how to do a multi-threaded file copy using "Tasks", yet even after making extensive changes, my Timer still never runs (Breakpoint on first line of _Tick event.)
Here is what I did to (supposedly) make the copy task multi-threaded so it does not consume ALL of my CPU time:
Code:
Imports System.IO
Imports System.Threading.Tasks
Public Class frmCopy
Private Sub frmCopy_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Me.Show()
Me.Refresh()
Timer1.Enabled = True
Timer1.Start()
CopyFiles()
End Sub
Private Sub CopyFiles()
Dim tasks As New List(Of Task)
...
Code:
Try
For Each myFile In Directory.GetFiles("D:\Save\", "Accessories*.*")
intCnt += 1
lblFileNum.Text = intCnt.ToString
lblSrcCopy.Text = myFile.ToString
Refresh()
tasks.Add(Task.Factory.StartNew(Sub() File.Copy(myFile,
strBackupPath & My.Computer.FileSystem.GetName(myFile), True)))
Next
Catch
' Attempting to copy "~Temp" files causes an error after the fact. Ignore.
End Try
This also performs the Copy function, yet the timer event still never runs (I'm guessing this method is still doing things the same way.) This method also has the serious drawback (AFAICT) that it can't be used to copy folders.
I tried commenting out all those "Refresh" statements, but the labels (showing the file name & number) never update w/o it. And the timer still didn't run.
I'll look into using "BackgroundWorker". Thx.
-
May 6th, 2022, 11:40 AM
#6
Addicted Member
Re: Can't start timer.
If you use the BGW then you won't even need a Timer, just call your copy sub from BGW.Dowork().
-
May 6th, 2022, 12:13 PM
#7
Thread Starter
Lively Member
Re: Can't start timer.
The timer is used to add an animation of the copy process.
-
May 6th, 2022, 12:17 PM
#8
Re: Can't start timer.
You may find this relevant/useful.
-
May 6th, 2022, 12:46 PM
#9
Re: Can't start timer.
How I'd do it (kinda) FWIW
First how to instantiate FrmCopy,
Code:
Dim foo As New frmCopy
foo.ShowDialog()
foo.Dispose()
The frmCopy
Code:
Public Class frmCopy
'note Shown event used, not Load <<<<<<<<<<
Private Sub frmCopy_Shown(sender As Object, e As EventArgs) Handles Me.Shown
Timer1.Enabled = True
Timer1.Start()
Dim t As Task
t = Task.Run(Sub()
CopyFiles()
End Sub)
End Sub
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
'didn't have real code
Static ct As Integer = 0
lblTmrTick.Text = ct.ToString("n0")
ct += 1
End Sub
Private _cpyFrom As String = "D:\Save\"
Private strBackupPath As String = "???????" ' <<<<<<<<<<<<<<<<<
Private intCnt As Long = 0L
Private Sub CopyFiles()
intCnt = 0L
Try
Dim _files As New Concurrent.ConcurrentBag(Of String)(IO.Directory.GetFiles(_cpyFrom, "Accessories*.*"))
Parallel.ForEach(_files, Sub(aFile As String)
Dim toPath As String
toPath = IO.Path.Combine(strBackupPath, IO.Path.GetFileName(aFile))
Try
IO.File.Copy(aFile, toPath, True)
Threading.Interlocked.Increment(intCnt)
'get on UI thread
Me.BeginInvoke(Sub()
lblSrcCopy.Text = aFile
lblFileNum.Text = Threading.Interlocked.Read(intCnt).ToString
' Me.Refresh '???
End Sub)
Catch ex As Exception
'?????
End Try
End Sub)
Catch ex As Exception
'?????
End Try
End Sub
End Class
-
May 6th, 2022, 02:05 PM
#10
Thread Starter
Lively Member
Re: Can't start timer.
Thx. I'll try this tonight.
One quick question though: What is the benefit of turning the form into it's own object ("foo")?
-
May 6th, 2022, 02:15 PM
#11
Re: Can't start timer.
Originally Posted by Mugsy323
Thx. I'll try this tonight.
One quick question though: What is the benefit of turning the form into it's own object ("foo")?
See this by jmcilhinney.
-
May 6th, 2022, 09:27 PM
#12
Re: Can't start timer.
Originally Posted by Mugsy323
What is the benefit of turning the form into it's own object ("foo")?
There is no benefit because that's not what is being done. Every form is an object. It's just a matter of where you get that object from. The default instance of a form, obtained via the class name, is still an object. Default instances are a nod to people who either can't (beginners) or won't (VB6 developers) understand proper OO principles. There are no default instances in C# and I've never heard anyone lament that. Most experienced VB developers don't use default instances because there's no need. Just treat forms like any other type/object because that's what they are.
-
May 7th, 2022, 08:59 PM
#13
Thread Starter
Lively Member
Re: Can't start timer.
Great. Your code appears to work (though I am having a bit of difficulty integrating it into my existing code.) My integration has somehow resulted in the form being reloaded recursively (my problem, not yours.) I'll have to spend some time tracking it down.
The new method has created a new problem though: Attempting to update any/all labels on my form now gives me the following error:
I tried prefixing each label with "foo." but that didn't help. Same error. But at least now the Timer event is finally being triggered (breakpoint tripped.)
Almost there. Thx.
-
May 8th, 2022, 03:28 AM
#14
Re: Can't start timer.
Originally Posted by Mugsy323
Great. Your code appears to work (though I am having a bit of difficulty integrating it into my existing code.) My integration has somehow resulted in the form being reloaded recursively (my problem, not yours.) I'll have to spend some time tracking it down.
The new method has created a new problem though: Attempting to update any/all labels on my form now gives me the following error:
I tried prefixing each label with "foo." but that didn't help. Same error. But at least now the Timer event is finally being triggered (breakpoint tripped.)
Almost there. Thx.
That exception is exactly what Shaggy warned you about back in post #4, you want update the UI from a background thread.
-
May 8th, 2022, 08:45 PM
#15
Thread Starter
Lively Member
Re: Can't start timer.
Originally Posted by PlausiblyDamp
That exception is exactly what Shaggy warned you about back in post #4, you want update the UI from a background thread.
Thanks. This whole "BackgroundWorker" and use of "Tasks" is completely new to me, so I haven't the foggiest on how to "update the UI from a background thread."
I thought that's what adding the "foo." prefix would do, but clearly not. Is there another Event or Object I should be using?
Thx.
-
May 8th, 2022, 09:17 PM
#16
Re: Can't start timer.
Originally Posted by Mugsy323
I haven't the foggiest on how to "update the UI from a background thread."
Follow the CodeBank link in my signature and check out some of the resources there. There is one thread specifically dedicated to updating the UI from a worker thread. You'll then see that that's what the provided code is doing when it calls BeginInvoke. There is also a thread on using a BackgroundWorker and that will show how and why you don't need to use that mechanism in that case. The BackgroundWorker exists specifically so that you don't have to and can just call methods and handle events, just as you've already many times. That same mechanism is still used but it's buried inside the BackgroundWorker object.
-
May 9th, 2022, 06:57 AM
#17
Thread Starter
Lively Member
Re: Can't start timer.
HALLELUJAH!
(For anyone searching this in the future, I found your tutorial here.)
There were a couple of details that I wasn't clear on (specifically, "SetTextBoxText" vs "SetTextBoxTextInvoker") but I was able to successfully integrate your code example (using the "SetControlTextInvoker" method so I could change different labels using the same function) but eventually go it to work so now the animation, the label updates, and the copy process all perform simultaneously.
One last concern though: After the copy process was done, I still had to use your Invoker to change my labels ("Done!", etc.)
How do I stop "multithreading" so I'm able to go back to update to updating objects "the old way"?
Big thanks (I feel like I'm in college again.)
-
May 9th, 2022, 07:31 AM
#18
Re: Can't start timer.
Originally Posted by Mugsy323
There were a couple of details that I wasn't clear on (specifically, "SetTextBoxText" vs "SetTextBoxTextInvoker")
SetTextBoxText is a method that sets the Text of a TextBox. SetTextBoxInvoker is a delegate that refers to that method. Delegates are basically objects that refer to methods, so that you can pass them around anywhere, just like other objects, and then invoke the method they refer to anywhere you like.
Originally Posted by Mugsy323
One last concern though: After the copy process was done, I still had to use your Invoker to change my labels ("Done!", etc.)
How do I stop "multithreading" so I'm able to go back to update to updating objects "the old way"?
You don't stop multithreading. Code is executed on whatever thread it's executed on. Code that is executed on the UI thread can modify a control directly and code that is executed on a secondary thread cannot. It's that simple. That's why the BackgroundWorker was created to make such situations easier. The DoWork event handler is executed on a secondary thread so that's where you do your background work while the ProgressChanged and RunWorkerCompleted events are raised on the UI thread so that's where you update the UI.
-
May 9th, 2022, 08:57 AM
#19
Thread Starter
Lively Member
Re: Can't start timer.
Thx. So if I understand you correctly, I must execute code "on the correct thread" to modify particular objects. And those threads will always exist once used.
So there is no way to go back to (eg):
MyLabel.Text = "sample"
...on the same form after using:
SetControlText(MyLabel, "sample")
Okay. Fortunately, it's not a big deal since the form isn't really needed once the process is done.
Now that I have the copying of Files (with wildcards) working using your method, the only thing left is to figure out how to do the same thing with folders (with wildcards):
My working loop for files:
Code:
Try
Dim _files As New Concurrent.ConcurrentBag(Of String)(IO.Directory.GetFiles("Save\Data", "File*.*"))
Parallel.ForEach(_files, Sub(aFile As String)
Dim toPath As String
toPath = IO.Path.Combine(strBackupPath, IO.Path.GetFileName(aFile))
Try
SetControlText(lblFileNum, intCnt.ToString)
SetControlText(lblSrcCopy, aFile.ToString)
IO.File.Copy(aFile, toPath, True)
Threading.Interlocked.Increment(intCnt)
Catch ex As Exception
' Attempting to copy "~Temp" files causes an error after the fact. Ignore.
End Try
End Sub)
Catch ex As Exception
' Attempting to copy "~Temp" files causes an error after the fact. Ignore.
End Try
This copies individual files just fine, but changing ".GetFiles" to ".GetDirectories" isn't enough to use the same method for folders (probably just a logic error.)
-
May 9th, 2022, 10:14 AM
#20
Re: Can't start timer.
No, at least parts of that are not correct. The UI thread exists as long as the program is running (there are some weird programs where that would not be the case, but if you get there, you'll know about it ahead of time, so you can ignore that for now). The UI thread, and any code executed on that thread, can access UI elements in the fashion you showed at any time:
MyLabel.Text = "Some text"
It is when you access controls from somewhere other than the UI thread that you have to do something different. However, those other threads exists as long as they have something to do, then they go away.
Your loop will use threads, so if they access controls, then you do have to do something special, since those aren't the UI thread. For that reason, it is generally best to not interact with controls from any background thread. You don't always get that option, but there is usually a way to do that. In your case, much of the complexity of the code is based on the fact that you want to keep updating those labels.
If you had written that using a BGW, then you could have raised the reported progress event, which would have worked on the UI thread and you wouldn't have had to worry about getting to controls across threads.
When it comes to objects other than UI elements, the situation is more complicated. Some can be thread local, others are not. That doesn't appear to be something you need to think about in the current situation, though.
My usual boring signature: Nothing
-
May 9th, 2022, 11:47 AM
#21
Thread Starter
Lively Member
Re: Can't start timer.
Quick question.
From JMC's example:
Code:
Private Sub SetTextBoxText(ByVal text As String)
If Me.TextBox1.InvokeRequired Then
Me.TextBox1.Invoke(New SetTextBoxTextInvoker(AddressOf SetTextBoxText), text)
...
...seems recursive. I'm glad it works, but when I don't understand the process, I won't be able to apply it to different situations.
Thx.
-
May 9th, 2022, 11:56 AM
#22
Re: Can't start timer.
When you call it initially, the InvokeRequired will be set to true... so it drops into the Me.TextBox1.Invoke line ... which then calls the method on the UI tread... so yes, it's recursive... but on second entry, the InvokeRequired is NOT set - because now it's on the UI trhead - so it goes into the Else condition, where you can actually set the textbox itself.
-tg
-
May 9th, 2022, 01:37 PM
#23
Thread Starter
Lively Member
Re: Can't start timer.
Great. I think we can label this Case Closed.
Tags for this Thread
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|