Winform TextBox databind to Listbox using INotifyPropertyChanged - vb.net

I have set up Entity Framework with my VB.NET project. I have a model class that takes the data from a table from my MS SQL Server DB. My ListBox object is filled from the model class. What I am trying to do is when a user clicks on an item on from the listbox the text box is populated with the data from the table. If the user clicks on a mustang the text boxes are filled with the model, make, and year of a mustang.
I am using INotifyPropertyChanged in a viewmodel class that I thought would allow me to get each part of the dataset.
This code shows only one part of the requirements to better simplify the post
Public Class CarViewModel
Implements INotifyPropertyChanged
Private _SelectedCarModelName As CarModel
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Property SelectedCarModelName () As CarModel
Get
Return _SelectedCarModelName
End Get
Set(ByVal value As CarModel)
_SelectedCarModelName = value
OnPropertyChanged("CarName")
End Set
End Property
Public Overridable Sub OnPropertyChanged(propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
My Textbox Data bind looks like this. My Textbox resides on the frm.vb.
txtCarName.DataBindings.Add("Text", _carViewModel.SelectedCarModelName.CarName, "CarName", True, DataSourceUpdateMode.OnPropertyChanged)
When I click on an item on the list it will access the property, but it will not set the property change. I know I am doing something wrong but I just don't know what it is.
******UPDATED*******
ViewModel
Public Class CarViewModel
Implements INotifyPropertyChanged
Private _SelectedCarModelName As CarModel
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Property SelectedCarModelName As CarModel
Get
Return _SelectedCarModelName
End Get
Set(ByVal value As CarModel)
_SelectedCarModelName= value
OnPropertyChanged("SelectedCarModelName ")
End Set
End Property
Public Sub OnPropertyChanged(propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
Windows Form TextBox
**************Edited*******************
private _CarViewModel As CarViewModel
Dim bs As New BindingSource(_CarViewModel , Nothing)
txtCarName.DataBindings.Add _
("Text", bs, "SelectedCarModelName", True, DataSourceUpdateMode.OnPropertyChanged, String.Empty)
After I have restarted everything, for some reason my machine likes that, I am not getting an error anymore. When I debug the code is iterating through the Getter 3x, the number of fields. but it is not getting an output for the textbox.

Related

Databound TextBox not updating with Source

I have a custom Class that implements INotifyPropertyChanged as follows (extraneous and repeated properties removed):
Public Class Map
Implements INotifyPropertyChanged
Private _wages As Decimal
Property Wages As Decimal
Get
Return _wages
End Get
Set(value As Decimal)
Debug.Print("Event Raised: " & _wages & " " & value)
_wages = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(Wages))
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
The Class is being Serialized/Deserialized correctly. When I load the XML file the TextBox that displays wages is not updating to reflect the Wages value.
I have the (Devexpress) TextBox DataBindings set with:
txtWages.DataBindings.Add(New Binding("EditValue", mymap, "Wages", False, DataSourceUpdateMode.OnPropertyChanged))
When I load the file I can see the old and new value from Debug.Print:
Event Raised: 0 13
However, txtWages stays as 0
I have read these answers Here and Here and tried to look for ideas but I am out of ideas.
EDIT: It seems that if I populate the class with the Deserialized XML and then set the Bindings it works, but not when the bindings are set first. I would have thought it would work either way.
U have Error in code
PropertyChangedEventArgs need String (Name of property)
so "Wages"
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Wages"))
You should provide name of the property to the PropertyChangedEventArgs
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(Nameof(Wages))
You did not show, but obviously when you "load" file and deserialize class you create new instance of Map and set it to mymap. Where control's binding will still refer to the original instance and listen it's events.
Create "wrapper" viewmodel with property of type Map and raise property change event when you load file.
Public Class ViewModel
Private _Map As Map
Property Map As Map
Get
Return _Map
End Get
Set(value As Map)
_Map = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(Nameof(Wages))
End Set
End Property
End Class
Then in the form do binding
Dim viewmodel = new ViewModel()
viewmodel.Map = DeserializeMap() ' Your deserialize logic
txtWages.DataBindings.Add("EditValue",
viewmodel,
"Map.Wages",
False,
DataSourceUpdateMode.OnPropertyChanged)

How do I get INotifyPropertyChanged to update bound items from a list stored in SQL?

I am still learning how to use INotifyPropertyChanged. I was able to see it work with a non-persisted list in this post. I am now trying to use an SQL persisted list.
XAML. Some of the unused grid space was for comparisons to the non-persisted list, but not relevant to this post's question.
<StackPanel Grid.Column="0">
<Button Margin="25" Content="Insert an item" Click="InsertAnItem_Click"/>
</StackPanel>
<StackPanel Grid.Column="1">
<Button Margin="25" Content="Clear stored list" Click="ClearStoredList_Click"/>
<Label Content="{}{Binding MyList2}"/>
<ListBox Name="listBox3" ItemsSource="{Binding MyList2}" DisplayMemberPath="Name" Height="100">
</ListBox>
<Label Content="updated with MyList2 using code behind"/>
<ListBox Name="listBox4" DisplayMemberPath="Name" Height="100">
</ListBox>
</StackPanel>
CodeBehind
Partial Class MainWindow
Private vm = New ViewModel
Sub New()
InitializeComponent()
DataContext = vm
For Each item In vm.MyList2
listBox4.Items.Add(item)
Next
End Sub
Private Sub InsertAnItem_Click(sender As Object, e As RoutedEventArgs)
vm.InsertAnItem()
listBox4.Items.Clear()
For Each item In vm.MyList2
listBox4.Items.Add(item)
Next
End Sub
Private Sub ClearStoredList_Click(sender As Object, e As RoutedEventArgs)
vm.ClearStoredList()
End Sub
End Class
ViewModel
Public Class ViewModel
Implements INotifyPropertyChanged
Public Property dbList As New Mvvm2DbContext
Public Property MyList2 As New List(Of MyListItem)
Public Sub New()
MyList2 = dbList.SqlStoredList.ToList()
End Sub
Public Sub InsertAnItem()
Dim anItem As New MyListItem
anItem.Name = "Item " & MyList2.Count()
MyList2.Add(anItem)
dbList.SqlStoredList.Add(anItem)
dbList.SaveChanges()
NotifyPropertyChanged("MyList2")
End Sub
Public Sub ClearStoredList()
Using db = New Mvvm2DbContext
Dim allItems = db.SqlStoredList
Try
db.SqlStoredList.RemoveRange(allItems)
Catch ex As Exception
Dim s = ex.Message
MessageBox.Show(s)
End Try
db.SaveChanges()
End Using
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
Public Sub NotifyPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
Model
Public Class MyListItem
Public Property MyListItemID() As Integer
Get
Return _MyListItemID
End Get
Set(ByVal value As Integer)
_MyListItemID = value
End Set
End Property
Private _MyListItemID As Integer
Public Property Name() As String
Get
Return _Name
End Get
Set(ByVal value As String)
_Name = value
End Set
End Property
Private _Name As String
Public Property Desc() As String
Get
Return _Desc
End Get
Set(ByVal value As String)
_Desc = value
End Set
End Property
Private _Desc As String
End Class
dbContext
Public Class Mvvm2DbContext
Inherits DbContext
Public Sub New()
MyBase.New("name=MVVM2DbConnection")
End Sub
Public Property SqlStoredList As DbSet(Of MyListItem)
End Class
My line NotifyPropertyChanged("MyList2") in the ViewModel doesn't cause the bound list to update.
I think the MyList2 definition in the ViewModel must be incorrect, but I don't understand how to make it different if that's the problem.
What is the proper code to get changes to percolate to the display? As always, if there are best practices I'm missing, please advise.
Updated after initial post but questions still remain:
The following changes appear to cause INotifyPropertyChanged to now work in this code. I'm guessing INotifyPropertyChanged doesn't work unless a property is defined with specific get/set declarations.
ViewModel. The MyList2 definition is changed and New() is changed.
Public Property MyList2() As ObservableCollection(Of MyListItem)
Get
Return _MyList2
End Get
Set(ByVal value As ObservableCollection(Of MyListItem))
_MyList2 = value
End Set
End Property
Private _MyList2 As New ObservableCollection(Of MyListItem)
Public Sub New()
For Each item In dbList.SqlStoredList
MyList2.Add(item)
Next
End Sub
The reason I previously defined MyList2 as:
Public Property MyList2 As New List(Of MyListItem)
Was because in New() initializing MyList2 with this line wouldn't compile:
MyList2 = dbList.SqlStoredList.ToList()
Even if I tried a DirectCast, List(Of T) can't be converted to an ObservableCollection(Of T). I'm guessing that's because of all the 'plumbing' behind the scenes?
Anyway, INotifyPropertyChanged seems to work fine now. But, I would appreciate any instruction regarding mistakes I'm making implementing this simple persisted list with INotifyPropertyChanged.
However, the persisted list is not fully bound. MyList2 doesn't clear when the SQL database is cleared. It seems to me that there must be more to this because changes to the SQL database could be made by other processes, and I don't see how this code would know that.
What do I do to ensure all updates get reflected in the UI?
The problem with your code is that the property MyList2 is not changing...its contents are changing. So that is why the property notification on MyList2 does nothing...MyList2 is still MyList2.
What you need is a collection class that implements notification. ObservableCollection would fit this need. So if you simply replace List(Of MyListItem) with ObservableCollection(Of MyListItem) I think things will start working.

How to update UI when a property change

I'm trying to update my UI when a property in my BL class changes. Please can someone advise the best way to do this in vb.net
Not a really precise question so I will explain the standard way (in my opinion).
Implement the INotifyPropertyChanged interface in your class and handle the PropertyChanged event of your object.
First the the class of the object that contains the property in question:
Public Class MySweetClass
Implements System.ComponentModel.INotifyPropertyChanged
Private _MyProperty As String
Public Property MyProperty As String
Get
Return _MyProperty
End Get
Set(value As String)
_MyProperty = value
RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("MyProperty"))
End Set
End Property
Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
End Class
Notice that the PropertyChanged event is raised once the value of the property changes.
In your form handle this event:
Public Class Form1
Private WithEvents MySweetObject As MySweetClass
Private Sub MySweetObject_PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Handles MySweetObject.PropertyChanged
'Update gui here
End Sub
End Class
This lets you update the GUI whenever the value changes.

