System.timers.timer - unable to update label with time elapsed - multithreading

I want to display time elapsed on Label control on windows form.
for that i am using System.Timers.timer but unable to update Label with the elapsed time on button click event.
For example
Private Shared mtimer As System.Timers.Timer
Private Sub btnprocess_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnprocess.Click
'Counter to Calculate time as Process Starts
mDate = Date.Now
''Create a timer with second interval
mtimer = New System.Timers.Timer()
''Hook the Elapsed event
**AddHandler mtimer.Elapsed, AddressOf Processtick**
mtimer.Start()
'set the interval 1 second
mtimer.Interval = 1000
mtimer.Enabled = True
''Some functions
end sub
Private Sub Processtick(ByVal source As Object, ByVal e As ElapsedEventArgs)
Dim ts As TimeSpan = DateTime.Now.Subtract(mDate)
lblelapsed.Text = ts.Hours & ":" & ts.Minutes & ":" & ts.Seconds
End sub
tried above code but it doesnt work,
i want to update elapsed time on label control as soon as user click Process button
till all the functions get executed on button click.
Please help.

Since System.Timers.Timer runs on a separate thread it cannot directly update the GUI. You'll need to use BeginInvoke on the control to forward updates to the GUI thread. Unfortunately I'm not really familiar with vb.net, but you can find an example of how to do it here: http://msdn.microsoft.com/en-us/library/0b1bf3y3.aspx

Related

Events causing cross-thread error in backgroundworker_progresschanged and backgroundworker_complete

My VB.NET winforms app runs a timer which creates a background worker to update the objects in an ObjectListView.
In the timer loop, a number of 'device' objects are added to an observable collection (in the backgroundworker_progresschanged event) and (in the backgroundworker_complete event), I use an OLV.SetObjects(allDevices, true) to populate them.
This all works flawlessly. However, the currently selected items in the OLV are lost during the OLV.setobjects so I need to restore them.
To do this, (in the backgroundworker_complete event), I want to access the selecteditems property of the OLV but I keep getting a "Cross-thread operation not valid: Control 'DeviceListView1' accessed from a thread other than the thread it was created on." All attempts at trying to read the selected listviewitems (either by OLV.selecteditems or a loop reading them from the OLV) fail with the cross-thread exception.
I may misunderstand but I thought I could access GUI elements on the backgroundworker_progresschanged and backgroundworker_complete events?
Here's the relevant code:
The PopulateDevices sub is called when the timer is started and will not run again until a specific time has passed. It runs the RunWorkerAsync of the Worker.
Public Sub PopulateDevices()
' Debug
_UpdateCount += 1
' Pause the Update Timer
UpdateTimer.Stop()
' Get the Starting Time of this Update
StartTime = DateTime.Now
' Stop updating the DeviceListView1 ObjectListView
ControlHelper.ControlInvoke(DeviceListView1, Sub() DeviceListView1.BeginUpdate())
' Clear Existing Devices from the List
AllDevices = New TrulyObservableCollection(Of DeviceItem)
' Get the selected devices
'_SelectedDevices = GetSetSelectedDevices(DeviceListView1)
' Prep the BackgroundWorker
PopulateDevicesWorker = New BackgroundWorker
PopulateDevicesWorker.WorkerReportsProgress = True
' Add the Event Handlers
AddHandler PopulateDevicesWorker.DoWork, AddressOf PopulateDevicesWorkerDoWork
AddHandler PopulateDevicesWorker.ProgressChanged, AddressOf PopulateDevicesWorkerProgressChanged
AddHandler PopulateDevicesWorker.RunWorkerCompleted, AddressOf PopulateDevicesWorkerCompleted
' Start the BackgroundWorker
If Not PopulateDevicesWorker.IsBusy Then
PopulateDevicesWorker.RunWorkerAsync()
End If
End Sub
The worker will read a list of devices from a SQLite DB and (in the progresschanged event) populate an observable collection (AllDevices):
Private Sub PopulateDevicesWorkerDoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs)
' We only continue if the \Clients\_Cache File exists and can be read
If Not File.Exists(CacheFilePath) Then
Exit Sub
End If
' Create a new SQLite Connection & Connect to database
Dim DBC As SQLiteDatabase = OpenDB(CacheFilePath)
If Not IsNothing(DBC) Then
' Count the Rows in the \Clients\_Cache file
Dim RowCount As Integer = CountTableRows(DBC, "_Cache")
' Set the SQL Query
SqlQuery = "SELECT * FROM _Cache WHERE Archived = #Archived"
' Create the SQLite Command
Using SQLitecmd As SQLiteCommand = New SQLiteCommand(SqlQuery, DBC.Connection)
SQLitecmd.Parameters.AddWithValue(String.Empty & "Archived", IIf(fMain.ButtonItem_VIEWARCHIVE.Checked, "True", "False"))
Using SQLiteReader = SQLitecmd.ExecuteReader()
Dim Counter As Integer = 0
' Read All Properties into the Array
While SQLiteReader.Read()
Using DeviceItem As New DeviceItem
With DeviceItem
' Get the Device Info here
End With
' Report progress at regular intervals
PopulateDevicesWorker.ReportProgress(CInt(100 * Counter / RowCount), DeviceItem)
' Increment the Counter (for Progress)
Counter += 1
End Using
End While
End Using
End Using
End If
CloseDB(DBC)
End Sub
Here is the WorkerProgressChanged event. It adds the current device (from the worker) into the observable collection (AlLDevices)
Private Sub PopulateDevicesWorkerProgressChanged(sender As Object, e As ProgressChangedEventArgs)
' Update Status
LabelItem_STATUS.Text = "Working.. (" & e.ProgressPercentage & "%)"
' Add the Device to Collection
AllDevices.Add(TryCast(e.UserState, DeviceItem))
End Sub
The WorkerCompleted event will set the objects in AllDevices to the OLV (DeviceListView1)
Private Sub PopulateDevicesWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) ' Handles PopulateDevicesWorker.RunWorkerCompleted
' This is producing Cross-Thread error
If Not IsNothing(_SelectedDevices) Then
For Each item As ListViewItem In _SelectedDevices
Debug.Print(item.Text)
Next
End If
' Populate the ObjectListView
ControlHelper.ControlInvoke(DeviceListView1, Sub() DeviceListView1.SetObjects(AllDevices, True))
' Re-enable Form Updates
ControlHelper.ControlInvoke(DeviceListView1, Sub() DeviceListView1.EndUpdate())
' If the refresh rate isn't already set, set it to the time taken to complete the Update PLUS the Seconds specified in the SETTINGS.INI File
Dim difference As TimeSpan = DateTime.Now.Subtract(StartTime)
If UpdateTimeInSeconds = -1 Then
UpdateTimer.Interval = (RefreshRate + difference.TotalSeconds) * 1000
End If
' Restart the Update Timer
UpdateTimer.Start()
End Sub
I was under the impression, that I can update the GUI (get the OLV selecteditems, etc.) from the WorkerProgressChanged and WorkerCompleted backgroundworker events but I get the darn cross-thread error.
I'm also having to INVOKE the BEGIN\END UPDATE as calling them directly produces error.
I have read that the olv.setobjects in ObjectListView 2.91 (the version I am using) should persist the selections but I haven't seen this at all.
Please! What am I missing? Its probably something daft or is there another way of doing this?
If you are not using a Forms.Timer (but Timers.Timer or Threading.Timer) for your UpdateTimer, anything called from the timer "tick" event will run on a different thread.
Thus, PopulateDevices would also be called from a non GUI thread and the BackgroundWorker will run on that thread as well.

Catch event trigger from external application in Excel VBA

