UI freezes on different thread - vb.net

I've tried elsewhere but nothing came of it. Basically i want the image to show up in the picturebox.
My code:
Dim ScreenReceiverClient As New TcpClient
Dim ScreenReceiverServer As New TcpListener(ScreenReceiverPort)
Dim ScreenReceiverListening As New Thread(AddressOf ScreenListen)
Dim GetScreen As New Thread(AddressOf ReceiveScreen)
Private Sub ReceiveScreen()
While ScreenReceiverClient.Connected = True
Call New Action(AddressOf ChangeImage).BeginInvoke(Nothing, Nothing)
End While
End Sub
Private Sub ChangeImage()
Dim bf As New BinaryFormatter
PictureBox1.Image = bf.Deserialize(ScreenReceiverClient.GetStream)
End Sub
Private Sub ScreenListen()
While ScreenReceiverClient.Connected = False
ScreenReceiverServer.Start()
ScreenReceiverClient = ScreenReceiverServer.AcceptTcpClient
End While
GetScreen.Start()
End Sub
ChangeImage() is supposed to be called on the UI thread instead of the ScreenReceiverListening thread, but the UI just freezes. Please help me!

You need to call the Method asynchronously:
http://msdn.microsoft.com/de-de/library/2e08f6yc(v=vs.110).aspx?cs-save-lang=1&cs-lang=vb#code-snippet-1
Another easy way is, to use a timer:
Private Sub ScreenListen()
While ScreenReceiverClient.Connected = False
ScreenReceiverServer.Start()
ScreenReceiverClient = ScreenReceiverServer.AcceptTcpClient
End While
t1.Interval = 1
t1.Start()
End Sub
Private WithEvents t1 As Windows.Forms.Timer
Private Sub t1_Tick(sender As Object, e As EventArgs) Handles t1.Tick
t1.Stop()
ReceiveScreen()
End Sub

Related

How long the PC has been started?

I would like to know how long the PC has been started.
That's why I made the following routine:
Public Function LipPCIsOn() As String
Dim iTempoPC As Integer
Dim tTempoPC As TimeSpan
Dim strTempoPC As String
iTempoPC = System.Environment.TickCount
tTempoPC = TimeSpan.FromMilliseconds(iTimePC)
strTempoPC = tTempoPC.Duration.ToString("hh:mm:ss")
Return strTempoPC
End Function
But I do not understand, the PC despite having been started by 3 minutes it tells me:
7:54:36
Where's the mistake?
Thank you all
There may be some other source of the last power-on time, but you can use the Windows System Event Log to get the last event from Kernel-Boot:
Function GetLastPowerOn() As DateTime?
Dim systemEventLog = New EventLog()
systemEventLog.Log = "System"
Dim lastPowerOn = systemEventLog.Entries.Cast(Of EventLogEntry).
Where(Function(eu) eu.Source = "Microsoft-Windows-Kernel-Boot").
OrderByDescending(Function(ev) ev.TimeGenerated).FirstOrDefault()
Return lastPowerOn?.TimeGenerated
End Function
I do not know the behaviour for if there is no entry, so I assumed that a Nullable(Of DateTime) would do. If you want to clear your System event log, you could let us know what happens; I don't want to do that.
Unfortunately, it takes ages to return a value (e.g. about 7 seconds on this computer), so you might want to call it asynchronously. Here is an example which uses one button and two labels on a form:
Public Class Form1
Dim tim As Timer
Friend Async Function GetLastPowerOnAsync() As Task(Of DateTime?)
Dim systemEventLog = New EventLog() With {.Log = "System"}
Dim tsk = Await Task.Factory.StartNew(Function()
Return systemEventLog.Entries.Cast(Of EventLogEntry).
Where(Function(eu) eu.Source = "Microsoft-Windows-Kernel-Boot").
OrderByDescending(Function(ev) ev.TimeGenerated).
FirstOrDefault()
End Function)
Return tsk?.TimeGenerated
End Function
Sub timTick(sender As Object, e As EventArgs)
Label1.Text = DateTime.Now.ToString("HH:mm:ss")
End Sub
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim lpo = Await GetLastPowerOnAsync()
If lpo.HasValue Then
Label2.Text = lpo.Value.ToString("yyyy-MM-dd HH:mm:ss")
Else
Label2.Text = "No System event log entry with a source of Microsoft-Windows-Kernel-Boot entry found."
End If
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
tim = New Timer() With {.Interval = 500}
AddHandler tim.Tick, AddressOf timTick
tim.Start()
End Sub
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
RemoveHandler tim.Tick, AddressOf timTick
tim.Dispose()
End Sub
End Class
Thank you all.
I just wanted to point out that:
1) my PC did not make a real shutdown but a suspension;
2) the correct code I rewrote is:
Public Function LipPCIsOnNew() As String
Dim EventoLogApp As New System.Diagnostics.EventLog("System")
Dim OraACCENSIONE As Date, stMachineName As String
' search from the end, to find the last boot faster
For i = EventoLogApp.Entries.Count - 1 To 1 Step -1
If EventoLogApp.Entries(i).InstanceId.ToString = 1 Then
OraACCENSIONE = EventoLogApp.Entries(i).TimeGenerated
stMachineName = EventoLogApp.Entries(i).MachineName.ToString
Exit For
End If
Next
Return OraACCENSIONE.ToString
End Function
Now everything is ok
Thank you all

