Inotify change to another class or object - vb.net

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

Related

DataBind a Simple String Property to Textbox

I have a Simple Property called Customer as string
I want to bind this property to a Textbox.Text Databinding
I use the INotifyPropertyChanged Interface.
If I want to add the Databindings with
TextBox1.DataBindings.Add("Text", Customer, "Text")
I get an Error with:
You cannot bind text to the property or column for the DataSource.
Parameter name: dataMember
Public Class Form1
Implements INotifyPropertyChanged
Private _Customer As String = "DEFAULT"
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
TextBox1.DataBindings.Add("Text", Customer, "Text")
End Sub
Public Property Customer As String
Get
Return _Customer
End Get
Set
_Customer = Value
NotifyPropertyChanged()
End Set
End Property
Private Sub NotifyPropertyChanged(<CallerMemberName()> Optional ByVal propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
The reason is that you can't bind to a property directly, you need to bind to an object which contains the property. Also you can't use a property that is inside the Form1 class. You need to set up an instance of an object.
I've created a sample that uses a class named Customer with a single property called Name. I've also created a base class. This is not required, but is useful if you create multiple classes.
Public Class BaseNotify
Implements INotifyPropertyChanged
Friend Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Friend Sub NotifyPropertyChanged(<CallerMemberName()> Optional propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
Public Class Customer
Inherits BaseNotify
Private _name As String = "DEFAULT"
Public Property Name As String
Get
Return _name
End Get
Set
If (_name = Value) Then Return
_name = Value
NotifyPropertyChanged()
End Set
End Property
End Class
Finally set up the form (I also renamed the textbox to something more meaningful.
Public Class Form1
Private _customer As Customer
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
BindProperties()
End Sub
Private Sub BindProperties()
_customer = New Customer()
Me.tbName.DataBindings.Add("Text", _customer, NameOf(Customer.Name))
End Sub
End Class
Using NameOf is recommended, as it won't break the code if you decide to change the property name at a later stage.

System.Timers.Timer and DataBinding

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.

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

Databinding Not updating

I have been trying to get a label to databind to a readonly property. I have a much more complex project which I am implementing this in and it isn't working. I have been unsuccessful in getting help with this so I have created a much simpler version of the project and my databinding still isn't updating.
To replicate my issue you will need a form with a textbox, label and button, and then a class.
The code for the class is as follows
Imports System.ComponentModel
Public Class databinding
Implements INotifyPropertyChanged
Public Sub New()
numbers = New List(Of number)
End Sub
Public Property numbers As List(Of number)
Get
Return m_number
End Get
Set(value As List(Of number))
m_number = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("hnumber"))
End Set
End Property
Private m_number As List(Of number)
Public ReadOnly Property hnumber As Integer
Get
Dim list As IList(Of number) = (From t As number In numbers Select t Order By t.value Descending).ToList()
If (list.Count > 0) Then
If (IsNothing(list(0).value)) Then
Return "0"
Else
Return list(0).value
End If
End If
Return "0"
End Get
End Property
Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
End Class
Public Class number
Public Property value As Integer
Get
Return t_number
End Get
Set(value As Integer)
t_number = value
End Set
End Property
Private t_number As Integer
End Class
The code for the form is as follows:
Public Class Form1
Public numberlist As New databinding
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Label1.DataBindings.Add(New Binding("text", numberlist, "hnumber"))
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim newnum As New number
newnum.value = TextBox1.Text
numberlist.numbers.Add(newnum)
End Sub
End Class
Now based on my understanding when you click the button a number from the textbox is added to this list, which happens, and the hnumber value updates, which using a breakpoint and a watch I can see also happens. From reading I need to implement inotifypropertychanged when I set the new number to get the label to re check the databind (which has been done).
However the label will stay at 0. If I run watch through Label1 I can see that under DataBindings > List > arrayList > (0) > System.Windows.Forms.Binding>DataSource>Databinding_test.databinding the details of the class (including the correct value for hnumber) is listed, so to me that shows that the Label does in fact know about the value it should be binding to.
Could someone please fill me in on what I am missing to make this all work, as it is almost causing me to pull out all of my hair.
Thanks,
mtg
I've tried to explain this to you before, and I will again.
The reason why your binding isn't updated is because you're adding the value to a list.
numberlist.numbers.Add(newnum)
However, if you "change" the list, this will trigger the propertychanged event.
numberlist.numbers.Add(newnum)
numberlist.numbers = numberlist.numbers '<--
Instead of using an IList<T> you should use the ObservableCollection<T> which allows you to track the changes made.
Public Class databinding
Implements INotifyPropertyChanged
Public Sub New()
Me.numbers = New ObservableCollection(Of number)
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Property numbers As ObservableCollection(Of number)
Get
Return m_number
End Get
Set(value As ObservableCollection(Of number))
If (Not m_number Is value) Then
Unhook(m_number)
Hook(value)
m_number = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("hnumber"))
End If
End Set
End Property
Public ReadOnly Property hnumber As Integer
Get
If (Not numbers Is Nothing) Then
Dim list As IList(Of number) = (From t As number In numbers Select t Order By t.value Descending).ToList()
If (list.Count > 0) Then
If (IsNothing(list(0).value)) Then
Return 0
Else
Return list(0).value
End If
End If
End If
Return 0
End Get
End Property
Private Sub Hook(collection As ObservableCollection(Of number))
If (Not collection Is Nothing) Then
AddHandler collection.CollectionChanged, AddressOf Me.OnNumbersChanged
End If
End Sub
Private Sub OnNumbersChanged(sender As Object, e As NotifyCollectionChangedEventArgs)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("hnumber"))
End Sub
Private Sub Unhook(collection As ObservableCollection(Of number))
If (Not collection Is Nothing) Then
RemoveHandler collection.CollectionChanged, AddressOf Me.OnNumbersChanged
End If
End Sub
Private m_number As ObservableCollection(Of number)
End Class

