I need to report progress changed. Consider the following code:
Public Class Calculator
Public Event CalculationProgress (ByVal sender As Object, ByVal e As MyCalculationProgressEventArgs)
Public Function Calculate(..)..
' Perform calculation here ...
' Reporting proggress
Dim args As New MyCalculationProgressEventArgs(myobj, myValue)
RaiseEvent CalculationProgress (Me, args)
...
End Class
*** Another class
Private WithEvents calculator As Calculator
Private Function PerformCalculation(ByVal obj As Object) As CalcParams
Dim params As CalcParams = CType(obj, CalcParams)
calculator = GetCalculator()
....
Return params.result = calculator.Calculate
End Function
Private Sub calculationWorker_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) _
Handles calculationWorker.DoWork
Dim calcResult As MyType = PerformCalculation(CType(e.Argument, MyType ))
e.Result = calcResult
End Sub
Private Sub calculationWorker_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs) _
Handles calculationWorker.ProgressChanged
CType(Parent.Parent, MainWindow).pbcCurrentProgress.Value = e.ProgressPercentage
End Sub
How and where should I subscribe to CalculationProgress event to call
calculationWorker.ReportProgress(MyCalculationProgressEventArgs.Percent)
?
Are you using a BackgroundWorker object here? If so what you want to do is to subscribe to the CalculationProgress event inside of the calculationWorker_DoWork event handler. You didn't post any information on MyType, so I'll assume you'll need to alter my code to get the Calculator instance.
Private Sub calculationWorker_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) _
Handles calculationWorker.DoWork
Dim calcResult As MyType = PerformCalculation(CType(e.Argument, MyType ))
Dim calc = calcResult.Calculator
AddHandler calc.CalculationProgress, AddressOf HandleCalculationProgress
...
RemoveHandler calc.CalculationProgress, AddressOf HandleCalculationProgress
e.Result = calcResult
End Sub
You would do this after your GetCalculator call, and before calling Calculate.
Related
I want to "override" an Event from a derived class - for example from a Forms-Control.
My actual state is, that the overriding (performed by the Command "Shadows") is working when I use the Handler of this Control directly.
Is the Control a member of a Collection it is only working with such Events which I have created by myself - if I try to use the overridden Event it isn't working. I suppose that the Collection uses the Event from the Base-Class.
Is that possible ?
And if "Yes" - what could I do ?
Code-Snippets from the described "Problem" :
This part collects the Event-Handler inside the Custom Control :
Private KalenderElemente As New Collection
Private Sub CreateElements()
KalenderElemente.Clear()
For i As Integer = 1 To 42
Dim myKalenderTag As New PP_Monatskalender_Tag
myKalenderTag.Name = "Tag_" + i.ToString("00")
myKalenderTag.ForeColor = my_ForeColor_Days
myKalenderTag.BackColor = my_BackColor_Days
myKalenderTag.Parent = Me
AddHandler myKalenderTag.Click, AddressOf KalenderTag_Click
AddHandler myKalenderTag.MouseMove, AddressOf KalenderTag_MouseMove
AddHandler myKalenderTag.MouseEnter, AddressOf KalenderTag_MouseEnter
AddHandler myKalenderTag.MouseLeave, AddressOf KalenderTag_MouseLeave
KalenderElemente.Add(myKalenderTag)
Next
End Sub
Private Sub Kalender_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) Handles MyBase.MouseMove
If Not KalenderElemente.Item(0).Visible Then
KalenderElemente.Item(0).DatumsTag = 0
RaiseEvent MouseMove(KalenderElemente.Item(0), e)
Else
KalenderElemente.Item(41).DatumsTag = 0
RaiseEvent MouseMove(KalenderElemente.Item(41), e)
End If
End Sub
Private Sub KalenderTag_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs)
RaiseEvent MouseMove(sender, e)
End Sub
Shadows Event MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs)
Private Sub KalenderTag_MouseEnter(ByVal sender As Object, ByVal e As EventArgs)
RaiseEvent MouseEnter(sender, e)
End Sub
Shadows Event MouseEnter(ByVal sender As Object, ByVal e As EventArgs)
Private Sub KalenderTag_MouseLeave(ByVal sender As Object, ByVal e As EventArgs)
RaiseEvent MouseLeave(sender, e)
End Sub
Shadows Event MouseLeave(ByVal sender As Object, ByVal e As EventArgs)
Now each of the internal Controls deliver it's Mouse-Event to outside.
If I put it on a Form and write a script which takes the Event I could see that all works fine (and as expected).
In the following you see the part of the collection which should manage this Control (and others) :
Public Class MessageDefinition
Public WithEvents Control As Control
Public HeaderText As String
Public MessageText As String
Public DisplayShadow As Boolean
Public ToolTipLocation As ToolTipLocationDefintion
Public Location As Point
End Class
Public Class Message_Collection
Inherits CollectionBase
Public Shadows Sub Clear()
Dim myItem As MessageDefinition
For i As Integer = 1 To List.Count
myItem = List.Item(i - 1)
RemoveHandler myItem.Control.MouseEnter, AddressOf Item_MouseEnter
RemoveHandler myItem.Control.MouseMove, AddressOf Item_MouseMove
RemoveHandler myItem.Control.MouseLeave, AddressOf Item_MouseLeave
Next
List.Clear()
End Sub
Overrides Function ToString() As String
Return "[...]"
End Function
Public Sub Dispose()
Clear()
End Sub
' ================================
Public Sub SetMessage(item As MessageDefinition)
Dim myItem As MessageDefinition
For i As Integer = 1 To List.Count
myItem = List.Item(i - 1)
If myItem.Control.GetType Is item.Control.GetType _
AndAlso myItem.Control.Name = item.Control.Name Then
'List.Item(i - 1) = item
'RaiseEvent MouseEnter(item, System.EventArgs.Empty)
Exit Sub
End If
Next
AddHandler item.Control.MouseEnter, AddressOf Item_MouseEnter
AddHandler item.Control.MouseMove, AddressOf Item_MouseMove
AddHandler item.Control.MouseLeave, AddressOf Item_MouseLeave
List.Add(item)
RaiseEvent MouseEnter(item, System.EventArgs.Empty)
End Sub
Private Sub Item_MouseEnter(sender As Object, e As System.EventArgs)
Dim myItem As MessageDefinition
Dim mySender As Control = sender
For i As Integer = 1 To List.Count
myItem = List.Item(i - 1)
If myItem.Control Is mySender Then
RaiseEvent MouseEnter(myItem, e)
Exit Sub
End If
Next
End Sub
Private Sub Item_MouseMove(sender As Object, e As System.EventArgs)
Dim myItem As MessageDefinition
Dim mySender As Control = sender
For i As Integer = 1 To List.Count
myItem = List.Item(i - 1)
If myItem.Control Is mySender Then
RaiseEvent MouseMove(myItem, e)
Exit Sub
End If
Next
End Sub
Private Sub Item_MouseLeave(sender As Object, e As System.EventArgs)
Dim myItem As MessageDefinition
Dim mySender As Control = sender
For i As Integer = 1 To List.Count
myItem = List.Item(i - 1)
If myItem.Control Is mySender Then
RaiseEvent MouseLeave(myItem, e)
Exit Sub
End If
Next
End Sub
Public Event MouseEnter(sender As Object, e As System.EventArgs)
Public Event MouseMove(sender As Object, e As System.EventArgs)
Public Event MouseLeave(sender As Object, e As System.EventArgs)
End Class
As described (on Top) the catched Events are fired with "Standard"-Controls but not with the "Customized" Control.
If I change it and build up my own Events (with different names), which are not shadowing the Events from the derived control, it is also working as expected.
You don't override events in .NET - you can only override inherited event handlers, if any.
The event pattern in .NET is to create a public event in the base class and a protected virtual (VB Protected Overridable) method that raises that event and that can be overridden by a derived class. This method should be named OnEventName.
The Windows Forms controls follow this pattern, so to e.g. override when the Click event happens, you override the OnClick method:
Public Class MyTextBox
Inherits TextBox
Protected Overrides Sub OnClick(ByVal e As System.EventArgs)
If SomeCondition() Then
MyBase.OnClick(e)
Else
Return 'Do not click
End If
End Sub
End Class
Obviously you can fiddle with the e argument as well.
I'm using a BackgroundWorker and I need to use a Delegate Function to see if a ListViewItem is checked or not but I keep recieving a cross-thread error. It must be the way I'm writing it. Any help?
Dim delListViewItemChecked As ListViewItemCheckedDelegate = AddressOf ListViewItemChecked
delListViewItemChecked.Invoke(ListViewPhotos, 0)
Private Delegate Function ListViewItemCheckedDelegate(ByVal listView As ListView, ByVal index As Integer) As Boolean
Private Function ListViewItemChecked(ByVal listView As ListView, ByVal index As Integer) As Boolean
If listView.Items(index).Checked = True Then
Return True
Else
Return False
End If
End Function
Try this:
Do not pass the listView as a parameter to ListViewItemCheckedDelegate.
Declare a new delegate instance inside the DoWork handler of your background worker.
This sample seems to work OK:
Private Delegate Function ListViewItemCheckedDelegate(ByVal index As Integer) As Boolean
Private Function ListViewItemChecked(ByVal index As Integer) As Boolean
Return ListView1.Visible
End Function
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
bkg1.RunWorkerAsync()
End Sub
Private Sub bkg1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bkg1.DoWork
Dim delListViewItemChecked As New ListViewItemCheckedDelegate(AddressOf ListViewItemChecked)
MsgBox(Me.Invoke(delListViewItemChecked, 3)) ' arbitrary 3
End Sub
I am having a little trouble understanding how I should be utilizing the Dispatcher to help me solve my problem of accessing a text box from a different thread. What I am trying to achieve is getting the thread to append to a chat box once it receives data form the server.
Public Class ChatScreen
Public client As Client
Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
client = Application.Current.Properties("Client")
Me.Title = "ChitChat - " & client.Name
txtMessage.Focus()
Dim serverHandler As New ServerHandler(client.clientSocket, client.networkStream, txtChat)
End Sub
Private Sub btnSend_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnSend.Click
client.SendMessage(txtMessage.Text)
End Sub
Private Sub Window_KeyDown(ByVal sender As System.Object, ByVal e As System.Windows.Input.KeyEventArgs) Handles MyBase.KeyDown
If e.Key = Key.Enter Then
client.SendMessage(txtMessage.Text)
End If
End Sub
Public Sub AppendToChat(ByVal message As String)
txtChat.AppendText(">> " & message)
End Sub
Public Class ServerHandler
Dim clientSocket As TcpClient
Public networkStream As NetworkStream
Dim thread As Thread
Public Sub New(ByVal clientSocket As TcpClient, ByVal networkStream As NetworkStream)
Me.clientSocket = clientSocket
Me.networkStream = networkStream
thread = New Thread(AddressOf ListenForServer)
thread.Start()
End Sub
Public Sub ListenForServer()
Dim bytesFrom(10024) As Byte
Dim message As String
While True
networkStream.Read(bytesFrom, 0, CInt(clientSocket.ReceiveBufferSize))
message = System.Text.Encoding.ASCII.GetString(bytesFrom)
message = message.Substring(0, message.IndexOf("$"))
'AppendToChat <--- This is where I would like to append the message to the text box
End While
End Sub
End Class
End Class
You can use SynchronizationContext to do this,
Store UI tread context in a variable like this
Private syncContext As SynchronizationContext
Private Sub frmClient_Shown(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Shown
syncContext = AsyncOperationManager.SynchronizationContext
End Sub
Now create a procedure to execute on main UI thread like this
Private Sub AddTextBox()
‘Do whatever you want you are in UI thread here
End Sub
From you background thread post request on UI thread like this
syncContext.Post(New SendOrPostCallback(AddressOf AddTextBox), Nothing)
you can even pass arguments also
Private Sub AddTextBox(ByVal argument As Object)
‘Do whatever you want you are in UI thread here
End Sub
.....
syncContext.Post(New SendOrPostCallback(AddressOf AddTextBox), objToPass)
A few weeks ago I wrote a wrapper for the ServiceController control to enhance and streamline the base ServiceController. One of the changes I made was to add a monitoring component using the System.Threading.Timer object. On any change of status, an event is raised to the parent class. The actual monitoring works fine, but when the event is handled in the main form, my program abruptly ends - no exceptions, no warning, it just quits. Here's a skeleton version of the control:
Public Class EnhancedServiceController
Inherits ServiceController
Public Event Stopped(ByVal sender As Object, ByVal e As System.EventArgs)
Public Event Started(ByVal sender As Object, ByVal e As System.EventArgs)
Private _LastStatus As System.ServiceProcess.ServiceControllerStatus
Private serviceCheckTimer as System.Threading.Timer
Private serviceCheckTimerDelegate as System.Threading.TimerCallback
...
Private Sub StartMonitor()
MyBase.Refresh()
_LastStatus = MyBase.Status
serviceCheckTimerDelegate = New System.Threading.TimerCallback(AddressOf CheckStatus)
serviceCheckTimer = New System.Threading.Timer(serviceCheckTimerDelegate, Nothing, 0, 60*1000)
End Sub
Private Sub CheckStatus()
MyBase.Refresh()
Dim s As Integer = MyBase.Status
Select Case s
Case ServiceControllerStatus.Stopped
If Not s = _LastStatus Then
RaiseEvent Stopped(Me, New System.EventArgs)
End If
Case ServiceControllerStatus.Running
If Not s = _LastStatus Then
RaiseEvent Started(Me, New System.EventArgs)
End If
End Select
_LastStatus = s
End Sub
End Class
And the form:
Public Class Form1
Private Sub ServiceStarted(ByVal sender As Object, ByVal e As System.EventArgs) Handles ESC.Started
Me.TextBox1.Text = "STARTED"
End Sub
Private Sub ServiceStopped(ByVal sender As Object, ByVal e As System.EventArgs) Handles ESC.Stopped
Me.TextBox1.Text = "STOPPED"
End Sub
End Class
If I had to guess, I'd say that there's some sort of thread problem, but I'm not sure how to handle that in the form. Any ideas?
IF it is a threading issue then you are probably trying to update the UI from a non-UI thread.
So something like this should solve that...
Private Delegate Sub UpdateTextBoxDelegate(byval tText as String)
Private Sub UpdateTextBox(byval tText As String)
If Me.InvokeRequired Then
Me.Invoke(New UpdateTextBoxDelegate(AddressOf UpdateTextBox), tText)
Exit Sub
End If
TextBox1.Text = tText
End Sub
Private Sub ServiceStarted(ByVal sender As Object, ByVal e As System.EventArgs) Handles ESC.Started
UpdateTextBox ("STARTED")
End Sub
Private Sub ServiceStopped(ByVal sender As Object, ByVal e As System.EventArgs) Handles ESC.Stopped
UpdateTextBox("STOPPED")
End Sub
I'm having trouble getting a progress bar to update. I'd be okay just with a moving marquee bar. Basically, I'm reading a database routine in a SqliteReader.vb class. I'm new to visual basic, and I'm sure I need to use the worker_DoWork routine, but I'm not sure how to expose my variables coming from Form1: graphData, graphComputations, m_debug to the worker_DoWork sub. How is this usually done?
Public Class SqliteReader
Public Sub ReadDataBase
End Sub
End Class
This is updating a graph (zedgraph element) on the main form, Form1.vb. I call the progressbar from the main form like this:
ProgressBar.Initialize(channelArray, computationArray, m_debug)
ProgressBar.vb below:
Partial Public Class ProgressBar
Dim DataAcquisition As New SqliteReader
Dim WithEvents worker As New BackgroundWorker
Public Sub Initialize(ByRef graphData As Channels(), ByRef graphComputations As Computations(), ByVal m_debug As Integer)
DataAcquisition = SqliteReader.GetInstance()
Me.Show()
Me.Update()
Dim Update_Thread As Thread(AddressOf Update_ThreadExecute)
Update_Thread.Priority = ThreadPriority.Normal
Update_Thread.Start()
DataAcquisition.ParseEntireDatabase(graphData, graphComputations, m_debug)
Me.Close()
End Sub
Private Sub ProgressBarStart(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
DataAcquisition = SqliteReader.GetInstance()
progress.Style = ProgressBarStyle.Marquee
worker.WorkerReportsProgress = True
worker.WorkerSupportsCancellation = True
worker.RunWorkerAsync()
End Sub
Private Sub worker_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles worker.DoWork
Dim worker As BackgroundWorker = DirectCast(sender, BackgroundWorker)
DataAcquisition = SqliteReader.GetInstance()
' I probably need
' DataAcquisition.ParseEntireDatabase(graphData, graphComputations, m_debug)
' here... but how do I expose graphdata, graphcomputations and m_debug to this sub?
End Sub
Private Sub worker_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles worker.ProgressChanged
dataProgress = CInt(((DataAcquisition.currentRow + 1) / DataAcquisition.totalRows) * 100)
progress.Value = dataProgress
End Sub
RunWorkerAsync has a second version that takes a parameter. You can use that to pass any values (or references) your worker needs.
That said, you shouldn't be updating form elements from inside the worker. Your worker should fire the ProgressChanged event when you want the UI to update, and you handle it there. That one also has a version that can send a value back. (Or many values if you send back an array, list, or custom class.)
The last step in this is that you need to actually fire ProgressChanged. DataAcquisition.ParseEntireDatabase may not do that, in which case using it won't allow this method to work.
If graphData, graphComputations, m_debug are already members of ProgressBar and worker_DoWork is a member of ProgressBar, then you have nothing more to do. You should be able to access them directly.
For Rapunzo, above.. My Final Solution was this:
Partial Public Class ProgressBar
Dim _mDataAcquisition As New SqliteReader
Public Property DataProgress As Integer = 0
Dim WithEvents _mProgressWorker As New BackgroundWorker
Public Sub Initialize(ByRef graphData As List(Of Channels), ByRef auxData As List(Of Channels), _
ByRef graphComputations As List(Of Computations))
_mDataAcquisition = SqliteReader.GetInstance()
Show()
Update()
_mDataAcquisition.ParseEntireDatabase(graphData, auxData, graphComputations)
Close()
End Sub
Private Sub ProgressBarStart(ByVal sender As System.Object, ByVal e As EventArgs) Handles MyBase.Load
progress.Style = ProgressBarStyle.Blocks
_mProgressWorker.WorkerReportsProgress = True
_mProgressWorker.WorkerSupportsCancellation = True
_mProgressWorker.RunWorkerAsync()
progress.Visible = True
progress.Maximum = 100
progress.Value = 0
End Sub
Public Sub WorkerProgressChanged()
progress.Value = DataProgress
Invalidate()
End Sub
Private Sub WorkerRunWorkerCompleted(ByVal sender As Object, _
ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) _
Handles _mProgressWorker.RunWorkerCompleted
progress.Visible = False
progress.Value = 0
Close()
End Sub
From there, just call ProgressBar.Initialize to start it
To update:
ProgressBar.DataProgress = CInt((currentIt / totalIt) * 100)
ProgressBar.WorkerProgressChanged()
and to end:
ProgressBar.DataProgress = 100
ProgressBar.WorkerProgressChanged()
Hope this helps.