How to show records added to datagridview in real time

A co-worker needs to search our network and her File Explorer search does not work well. I threw this app together quickly to allow her to search and it works well. The results are written to a datagridview, but the results are not shown until the search is complete.
I would like the datagridview to show records as they are added and allow her to cancel the search if she wants.
Using a backgroundworker, I tried to refresh the grid, but as soon as it finds a match, the code stops running. There are no errors, it just stops running.
So how can I get the grid to update as it continues to search?
Public dtResults As DataTable
Dim myDataSet As New DataSet
Dim myDataRow As DataRow
Dim colType As DataColumn
Dim colResult As DataColumn
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
dtResults = New DataTable()
colType = New DataColumn("Type", Type.GetType("System.String"))
colResult = New DataColumn("Search Result", Type.GetType("System.String"))
dtResults.Columns.Add(colType)
dtResults.Columns.Add(colResult)
DataGridView1.DataSource = dtResults
DataGridView1.Columns(1).AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
End Sub
Private Sub btnSearch_Click(sender As Object, e As EventArgs) Handles btnSearch.Click
btnSearch.Enabled = False
sbStatusBar.Text = "Searching..."
dtResults.Clear()
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
LoopSubFolders(txtSearchLocation.Text)
End Sub
Public Sub LoopSubFolders(sLocation As String)
Dim di = New DirectoryInfo(sLocation)
Dim mySearchterm As String = LCase(txtSearchTerm.Text)
Dim fiArr As FileInfo() = di.GetFiles()
Dim sSearchTarget As String
sbStatusBar.Text = "Searching " & sLocation
'Search File names in
If cbFileNames.Checked = True Then
For Each myFile In fiArr
sSearchTarget = LCase(myFile.Name)
If sSearchTarget.Contains(mySearchterm) Then
myDataRow = dtResults.NewRow()
myDataRow(dtResults.Columns(0)) = "File"
myDataRow(dtResults.Columns(1)) = Path.Combine(sLocation, myFile.Name)
dtResults.Rows.Add(myDataRow)
End If
Next
End If
For Each d In Directory.GetDirectories(sLocation)
If cbFolderNames.Checked = True Then
sSearchTarget = LCase(d)
If sSearchTarget.Contains(mySearchterm) Then
myDataRow = dtResults.NewRow()
myDataRow(dtResults.Columns(0)) = "Folder"
myDataRow(dtResults.Columns(1)) = d
dtResults.Rows.Add(myDataRow)
End If
End If
LoopSubFolders(d)
Next
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
btnSearch.Enabled = True
sbStatusBar.Text = "Complete"
DataGridView1.DataSource = Nothing
DataGridView1.DataSource = dtResults
DataGridView1.Columns(1).AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
End Sub
Here's an example of how you might do it using the suggested ReportProgress method and ProgressChanged event:
Private table As New DataTable
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'Configure table here.
DataGridView1.DataSource = table
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'Setup UI here.
'Note that you MUST pass in the TextBox data as you MUST NOT touch the UI directly on the secondary thread.
BackgroundWorker1.RunWorkerAsync({TextBox1.Text, TextBox2.Text})
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
'Get the data passed in and separate it.
Dim arguments = DirectCast(e.Argument, String())
Dim folderPath = arguments(0)
Dim searchTerm = arguments(1)
SearchFileSystem(folderPath, searchTerm)
End Sub
Private Sub SearchFileSystem(folderPath As String, searchTerm As String)
For Each filePath In Directory.GetFiles(folderPath)
If filePath.IndexOf(searchTerm, StringComparison.InvariantCultureIgnoreCase) <> -1 Then
'Update the UI on the UI thread.
BackgroundWorker1.ReportProgress(0, {"File", filePath})
End If
Next
For Each subfolderPath In Directory.GetDirectories(folderPath)
If subfolderPath.IndexOf(searchTerm, StringComparison.InvariantCultureIgnoreCase) <> -1 Then
'Update the UI on the UI thread.
BackgroundWorker1.ReportProgress(0, {"Folder", subfolderPath})
End If
SearchFileSystem(subfolderPath, searchTerm)
Next
End Sub
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
'Get the data passed out and separate it.
Dim data = DirectCast(e.UserState, String())
'Update the UI.
table.Rows.Add(data)
End Sub
Note that you should NEVER touch the UI directly in the DoWork event handler or a method called from it. ONLY touch the UI on the UI thread. That means that the text in your TextBoxes must be extracted BEFORE calling RunWorkerAsync. You can eithewr pass the Strings in as arguments or you can assign them to fields and access them from there on any thread. Don't EVER access a member of a control on other than the UI thread. Some times it will work, sometimes it will appear to work but not do as intended and sometimes it will crash your app. So that you don't have to remember which specific scenarios cause which result, avoid such scenario altogether.
I haven't tested this code so I'm not sure but you may have to call Refresh on the grid or the form after adding the new row to the DataTable.
Variables
Well, let's start from the top with some class level variables:
'Notice the enabled properties.
Private WithEvents BackgroundWorker1 As New BackgroundWorker With {.WorkerReportsProgress = True, .WorkerSupportsCancellation = True}
'To monitor the cancellation, set by the Cancel Button.
Private bgwCancel As Boolean = False
'The DGV source.
Private dtResults As New DataTable
'The start directory.
Private startDir As String
'The search keyword.
Private searchWord As String
'Whether to search the sub directories, from a check box for example.
Private includeSubDirectories As Boolean = True
'Whether to search the files, from another check box.
Private includeFiles As Boolean = True
The Constructor
Prepare your DGV and whatever else you need here.
Sub New()
dtResults.Columns.Add(New DataColumn("Type", Type.GetType("System.String")))
dtResults.Columns.Add(New DataColumn("Search Result", Type.GetType("System.String")))
DataGridView1.DataSource = dtResults
DataGridView1.Columns(1).AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
'Make sure you add the image column after binding the data source.
Dim imgCol As New DataGridViewImageColumn(False)
With imgCol
.Image = Nothing
.Name = "imgCol"
.HeaderText = ""
.Width = 50
.DefaultCellStyle.NullValue = Nothing
End With
DataGridView1.Columns.Insert(0, imgCol)
End Sub
Iterator
Now, let's write the search routine. I'd do that through an Iterator function:
Private Iterator Function IterateFolders(startDir As String, includeFiles As Boolean, includeSubDir As Boolean) As IEnumerable(Of String)
For Each dirName In IO.Directory.EnumerateDirectories(startDir)
Yield dirName
If includeFiles Then
For Each fileName In IO.Directory.EnumerateFiles(startDir)
Yield fileName
Next
End If
If includeSubDir Then
For Each subDir In IterateFolders(dirName, includeFiles, includeSubDir)
Yield subDir
Next
End If
Next
End Function
The Main Thread Updater
A routine called by the worker's thread to update the DataTable and any control that belongs to the main thread:
Private Sub AddSearchResult(path As String)
If InvokeRequired Then
Invoke(Sub() AddSearchResult(path))
Else
dtResults.Rows.Add(If(IO.File.Exists(path), "File", "Folder"), path)
sbStatusBar.Text = $"Searching {path}"
End If
End Sub
Start
In the click event of the start button, do the necessary validations, assign the values to their variables, and start the back ground worker:
If String.IsNullOrEmpty(txtSearchKeyword.Text) Then Return
If String.IsNullOrEmpty(txtSearchLocation.Text) Then Return
bgwCancel = False
dtResults.Rows.Clear()
startDir = txtSearchLocation.Text
searchWord = txtSearchKeyword.Text.ToLower
includeSubDirectories = chkIncludeSubDirs.Checked
includeFiles = chkFiles.Checked
btnSearch.Enabled = False
sbStatusBar.Text = "Searching..."
BackgroundWorker1.RunWorkerAsync()
Cancel
To cancel the search, in the click event of the cancel button I presume, True the bgwCancel variable:
bgwCancel = True
The BackgroundWorker - DoWork
Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) Handles BackgroundWorker1.DoWork
For Each item As String In IterateFolders(startDir, includeFiles, includeSubDirectories)
If bgwCancel Then
BackgroundWorker1.CancelAsync()
Return
End If
If item.ToLower.Contains(searchWord) Then
AddSearchResult(item)
End If
Threading.Thread.Sleep(100)
Next
End Sub
Note that, Its good practice to give a lengthy routine a BREATH through the Sleep(ms) method of that thread.
The BackgroundWorker - ProgressChanged
I don't think you need it here.
The BackgroundWorker - RunWorkerCompleted
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
If bgwCancel Then
sbStatusBar.Text = "Canceled!"
MessageBox.Show("Canceled by you!")
ElseIf e.Error IsNot Nothing Then
sbStatusBar.Text = "Error!"
MessageBox.Show(e.Error.Message)
Else
sbStatusBar.Text = "Complete"
'YOU DO NOT NEED TO DO THIS. Remove the following
'DataGridView1.DataSource = Nothing
'DataGridView1.DataSource = dtResults
'DataGridView1.Columns(1).AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
End If
btnSearch.Enabled = True
End Sub
The Image Column
Handle the RowsAdded event of the DGV as follow:
Private Sub DataGridView1_RowsAdded(sender As Object, e As DataGridViewRowsAddedEventArgs) Handles DataGridView1.RowsAdded
If DataGridView1.Columns.Count < 3 Then Return
'if you want to get rid of the default x image.
If e.RowIndex = 0 Then
DataGridView1.Rows(e.RowIndex).Cells("imgCol").Value = Nothing
End If
Dim path As String = DataGridView1.Rows(e.RowIndex).Cells(2).Value?.ToString
If Not String.IsNullOrEmpty(path) Then
If IO.File.Exists(path) Then
DataGridView1.Rows(e.RowIndex).Cells("imgCol").Value = Icon.ExtractAssociatedIcon(path).ToBitmap
Else
DataGridView1.Rows(e.RowIndex).Cells("imgCol").Value = My.Resources.Folder
End If
End If
End Sub
Where the My.Resources.Folder is an icon file of your choice for the folder entries.
Good luck.