Binding Combobox to Object Datasource

I've got a form bound to an object datasource. It has one text box and one combo box. I set up one binding source for the main object and one binding source for the combo box. When I run the form, the text box is bound correctly, and the list of values in the combo box is bound correctly, but the ValueMember of the combo box isn't working correctly.
The combo box shows the correct list, but it's selected index is 0 instead of what it should be 2. When I change the value in the text box, it's bound object's Property.Set method is called correctly, but the same Property.Set method is not called for the combo box.
I know I can hack up the OnSelectedIndex change methods in the form, but I would like to know what I am doing wrong in just using the Bindings.
Here is the code on the form:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load
Dim NameValueBindingSource1 As New BindingSource()
Dim WorkOrderBindingSource1 As New BindingSource
'Create main object to bind to
Dim wo As New WorkOrder
wo.WOIndex = "2012-0111"
wo.WorkOrderType = 3
'Create list object for combo box
Dim NameValues As BindingList(Of NameValue)
NameValues = FillNameValueList()
'Bind Text Box to Binding Source
WorkOrderBindingSource1.DataSource = wo
WOIndexTextBox1.DataBindings.Add("Text", WorkOrderBindingSource1, "WOIndex")
'Bind Combo Box to Binding Source
NameValueBindingSource1.DataSource = NameValues
WorkOrderTypeCombo.DataSource = NameValueBindingSource1
WorkOrderTypeCombo.DisplayMember = "Value"
WorkOrderTypeCombo.ValueMember = "Code"
End Sub
Function FillNameValueList() As BindingList(Of NameValue)
Dim bl As New BindingList(Of NameValue)
Dim nv As NameValue
nv = New NameValue
bl.Add(New NameValue("Short", 0))
bl.Add(New NameValue("Middle", 1))
bl.Add(New NameValue("Long", 2))
bl.Add(New NameValue("Very Long", 3))
Return bl
End Function
End Class
Here's the code for the main object - "WorkOrder"
Imports System.ComponentModel
Public Class WorkOrder
Implements IEditableObject
Implements INotifyPropertyChanged
Private mWOIndex As String
Private mWorkOrderType As Integer
Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Public Property WOIndex As String
Get
Return mWOIndex
End Get
Set(value As String)
mWOIndex = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("WOIndex"))
End Set
End Property
Public Property WorkOrderType As Integer
Get
Return mWorkOrderType
End Get
Set(value As Integer)
mWorkOrderType = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("WorkOrderType"))
End Set
End Property
Public Sub BeginEdit() Implements System.ComponentModel.IEditableObject.BeginEdit
End Sub
Public Sub CancelEdit() Implements System.ComponentModel.IEditableObject.CancelEdit
End Sub
Public Sub EndEdit() Implements System.ComponentModel.IEditableObject.EndEdit
End Sub
End Class
Here's the code for the object used in the combo box
Imports System.ComponentModel
Public Class NameValue
Implements IEditableObject
Implements INotifyPropertyChanged
Private mValue As String
Private mCode As Integer
Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Public Property Code As Integer
Get
Return mCode
End Get
Set(value As Integer)
mCode = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Code"))
End Set
End Property
Public Property Value As String
Get
Return mValue
End Get
Set(value As String)
mValue = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Value"))
End Set
End Property
Public Sub BeginEdit() Implements System.ComponentModel.IEditableObject.BeginEdit
End Sub
Public Sub CancelEdit() Implements System.ComponentModel.IEditableObject.CancelEdit
End Sub
Public Sub EndEdit() Implements System.ComponentModel.IEditableObject.EndEdit
End Sub
Public Sub New(InitValue As String, InitCode As Integer)
Value = InitValue
Code = InitCode
End Sub
End Class
In your code, you are merely assigning the DataSource to the ComboBox, but you're not establishing any DataBinding for it.
You need a line like this (using C# here):
WorkOrderTypeCombo.DataBindings.Add(new System.Windows.Forms.Binding("SelectedValue", WorkOrderBindingSource1, "WorkOrderType", true));
Hope this helps