Databinding Not updating - vb.net

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

Related

VB ToolStripControlHost remove Text property

I have a class that allows ToolStrip to have a NumericUpDown. I added a properties (Value, Minimum and Maximum). It is working but I want to remove the Text property since I added the Value property. Is there a way to do this?
Please see image where you can see the designer properties
Below is the class
<Windows.Forms.Design.ToolStripItemDesignerAvailabilityAttribute(Windows.Forms.Design.ToolStripItemDesignerAvailability.ToolStrip), DebuggerStepThrough()>
Public Class ToolStripNumericUpDown
Inherits Windows.Forms.ToolStripControlHost
Public Sub New()
MyBase.New(New System.Windows.Forms.NumericUpDown())
End Sub
Public ReadOnly Property ToolStripNumericUpDownControl() As Windows.Forms.NumericUpDown
Get
Return TryCast(Control, Windows.Forms.NumericUpDown)
End Get
End Property
Public Property Value() As Long
Get
Return ToolStripNumericUpDownControl.Value
End Get
Set(ByVal value As Long)
ToolStripNumericUpDownControl.Value = value
End Set
End Property
Public Property Minimum() As Long
Get
Return ToolStripNumericUpDownControl.Minimum
End Get
Set(ByVal value As Long)
ToolStripNumericUpDownControl.Minimum = value
End Set
End Property
Public Property Maximum() As Long
Get
Return ToolStripNumericUpDownControl.Maximum
End Get
Set(ByVal value As Long)
ToolStripNumericUpDownControl.Maximum = value
End Set
End Property
Protected Overrides Sub OnSubscribeControlEvents(ByVal c As Windows.Forms.Control)
MyBase.OnSubscribeControlEvents(c)
AddHandler DirectCast(c, Windows.Forms.NumericUpDown).ValueChanged, AddressOf OnValueChanged
End Sub
Protected Overrides Sub OnUnsubscribeControlEvents(ByVal c As Windows.Forms.Control)
MyBase.OnUnsubscribeControlEvents(c)
RemoveHandler DirectCast(c, Windows.Forms.NumericUpDown).ValueChanged, AddressOf OnValueChanged
End Sub
Public Event ValueChanged As EventHandler
Private Sub OnValueChanged(ByVal sender As Object, ByVal e As EventArgs)
RaiseEvent ValueChanged(Me, e)
End Sub
End Class

How to create a new event for my own custom control?

I have a class which inherit from Panel, and below are some member in this class
Public ItemName As String
Public Quantity As Integer
Public Price As Decimal
Public DiscountAmount As Decimal
How can I create a event when Quantity or DiscountAmount changed then run a function?
I try to write in this way but I get error:-
Private Sub info_Changed(sender As Object, e As EventArgs) Handles Quantity.Changed, DiscountAmount.Changed
myFunction()
End Sub
Error:
Handles clause requires a WithEvents variable defined in the
containing type or one of its base types.
You need to declare the events in user control and then consume those. See below code. I have created a user control UserControl1. This control raises events when Price or DiscountAmount is changed. The usercontrol is then used in Form1. You can use the same approach to change the Quantity.
Public Class Form1
Private WithEvents userCntrl As New UserControl1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
ChangeValues()
End Sub
Private Sub ChangeValues()
userCntrl.Price = 100
userCntrl.DiscountAmount = 12
End Sub
Private Sub userCntrl_Price_Changed(newValue As Decimal) Handles userCntrl.Price_Changed, userCntrl.DiscountAmount_Changed
MessageBox.Show("New value = " & newValue.ToString)
End Sub
End Class
Public Class UserControl1
Public Event Price_Changed(ByVal newValue As Decimal)
Public Event DiscountAmount_Changed(ByVal newValue As Decimal)
Public ItemName As String
Public Quantity As Integer
Private Price_ As Decimal
Public Property Price() As Decimal
Get
Return Price_
End Get
Set(ByVal value As Decimal)
If value <> Price_ Then
RaiseEvent Price_Changed(value)
End If
Price_ = value
End Set
End Property
Private DiscountAmount_ As Decimal
Public Property DiscountAmount() As Decimal
Get
Return DiscountAmount_
End Get
Set(ByVal value As Decimal)
If value <> DiscountAmount_ Then
RaiseEvent DiscountAmount_Changed(value)
End If
DiscountAmount_ = value
End Set
End Property
End Class

Add rows to class bound datagridview

