|
|
Title | Draw a continuous graph in a separate thread in VB .NET |
Description | This example shows how to draw a continuous graph in a separate thread in VB .NET. |
Keywords | VB .NET, graph, graphing, thread, threading |
Categories | VB.NET, Graphics, Software Engineering |
|
|
When the program starts, it draws grid lines for the graph. When the user clicks the Graph button, the program checks the m_GraphThread object to see if the graphing thread is running. If m_GraphThread is Nothing, then the program creates the new thread, making its startup routine the DrawGraph subroutine. It sets the thread's priority to BelowNormal so it won't preempt the main program and starts the thread.
If the m_GraphThread object is not Nothing, then the program stops the thread.
|
|
Private m_GraphThread As Thread
' Start drawing the graph.
Private Sub btnGraph_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnGraph.Click
If m_GraphThread Is Nothing Then
' The thread isn't running. Start it.
AddStatus("Starting thread")
m_GraphThread = New Thread(AddressOf DrawGraph)
m_GraphThread.Priority = ThreadPriority.BelowNormal
m_GraphThread.IsBackground = True
m_GraphThread.Start()
AddStatus("Thread started")
btnGraph.Text = "Stop"
Else
' The thread is running. Stop it.
AddStatus("Stopping thread")
m_GraphThread.Abort()
' m_GraphThread.Join()
m_GraphThread = Nothing
AddStatus("Thread stopped")
btnGraph.Text = "Start"
End If
End Sub
|
|
When the new thread starts, it runs subroutine DrawGraph. This routine repeatedly calls subroutine NewValue to generate a value and PlotValue to plot the value. When the user clicks the Start button, the program aborts the thread and the thread's DrawGraph code catches an exception.
|
|
' Draw a graph until stopped.
Private Sub DrawGraph()
Try
' Generate pseudo-random values.
Dim y As Integer = m_Y
Do
' Generate the next value.
NewValue()
' Plot the new value.
PlotValue(y, m_Y)
y = m_Y
Loop
Catch ex As Exception
AddStatus("[Thread] " & ex.Message)
End Try
End Sub
|
|
Subroutine NewValue generates a random new value in a straightforward manner.
|
|
' Generate the next value.
Private Sub NewValue()
' Delay a bit before calculating the value.
Dim stop_time As Date = Now.AddMilliseconds(20)
Do While Now < stop_time
Loop
' Calculate the next value.
Static rnd As New Random
m_Y += rnd.Next(-4, 5)
If m_Y < 0 Then m_Y = 0
If m_Y >= picGraph.ClientSize.Height - 1 Then m_Y = _
picGraph.ClientSize.Height - 1
End Sub
|
|
Subroutine PlotValue is a bit odd. It cannot directly access the program's main form because controls (including the form) can only be modified by the thread that created them.
PlotValue starts by checking the InvokeRequired property. If InvokeRequired is True, then the code is running on the thread so it must invoke the main UI thread. In that case, the code makes an array of parameters to pass to the main UI thread and invokes the main thread's PlotValue method.
At this point, the main UI thread executes PlotValue. This time InvokeRequired returns False because we're running on the main UI thread. The program uses the Graphics object's DrawImage method to move the existing graph one pixel to the left and then draws the new value.
|
|
' Plot a new value.
Private Delegate Sub PlotValueDelegate(ByVal old_y As _
Integer, ByVal new_y As Integer)
Private Sub PlotValue(ByVal old_y As Integer, ByVal new_y _
As Integer)
' See if we're on the worker thread and thus
' need to invoke the main UI thread.
If Me.InvokeRequired Then
' Make arguments for the delegate.
Dim args As Object() = {old_y, new_y}
' Make the delegate.
Dim plot_value_delegate As PlotValueDelegate
plot_value_delegate = AddressOf PlotValue
' Invoke the delegate on the main UI thread.
Me.Invoke(plot_value_delegate, args)
' We're done.
Exit Sub
End If
' Make the Bitmap and Graphics objects.
Dim wid As Integer = picGraph.ClientSize.Width
Dim hgt As Integer = picGraph.ClientSize.Height
Dim bm As New Bitmap(wid, hgt)
Dim gr As Graphics = Graphics.FromImage(bm)
' Move the old data one pixel to the left.
gr.DrawImage(picGraph.Image, -1, 0)
' Erase the right edge and draw guide lines.
gr.DrawLine(Pens.Blue, wid - 1, 0, wid - 1, hgt - 1)
For i As Integer = m_Ymid To picGraph.ClientSize.Height _
Step GRID_STEP
gr.DrawLine(Pens.LightBlue, wid - 2, i, wid - 1, i)
Next i
For i As Integer = m_Ymid To 0 Step -GRID_STEP
gr.DrawLine(Pens.LightBlue, wid - 2, i, wid - 1, i)
Next i
' Plot a new pixel.
gr.DrawLine(Pens.White, wid - 2, old_y, wid - 1, new_y)
' Display the result.
picGraph.Image = bm
picGraph.Refresh()
gr.Dispose()
End Sub
|
|
This program's AddStatus subroutine adds a status message to the txtStatus TextBox. Because the TextBox was created by the main UI thread, the program must check InvokeRequired as before.
|
|
' Add a status string to txtStatus.
Private Delegate Sub AddStatusDelegate(ByVal txt As String)
Private Sub AddStatus(ByVal txt As String)
' See if we're on the worker thread and thus
' need to invoke the main UI thread.
If Me.InvokeRequired Then
' Make arguments for the delegate.
Dim args As Object() = {txt}
' Make the delegate.
Dim add_status_delegate As AddStatusDelegate
add_status_delegate = AddressOf AddStatus
' Invoke the delegate on the main UI thread.
Me.Invoke(add_status_delegate, args)
' We're done.
Exit Sub
End If
txtStatus.Text &= vbCrLf & txt
txtStatus.Select(txtStatus.Text.Length, 0)
txtStatus.ScrollToCaret()
End Sub
|
|
Notes: This example uses a Timer to update a label holding the current time. Because the graphing is performed on a separate thread, the Timer continues to operate while graphing is occurring.
Compare this example to Draw a continuous graph that starts and stops in VB .NET. That example performs roughly the same operations as this one without threading and that makes the Timer suspend operation while graphing is in progress.
Note that you can also avoid threading by calling Application.DoEvents inside the graphing loop.
|
|
|
|
|
|