System.Timers.Timer and DataBinding - vb.net

If I have a class that implements INotifyPropertyChanged, and I have a property in that class that is bound to a label on a form, how do I avoid a Cross-threaded exception if I set the property value from a System.Timers.Timer.Elapsed event handler?
The following code demonstrates the exception.
Imports System.ComponentModel
Imports System.Timers
Public Class Form1
Private thisClass As New aClass
Private WithEvents tmr As New System.Timers.Timer()
Private lbl As System.Windows.Forms.Label
Public Sub New()
InitializeComponent()
'create the label and add it to the form
lbl = New Label
lbl.Text = "some text"
Me.Controls.Add(lbl)
'set the data binding and start a timer
lbl.DataBindings.Add("Text", thisClass, "X")
tmr.Interval = 1000
AddHandler tmr.Elapsed, AddressOf tmr_Elapsed
tmr.Start()
End Sub
Private Sub tmr_Elapsed(sender As Object, e As ElapsedEventArgs)
'change the property value when the timer elapses
thisClass.X = Guid.NewGuid.ToString
End Sub
End Class
Public Class aClass
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private _x As String = ""
Public Property X As String
Get
Return _x
End Get
Set(value As String)
_x = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("X"))
End Set
End Property
End Class

Alternative approach could be to use an asynchronous method to keep updating code on same thread.
Public Class MyForm
Private _viewmodel As MyViewModel
Private _intervalTask As Task
Private _canContinueIntervalRunning As Boolean
Public Sub New()
InitializeComponent()
_viewmodel = New MyViewModel()
CreateLabel() ' Create label and bind to viewmodel property
' Start interval without blocking this code execution
' Later we can use this saved task to make sure that task is complete
_canContinueIntervalRunning = True
_intervalTask = StartInterval()
End Sub
Private Async Function StartInterval() As Task
While _canContinueIntervalRunning
Await Task.Delay(1000)
_viewmodel.X = Guid.NewGuid().ToString()
End While
End Function
Private Async Sub FormClosing(sender as Object, e as FormClosingEventArgs) Handles MyForm.FormClosing
' Set "flag" to false and wait for task to complete
_canContinueIntervalRunning = False
Await _intervalTask
End Sub
End Class
With Await Task.Delay(1000) next line will be executed on same thread, which allows to update form controls without extra effort.
This is simple approach with boolean flag, another way of doing it would be to use cancellation token which you can pass to Task.Delay - then form don't need to wait for extra second, but can cancel Delay straight away.

Related

UseWaitCursor doesn't work in an async Task

My current project requires a lot of forms. A lot of logic is the same so I want to create a BaseForm from which all Forms inherit.
I encounter a problem with UseWaitCursor and Cursor.Curent = Cursors.WaitCursor, it doesn't work if I load the data async (in a new Task).
This is the BaseForm:
Public MustInherit Class BaseForm
Inherits System.Windows.Forms.Form ' Normaly in BaseForm.Designer.vb put I put it here for symplicity
Protected Sub BeginLoadingData()
Application.UseWaitCursor = True
Cursor.Current = Cursors.WaitCursor
Application.DoEvents()
End Sub
Protected Sub EndLoadingData()
Application.UseWaitCursor = False
Cursor.Current = Cursors.Default
End Sub
End Class
And I use it like this:
Public Class MainForm
Inherits BaseForm ' Normaly in Mainform.Designer.vb put I put it here for symplicity
Public Sub New()
InitializeComponent()
LoadData()
End Sub
Private Sub LoadData()
BeginLoadingData() ' Working as long as I doesn't move the mouse
Task.Factory.StartNew(Sub()
' ... Long running task
Me.Invoke(Sub() EndLoadingData())
End Sub)
End Sub
End Class
If I run the loading synchronous, all works.
I wonder how I can force the cursor to stay WaitCursor.

SerialPort.DataReceived event not firing

