I have this for/next loop where I download a file and then process and load its contnets into a data base:
For Each f As WinSCP.RemoteFileInfo In remotefilesinf
If DownloadFile(FTPSession, CacheDirPath, "/mnt/usb", f) Then
LoadDB(System.IO.Path.Combine(CacheDirPath, f.Name))
Else
MsgBox("Download failed.")
End If
Next
In order to speed things up, how can I do the DB loading while the next file is downloading? I cannot do the DBLoad until each file download is complete and I can only do one DBLoad task at a time due to locking of the database.
I tried using a background worker for the LoadDB task but the RunWorkerCompleted event will not fire while the UI thread is busy with the download so i do not know when I can do the next DBload (DB not locked).
Any advice appreciated.
Here is another try since the requirement for the question have changed:
Public Class Form1
Shared rnd As New Random
Private download_que As New Queue(Of String)
Private process_que As New Queue(Of String)
Private download_thread As Thread
Private process_thread As Thread
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
download_que.Enqueue("File 1.db")
download_que.Enqueue("File 2.db")
download_que.Enqueue("File 3.db")
download_que.Enqueue("File 4.db")
download_que.Enqueue("File 5.db")
download_que.Enqueue("File 6.db")
If download_thread Is Nothing then
download_thread = New Thread(AddressOf DownloadFiles)
download_thread.Start()
End If
End Sub
Private AppendTextCaller As New Action(Of TextBox, String)(AddressOf AppendText)
Public Sub AppendText(ByVal control As TextBox, ByVal text As String)
control.AppendText(text)
End Sub
Public Sub DownloadFiles()
Dim file As String
While download_que.Count > 0
SyncLock download_que
file = download_que.Dequeue()
End SyncLock
Dim path As String = Download(file)
SyncLock process_que
process_que.Enqueue(path)
End SyncLock
If process_thread Is Nothing Then
process_thread = New Thread(AddressOf ProcessFiles)
process_thread.Start()
End If
End While
download_thread = Nothing
End Sub
Public Sub ProcessFiles()
Dim path As String, ok As Boolean
ok = True
While process_que.Count > 0 And ok
SyncLock process_que
path = process_que.Dequeue()
End SyncLock
ok = LoadDB(path)
End While
process_thread = Nothing
End Sub
Public Function Download(ByVal filename As String) As String
Dim sw = Stopwatch.StartNew()
Me.Invoke(AppendTextCaller, TextBox1, filename)
Thread.Sleep(1500 + 500*rnd.Next(15))
Dim message As String = String.Format(" ({0:F1} sec)", sw.ElapsedMilliseconds / 1000)
Me.Invoke(AppendTextCaller, TextBox1, message)
Me.Invoke(AppendTextCaller, TextBox1, Environment.NewLine)
Return IO.Path.Combine(IO.Path.GetTempPath(), filename)
End Function
Public Function LoadDB(ByVal path As String) As Boolean
Dim sw = Stopwatch.StartNew()
Dim filename = IO.Path.GetFileName(path)
Me.Invoke(AppendTextCaller, TextBox2, filename)
Thread.Sleep(800 + 500*rnd.Next(6))
Dim message As String = String.Format(" ({0:F1} sec)", sw.ElapsedMilliseconds / 1000)
Me.Invoke(AppendTextCaller, TextBox2, message)
Me.Invoke(AppendTextCaller, TextBox2, Environment.NewLine)
Return True
End Function
End Class
What about using two backgroundworkers? Use one to download files, the other one to stuff them into the db.
If a download completes, append the file to a list and each time a db update finishes, look at that list from bgw2...
You can run the DBLoad on a thread and set a ManualResetEvent to stop the execution before lauching the new DBLoad thread until the other ona finished.
Dim locker as New ManualResetEvent(True)
The locker acts like a traffic light, stoping the execution and waiting when is marked and going throught when otherwise.
Block the locker anywhere with:
locker.Reset()
Unblock the locker anywhere with:
locker.Set()
Set a stoping spot:
locker.WaitOne()
To see full capabilities see MSDN.
I think this is what you want:
Public Function DownLoadFile(ByVal f As String) As String
Trace.WriteLine("Start Downloading " & f)
Dim x As Integer = ProgressBar1.Value
Threading.Thread.Sleep(2000)
Me.Invoke(SetProgressCaller, x + 25)
Trace.WriteLine("Done Downloading " & f)
Return IO.Path.Combine(IO.Path.GetTempPath(), f)
End Function
Public Sub LoadDB(ByVal f As String)
Trace.WriteLine("Start Loading " & f)
Dim x As Integer = ProgressBar1.Value
Threading.Thread.Sleep(1000)
Me.Invoke(SetProgressCaller, x + 25)
Trace.WriteLine("Done Loading " & f)
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
ProgressBar1.Value = 0
Dim f_path_1 = DownLoadFile("File 1")
Dim t1 As New Threading.Thread(AddressOf LoadDB)
t1.Start(f_path_1)
ProgressBar1.Value = 50
Dim f_path_2 = DownLoadFile("File 2")
Dim t2 As New Threading.Thread(AddressOf LoadDB)
t2.Start(f_path_2)
End Sub
' Can be called using Form.Invoke() from any thread
Private SetProgressCaller As New Action(Of Integer)(AddressOf SetProgress)
' Set progress bar in main thread
Public Sub SetProgress(ByVal pct As Integer)
ProgressBar1.Value = pct
End Sub
with the results:
Start Downloading File 1
Done Downloading File 1
Start Downloading File 2
Start Loading C:\Users\#####\AppData\Local\Temp\File 1
Done Loading C:\Users\#####\AppData\Local\Temp\File 1
Done Downloading File 2
Start Loading C:\Users\#####\AppData\Local\Temp\File 2
Done Loading C:\Users\#####\AppData\Local\Temp\File 2
which translates to
Downloading of file 1 (takes 2 sec)
Loading file 1 into DB (takes 1 sec) AND
Downloading of file 2 (takes 2 sec)
Loading of file 2 into DB (takes 1 sec)
Related
Missing last few bytes and file gets corrupted - bounty
I now added a bounty to solve this problem. I changed the integer types to int64 which seem to have solved part of the problem, but now when ever it finishes the download it sometimes misses the last 1-5 bytes, which in return corrupts the file, so it can't be unzipped. Is there another way of closing the stream so it ensures the files are fully downloaded, and avoid getting corrupted? I've since tried this simple code, but same problem happens.
Imports System.ComponentModel
Imports System.Net
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Control.CheckForIllegalCrossThreadCalls = False
End Sub
Dim WithEvents WC As New WebClient
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
WC.DownloadFileAsync(New Uri("ftp://dmr-ftp-user:dmrpassword#5.44.137.84/ESStatistikListeModtag/ESStatistikListeModtag-20160327-094743.zip"), "C:\XML\ESStatistikListeModtag-20160327-094743.zip.zip")
End Sub
Private Sub WC_DownloadProgressChanged(ByVal sender As Object, ByVal e As DownloadProgressChangedEventArgs) Handles WC.DownloadProgressChanged
ProgressBar1.Value = e.ProgressPercentage
If e.ProgressPercentage = 100 Then
MsgBox("File download - 100%") 'This message box does trigger once the download is complete, but file is still corrupted.
End If
End Sub
Private Sub WC_DownloadFileCompleted(sender As Object, e As AsyncCompletedEventArgs) Handles WC.DownloadFileCompleted
MsgBox("Complete") ' This message box doesn't trigger!
End Sub
End Class
OLD QUESTION:
I'm trying to download a zip file from an FTP server with my vb.net application. My current source code is posted below. This works fine for smaller files, but when I exceed a limit of 2GB I get the following exception:
"Arithmetic operation resulted in an overflow"
It's a file with the size of about 2.5 GB and increasing slightly each weak (about 20 MB), so I need a solution which can handle large files, hopefully with no limit. Eventually I would like to unzip the file with the program too, so if you have any ideas for doing this, you can post this as well. Thanks!
Private Sub Download(ByVal filePath As String, ByVal fileName As String)
FTPSettings.IP = "0.0.0.0"
FTPSettings.UserID = "ftp-user"
FTPSettings.Password = "ftp-pass"
Dim reqFTP As FtpWebRequest = Nothing
Dim ftpStream As Stream = Nothing
Try
Dim outputStream As New FileStream(filePath + "\" + fileName, FileMode.Create)
reqFTP = DirectCast(FtpWebRequest.Create(New Uri("ftp://" + FTPSettings.IP + "/" + fileName)), FtpWebRequest)
reqFTP.Method = WebRequestMethods.Ftp.DownloadFile
reqFTP.UseBinary = True
reqFTP.Credentials = New NetworkCredential(FTPSettings.UserID, FTPSettings.Password)
Dim response As FtpWebResponse = DirectCast(reqFTP.GetResponse(), FtpWebResponse)
ftpStream = response.GetResponseStream()
Dim cl As Long = response.ContentLength
Dim bufferSize As Integer = 2048
Dim readCount As Int64
Dim buffer As Byte() = New Byte(bufferSize - 1) {}
Dim size As Int64
readCount = ftpStream.Read(buffer, 0, bufferSize)
While readCount > 0
outputStream.Write(buffer, 0, readCount)
readCount = ftpStream.Read(buffer, 0, bufferSize)
If readCount = bufferSize Then
size += readCount
Label1.Text = size
Label1.Refresh()
End If
End While
ftpStream.Close()
outputStream.Close()
response.Close()
Catch ex As Exception
MsgBox(ex.Message)
If ftpStream IsNot Nothing Then
ftpStream.Close()
ftpStream.Dispose()
End If
Throw New Exception(ex.Message.ToString())
End Try
End Sub
Public NotInheritable Class FTPSettings
Private Sub New()
End Sub
Public Shared Property IP() As String
Get
Return m_IP
End Get
Set(ByVal value As String)
m_IP = value
End Set
End Property
Private Shared m_IP As String
Public Shared Property UserID() As String
Get
Return m_UserID
End Get
Set(ByVal value As String)
m_UserID = value
End Set
End Property
Private Shared m_UserID As String
Public Shared Property Password() As String
Get
Return m_Password
End Get
Set(ByVal value As String)
m_Password = value
End Set
End Property
Private Shared m_Password As String
End Class
End Class
I've had similar problems with WebClient before, specially if using it with the WithEvents statement.
See if re-writing your code like this solves the problem:
Imports System.ComponentModel
Imports System.Net
Public Class Form1
Private wc As New WebClient()
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
wc = New WebClient()
AddHandler wc.DownloadProgressChanged, Sub(s As Object, ByVal e As DownloadProgressChangedEventArgs)
Me.Invoke(New MethodInvoker(Sub() ProgressBar1.Value = e.ProgressPercentage))
End Sub
AddHandler wc.DownloadFileCompleted, Sub(s As Object, e As ComponentModel.AsyncCompletedEventArgs)
MsgBox("Complete")
End Sub
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
wc.DownloadFileAsync(New Uri("ftp://dmr-ftp-user:dmrpassword#5.44.137.84/ESStatistikListeModtag/ESStatistikListeModtag-20160327-094743.zip"), "C:\XML\ESStatistikListeModtag-20160327-094743.zip.zip")
End Sub
End Class
I have a simple form with 2 RichTextBoxes and 1 button, the code grabs the url address from RichTextBox1 and phrases the page for the title field using regex and appends it to RichTextBox2. I want to multithread everything in such way that none of the url's are skipped and the thread numbers can be set ( according to the system free resources ) For example, let's say 10 threads to run in parallel. I searched everything and the best that I managed to do is run everything in a background worker and keep the GUI from freezing while working. A short code sample will be of much help, I am a beginner in VB.net.
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
For i = 0 To RichTextBox1.Lines.Length - 1
If RichTextBox1.Lines(i).Contains("http://") Then
Dim html As String = New System.Net.WebClient() _
.DownloadString(RichTextBox1.Lines(i))
Dim pattern As String = "(?<=\<title\>)([^<]+?)(?=\</title\>)"
Dim match As System.Text.RegularExpressions.Match = _
System.Text.RegularExpressions.Regex.Match(html, pattern)
Dim title As String = match.Value
RichTextBox2.AppendText(title & vbCrLf)
End If
Next
End Sub
End Class
Updated code ( throwing "Index was outside the bounds of the array." errors. )
Imports System
Imports System.Threading
Public Class Form1
Public Sub test(ByVal val1 As String, ByVal val2 As String)
Dim zrow As String
zrow = RichTextBox1.Lines(val1)
If zrow.Contains("http://") Then
Dim html As String = New System.Net.WebClient().DownloadString(zrow)
Dim pattern As String = "(?<=\<title\>)([^<]+?)(?=\</title\>)"
Dim match As System.Text.RegularExpressions.Match = System.Text.RegularExpressions.Regex.Match(html, pattern)
Dim title As String = match.Value
RichTextBox2.AppendText(val2 & title & vbCrLf)
End If
End Sub
Public Sub lastfor(ByVal number)
Dim start As Integer = number - 100
For x = start To number - 1
Try
test(x, x)
RichTextBox2.AppendText(x & RichTextBox1.Lines(x).Trim & vbCrLf)
Catch ex As Exception
'MsgBox(ex.Message)
RichTextBox3.AppendText(ex.Message & vbCrLf & vbCrLf)
End Try
Next
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Control.CheckForIllegalCrossThreadCalls = False
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim TotalLines As String = RichTextBox1.Lines.Length - 1
Dim TotalThreads As Integer = 10
Dim LinesPerThread As Integer = TotalLines / TotalThreads
Dim increment As String = LinesPerThread
Dim zdata(TotalThreads) As String
For i = 0 To TotalThreads - 1
zdata(i) = increment
increment = increment + LinesPerThread
Next
Dim lst As New List(Of Threading.Thread)
For Each bump As String In zdata
Dim t As New Threading.Thread(Function(l As String)
'Do something with l
'Update GUI like this:
If bump = String.Empty Or bump Is Nothing Then
Else
lastfor(l)
'MsgBox(l)
End If
End Function)
lst.Add(t)
t.Start(bump)
Next
'test(1)
End Sub
End Class
There are two ways two achieve this:
First, if you are using .NET 4.0, you could use a Parallel.ForEach loop:
Parallel.ForEach(RichTextBox1.Lines, Function(line As String)
' Do something here
' To update the GUI use:
Me.Invoke(Sub()
' Update GUI like this...
End Sub)
Return Nothing
End Function)
The other way is to do this manually (and you will have slightly more control):
Dim lst As New List(Of Threading.Thread)
For Each line In RichTextBox1.Lines
Dim t As New Threading.Thread(Function(l As String)
'Do something with l
'Update GUI like this:
Me.Invoke(Sub()
'Update Gui...
End Sub)
End Function)
lst.Add(t)
t.Start(line)
Next
Both of these are very crude, but will get the job done.
EDIT:
Here is a sample code that will control the number of threads:
Dim lst As New List(Of Threading.Thread)
Dim n As Integer = 1 ' Number of threads.
Dim npl As Integer = RichTextBox1.Lines / n
Dim seg As New List(Of String)
For Each line In RichTextBox1.Lines
For i = npl - n To npl
seg.Add(RichTextBox1.Lines.Item(i))
Next
Dim t As New Threading.Thread(Function(l As String())
For Each lin In l
' TO-DO...
Next
'Do something with l
'Update GUI like this:
Me.Invoke(Sub()
'Update Gui...
End Sub)
End Function)
lst.Add(t)
t.Start(seg.ToArray())
Next
*The above code might have bugs.
What I want is for the app to read the first line from a text file and output it to the textReply textbox, then wait for X time before reading and outputting the next line.
Dim fileIn As New System.IO.StreamReader("C:\test.txt")
Dim strData As String = ""
While (Not (fileIn.EndOfStream))
strData = fileIn.ReadLine()
textReply.Text = textReply.Text & strData & vbCr
System.Threading.Thread.Sleep(1000)
End While
As you can see I have tried sleeping the thread (not ideal as I want the app to stay responsive but thats another matter!) but every time it reads the whole file and dumps the lot into the textbox.
The reason is eventually it will be used for a serial connection to a device which needs time for the data transfer to the device and for the device to respond to each line sent.
You can create a Queue of string add each line to the list as you are reading the file, then you can poll it with a timer and add them to your textbox one at a time.
Public Class Form1
Dim myQueue As Queue(Of String) = New Queue(Of String)
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Dim fileIn As New System.IO.StreamReader("C:\test.txt")
Timer1.Start()
Dim strData As String = ""
While (Not (fileIn.EndOfStream))
strData = fileIn.ReadLine()
myQueue.Enqueue(strData)
End While
End Sub
Private Sub Timer1_Tick(sender As System.Object, e As System.EventArgs) Handles Timer1.Tick
If myQueue.Count > 0 Then
textReply.Text += myQueue.Dequeue & vbCrLf
End If
End Sub
End Class
I have a sub-procedure which I want to run a different process, depending on what is currently running. I thought the easiest way to do this was by using an ArrayList of each of the campaign details & adding an 'Inuse' field to check to see if the Inuse field is set to 0 or 1. The problem that I have is that when running the process it is all happening at once & the integer hasn't been changed before the next thread kicks in so my threads are running the same campaigns.
I tried to avoid the problem by adding a Thread.Sleep(100) delay inbetween starting threads but this led to exactly the same problem.
Here's an example of what I am trying to do:
Imports System.Threading
Public Class Form1
Private Campaigns As New ArrayList
Private ProcessRunning As Boolean = False
Friend StopProcess As Boolean = False
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
For i = 0 To Campaigns.Count - 1
Dim objNewThread As New Thread(AddressOf RunProcess)
objNewThread.IsBackground = True
objNewThread.Start()
Next
End Sub
Private Sub UpdateCells(ByVal CampID As Integer, ByVal Column As String, ByVal newText As String)
Dim CellItemNum As Integer
If Column = "Status" Then CellItemNum = 4
DataGridView2.Rows(CampID).Cells.Item(CellItemNum).Value = newText
End Sub
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
For i As Integer = 0 To 10
Campaigns.Add({Campaigns.Count(), "Campaign " & i, "Keywords " & i, "Link " & i, 5, True, 0, 0})
Next
DataGridView2.Rows.Clear()
For Each Campaign In Campaigns
DataGridView2.Rows.Add(New String() {Campaign(1), Campaign(2), Campaign(3), Campaign(6), ""})
Next
End Sub
Private Sub RunProcess()
' Set Variables
Dim CampID As Integer
Dim CampName As String
Dim Keywords As String
Dim Link As String
Dim CheckEvery As Integer
Dim OkToUse As Boolean
Dim Sent As Integer
' Find A Free Campaign
For i As Integer = 0 To Campaigns.Count - 1
' Check If Inuse
If Campaigns(i)(7) = 1 Then Continue For Else Campaigns(i)(7) = 1 ' This Line Sets Campaign To Inuse
' Most of the time only campaign(0) and campaign(1) are selected & multiple threads are running them instead of choosing unique campaigns
' Set Campaign Details
CampID = Campaigns(i)(0)
CampName = Campaigns(i)(1)
Keywords = Campaigns(i)(2)
Link = Campaigns(i)(3)
CheckEvery = Campaigns(i)(4)
OkToUse = Campaigns(i)(5)
Sent = Campaigns(i)(6)
' Start Process
UpdateCells(CampID, "Status", "Looking Up New Links (" & CampID & ")")
Exit For
Next
While StopProcess = False
Thread.Sleep(1000)
UpdateCells(CampID, "Status", "Running Process (" & CampID & ")")
Thread.Sleep(1000)
For i = 0 To CheckEvery
UpdateCells(CampID, "Status", "Re-Checking In " & (CheckEvery - i) & " Seconds")
Thread.Sleep(1000)
Next
End While
' Closing Processes
Campaigns(CampID)(7) = 0
End Sub
End Class
You can use SyncLock to force your threads to wait.
class level so all threads access same lock
private myLock as new Object
Then use syncLock when you start your process and end it when you are done.
SyncLock myLock
'process code here
End SyncLock
More MSDN info on the subject
Try looking into QueueUserWorkItem():
Private Sub Button1_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles Button1.Click
For i = 0 To Campaigns.Count - 1
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf RunProcess), Campaigns[i])
Next
End Sub
Then change the RunProcess() method to include the Campaign object sent in by the work item:
Private Sub RunProcess(ByVal o As System.Object)
' Process the Campaign
Dim campaign As Campaign = Ctype(o, Campaign)
End Sub
There will be no need for inuse, plus threads will be managed by the managed ThreadPool!
Ok, so I'm making a program that will read from a text file, convert each line into an array, the. Send each of those lines 1 per tick. This is in 2010 Visual Basic.
Closest I've gotten is sending all at once, I worked on it over night and am slowly destroying it.
Ideally, I want Button 1 click to populate the array from the file at LocationTB then start the timer. The timer should send a line at a time on the GapTB interval.
Public Class Form1
Public TextLine As String
Public MyFileName As String
Public MyNewLine(1000) As String
Private Property z As Integer
Private Property objReader As Object
Public Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
If RadioButton2.Checked = True Then
Dim Textline As String = ""
Dim FILE_NAME As String = LocationTB.Text
Dim objReader As New System.IO.StreamReader(FILE_NAME)
MyFileName = LocationTB.Text
FileOpen(1, MyFileName, OpenMode.Input, OpenAccess.Read, OpenShare.Shared)
z = 0
Do Until EOF(1)
MyNewLine(z) = LineInput(1)
z = z + 1
Loop
FileClose(1)
End If
Timer1.Interval = GapTB.Text
Timer1.Enabled = True
End Sub
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
If RadioButton1.Checked = True Then
AppActivate(Hook.Text)
SendKeys.Send(SimpleTB.Text)
SendKeys.Send((Chr(13)))
ElseIf RadioButton2.Checked = True Then
For Each
TextLine = TextLine & objReader.ReadLine
AppActivate(Hook.Text)
SendKeys.Send(TextLine)
SendKeys.Send((Chr(13)))
Next
Else
MsgBox("File Does Not Exist")
End If
End Sub
Is this the kind of thing you're looking for?
It'll write the contents of a file (in this instance "C:\mark.txt") to the output window in Visual Studio.
Public Class Form1
Private myTimer As Timer
Private lines As String()
Private currentLine As Integer
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
lines = IO.File.ReadAllLines("C:\mark.txt")
currentLine = 0
myTimer = New Timer()
AddHandler myTimer.Tick, AddressOf myTimer_Tick
myTimer.Interval = 1000
myTimer.Enabled = True
End Sub
Private Sub myTimer_Tick(sender As Object, e As EventArgs)
If currentLine < lines.Count Then
Dim lineToSend As String = lines(currentLine)
Debug.Print(lineToSend)
currentLine += 1
Else
myTimer.Enabled = False
End If
End Sub
End Class
Caveat
The above code isn't scalable. If you're SURE the file will always be small then it will do (that said, they're never always small).
To make this scalable you'd need to hold the file open, and read each line as you need it, not load the entire file contents at once.
I know how to do it to a application I started myself: Here's the code to make Calculator work:
Dim ProcID As Integer
' Start the Calculator application, and store the process id.
ProcID = Shell("CALC.EXE", AppWinStyle.NormalFocus)
' Activate the Calculator application.
AppActivate(ProcID)
' Send the keystrokes to the Calculator application.
My.Computer.Keyboard.SendKeys("22", True)
My.Computer.Keyboard.SendKeys("*", True)
My.Computer.Keyboard.SendKeys("44", True)
My.Computer.Keyboard.SendKeys("=", True)
' The result is 22 * 44 = 968.
AppActivate should know the exact title of the form to activate. If not known, iterate the Processes to locate yours. If the title is correctly specified it should work