Hi I'm trying to bind a list of objects to a datagridview
Binding a existing list(Of is working but I'm trying to add or remove a object from, my dgv isn't updating.
Public Class Form1
Dim lt As New List(Of Test)
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
lt.Add(New Test("Mac", 2200))
lt.Add(New Test("PC", 1100))
dgv.DataSource = lt
lt.Add(New Test("Android", 3300)) 'This line won't appear in the dgv
End Sub
End Class
Public Class Test
Public Sub New(ByVal name As String, ByVal cost As String)
_name = name
_cost = cost
End Sub
Private _name As String
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
Private _cost As String
Public Property Cost() As String
Get
Return _cost
End Get
Set(ByVal value As String)
_cost = value
End Set
End Property
End Class
How can I add or remove or change a value from the dgv to the list and inverse?
NoiseBe
Change this line:
Dim lt As New List(Of Test)
To:
Imports System.ComponentModel
...
Private lt As New BindingList(Of Test)
When the collection contents will change, you should use a BindingList(Of T). This collection has events associated with it which make it aware of changes to the list contents.
If in addition to the list contents, the list items will change (like Test.Name), you should also implement INotifyPropertyChanged on the class itself.
Another way to do it:
dgv.DataSource = Nothing
lt.Add(New Test("Android", 3300))
dgv.DataSource = lt
This "resets" the DataSource so that the new contents will show up. However, it means that several other things get reset like selected items; if you are binding to a List/Combo control, you will also have to reset the ValueMember and DisplayMember properties as well.

Linq to SQL to ObservableCollection for treeview in VB.Net

I have some Linq to SQL table classes that are joined together. I currently have it bound to a treeview using just the LINQ to SQL query. It works, but it doesn't show when stuff is added or removed from the database.
I implemented INotifyPropertyChanged but it isn't updating the treeview.
I also tried using Bindable Linq, but it doesn't seem to make a difference.
I found an example of a way to easily create ObservableCollections without having to create more classes: jimlynn.wordpress.com/2008/12/09/using-observablecollection-with-linq/, (which is kind of important because I have a future project looming that will require interacting with a lot of tables (30 or so) and just creating the Linq to SQL classes is going to be a pain).
Property ModelQuery As ObservableCollection(Of dbModels) = New ObservableCollection(Of dbModels)().PopulateFrom(From mm In tblModels.AsBindable Order By mm.ModelName)
Is this a good way to go, or am I going to have to create a separate ObservableCollection and maintain them both in code. If I'm going to use this binding stuff, I'm really looking for a way to have stuff just linked together so I don't have to update multiple structures whenever a change is made.
Main table:
<Table(Name:="Models")> Public Class dbModels
Implements INotifyPropertyChanged
Private _changed As Boolean
Public Event PropertyChanged(ByVal sender As Object, ByVal e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
Protected Overridable Sub OnPropertyChanged(ByVal Propertyname As String)
If Not Propertyname.Contains("Changed") Then
Changed = True
End If
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(Propertyname))
End Sub
Public Property Changed() As Boolean
Get
Return _changed
End Get
Set(ByVal value As Boolean)
If _changed <> value Then
_changed = value
OnPropertyChanged("Changed")
End If
End Set
End Property
Private _ModelID As Integer
<Column(Storage:="_ModelID", DbType:="int IDENTITY NOT NULL", IsPrimaryKey:=True, IsDbGenerated:=True, Name:="ModelID")> _
Public Property ModelID() As Integer
Get
Return Me._ModelID
End Get
Set(value As Integer)
Me._ModelID = value
End Set
End Property
Private _ModelName As String
<Column(Storage:="_ModelName", DbType:="Varchar(200)", Name:="ModelName")> _
Public Property ModelName() As String
Get
Return Me._ModelName
End Get
Set(value As String)
Me._ModelName = value
End Set
End Property
Private _ModelYears As EntitySet(Of dbModelYears) = New EntitySet(Of dbModelYears)
<Association(Storage:="_ModelYears", DeleteRule:="CASCADE", OtherKey:="ModelID")> _
Public Property ModelYears As EntitySet(Of dbModelYears)
Get
Return _ModelYears
End Get
Set(value As EntitySet(Of dbModelYears))
_ModelYears.Assign(value)
End Set
End Property
End Class
Joined table:
<Table(Name:="ModelYears")> Public Class dbModelYears
Implements INotifyPropertyChanged
Private _changed As Boolean
Public Event PropertyChanged(ByVal sender As Object, ByVal e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
Protected Overridable Sub OnPropertyChanged(ByVal Propertyname As String)
If Not Propertyname.Contains("Changed") Then
Changed = True
End If
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(Propertyname))
End Sub
Public Property Changed() As Boolean
Get
Return _changed
End Get
Set(ByVal value As Boolean)
If _changed <> value Then
_changed = value
OnPropertyChanged("Changed")
End If
End Set
End Property
Private _ModelYearID As Integer
<Column(Storage:="_ModelYearID", DbType:="int IDENTITY NOT NULL", IsPrimaryKey:=True, IsDbGenerated:=True, Name:="ModelYearID")> _
Public Property ModelYearID() As Integer
Get
Return Me._ModelYearID
End Get
Set(value As Integer)
Me._ModelYearID = value
End Set
End Property
Private _ModelID As Integer
<Column(Storage:="_ModelID", DbType:="int", Name:="ModelID")> _
Public Property ModelID() As Integer
Get
Return Me._ModelID
End Get
Set(value As Integer)
Me._ModelID = value
End Set
End Property
Private _ModelYear As String
<Column(Storage:="_ModelYear", DbType:="Varchar(50)", Name:="ModelYear")> _
Public Property ModelYear() As String
Get
Return Me._ModelYear
End Get
Set(value As String)
Me._ModelYear = value
End Set
End Property
End Class
Implementing INotifyPropertyChanged or using ObservableCollection wont notify you of changes in the database. You are going to have to execute the query every time you want to retrieve new data from the database.
Alternatively you could possibly use SqlDependency to set up a query notification dependency between your application and an instance of SQL Server.
EDIT:
From comments.
To hook up the PropertyChanged event of the items in the collection.
For Each item As var In itemsCollection
Dim notifyItem = TryCast(item, INotifyPropertyChanged)
If item IsNot Nothing Then
AddHandler notifyItem.PropertyChanged, AddressOf ItemChanged
End If
Next
Public Sub ItemChanged(sender As Object, e As PropertyChangedEventArgs)
'Handle event
End Sub

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