Check if element does exist in WebBrowser using BackgroundWorker - vb.net

I'm using background worker to manipulate some of the elements on my WebBrowser using vb.net.
For example the background worker RFIDReader will check if I'm on a specific link.
ElseIf TerminalBrowser.Url.ToString.Contains(baseUrl + "box-office/ticket-verification") And rf_code <> "" Then
' Insert RF Code in "rf_code" hidden text field
Me.Invoke(Sub() Me.TerminalBrowser.Document.GetElementById("rf_code").SetAttribute("value", rf_code.ToLower))
End If
What happens here is, if I tap my RFID card. It will include that corresponding value to my rf_code element in my browser.
Now what I want to happen is, I want to check if the container itself (synchronize-rfid) does exist (Since it's a pop up). See image for reference.
Here's our code for that.
If Me.TerminalBrowser.Document.GetElementById("synchronize-rfid") IsNot Nothing Then
' Code here
end if
Reference: https://stackoverflow.com/a/2022120/1699388
The problem is, BackgroundWorker does not really interacts with UI as per the code above.
Is there any method for me to determine if that element exist using background worker?
I think I've done this and it does not work though.
Dim synchronize_rfid = Me.Invoke(Sub() Me.TerminalBrowser.Document.GetElementById("synchronize-rfid"))
If synchronize_rfid IsNot Nothing Then
' Code here
end if

Try declaring the variable first, then set it during the invocation (which will set it from the main thread).
The Sub() lambda behaves like a normal Sub()-End Sub method, which means you can do the same things in there as if you had a separate method.
This works for me:
Dim synchronize_rfid As HtmlElement
If Me.InvokeRequired = True Then
Me.Invoke(Sub() synchronize_rfid = Me.TerminalBrowser.Document.GetElementById("synchronize-rfid"))
Else
synchronize_rfid = Me.TerminalBrowser.Document.GetElementById("synchronize-rfid")
End If
If synchronize_rfid IsNot Nothing Then
' Code here
End If

Related

FindWindowEx random failures getting child window handle

I have a VB.NET 4.6.1 desktop app that has been using FindWindow and FindWindowEx for over 2 years with no issue to locate a MDI child window and capture the window caption text, it has worked flawlessly until recent.
The behavior now is my app can only successfully obtain the MDI client window handle if I go back to either the parent window or MDI client and click anywhere on either window, then return to my app and the process succeeds.
I have tried adding threading sleep events, running the action continuously in a loop multiple times, calling AppActivate method using process ID (thinking I just needed to execute again), my next workaround thought is to try and send a click event to the parent window prior to my action being executed or maybe to use Enumerate all child windows of the parent, hope someone can suggest something because I am at a roadblock, been doing this for years but this one doesn't make sense to me, I have the suspicion that it is related to recent ownership of the software company and them revising this section, but I have no idea why it would interfere with these root level API methods.
Sample Code:
MDIhWnd = FindWindowEx(ParenthWnd, IntPtr.Zero, "WindowsForms10.MDICLIENT.app.0.34f5582_r7_ad1", Nothing)
'Threading.Thread.Sleep(100)
'AppActivate(proc(0).Id)
If MDIhWnd = 0 Then
Threading.Thread.Sleep(100)
'Dim hw = GetTopWindow(ParenthWnd)
For i = 0 To 500
AppActivate(proc(0).Id)
MDIhWnd = FindWindowEx(ParenthWnd, IntPtr.Zero, "WindowsForms10.MDICLIENT.app.0.34f5582_r7_ad1", Nothing)
If MDIhWnd <> 0 Then
Exit For
End If
Next
End If
The solution for me was, based on the above suggestion, to use UI Automation, I
had never worked with it before, however after looking it over I gave a go and
found that it did indeed simplify my needs to capture window text from a 3rd party application window with MDI Client Interface.
Below is a lessor version in VB.NET of the process for anyone needing to do the
same thing:
Imports System.Windows.Automation
' You will also need references to UIAutomationClient, and UIAutomationTypes
Private Sub test_ui_automation()
Dim ParenthWnd As Integer = 0
Dim _AutomationElementA As System.Windows.Automation.AutomationElement = Nothing
Dim _AutomationElementB As System.Windows.Automation.AutomationElement = Nothing
Dim _AutomationElementC As System.Windows.Automation.AutomationElement = Nothing
Dim propCondition As Condition
Try
'Parent Windows Process Stuff
ParenthWnd = FindWindow(Nothing, "Application to Find")
_AutomationElementA = AutomationElement.FromHandle(ParenthWnd)
If _AutomationElementA Is Nothing Then
NotifyIcon1.BalloonTipIcon = ToolTipIcon.Error
NotifyIcon1.BalloonTipText = "Couldn't Locate Parent Window."
NotifyIcon1.Visible = True
NotifyIcon1.ShowBalloonTip(3000)
Exit Sub
End If
' MDI Client Stuff
' I used ClassNameProperty but other conditions are available
propCondition = New PropertyCondition(AutomationElement.ClassNameProperty, "WindowsForms10.MDICLIENT.app.0.34f5582_r7_ad1", PropertyConditionFlags.IgnoreCase)
_AutomationElementB = _AutomationElementA.FindFirst(TreeScope.Element Or TreeScope.Children, propCondition)
If _AutomationElementB Is Nothing Then
NotifyIcon1.BalloonTipIcon = ToolTipIcon.Warning
NotifyIcon1.BalloonTipText = "Application warning MDIClient not Available!"
NotifyIcon1.Visible = True
NotifyIcon1.ShowBalloonTip(3000)
Exit Sub
End If
' Final Stage Stuff Locate Window Containing Class with Caption
propCondition = New PropertyCondition(AutomationElement.ClassNameProperty, "WindowsForms10.Window.8.app.0.34f5582_r7_ad1", PropertyConditionFlags.IgnoreCase)
_AutomationElementC = _AutomationElementB.FindFirst(TreeScope.Element Or TreeScope.Children, propCondition)
If _AutomationElementC Is Nothing Then
NotifyIcon1.BalloonTipIcon = ToolTipIcon.Warning
NotifyIcon1.BalloonTipText = "Automation warning, MDI Details are open."
NotifyIcon1.Visible = True
NotifyIcon1.ShowBalloonTip(3000)
Exit Sub
End If
Caption = _AutomationElementC.Current.Name
' If needed you can now parse/strip any data needed from the Caption text.
' I had other processes here but could not include in the post.
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub

vb.net TextBox does not change

I'm not able to update the TextBox content from another module.
The TextBox is in a Form called frm_main and the EventHandler in another module called md_zeiss.
Init() is called by a button on frm_main.
Problem:
If I directly call Test() from frm_main it does change the text.
If called by the event, it does not change the text, but displays the correct MessageBox.
Code:
Module md_zeiss
Sub Init()
Dim fsw As New FileSystemWatcher
fsw.Path = "C:\Output"
fsw.Filter = "*.txt"
fsw.NotifyFilter = NotifyFilters.Attributes Or NotifyFilters.CreationTime Or NotifyFilters.DirectoryName _
Or NotifyFilters.FileName Or NotifyFilters.LastAccess Or NotifyFilters.LastWrite Or NotifyFilters.Security Or NotifyFilters.Size
fsw.EnableRaisingEvents = True
AddHandler fsw.Changed, AddressOf md_zeiss.Main
End Sub
Sub Main(sender As Object, e As IO.FileSystemEventArgs)
Do While IsLocked(e.FullPath) = True
Application.DoEvents()
Loop
Dim fs As New FileStream(e.FullPath, FileMode.Open, FileAccess.Read)
Dim sr As New StreamReader(fs, System.Text.Encoding.Default)
Dim textline As String = vbNullString
Dim nr As String
Dim gi As String
Dim le As String
Do Until sr.Peek = -1
textline = sr.ReadLine
Select Case True
Case InStr(textline, vbTab & "Ø MOLDING_NR_SIDE" & vbTab)
nr = ReadVal(textline, 5)
Case InStr(textline, vbTab & "LENGTH" & vbTab)
gi = ReadVal(textline, 5)
Case InStr(textline, vbTab & "Ø MOLDING_GI_SIDE" & vbTab)
le = ReadVal(textline, 5)
End Select
Loop
Test()
End Sub
Sub Test()
frm_Main.TextBox1.Text = "Test"
MsgBox(frm_Main.TextBox1.Text)
End Sub
The FileSystemWatcher raises it events on a secondary thread by default. Default instances of forms are thread-specific so if you display the default instance of a form on the UI thread and then try to access the default instance from the handler of a FileSystemWatcher event (or a method called from that handler) then you're actually referring to two different form objects.
The simplest option is to set the SynchronizingObject property of the FileSystemWatcher on the UI thread. You can assign a form or other control to that property and the FileSystemWatcher will then raise its events on the thread that owns that control, i.e. the UI thread. If you do go down that route, just be sure that your event handler is executed quickly. You don;t want to tie up the UI thread with long-running code, which is why a secondary thread is used by default.
Another option is to use the SynchronizationContext class in your module to allow you to marshal a method call to the UI thread. You would continue to have the FileSystemWatcher raise its events on a secondary thread and do the background work there, then call Send or Post on the SynchronizationContext to invoke a method on the UI thread, where using the default instance of that form would refer to the same instance as you have already displayed.
Basically though, the architecture there is bad. You should almost certainly be using a class rather than a module and the form can then keep a reference to an instance of that class. The class could then raise an appropriate event that the form could handle and then the form could update its own TextBox. If you are access the controls on a form outside that form then the code is inherently bad. Default form instances make doing that easier, which is one reason that seasoned developers generally don't like them. They make it easier for beginners to get up and running, but it also makes it easier for beginners to paint themselves into a corner when things get remotely complex.

Using Filewatcher for a progress bar with subdirectories, Wont Update Properly

I'm trying to copy Files from a local Computer to a Network device. I'm trying to get a Progress Bar working for the File copy, and got it working for a Single Directory with no Subdirectory:
Private Sub CopyPictures()
Try
If Not Directory.Exists(DestinationPath) Then
My.Computer.FileSystem.CreateDirectory(DestinationPath)
End If
Dim counterLocalFiles = My.Computer.FileSystem.GetFiles(SourcePath)
UpdateProgressBarMaximum1(CInt(counterLocalFiles.Count))
UpdateLabelText2(CStr(counterLocalFiles.Count)) 'is a label which shows copied X files of Label2 Files
fsw1 = New IO.FileSystemWatcher(DestinationPath)
fsw1.EnableRaisingEvents = True
My.Computer.FileSystem.CopyDirectory(SourcePath, DestinationPath)
GetSettingsFromFile()
Catch Exec As System.IO.IOException
Dim dr As DialogResult = MessageBox.Show("Some Random Error Code", "Exception Title", MessageBoxButtons.OKCancel)
If (Not DialogResult.OK = dr) Then
Exit Sub
Return
End If
End Try
End Sub
Private Sub fsw1_Created(sender As Object, e As FileSystemEventArgs) Handles fsw1.Created
Dim counterRemoteFiles = My.Computer.FileSystem.GetFiles(DestinationPath)
UpdateProgressBar1(CInt(counterRemoteFiles.Count))
UpdateLabelText1(CStr(counterRemoteFiles.Count))
End Sub
The Update ObjectX Subs are just invoke Functions since the CopyPictures is raised by a backgroundworker as well looking all like this one for example
Private Sub UpdateProgressBar1(Value As Int32)
If ProgressBar1.InvokeRequired Then
ProgressBar1.Invoke(New Action(Of Integer)(AddressOf UpdateProgressBar1), Value)
Else
'We are on the UI thread so update the control.
ProgressBar1.Value = Value
End If
End Sub
This code works perfectly fine for me, but I have to deal with SubDirectories which contain the Images, and the names of the subs are random so i cant predetermine them so I came up with slight changes:
The Counter is looking now like this:
Dim counterLocalFiles = System.IO.Directory.GetFiles(SourcePath, "*.jpg*", SearchOption.AllDirectories).Length
UpdateProgressBarMaximum1(CInt(counterLocalFiles))
UpdateLabelText2(CStr(counterLocalFiles))
And this:
Dim counterRemoteFiles = IO.Directory.GetFiles(DestinationPath, "*.jpg", SearchOption.AllDirectories).Length
UpdateProgressBar1(CInt(counterRemoteFiles))
UpdateLabelText1(CStr(counterRemoteFiles))
And I added:
fsw1.IncludeSubdirectories = True
Now the weired Problems started: It would properly count the file in the source Directory setting label2 to the correct amount of files in all subdirectories and then start copying. It would NOT update the Progressbar though in real time. It just updated it once when it was done with the first directory and just adding the amount of files to it which it contained. After that it completly stoppedd nored the second directory and didn't add that at all to the progressbar. What am I doing wrong here? I hope my english is fine, If you have any question or If I was not clear enough, please let me know. Thank you
You don't have an event consumer that triggers your progressbar update routine - you call it once when your filesystemwatcher is instantiated.
You need to declare an event that handles the copy event and fires off your progress update code. Because Filesystemwatcher cannot monitor network drives, you may want to declare an event that fires off your progress update method when the counterRemoteFiles count increments.
Turns out I just made a mistake with correctly putting the
fsw1.IncludeSubdirectories = True
I was setting it to true in the Form Editor instead of doing it in the code. Once i actually put that in the code after initialising the fsw, it would work just fine

Handling Cross Thread Exception in Vb.Net Forms

I have a windows form on to which i have declared custom controls ( label, panels text boxes), The issue is I am loading images of that control in local thread , but some of the pictures not not downloading from web stream, hence the exception, Now i am setting image property Nothing in Catch block, and trying to set other UI panels properties, but it throws exception,
Exception :
"Cross thread operation not valid: Control "XXXXXXXXXX" accessed from a thread other than the thread it was created. "
kindly somebody tell the simplest way to set the property in case of exception occur so I may set the properties of other controls.
Basically, you don't touch controls in methods executed on a secondary thread. Only touch controls on the UI thread. Where you might usually just do this:
myPictureBox.Image = myImage
you now write a method like this:
Private Sub SetPictureBoxImage(img As Image)
If myPictureBox.InvokeRequired Then
myPictureBox.BeginInvoke(New Action(Of Image)(AddressOf SetPictureBoxImage), img)
Else
myPictureBox.Image = img
End If
End Sub
and then call it on the secondary thread instead of setting the Image property directly:
SetPictureBoxImage(myImage)
Note that that method will succeed whether it's called on the UI thread or a secondary thread so you can call it whether you know that you're on a secondary thread or not.
Check this out for more information.
EDIT
Private Sub UpdateUI(img As Image, visible As Boolean)
If Me.InvokeRequired Then
Me.BeginInvoke(New Action(Of Image, Boolean)(AddressOf UpdateUI), img, visible)
Else
myPictureBox.Image = img
myPanel.Visible = visible
End If
End Sub
Note that I used the InvokeRequired and Invoke members of the form instead of a specific control. It actually doesn't matter which form or control those are members of as long as they are owned by the same UI thread but to me it seems logical to use the same control your updating if there's only one or else use the form.
Note also that the signature of the delegate changes to match the signature of the method so that they have the same number and type of parameters.
I have done that UI change in this function:MethodInvoker
Try
'Code that was throwing exception here
Catch ex As Exception
Me.Invoke(New MethodInvoker(Sub()
'All UI changes made here
End Sub))
End Try
Control.CheckForIllegalCrossThreadCalls = False
Will allow you to change your TextBox,Label and etc... in execution

Adding nodes to treeview with Begin Invoke / Invoke

I've been working through my first project and have had a great deal a valuable help from the guys on SO but now I'm stuck again.
The below sub is used to add TreeNodes to a TreeView, excluding certain filetypes/names, upon addition of new data:
Sub DirSearch(ByVal strDir As String, ByVal strPattern As String, ByVal tvParent As TreeNodeCollection)
Dim f As String
Dim e As String
Dim tvNode As TreeNode
Dim ext() As String = strPattern.Split("|"c)
Try
For Each d In Directory.GetDirectories(strDir)
If (UCase(IO.Path.GetFileName(d)) <> "BACKUP") And (UCase(IO.Path.GetFileName(d)) <> "BARS") Then
tvNode = tvParent.Add(IO.Path.GetFileName(d))
For Each e In ext
For Each f In Directory.GetFiles(d, e)
If (UCase(IO.Path.GetFileName(f)) <> "DATA.XLS") And (UCase(IO.Path.GetFileName(f)) <> "SPIRIT.XLSX") Then
tvNode.Nodes.Add(IO.Path.GetFileName(f))
End If
Next
Next
DirSearch(d, strPattern, tvNode.Nodes)
End If
Next
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub
I'm now getting an error:
Action being performed on this control is being called from the wrong thread. Marshal to the correct thread using Control.Invoke or Control.BeginInvoke to perform this action.
On the following line:
tvNode = tvParent.Add(IO.Path.GetFileName(d))
Obviously, I understand its to do with 'threading' and the use of BeginInvoke / Invoke but even after reading the MSDN documentation on the error, I have no idea where to start.
This error only occurs, if I add a file to the initial directory (which is also the subject of a File System Watcher to monitor new additions).
Would someone be so kind as to give me an explanation in layman's terms so I may be able to understand.
This code is being run on a background thread where it's illegal to modify UI elements. The Invoke / BeginInvoke methods are ways to schedule a piece of code to run on UI thread where elements can be modified. For example you could change your code to the following
Dim action As Action = Sub() tvNode.Nodes.Add(IO.Path.GetFileName(f))
tvNode.TreeView.Invoke(action)
This code will take the delegate instance named action and run it on the UI thread where edits to tvNode are allowed
Fixing the earlier Add call is a bit trickier because there is no Control instance on which we can call BeginInvoke. The signature of the method will need to be updated to take a Dim control as Control as a parameter. You can pass in the TreeView for that parameter if you like. Once that is present the first Add can be changed as such
Dim outerAction As Action = Sub() tvNode = tvParent.Add(IO.Path.GetFileName(d))
control.Invoke(outerAction)