vb.net - How to get webbrowser InnerHtml under multithread

I use SetApartmentState in webbrowser:
Private Sub frmMain_Shown(sender As Object, e As EventArgs) Handles Me.Shown
Dim th As System.Threading.Thread = New Threading.Thread(AddressOf Task_A)
th.SetApartmentState(ApartmentState.STA)
th.Start()
End Sub
Public Sub Task_A()
Dim frmBuild = New NewForm()
Dim WebBrowser1 = New WebBrowser()
Application.Run(frmBuild)
frmBuild.Controls.Add(WebBrowser1)
WebBrowser1.CreateControl()
End Sub
Private Delegate Function GetInnerHTMLCallBack() As String
Private Function GetInnerHTML() As String
If WebBrowser1.InvokeRequired Then
Return CStr(WebBrowser1.Invoke(New GetInnerHTMLCallBack(AddressOf GetInnerHTML)))
Else
Return WebBrowser1.Document.Body.InnerHtml
End If
End Function
Dim innerHTML As String = GetInnerHTML()
frmMain.textbox1.text=innerHTML
Dim docs As mshtml.HTMLDocument = WebBrowser1.Document.DomDocument
I get nothing, how can I get or set string in cross threading?
I am very poor in multi-threading.

Cross-thread operation not valid: VB.net I'm a noob..