I've defined a SerialPort object as the following:
Public WithEvents SerialComm As New System.IO.Ports.SerialPort
SerialComm is setup and opened in the object constructor and closed when the object is disposed. My handler signature is as follows:
Private Sub OnComm(sender as Object, e as SerialDataReceivedEventArgs) Handles SerialComm.DataReceived
I'm trying to get a DataReceived event to fire from HyperTerminal. After sending some data, I can set a breakpoint and check the value of SerialComm.BytesToRead and see that it has updated to the proper number of bytes.
I have confirmed that the SerialPort is open and is receiving data, but I can't get the event to fire.
I've also tried manually wiring up the event (after removing the WithEvents definition) using AddHandler, but I was still unable to get the event to trigger.
What am I missing?
Update:
This is the class that I'm having trouble with:
Imports System.IO.Ports
Public Class CopyCat
Implements IDisposable
Private RxString As String
Private LastReceiveTime As DateTime
Public WithEvents SerialComm As New SerialPort
Public Property Timeout As TimeSpan
Public Sub New(commPort As String, timeout As TimeSpan)
Me.Timeout = timeout
Enable(commPort)
End Sub
Public Sub New(commPort As String)
Me.New(commPort, TimeSpan.FromMilliseconds(1000))
End Sub
Public Sub Enable()
CommUtilities.SetupComm(SerialComm, commPort, 115200, 8, Parity.Odd, StopBits.One, Handshake.None, System.Text.Encoding.Default)
End Sub
Public Sub Disable()
SerialComm.Close()
End Sub
Private Sub OnComm(sender As Object, e As SerialDataReceivedEventArgs) Handles SerialComm.DataReceived
If(DateTime.Now - LastReceivedTime > Timeout) Then RxString = ""
Do While(SerialComm.BytesToRead > 0)
Dim readChar As String
Dim termChar As Char = Chr(RxString.ToByteList().XorAll())
readChar = SerialComm.ReadChar
RxString &= readChar
if (readChar = termChar) Then
SerialComm.Write((From item In GetResponse(RxString).Build()
Select item.Data).ToRawString)
RxString = ""
End If
Loop
End Sub
Public Function GetResponse(commandString As String) As MessageResponse
Dim response As MessageResponse = MessageResponse.GetMessageResponseFromByteList(commandString.ToByteList())
if (response.GetType = GetType(GetStatusResponse)) Then
response.DataBytes(8).Data = Math.Floor(Rnd() * 255)
response.DataBytes(9).Data = Math.Floor(Rnd() * 255)
End If
Return response
End Function
Private disposedValue as Boolean
Protected Overridable Sub Dispose(disposing As Boolean)
If Not Me.disposedValue Then
If disposing Then
SerialComm.Dispose()
End If
End If
Me.disposedValue = True
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
End Class

Unit Testing RaiseEvent in Vb.net using MSTest+MSFakes only

