I have extended on the FileSystemWatcher class to incorporate a FolderCount monitor and FolderEmpty monitor that raise events if a folder reaches a specified amount of files or if a folder returns to an empty status. I seem to have this working and I'm getting events raised when these conditions occur.
However, my problem is that when my FileSystemWatcher first initializes, it automatically goes in to check the folder contents of the specified folder to get a file count. If the limit is already reached, I need to raise an event immediately rather than wait for the FileSystemWatcher to report it.
Currently I can only seem to raise events by plugging into the .Created and .Deleted calls, however, because no files are getting created or deleted, I don't know how to raise my event manually.
Public Sub Initialize()
SetFolderCountStatus() 'Set the isFolderEmpty flag based on file contents
If Not isFolderEmpty Then
If options.WatchForFolderCount Then
If FileCountReached(options.FileCountToWatch) Then
RaiseEvent EventFolderCount(sender, e) 'Sender and e are never defined
End If
End If
End If
End Sub
My problem is that both sender and e are never populated with anything because they sit outside of my WatcherEventArgs.
I'm sure this can be done a better way, but I am unsure. Any help would be appreciated. Thanks
Do you actually use the sender and EventArgs in your EventFolderCount method? You can pass Me for the sender and an empty EventArgs object.
However What are the event arguments “sender” and “e” suggests attempting to raise the event isn't preferred. Instead you should have a single method that accomplishes the task and have that called in both places.
I actually resolved this by changing my EventHandler to only require a String variable, rather than EventArgs:
Public Event EventFolderCount(filename As String)
This way I could call it easily inside and outside of the FileSystemWatcher like so:
RaiseEvent EventFolderCount(filename)
Thanks #Dave Anderson for pointing me in the right direction.
Related
My code is designed to be a control system for a 2-axis motion system. I have 2 drives that each output a count of their steps. I can read the device, update a property, and update the text field of a label. However, it does not update the form. When I use a message box, I can display the text value being correct, but nothing updates the label.
I'm happy to try any suggestions, but I've been fighting this for about 16 hours and I'm at my wits end - as evidenced by the clear overkill/terrible coding that is shown in the code. I can't understand why it's not updating.
Additionally, a manual button with all versions seen below to refresh a form doesn't update the control.
Direction, recommendations?
Private Sub PositionChanged(ByVal sender As Object, ByVal e As EventArgs)
If TraverseController.InvokeRequired Then
TraverseController.Invoke(
New EventHandler(Of EventArgs)(AddressOf PositionChanged), sender, e)
Return
End If
'RaiseEvent PropertyChanged(TraverseController, New System.ComponentModel.PropertyChangedEventArgs("Position"))
MessageBox.Show(TraverseController.lblLinearDrivePosDisp.Text)
TraverseController.lblLinearDrivePosDisp.Text = CStr(_position)
Application.DoEvents()
TraverseController.lblLinearDrivePosDisp.ResetBindings()
TraverseController.GBDrivePositionDisp.Refresh()
TraverseController.lblLinearDrivePosDisp.Refresh()
TraverseController.Refresh()
TraverseController.Invalidate()
TraverseController.Update()
Application.DoEvents()
MessageBox.Show(TraverseController.lblLinearDrivePosDisp.Text)
End Sub
Assumption: TraverseController is form's class name.
This looks like a VB default form instance issue. It is apparent that you are trying to properly marshal control interaction back to the UI thread by using checking TraverseController.InvokeRequired. However, due to the way these default instance are created, TraverseController.InvokeRequired is creating a new instance of TraverseController on the secondary thread and all subsequent code is modifying that instance and not the one created on the UI thread.
One way to deal with this is to pass a synchronizing control instance to the class where PositionChanged changed method is defined and check that control's InvokeRequired method instead of TraverseController.InvokeRequired. If the containing class is itself a UI control, then use that class instance (Me.InvokeRequired).
In a Windows form I have:
a timer tmrBackup that regularly performs a backup of a local database
an event procedure that raises a procedure ImportNewFile when FileSystemWatcher detects a new file in a given directory.
Each of these work perfectly as long as they don't interfere each other.
I try to prevent that by stopping the FileSystemWatcher when a backup starts, and starting it again when it's finished.
On the other hand I set tmrBackup.Stop() in the beginning of ImportNewFile, then the contents of the file are transformed into a record in the local database. At the end of ImportNewFile I set tmrBackup.Start() again. But the timer is never started again, so that seems to be not a viable way.
When I don't set tmrBackup.Stop(), though, I see a strange behavior during debugging: when somewhere in ImportNewFile the timer fires, it seems that both routines are running parallel: one line in the tmrBackup routine is executed, then VB.Net jumps back to ImportNewFile for one line, then back to the timer, etc.
Question: what is a correct method to work with two objects with each being able to fire while the other's event is handled? Thanks!
Rather than attempting to temporarily disable the opposite timer, which is hokey and not guaranteed to work, the typical solution in such cases is to put SyncLock blocks around the necessary code in each of the event handlers, and have them both synchronize to the same object.
When code is surrounded by a SyncLock, execution will stop and wait, before entering the SyncLock block until any other related SyncLock code is finished. The way to relate them is by giving the SyncLock statement an object to synchronize against. If two SyncLock blocks synchronize to the same object, then neither of those two blocks of code will ever be allowed to run simultaneously.
In other words, if both blocks of code get called simultaneously, whichever starts first wins, and it runs through until it's complete, and the second just waits to begin executing until the first one is done.
Public Class SyncLockExample
Private WithEvents object1 As MyType1
Private WithEvents object2 As MyType2
Private syncObj As New Object()
Private Sub object1_MyEvent1(Object sender, EventArgs e) Handles object1.MyEvent1
SyncLock syncObject
' Do something that can't happen at the same time as object2_MyEvent2
End SyncLock
End Sub
Private Sub object2_MyEvent2(Object sender, EventArgs e) Handles object2.MyEvent2
SyncLock syncObject
' Do something that can't happen at the same time as object1_MyEvent1
End SyncLock
End Sub
End Class
I'm looking to call a pre-existing event handler subroutine from the form_Load event handler.
But the below doesn't work because control doesn't come back and I want to do more.
UPDATE:
I'm new to this so I don't know what the proper protocol is but...
The reason for the non-return was that a statement like the below ended the subroutines execution.
If aLabel.Tag = 1...
the fix was adding New to the declaration to create an instance of it, ie..
changing....
Dim aLabel As Label
... to ...
Dim aLabel As New Label
I'm surprised I didn't get a warning but instead they just abruptly stopped execution of the sub. That wasn't very helpful :)
Thanks again for your time guys...
(Maybe this question should be deleted now that it has served its purpose)
#konrad #karl
END OF UPDATE
What doesn't work is....
Private Sub Form1_Load...
button1_Click(sender, e) 'But Control doesn't come back.
end sub
Do I change the sender to something?
Thanks in advance
Dave
Invoking event handlers like this is a bad idea, because you are trying to simulate the event context by making sender and/or EventArgs be something else.
Instead, put the logic that you want to invoke into a Subroutine or Function and have your Form1_Load method call that; likewise if you really do have a real click event handler, then that handler code can call the method too, like this:
Private Sub Form1_Load()
DoSomeWork()
End Sub
Protected Sub button1_Click(ByVal sender As Object, ByVal e As EventArgs)
DoSomeWork()
End Sub
Private Sub DoSomeWork()
' Put logic here that you want to do from form load and a button click
End Sub
This has the benefit of making the code cleaner, clearer and easier to maintain as you only need to change the logic in one place should you need to change the logic.
Note: Obviously, you can pass parameters to the DoSomeWork method, if need be, and change it to a Function if you need it to return something.
Team,
I have build a VB.Net windows application which does uploads data into database and basically updates two controls:
1. A textbox which is constantly updated with one line per database record upload.
2. A label which keeps track of the count of database record uploaded.
I have used BackgroundWorker thread concept, where the thread's bgwWorker_DoWork() method contains the business logic for upload and bgwWorker_ProgressChanged() updates the 2 UI controls based on uploads.
But the issue I am facing is that I do not get complete updates on both the UI controls. Sometimes the thread bypasses update of textbox and sometimes of label. I could resolve this issue by adding System.Threading.Thread.Sleep(25) before each UI control update code. Is this correct way of solving the issue? OR is there something I am missing?
Kindly suggest.
Below is the code in both these methods:
Private Sub bgwWorker_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bgwWorker.DoWork
.................
.................
'Updates database record related update in textbox
System.Threading.Thread.Sleep(25)
updater.eventName = "UpdateStatusBox"
updater.errorMessageToLog = String.Empty
updater.errorMessageToLog += GetErrorMessage(dataTable(rowNumber)("Name").ToString(), ExceptionData)
bgwWorker.ReportProgress(1, updater)
.................
.................
'Updates Status Count in LABEL
System.Threading.Thread.Sleep(25)
updater.eventName = "UpdateStatusBar"
updater.successCount = successCount.ToString()
updater.failureCount = failureCount.ToString()
bgwWorker.ReportProgress(2, updater)
End Sub
Private Sub bgwWorker_ProgressChanged(ByVal sender As System.Object, ByVal e As ProgressChangedEventArgs) Handles bgwWorker.ProgressChanged
Dim updater As UIUpdater = TryCast(e.UserState, UIUpdater)
..........................................
If updater.eventName = "UpdateStatusBar" Then
UpdateStatusBar(updater.successCount, updater.failureCount)
ElseIf updater.eventName = "UpdateStatusBox" Then
txtUpdates.Text = txtUpdates.Text & updater.errorMessageToLog
End If
.....................................
End Sub
I'm almost positive that your problem is your instance of the UIUpdater object called updater. This object appears to be declared globally and is thus shared between calls.
Omitting a little bit of code this is what you have:
updater.eventName = "UpdateStatusBox"
bgwWorker.ReportProgress(1, updater)
updater.eventName = "UpdateStatusBar"
bgwWorker.ReportProgress(2, updater)
Although you call ReportProgress() linearly, it doesn't fire your ProgressChanged event immediately nor does it block until that method completed. To do so would defeat the purpose of threading if you think about it.
To put it another way, you have a global object that you are setting a property on. You then say "when someone gets a chance, do something with this". You then change a property on that global object and sometimes this happens before "someone has done something" happens.
The solution is either to create two global variables, one for each possible event or to just create an instance variable when needed. I'm not sure that its thread safe to use a global variable the way you are so I would recommend just creating an instance variable. In fact, the state object you pass to ReportProgress could just be a string.
I would NOT use a sleep in your DoWork event.
Have you tried refreshing the control after you update it? Each control has a Refresh method which forces a redraw. This may result in flickering though.
Another option is to include the information needed for both controls (textbox and label) in a single call to ReportProgress rather than trying to make two calls.
At the beginning of a VB .NET function I remove an event handler and add it again at the end of the function because the function's code would trigger those events and I do not want them to trigger for the duration of the function. This usually works, but I have run into a few situations where the event still gets called even though I have already removed it. Sometimes removing it twice at the beginning of the function fixes it, but other times no matter how many times I remove it, it still fires. Any ideas on what could be causing this?
Edit
The code is in a Form that has a virtual mode datagridview. I want to run some operations that will trigger the CellValueNeeded event for the datagridview without that event being fired (because it will interfere).
Public Sub DoEventRaisingStuff()
RemoveHandler grid.CellValueNeeded, AddressOf grid_CellValueNeeded
'Do things that would trigger CellValueNeeded
AddHandler grid.CellValueNeeded, AddressOf grid_CellValueNeeded
End Sub
Removing the handler multiple times does not prevent the event from firing, so it doesn't seem to be added multiple times somewhere else by accident.
Is there a way to find out what event handlers are active?
If the event handling code is being called then one of two things is happening:
You aren't removing the event handler.
You are adding the event handler multiple times. This is the more usual case.
In the past the only way I've been able to spot 2. is to find all the places where the event handler is added (hopefully only one or two) and put break points on those lines. I've then run the application under the debugger and found that it breaks more times than I expect. I use the call stack to work out why - it's always me putting the add code in the wrong place (on a button press rather than on form instantiation for example).
You can do the same with the removal code. Count the number of times each break point is hit and if they're not the same work back up the call stack to see if you can work out why.
Use class scoped flag in the function and check the flag in the event handler.
i.e.:
Private RunFunction as Boolean = False
...
Private Sub MyEvent(e as system.eventargs) handles myObject.Method
If RunFunction Then
...
End If
End Sub
...
Private Sub MyFunction()
RunFunction = False
...
RunFunction = True
End Sub