I have two codes here separately it works but when i put it together i get the error "Cross-thread operation not valid". i tried to search the web how to solve this, i just don't get how to apply it in my codes.
Cross-Thread operation not valid VB.NET
http://forums.asp.net/t/1467258.aspx?Error+Cross+thread+operation+not+valid+Control+Listbox1+accessed+from+a+thread+other+than+the+thread+it+was+created+on+
CODE 1 use to Screen Shot my PANEL control.
Private Sub CaptureSHOT(ctrl As Control, fileName As String)
Dim bounds As Rectangle = ctrl.Bounds
Dim pt As Point = ctrl.PointToScreen(bounds.Location)
Dim bitmap As New Bitmap(bounds.Width, bounds.Height)
Using g As Graphics = Graphics.FromImage(bitmap)
g.CopyFromScreen(New Point(pt.X - ctrl.Location.X, pt.Y - ctrl.Location.Y), Point.Empty, bounds.Size)
End Using
bitmap.Save(fileName, ImageFormat.Png)
End Sub
CODE 2 use to call my "CaptureShot" Function via timer when the FORM loads.
Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
MyBase.OnLoad(e)
Dim tmr As New System.Timers.Timer()
tmr.Interval = 2000
tmr.Enabled = True
tmr.Start()
AddHandler tmr.Elapsed, AddressOf OnTimedEvent
End Sub
Private Delegate Sub CloseFormCallback()
Private Sub CloseForm()
If InvokeRequired Then
Dim d As New CloseFormCallback(AddressOf CloseForm)
Invoke(d, Nothing)
Else
Close()
End If
End Sub
Private Sub OnTimedEvent(ByVal sender As Object, ByVal e As ElapsedEventArgs)
CaptureSHOT(Panel1, "D:\SC\" & erk & ".png")
CloseForm()
End Sub
The Error i'm getting
Geez you're all hostile today haha kidding aside thanks to Enigmativity and varocarbas strict lecture i notice that i already have a "Private Delegate Sub" for my CloseFormCallback() and i keep getting an error because i was adding another "Private Delegate Sub" for my "CaptureShot" Function. now i just add the "CaptureSHOT(Panel1, "D:\SC\" & erk & ".png")" to my Private Delegate Sub CloseFormCallback(). and it works!
Private Delegate Sub CloseFormCallback()
Private Sub CloseForm()
If InvokeRequired Then
Dim d As New CloseFormCallback(AddressOf CloseForm)
Invoke(d, Nothing)
Else
CaptureSHOT(Panel1, "D:\SC\" & erk & ".png")
Close()
End If
End Sub

VB Thread Timer Update UI

I made a small program that allows for individual timer countdowns for each button clicked. (e.g. clicking on button 1 will start a countdown for button 1 whilst updating the text on the button itself to reflect the time remaining.)
My worry now is that I'm not sure how well my program would work in the long run. Here's a snippet of the code.
Private Sub depBtn_Clicked(sender As Button, e As EventArgs)
If sender.BackColor = Color.Green Then
Dim depRow() As Data.DataRow
Dim id As String = sender.Name
depRow = DepartmentDataSet.Departments.Select("ID Like '" & id & "'")
sender.BackColor = Color.Red
Dim timerBtn As New DepartmentTimer(sender, depRow(0)("Duration"), depRow(0)("ID"))
Dim TimerDelegate As New System.Threading.TimerCallback(AddressOf TimerTask)
Dim TimerItem As New System.Threading.Timer(TimerDelegate, timerBtn, 0, 1000)
timerBtn.timerRef = TimerItem
End If
End Sub
Private Delegate Sub TimerTaskDelegate(ByVal obj As Object)
Private Sub TimerTask(ByVal obj As Object)
If Me.InvokeRequired() Then
Me.Invoke(New TimerTaskDelegate(AddressOf TimerTask), obj)
Else
Dim depTimer As DepartmentTimer = DirectCast(obj, DepartmentTimer)
depTimer.countDown()
If depTimer.duration = -1 Then
depTimer.finish()
depTimer.timerRef.Dispose()
End If
End If
End Sub
I have read and also experienced that if I were to update on the UI thread directly from the timer callback the whole program would crash. So I ended up using a delegate in accordance to here http://tech.xster.net/tips/invoke-ui-changes-across-threads-on-vb-net/.
Is this a proper way of doing it or am I doing anything redundant/inefficient?
Also when I dispose of the Timer object. How would I go about cleaning up the DepartmentTimer class instance (timerBtn)? The button can be activated again once the timer runs out so I'm afraid that the instances would build up if I don't take care of them properly.
Thanks in advance for any help.
Since you're not actually doing anything with the Timer except immediately Invoking back to the main UI thread, you might as well just use one System.Windows.Forms.Timer and update them all in the same handler.
Something like:
Public Class Form1
Private timers As New List(Of DepartmentTimer)
Private WithEvents Tmr As New System.Windows.Forms.Timer
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Tmr.Interval = 1000
Tmr.Start()
End Sub
Private Sub depBtn_Clicked(sender As Button, e As EventArgs)
If sender.BackColor = Color.Green Then
Dim depRow() As Data.DataRow
Dim id As String = sender.Name
depRow = DepartmentDataSet.Departments.Select("ID Like '" & id & "'")
sender.BackColor = Color.Red
timers.Add(New DepartmentTimer(sender, depRow(0)("Duration"), depRow(0)("ID")))
End If
End Sub
Private Sub Tmr_Tick(sender As Object, e As EventArgs) Handles Tmr.Tick
For i As Integer = timers.Count - 1 To 0 Step -1
Dim depTimer As DepartmentTimer = timers(i)
depTimer.countDown()
If depTimer.duration = -1 Then
depTimer.finish()
timers.RemoveAt(i)
End If
Next
End Sub
End Class