Conceptually, I am a little bit lost on Events themselves, so any information here would be helpful.
Specifically, I have a very simple Property Setter which raises an event, and I want to confirm
that this event is raised and also
that the proper parameters have been passes to said event. (although 2. may be unnecessary for this particular event.)
I am trying to contain the overhead in my Unit Testing project, that is, avoid any additional coding in the project being tested.
The code is as follows.
Public Class myItem
Shared Event ItemOpened As EventHandler(Of EventArgs)
.........
Public Property Open() As Boolean
Get
Return mOpen
End Get
Set(ByVal value As Boolean)
mOpen = value
RaiseEvent ItemOpened(Me, New EventArgs)
End Set
End Property
All code is being done in VB.net, which is primary reason why I have not found a good enough resource online for this yet. And also, I am not using a third-party mocking framework such as Nunit or RhinoMock, only MS built in Unit Testing frameworks in VS2012.
Also, similarly, I would like to test FirePropertyChangedNotification() on certain setter methods such as follows....
Public Property myBool() As Boolean
Set(ByVal Value As Boolean)
FirePropertyChangedNotification("myBool")
mViewOnly = Value
End Set
End Property
In which FirstPropertyChangedNotification() is as follows.....
Protected Sub FirePropertyChangedNotification(ByVal propName As String)
If Not Initializing Then
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propName))
RaiseEvent EntityChanged(Me, New PropertyChangedEventArgs(propName))
End If
End Sub
I'm not sure why you want to use Fakes here... You can just use a delegate in you test to subscribe to your event. Now change the property from the test and set a boolean or even place the assert in the delegate.
This is what I'd do in C#, autoconverted to VB.NET (which in my case is very rusty...) This works on my machine. Any improvements from a VB.NET expert are welcome:
Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports System.ComponentModel
Namespace UnitTestProject2
Public Class Sut
Implements INotifyPropertyChanged
Public Property StringProperty() As String
Get
Return String.Empty
End Get
Set(value As String)
OnPropertyChanged("StringProperty")
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Sub OnPropertyChanged(ByVal name As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
End Sub
End Class
<TestClass> _
Public Class UnitTest1
<TestMethod> _
Public Sub TestMethod1()
Dim sut As New Sut()
Dim triggered = False
AddHandler sut.PropertyChanged, Sub(o, e)
Assert.AreEqual("StringProperty", e.PropertyName)
triggered = True
End Sub
sut.StringProperty = "test"
Assert.IsTrue(triggered)
End Sub
<TestMethod> _
Public Sub TestMethod2()
Dim sut As New Sut()
Dim triggered = False
AddHandler sut.PropertyChanged, Sub(o, e)
Assert.AreSame(sut, o)
triggered = True
End Sub
sut.StringProperty = "test"
Assert.IsTrue(triggered)
End Sub
End Class
End Namespace

How to Pass Additional Parameters When Calling and Event VB.net

Public Event DocumentCompleted As WebBrowserDocumentCompletedEventHandler
Dim arg() As Object = {homeTeam, guestTeam}
AddHandler browser.DocumentCompleted, New
WebBrowserDocumentCompletedEventHandler(AddressOf DoStuff)
Private Sub DoStuff(ByVal sender As Object, ByVal e As WebBrowserDocumentCompletedEventArgs)
End Sub
How can I pass the homeTeam and guestTeam when firing the DocumentCompleted event.
I want to ge the above to values to inside the Dostuff method.
Please help.
First of all, you cannot have this hanging in the middle of nowhere:
Dim arg() As Object = {homeTeam, guestTeam}
AddHandler browser.DocumentCompleted,
New WebBrowserDocumentCompletedEventHandler(AddressOf DoStuff)
AddHandler probably needs to be in some Initialize method, which could be inside Sub New, after InitializeComponent, or inside Form_Load, or as soon as you expect it to be triggered (after a specific event). Notice here that you are using a default event of a native .NET component, with a default event type. In this case you cannot directly consume anything other than what it already provides, when triggered. See WebBrowser.DocumentCompleted Event on MSDN.
You can, however, override all relevant classes and have your own MyWebBrowser control and your own event, with would contain additional properties. See below example:
Public Class Form1
Sub New()
' This call is required by the designer.
InitializeComponent()
Dim browser As New MyWebBrowser
AddHandler browser.MyDocumentCompleted, AddressOf DoStuff
End Sub
Private Sub DoStuff(ByVal sender As Object, ByVal e As MyWebBrowserDocumentCompletedArgs)
Dim guestTeam As String = e.GuestTeam 'guest team
Dim homeTeam As String = e.HomeTeam 'and home team are both accessible
'so you can do some processing on them
End Sub
Public Class MyWebBrowserDocumentCompletedArgs : Inherits WebBrowserDocumentCompletedEventArgs
Dim _homeTeam As String
Dim _guestTeam As String
Public ReadOnly Property HomeTeam
Get
Return _homeTeam
End Get
End Property
Public ReadOnly Property GuestTeam
Get
Return _guestTeam
End Get
End Property
Sub New(url As Uri, homeTeam As String, guestTeam As String)
MyBase.New(url)
_homeTeam = homeTeam
_guestTeam = guestTeam
End Sub
End Class
Public Class MyWebBrowser : Inherits WebBrowser
Public Delegate Sub MyWebBrowserDocumentCompletedEventHandler(e As MyWebBrowserDocumentCompletedArgs)
Public Event MyDocumentCompleted As MyWebBrowserDocumentCompletedEventHandler
Protected Overrides Sub OnDocumentCompleted(e As System.Windows.Forms.WebBrowserDocumentCompletedEventArgs)
MyBase.OnDocumentCompleted(e)
'homeTeam and guestTeam need to be extracted from the current instance of MyWebBrowser, and passed further
RaiseEvent MyDocumentCompleted(New MyWebBrowserDocumentCompletedArgs(e.Url, "homeTeam", "guestTeam"))
End Sub
End Class
End Class
If your project is relatively small, you can indeed have those as global variables, as #Vlad suggested in the comments.

Inotify change to another class or object

I am in a small fix, I have a class as below.
Public Class Bill
Public prime As BillPrime
Public items As System.Collections.ObjectModel.ObservableCollection(Of ItemDetails)
Public status As New BillStatus
Public Sub New()
prime = New BillPrime
items = New System.Collections.ObjectModel.ObservableCollection(Of ItemDetails)
status = New BillStatus
End Sub
End Class
How can I update some x value in prime when there is a change in any of the ItemDetails object in items.
Could you please help on how can I come to a solution?
Try using a BindingList(of T) instead, then you can listen for a change event:
Imports System.ComponentModel
Public Class Bill
Public prime As BillPrime
Public WithEvents items As BindingList(Of ItemDetails)
Public status As New BillStatus
Public Sub New()
prime = New BillPrime
items = New BindingList(Of ItemDetails)
status = New BillStatus
End Sub
Public Sub items_ListChanged(ByVal sender As Object, ByVal e As ListChangedEventArgs) Handles items.ListChanged
prime.X = "something"
End Sub
End Class
This would require your classes to implement INotifyPropertyChanged:
Public Class ItemDetails
Implements INotifyPropertyChanged
Public Event PropertyChanged(ByVal sender As Object, ByVal e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
Private _DetailOne As String
Property DetailOne() As String
Get
Return _DetailOne
End Get
Set(ByVal value As String)
_DetailOne = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("DetailOne"))
End Set
End Property
End Class
The ItemDetails class will need to raise an event whenever any of its properties change. I would suggest implementing the INotifyPropertyChanged interface on the ItemDetails class, but you could implement your own event, instead. You will then need to add an event handler to each ItemDetails.PropertyChanged event as it is added to the list and remove the handler from each item as it is removed from the list. For instance:
Public Class Bill
Public prime As BillPrime
Public items As System.Collections.ObjectModel.ObservableCollection(Of ItemDetails)
Public status As New BillStatus
Public Sub New()
prime = New BillPrime
items = New System.Collections.ObjectModel.ObservableCollection(Of ItemDetails)
AddHandler items.CollectionChanged, AddressOf items_CollectionChanged
status = New BillStatus
End Sub
Private Sub items_CollectionChanged(sender As Object, e As NotifyCollectionChangedEventArgs)
For Each i as ItemDetails in e.NewItems
AddHandler i.PropertyChanged, AddressOf item_PropertyChanged
Next
For Each i as ItemDetails in e.OldItems
RemoveHandler i.PropertyChanged, AddressOf item_PropertyChanged
Next
End Sub
Private Sub item_PropertyChanged(sender As Object, e As PropertyChangedEventArgs)
'Do work
End Sub
End Class