Bubble up events from a one-to-many relationship

I can bubble up an event from a one-to-one relationship like this
Public Class Husband
Public WithEvents Wife As Wife
Public Sub WifeChangedLastName() Handles Wife.LastNameChanged
MsgBox("Wife Changed Last Name")
End Sub
End Class
Public Class Wife
Public _LastName As String
Public Property LastName As String
Get
Return Me._LastName
End Get
Set(ByVal Value As String)
Me._LastName = Value
Raise Event LastNameChanged(Me, EventArgs.Empty)
End Set
End Property
Public Event LastNameChanged As EventHandler
End Class
But how do I do something similar with a one-to-many relationship? Here's what I have so far:
Public Class Organization
Public WithEvents Group As New Group 'A one-to-one relationship
Public Sub PersonAddedToGroup() Handles Group.PersonAdded
MsgBox("A person has been added to the group.") 'This works
End Sub
'I want to do something here when a person's name changes
End Class
Public Class Group
Public WithEvents People As List(Of Person) 'A one-to-many relationship
Public Sub Add(ByVal Person As Person)
Me.People.Add(Person)
RaiseEvent PersonAdded(Me, EventArgs.Empty)
End Sub
Public Event PersonAdded As EventHandler
End Class
Public Class Person
Private _Name As String
Public Property Name As String
Get
Return Me._Name
End Get
Set(ByVal Value As String)
Me._Name = Value
RaiseEvent PersonChanged(Me, EventArgs.Empty)
End Set
End Property
Public Event PersonChanged As EventHandler
End Class
I'd like to handle a PersonChanged event inside Organization. How do I do this?
You'll need to add the event-handler for each person... they can all be handled by the same method though. Here's what I'd suggest.
Change the PersonAdded event to pass the new Person object that was added. You'll need to update where you declared the event/handler to include this, I believe...
'Inside Group.Add(person As Person)
RaiseEvent PersonAdded(Me, person)
In the event handler for PersonAdded, subscribe to the PersonChanged event for that particular person:
Public Sub PersonAddedToGroup(person As Person) Handles Group.PersonAdded
MsgBox("A person has been added to the group.") 'This works
AddHandler person.PersonChanged, AddressOf OnPersonChanged
End Sub
Something like that should accomplish what you want (this is rough code, not testing in VS). If you are adding and removing people, remember that events can lead to memory leaks (i.e. you'll want to call RemoveHandler when the object subscribing to the events goes away. In this case Organization will probably outlast the Person object, so it's not that much of an issue, if I'm not mistaken.
I found an answer from LarsTech. He suggested using a (System.ComponentModel.)BindingList which handles property change events.

Binding control property to user control property

I have a user control that has some public properties (like Dirty :boolean) and an event (ControlValueChanged) that change that property.
I added that control to a form. In the form I have a button (btnOK) and I want to bind the property Enabled of the button to the Dirty property.
I read http://msdn.microsoft.com/en-us/library/ms229614.aspx but I face some problems to implement this to my project.
My code in the form:
btnOK.DataBindings.Add("Enabled", Me.wwdp, "Dirty") 'wwdp is my user Control
So from my research I have to add in my custom control:
Imports System.ComponentModel
Public Class wwDynamicPanel
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
Public Property Dirty As Boolean
Get
Return mbDirty
End Get
Set(ByVal value As Boolean)
mbDirty = value
NotifyPropertyChanged()
End Set
End Property
Private Sub NotifyPropertyChanged(<CallerMemberName()> Optional ByVal propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
I get an error : Type 'CallerMemberName' is not defined.
The thing is that I haven't found in msdn anything more.
I am very sorry. The link in MSDN was for framework 4.5
I found the right http://msdn.microsoft.com/en-us/library/ms184414(v=vs.100).aspx. for my framework
and I solved the problem.
I am leaving the question because someone else find it useful.
So the working code is:
Imports System.ComponentModel
Public Class wwDynamicPanel
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
Public Property Dirty As Boolean
Get
Return mbDirty
End Get
Set(ByVal value As Boolean)
mbDirty = value
NotifyPropertyChanged("Dirty")
End Set
End Property
Private Sub NotifyPropertyChanged(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub