Updating UI from worker thread through delegates - vb.net

For some reason invoking delegates to update to UI doesn't work for one of my threads but using different delegates to update the same controls in a slightly different way does work.
Here's the broken code, I've commented around the lines that are broken to explain
Private Sub RunBtn_Click(sender As Object, e As EventArgs) Handles RunBtn.Click
Dim transferThread As New System.Threading.Thread(AddressOf RunTransfer)
StartContinuousProg()
UpdateStatus("Running Transfer...")
StartTime = Date.Now
incrementProgMethod = New incrementProgDelegate(AddressOf incrementProg)
finishProgMethod = New finishProgDelegate(AddressOf finishProg)
updateStatusMethod = New updateStatusDelegate(AddressOf UpdateStatus)
writeErrorMethod = New writeErrorDelegate(AddressOf WriteError)
writeWarningMethod = New writeWarningDelegate(AddressOf WriteWarning)
writeAlertMethod = New writeAlertDelegate(AddressOf WriteAlert)
EndTransferMethod = New EndTransferDelegate(AddressOf endTransfer)
transferThread.Start()
End Sub
Private Sub RunTransfer()
'(...Some work...)
For catRow = 0 To CATImportArr.Length - 1
Dim currentCATSerial, currentCATAsset As String
currentCATSerial = LCase(CATImportArr(catRow).getSerialNumber)
currentCATAsset = LCase(CATImportArr(catRow).getAssetNumber)
'This line produces this error: Unable to cast object of type 'System.String' to type 'System.Delegate'.
Invoke(updateStatusMethod("Searching " & currentCATSerial & ", " & currentCATAsset & "..."))
'This line doesn't crash but the UI label doesn't change
updateStatusMethod.Invoke("Searching " & currentCATSerial & ", " & currentCATAsset & "...")
'This line doesn't crash but the UI progress bar doesn't change
Invoke(finishProgMethod)
These are the methods being called through the delegates
Public Function UpdateStatus(ByRef text As String)
ParentForm.StatusLbl.Text = text
Return text
End Function
Public Sub finishProg()
ParentForm.StatusProg.Value = 100
End Sub

A Delegate is simply a class holding a reference to a method. You cannot pass parameters to the Delegate itself.
To pass parameters to the method you want to invoke you have to use the Control.Invoke(Delegate, Object()) overload where you pass the parameter(s) to the Invoke() method, after you've specified the delegate:
Invoke(updateStatusMethod, "Searching " & currentCATSerial & ", " & currentCATAsset & "...")
Since the second parameter of Control.Invoke(Delegate, Object()) is declared ParamArray you may keep on specifying parameters if you need to by just separating them with a comma:
Invoke(updateStatusMethod, [param1], [param2], [param3], ...)

Related

Property Copying Between Two Objects using Reflection Recursive

I am trying to do a deep copy a source object's property values to the destination object's property of the same name.
The problem I have run into is is that if the source property is a collection, List(of integer) in this case. It will copy it as a reference and not an independent copy which is expected and can be seen in the output results.
I plan to recursively call the Copy function for each property that is a collection to do a deep copy on that object. L'ii also add some associate code.
The problem I am having is I can't detect a List(of) object when doing a type comparison 2)
Although this works when doing a Int16 type comparison 1)
I can get it to work when I compare the type.Name 3)
Is this 3) robust enough or is there a way to make 2) work?
Output:
Unchanged
Source.ID:1 Dest.ID:1
Source.Description:Source Description Dest.Description:Source Description
Source.Links(0):10 Dest.Links(0):10
Source.Links(1):11 Dest.Links(1):11
changed
Source.ID:1 Dest.ID:100
Source.Description:Source Description Dest.Description:Dest Description
Source.Links(0):50 Dest.Links(0):50
Source.Links(1):11 Dest.Links(1):11
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Dim Source As New DataClass
Dim SourceLinks As New List(Of Integer)({10, 11, 12})
Source.ID = 1
Source.Description = "Source Description"
Source.Links = SourceLinks
Dim Dest As New DataClass
PropertyCopier(Of DataClass, DataClass).Copy(Source, Dest)
Debug.Print("Unchanged")
Debug.Print("Source.ID:" & Source.ID & vbTab & "Dest.ID:" & Dest.ID)
Debug.Print("Source.Description:" & Source.Description & vbTab & "Dest.Description:" & Dest.Description)
Debug.Print("Source.Links(0):" & Source.Links(0) & vbTab & "Dest.Links(0):" & Dest.Links(0))
Debug.Print("Source.Links(1):" & Source.Links(1) & vbTab & "Dest.Links(1):" & Dest.Links(1))
Debug.Print("changed")
Dest.ID = 100
Dest.Description = "Dest Description"
Dest.Links(0) = 50
Debug.Print("Source.ID:" & Source.ID & vbTab & "Dest.ID:" & Dest.ID)
Debug.Print("Source.Description:" & Source.Description & vbTab & "Dest.Description:" & Dest.Description)
Debug.Print("Source.Links(0):" & Source.Links(0) & vbTab & "Dest.Links(0):" & Dest.Links(0))
Debug.Print("Source.Links(1):" & Source.Links(1) & vbTab & "Dest.Links(1):" & Dest.Links(1))
End Sub
End Class
Public Class DataClass
Public Property ID As Int16
Public Property Description As String
Public Property Links As List(Of Integer)
End Class
Public Class PropertyCopier(Of TSource As Class, TDestination As Class)
Public Shared Sub Copy(ByVal Source As TSource, ByVal Destination As TDestination)
Dim SourceProperties = Source.[GetType]().GetProperties()
Dim DestinationProperties = Destination.[GetType]().GetProperties()
For Each SourceProperty In SourceProperties
For Each DestinationProperty In DestinationProperties
If SourceProperty.Name = DestinationProperty.Name AndAlso SourceProperty.PropertyType = DestinationProperty.PropertyType Then
If SourceProperty.PropertyType = GetType(Int16) Then
'1) Correctly detects the property type as a integer
ElseIf SourceProperty.PropertyType = GetType(List(Of)) Then
'2) Does not detect the List(of ) type
ElseIf SourceProperty.PropertyType.Name = GetType(List(Of)).Name Then
'3) Correctly detects the property type as List(of )
End If
If SourceProperty.CanWrite Then DestinationProperty.SetValue(Destination, SourceProperty.GetValue(Source))
Exit For
End If
Next
Next
End Sub
End Class
The problem is that the generic type parameter of your object is set to something specific, e.g. you're comparing List(Of String) to List(Of) and they are not the same. What you need to do is get the generic type definition from that generic type, i.e.
ElseIf SourceProperty.PropertyType.IsGenericType AndAlso
SourceProperty.PropertyType.GetGenericTypeDefinition() Is GetType(List(Of)) Then
Notice the correct us of Is rather than = as well.
Depending on what you need to work with, you may also want to look for interface compatibility rather than exact equality. You can do that using Type.IsAssignableFrom. I have similar code that looks for a list like this:
If sourceProperty.PropertyType.IsGenericType _
AndAlso GetType(IList).IsAssignableFrom(sourceProperty.PropertyType) Then
'...
End If
(building upon the previous answer)
If you need it, you can get the generic type argument using e.g. sourceProperty.PropertyType.GenericTypeArguments(0).

VB optimize loop avoid with an Evaluate()

I am running a loop that evaluates input given by the user over more than 150k objects. The user sets the info to read like "obj.Name", "obj.Path", "obj.Date"... which may contain also logic evaluations such as "IIf(obj.Params>5,1,0)". It is then provided into the programm as a string.
I used the Evaluate() functions and it does work well, just that it is slow. It takes almost 6h to go over all elements. I was thinking if there is a way in which I can take the requested info and turn it into a straight-forward executable somehow, and avoid using the Evaluate expression in the whole loop (it runs for the number of requested data by the user * 150k).
This is a schematic of the loop I am running:
For Each Object in ObjectsList
For Each UserRequest in Requests
ResultsMatrix(i,j) = Evaluate(Requests(i))
j += 1
Next
i += 1
Next
I store then the results in a Matrix which is pasted in an Excel file at the end. Is there a way in which I can do sort of working the string to be evaluated into a function's return? I'd like to avoid usig the Eval function and parse directly the string in an executable and dont evaluate it for each object. Any tips on speeding up the loop?
It might be worth considering writing the requests into a set of functions and using the .NET CodeDom compilers to build it into a DLL. You can then load the assembly, find the right functions using reflection and put them into an array, then call them using reflection - that way you'll be calling .NET code and it should be far faster. Some (incomplete) code to get you started from a project where I have done this...
Private Function CombineCode() As String
Dim ret As New System.Text.StringBuilder
ret.AppendLine("Imports System")
ret.AppendLine("Imports Microsoft.VisualBasic")
ret.AppendLine()
ret.AppendLine("Namespace " & MainNamespace)
ret.AppendLine("Public Class " & MainClassName)
For Each e In _Entries
ret.AppendLine(e.Value.Code)
Next
ret.AppendLine("End Class")
ret.AppendLine("End Namespace")
Return ret.ToString
End Function
Private Function Compile(Code As String) As Assembly
'Dim d As New Dictionary(Of String, String)
'd.Add("langversion", "14")
Dim VBP As New Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider()
Dim PM As New System.CodeDom.Compiler.CompilerParameters
'PM.GenerateInMemory = True
PM.GenerateExecutable = False
PM.OutputAssembly = IO.Path.Combine(_Path, GenerateFileName() & ".dll") ' "Generated.dll"
PM.MainClass = MainClassName
PM.IncludeDebugInformation = True
Dim ASM As System.Reflection.Assembly
For Each ASM In AppDomain.CurrentDomain.GetAssemblies()
Try
If ASM.Location <> "" Then PM.ReferencedAssemblies.Add(ASM.Location)
Catch
End Try
Next
PM.ReferencedAssemblies.Add("System.Web.dll")
'Get compilation results
Dim Results As System.CodeDom.Compiler.CompilerResults
Results = VBP.CompileAssemblyFromSource(PM, Code)
'Show possible compilation errors
Dim Err As System.CodeDom.Compiler.CompilerError
For Each Err In Results.Errors
Throw New SyntaxErrorException("Error N. " & Err.ErrorNumber &
" Message: " & Err.ErrorText & " Line " & Err.Line & " in code " & vbCrLf & Code)
Next
Return Results.CompiledAssembly
End Function
Private Sub FindMethods()
Dim dt = (From t In _LatestAssembly.GetTypes() Where t.Name = MainClassName).Single
For Each e In _Entries.Values
e.Method = dt.GetMethod(e.MethodName)
Next
End Sub
Assembly = Assembly.LoadFrom(System.IO.Path.Combine(Path, sd.LatestAssemblyFile))
The Evaluate function is just resources on the computer itself. It's a great candidate for using Parallel.For.
In this case, j is the implied index.
For Each Object in ObjectsList
Parallel.For(0, Requests.Length, New ParallelOptions(), Sub(j, loopState)
ResultsMatrix(i,j) = Evaluate(Requests(j))
End Sub
)
i += 1
Next
Note, that Requests(i) is getting called repeatedly and produces the same result, so I assume you mean Requests(j).

Checking with VB.net if File exists in dropbox folder

Here is my code.
Yes, I am using both, DropNet and Dropbox APIs as I found the DropNet upload works nicely. But I am trying to use the Dropbox one to check for filename (as I couldn't get it to work on DropNet and could not find any help about it online)
I have little doubt that my problem has something to do with the whole Async & Await , as I have never worked with this stuff before.
The File Upload & Get Share both work just fine.
This is a VB.Net Website.
When I run it, it freezes in side the DoesDropBoxFileExist function
Imports Dropbox.Api
Imports DropNet
Imports DropNet.Models
Partial Class _Default
Inherits System.Web.UI.Page
Dim br As String = "<br>"
Public FileName As String
Protected Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If FileUpload1.HasFile Then
Dim dropNet_client As New DropNetClient("", "", "")
Dim dropBox_client As New DropboxClient("")
FileName = FileUpload1.PostedFile.FileName
Response.Write("before: " & FileName & br)
MsgBox(1)
FileName = DoesDropBoxFileExist(dropBox_client).Result
MsgBox(3)
Response.Write("after: " & FileName & br)
Dim content As Byte() = FileUpload1.FileBytes
Dim pathToFile As String = Server.MapPath("~")
'Response.Write(pathToFile)
dropNet_client.UploadFile("/AlertImages/", FileName, content, True)
Dim shareResponse As ShareResponse = dropNet_client.GetShare("/AlertImages/" & FileName)
Response.Write(shareResponse.Url)
If Not FileName.ToLower.Contains("pdf") Then
Dim rawBytes As Byte() = dropNet_client.GetThumbnail("/AlertImages/" & FileName, 2)
Dim base64String As String = Convert.ToBase64String(rawBytes, 0, rawBytes.Length)
Image1.ImageUrl = "data:image/png;base64," & base64String
Image1.Visible = True
End If
dropBox_client.Dispose()
End If
End Sub
Private Async Function DoesDropBoxFileExist(_client As DropboxClient) As Threading.Tasks.Task(Of String)
Dim rtn As String = FileName
Dim list = Await _client.Files.ListFolderAsync("/AlertImages")
MsgBox(2)
' show folders then files
For Each item As Files.Metadata In list.Entries.Where(Function(i) i.IsFolder)
If item.Name = FileName Then
FileName = FileName & Now.ToString
End If
Response.Write(" < b > " & item.Name & "</b>" & br)
'Dim list2 As ListFolderResult = Await dbx.Files.ListFolderAsync(item.Name)
'For Each itm As Files.Metadata In list2.Entries.Where(Function(j) j.IsFile)
' Response.Write(item.Name & " : " & item.AsFile.Size & br)
'Next
Next
For Each item As Files.Metadata In list.Entries.Where(Function(i) i.IsFile)
Response.Write("'" & item.Name & "' '" & FileName & "'" & br)
If item.Name = FileName Then
Response.Write("test" & br)
rtn = FileName & "_" & Now.ToString
End If
Next
Return rtn
End Function
End Class
METHOD 1
To check in VB.NET if a file exists in Dropbox using the API, you can use this method.
First we create a button with a click event as follows:
Private Sub btnCheck_Click(sender As Object, e As EventArgs) Handles btnCheck.Click
'FileToCheck is declared in Form1 as Public Shared
'FileFound is declared in Form1 as Public Shared
FileToCheck = cmbFiles.Text
FileFound = False
Dim task1 = Task.Run(Function() CheckFileMetadata())
task1.Wait()
If FileFound = True Then
'Do something
Else
'Do something else
End If
End Sub
And now the function:
Private Async Function CheckFileMetadata() As Task
Using dbx = New DropboxClient(DbxToken) 'DbxToken = your token text
Try
Await dbx.Files.GetMetadataAsync(Form1.FileToCheck)
FileFound = True
Debug.WriteLine("Found it!")
Catch exapi As ApiException(Of Dropbox.Api.Files.GetMetadataError)
If exapi.ErrorResponse.IsPath And exapi.ErrorResponse.AsPath.Value.IsNotFound Then
Debug.WriteLine("Nothing found at " + Form1.FileToCheck)
End If
Catch ex As Exception
Debug.WriteLine("Error checking file metadata" + vbCrLf + ex.ToString)
End Try
End Using
End Function
This method was adapted from the code here.
METHOD 2
This example demonstrates using VB.NET to recursively iterate through all Dropbox folders to retrieve the names of all files and put them into a collection. Then we check to see if our file is in the collection or not. This method does work, but it's not as efficient as the method above for obvious reasons. I've left it here because it illustrates some additional methods that might help someone.
A couple of notes:
If you have a lot of files and/or folders, there can be a delay due to all of the calls that have to be made to do the recursive processing.
DbxFolders and DbxFiles are declared as Public in the main form class, like so:
Public DbxFolders As New List(Of String)
Public DbxFiles As New List(Of String)
Note use of the .tolower since the Dropbox API returns all found paths in all lowers:
Private Sub btnWalk_Click(sender As Object, e As EventArgs) Handles btnWalk.Click
DbxFolders.Clear()
DbxFiles.Clear()
Dim FindIt As String = "/Folder/File-To-Find.txt".ToLower
Dim task2 = Task.Run(Function() GetTree(String.Empty))
task2.Wait()
If DBFileExists(FindIt) Then MsgBox("Found it!") Else MsgBox("File not found")
End Sub
Private Async Function GetTree(dir As String) As Task
Using dbx = New DropboxClient("Your_Token_Goes_Here")
Dim list = Await dbx.Files.ListFolderAsync(dir)
For Each item In list.Entries.Where(Function(i) i.IsFile)
DbxFiles.Add(item.PathLower)
Next
For Each item In list.Entries.Where(Function(i) i.IsFolder)
DbxFolders.Add(item.PathLower)
Await GetTree(item.PathLower)
Next
End Using
End Function
Private Function DBFileExists(file As String) As Boolean
If DbxFiles.IndexOf(file) > -1 Then Return True Else Return False
End Function
DISCUSSION
Method 1 is obviously the more efficient of the two methods by far because we only call the API once. Note how the ApiException is used in Try-Catch to determine that the file was not found.
Method 2 illustrates some additional concepts that were helpful to me to learn, so I've left it here because someone may have a scenario where this code and the lists that it creates comes in handy.
Note that when we call GetTree(String.Empty), it would be more efficient to pass the specific folder to look in, instead of starting at the root, since we are attempting to match the full path (/path/to/file.txt) in this example anyway, but I wanted to illustrate the recursive iteration because it might be needed in a different situation.
If you don't care what folder an item is in, but only want to see if it exists in a folder without regard to which folder that is, then you would need to use this recursive iteration but instead of item.pathlower you would want to collect item.name instead.
If desired, you can process the collected file list from Method 2 with a simple loop:
For each DbxFile as string in DbxFiles
'Do something
Next

Threading Problems (I don't understand it)

There's lots and lots of pages on the internet regarding threading but I can't seem to get my head around it.
I have a Form, which on the click of a button, loops through a file and reads it line by line. Each line is the login details for different FTP sites.
When it reads a line, it Dim's a variable as a new instance of a class named CallFTP using the login details.
It then Dim's a variable as a new Thread using a function in CallFTP named PerformFTP.
PerformFTP returns a string with the results of the FTP and I want to add this to a ListBox on the form that began it all.
The code for the button goes like this...
Private Sub cmdRun_Click(sender As Object, e As EventArgs) Handles cmdRun.Click
For Each _FTPLine As String In Split(_FTPDetails, vbNewLine)
Dim _Active As Boolean = CBool(Split(_FTPLine, "|")(7))
If _Active Then
_CurNum += 1
_ID = Format(Now.Year, "0000") & Format(Now.Month, "00") & Format(Now.Day, "00") & Format(Now.Hour, "00") & Format(Now.Minute, "00") & Format(Now.Second, "00") & Format(Now.Millisecond, "000") & Format(_CurNum, "00000")
Dim _FTP As New CallFTP(_ID, Split(_FTPLine, "|")(0), Split(_FTPLine, "|")(1), Split(_FTPLine, "|")(2), Split(_FTPLine, "|")(3), Split(_FTPLine, "|")(4), Split(_FTPLine, "|")(5), Split(_FTPLine, "|")(6))
Dim _Thread = New Thread(New ThreadStart(AddressOf _FTP.PerformFTP))
With _Thread
.IsBackground = True
.Start()
End With
End If
Next _FTPLine
End Sub
The class is as below (not quite but you don't need the rest of the code lol)
Public Class CallFTP
Private _ID As String = ""
Private _Response As String = ""
Private _IPAddress As String = ""
Private _Port As String = ""
Private _User As String = ""
Private _Pass As String = ""
Private _Remote As String = ""
Private _Local As String = ""
Private _InOut As String = ""
Public Sub New(ID As String, Server As String, PortNum As String, Username As String, Password As String, RemoteDir As String, LocalDir As String, InOrOut As String)
_ID = ID
_IPAddress = Server
_Port = PortNum
_User = Username
_Pass = Password
_Remote = RemoteDir
_Local = LocalDir
_InOut = InOrOut
End Sub
Public Function PerformFTP() As String
Return "This is a test"
End Function
End Class
Could anyone explain how I would call a sub named LogMessage on a module named modMisc (which adds a string to a ListBox on the main form)?
I've read that you need to invoke it but everything I read seems to give me a headache and make me need to lie down in a dark room for a few hours.
Is anyone capable of explaining as though you're speaking to a 2 year old? :)
Any help would be much appreciated.
You need to invoke a delegate to update your GUI if you're going to update it from another thread that from where it was created.
1º Your delegate must match (have the same signature) than the method you'll use:
Delegate Sub LogMessageExampleDelegate(ByVal x As Integer, ...)
Signature means that the delegate must return and receive the same types than your function/method.
2º Call your function to update GUI using delegate. This for example inside your update GUI function:
If yourListBox.InvokeRequired Then
yourListBox.Invoke(New LogMessageExampleDelegate(AddressOf THE_FUNCTION_WHICH_UPDATES_THE_GUI_NAME), parameter_value)
Else
'Just call your function
End If
With, as example:
sub addToListBox(byval text as string)
myListBox.Items.add(text)
end sub
So your invoke would be:
If yourListBox.InvokeRequired Then
yourListBox.Invoke(New LogMessageExampleDelegate(AddressOf addToListBox), "Item 1")
Else
'Just call your function
addToListBox("Item 1")
End If
PS: I wrote it two times so hope I didn't mess up with something without noticing it.

timing issues in vb.net

I think i have a timing issue. It perplexes me why.
On the trigger of an event I call a function to update a value:
Private Sub t0_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles t0.Click
Input.ppProperty = "blank"
UpdateRecord("Hey", Input.ppProperty)
MsgBox(Input.ppProperty)
End Sub
UpdateRecord() should update the property Input.ppProperty to the value "Hey". It does update it, but not until UpdateRecord() has finished. The code:
Sub UpdateRecord(ByVal updateValue As String, ByRef recordToUpdate As String)
If recordToUpdate <> Nothing Then
MsgBox(updateValue & " " & recordToUpdate & " " & Input.ppProperty)
End If
recordToUpdate = updateValue
If recordToUpdate <> Nothing Then
MsgBox(updateValue & " " & recordToUpdate & " " & Input.ppProperty)
End If
End Sub
Output:
Hey Blank Blank 'initial values
Hey Hey Blank 'values at the end of the function
Hey 'value of property when function is finished
What it seems like it should be is:
Desired Output:
Hey Blank Blank 'initial values
Hey Hey Hey 'values at the end of the function
Hey 'value of property when function is finished
Notice the difference, the property does not update until the function is completely finished.
Why?
EDIT:
Also, how would I go about fixing this so it updates within the function?
When you pass a property ByRef, the equivalent of the following happens:
Dim temporaryValue As String = Input.ppProperty
UpdateRecord("Hey", temporaryValue)
Input.ppProperty = temporaryValue
There’s no direct way around this. A potential solution is to rewrite the UpdateRecord method so that you can pass the whole object (Input) into it and manipulate the property value directly. Only then will the change be reflected directly.
I would imagine that it has something to do with Strings in .Net being an immutable type.
You get a temporary string that is passed in ByRef and then Returned out causing the Input.ppProperty to be set afterwards.
Something like this:
Dim tempInput As String = Input.ppProperty
UpdateRecord("Hey", tempInput)
Input.ppProperty = tempInput
Since it seems that Input is in Scope in both cases why not do this as so.
Sub UpdateRecord(ByVal updateValue As String)
Input.ppProperty = updateValue
End Sub
However if this actually isn't the case I'll need to think about this a little more.