I use VBA to automate an external application that recently changed their COM API. The new API loads files asynchronously (used to be synchronous) so I need to wait for the file loaded trigger before I continue when I try to load a file.
I have tried the methods listed on the Microsoft website (EX1, EX2) which were also part of an accepted answer on StackOverflow.
Below is the code I have in a class module named UCExternal to contain the external application object:
Public WithEvents obj As External.Application
Private fileLoaded As Boolean
Private Sub obj_OnFileLoaded(ByVal lLayer As Long, ByVal strUNCPath As String)
Debug.Print lLayer
Debug.Print strUNCPath
fileLoaded = True
End Sub
Public Sub LoadSingleFile(fileStr As String)
fileLoaded = False
obj.LoadFile 0, fileStr
Do
DoEvents
Loop Until fileLoaded
End Sub
And then this is what I had in a normal code module to run using a button on the sheet:
Sub TryLoadFile()
Dim extObj as New UCExternal
set extObj.obj = CreateObject("External.Application")
filePath = "path/to/file"
extObj.LoadSingleFile filePath
End Sub
The event code never seems to fire and instead the Do Loop just runs until Excel crashes. I don't know if there is a way to confirm the application actually sent the event trigger? I have read through the new documentation for the application and that is the event they say to wait for. I have reached out to them for help as well but I wasn't sure if there was something more general I may have been missing. I have not worked with events external to Excel in the past. If I just step through it using the debugger and manually exit the Do Loop eventually the rest of the code that works on the loaded file works as well, so it does load the file.
extObj needs to be declared outside of TryLoadFile, or it will go out of scope and get cleared as soon as TryLoadFile completes
Dim extObj as New UCExternal
Sub TryLoadFile()
Set extObj = New UCExternal
set extObj.obj = CreateObject("External.Application")
filePath = "path/to/file"
extObj.LoadSingleFile filePath
End Sub

Excel VBA - on Multi Page Change but only once

I have a flexgrid within a Multipage under Main_Window.MultiPage2.Value = 2 this flexgrid has 8000 rows and I don't want those to load unless this page is actually clicked on. The code I have does just that, but the problem is is that it loads every single time and not just once. Is there a way to make it load on the first change, and then that's it?
Private Sub MultiPage2_Change()
If Main_Window.MultiPage2.Value = 2 Then
Call form_segment_carrier_auto
End If
End Sub
in form_segment_carrier_auto is a module that populates the flexgrid.
If I understand you correctly, you could declare a Public Boolean variable, for example:
Public ChangedOnce As Boolean
This should be in some standard code module.
Then change your event handler to:
Private Sub MultiPage2_Change()
If ChangedOnce Then Exit Sub
ChangedOnce = True
If Main_Window.MultiPage2.Value = 2 Then
Call form_segment_carrier_auto
End If
End Sub
The event handler will still be called on multiple occasions if the event occurs on multiple occasions, but only the first call will do anything.

VB.net Multithreaded keypresses

I have a working 3D object viewer in VB.net (I know that VB.net is not the best language to use for this but still)
So if I press the W key the box moves up. If I press the D key it moves to the right. But I wanna do the simultaneously. And to do so I figured that I could give each key its own thread.
So the is the code I wound upwith.
Dim thread1 As System.Threading.Thread
Dim thread2 As System.Threading.Thread
Private Sub MoveUp_Keydown(sender As Object, e As System.Windows.Forms.KeyEventArgs) Handles GlControl1.KeyDown
If e.KeyCode = Keys.W Then
If NumericUpDown1.Value < 100 Then
NumericUpDown1.Value = NumericUpDown1.Value + 1
Me.Refresh()
End If
End If
End Sub
Private Sub MoveUp1_Keydown(sender As Object, e As System.Windows.Forms.KeyEventArgs) Handles GlControl1.KeyDown
If e.KeyCode = Keys.W Then
thread1 = New System.Threading.Thread(AddressOf MoveUp_Keydown)
End If
End Sub
But the error I am getting is
error BC30518: Overload resolution failed because no accessible 'New' can be called with these arguments
I have tried to google this but the problem is that nobody uses the threading for a keypress resulting in different solutions.
Thanks for any help
You can do it without a Timer, but still using the GetKeyState() API:
Public Class Form1
<Runtime.InteropServices.DllImport("user32.dll", SetLastError:=True, CharSet:=Runtime.InteropServices.CharSet.Unicode)> _
Private Shared Function GetKeyState(ByVal nVirtKey As Integer) As Short
End Function
Private Sub Form1_KeyDown(sender As Object, e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
If IsKeyDown(Keys.W) Then
If NumericUpDown1.Value < 100 Then
NumericUpDown1.Value = NumericUpDown1.Value + 1
Me.Refresh()
End If
End If
If IsKeyDown(Keys.D) Then
If NumericUpDown2.Value < 100 Then
NumericUpDown2.Value = NumericUpDown2.Value + 1
Me.Refresh()
End If
End If
' ...etc...
End Sub
Private Function IsKeyDown(ByVal key As Keys) As Boolean
Return GetKeyState(key) < 0
End Function
End Class
The KeyDown() event will fire when any key is being held down, and you simply check each key you're interested in with GetKeyState(). There is no need for multiple threads...and I'm not even sure how that would work with form events. I don't know what you wanted to do with the "D" key, so I just put in NumericUpDown2 so you could see each block can do a different thing. You might not want to call Me.Refresh until the very bottom of the KeyDown() event so it only gets called once.

Emulate document locking in Lotusscript

In my Lotus Notes Application, when a user clicks an action, the action will call a run-on-server agent that will process the current document. The invoked agents sometimes doesn't run (which I think because of the concurrent agent limit of the server). This is why every 5 minutes there is a maintenance agent that runs to processed documents that are not processed by the invoked agents. The problem is, sometimes, a document is SIMULTANEOUSLY processed by these 2 agents, producing unacceptable results.
Is there a way I can emulate the document locking, such that documents can only be processed by one agent at a time? I don't like to use the native document locking because problems with the business rules might arise. I tried tagging the documents when one of the agents process it, then clears the flag after it is done. But the problem here is that there will still be a chance that the agents get hold of the document reference AT THE SAME TIME (due to the delay of saving the document, maybe).
Please help me. Thanks! :D
Yeah, that is not hard. Create a locking database where you have locking documents. They really only need to contain the UNID of the document being locked.
When your agents start processing a document, check if a locking document exists. If not, create one.
If there is one, either wait or skip the document for now.
After the document is done processing, delete the locking document.
This is trivial. Back when we were still on Notes/Domino 5, I even wrote a simple class to handle document locking in one of my application. The code below is referencing some functions and variables from another script library, but you get the idea. I am sure you can easily modify the code to work for you.
Option Public
Option Declare
Use "Functions.Globals"
Class DocumentLock
Private lockdb As NotesDatabase
Private lockview As NotesView
Private lockdoc As NotesDocument
Private lockservername As String
Private lockdbname As String
Private lnpdoc As NotesDocument ' Document to lock/unlock
Public Sub New(doc As NotesDocument)
me.lockservername = globals.GetValue("LockServer")
me.lockdbname = AppHomeDir + globals.GetValue("LockDBname")
If me.lockdb Is Nothing Then
Set me.lockdb = New NotesDatabase(me.lockservername, me.lockdbname)
End If
Set me.lockview = me.lockdb.GetView("LockedDocs")
Call me.lockview.Refresh()
Set me.lnpdoc = doc
End Sub
Public Sub LockMe()
Set me.lockdoc = New NotesDocument(me.lockdb)
me.lockdoc.Form="Locked"
me.lockdoc.LockUNID=me.lnpdoc.UniversalID
me.lockdoc.LockUser= globalcurrentusername
me.lockdoc.LockTime=Str(Now())
me.lockdoc.ClaimNumber = me.lnpdoc.GetItemValue("ClaimNumber")(0)
me.lockdoc.DocumentForm = me.lnpdoc.GetItemValue("Form")(0)
Call me.lockdoc.Save(True,True)
End Sub
Public Sub UnlockMe()
Call me.lockview.Refresh()
Set me.lockdoc = me.lockview.GetDocumentByKey(me.lnpdoc.UniversalID)
If Not me.lockdoc Is Nothing Then
Call me.lockdoc.Remove(True)
Call me.lockview.Refresh()
End If
End Sub
Public Function IsLocked(flagShowInfo As Boolean) As Boolean
Call lockview.Refresh()
Set me.lockdoc = me.lockview.GetDocumentByKey(me.lnpdoc.UniversalID)
If me.lockdoc Is Nothing Then
me.IsLocked = False
Else
me.IsLocked = True
If flagShowInfo = True Then
MsgBox "Document locked " & locktext & "." & Chr$(13) & "Please wait a while and try again.."
End If
End If
End Function
Public Function LockText() As String
LockText = "by " & LockUserName() & " at " & me.lockdoc.LockTime(0)
End Function
Public Function LockUserName() As String
Dim lockedby As String
lockedby = me.lockdoc.LockUser(0)
If lockedby = globalcurrentusername Then
LockUserName = "you"
Else
LockUserName = lockedby
End If
End Function
End Class

Resources