Keep two-way binding when whole object has changed - vb.net

I have a class:
Public Class TestClass
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Sub OnNotifyChanged(ByVal pName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(pName))
End Sub
Private _One As Integer
Private _Two As Integer
Public Sub New(ByVal One As Integer, ByVal Two As Integer)
_One = One
_Two = Two
End Sub
Public Property One() As Integer
Get
Return _One
End Get
Set(ByVal value As Integer)
_One = value
OnNotifyChanged("One")
End Set
End Property
Public Property Two() As Integer
Get
Return _Two
End Get
Set(ByVal value As Integer)
_Two = value
OnNotifyChanged("Two")
End Set
End Property
End Class
I can create an instance and bind two TextBoxes to the object:
Dim MyObject As New TestClass(1, 2)
TextBoxOne.DataBindings.Add("Text", MyObject, "One")
TextBoxTwo.DataBindings.Add("Text", MyObject, "Two")
Now I can change the TextBoxes or the object:
MyObject.Two = 3
..the object and TextBoxes are updated in two ways.
Now I want to update the whole object:
MyObject = New TestClass(3, 4)
...but this doesn't update the TextBoxes.
What am I doing wrong?

Your text boxes hold a reference to the first instance of the object you've created.
Now you're creating a second instance, supposedly in order to replace the existing instance, but the text boxes are unaware of the change.
You needs to either:
Pass the new instance to the text boxes (directly, as you assigned the first instance, or indirectly by having some "Model" object that both boxes are bound to).
Update the existing instance instead of replacing it with a new one (you can simply assign values to the fields, or create some "AssignFrom (other)" method, etc.)
Get yourself some other - more orderly - way of notifiying the controls that their underlying data source has changed / should be changed.

Related

Property Set not fire when deserialize(xml) a list of

I got a class containing integer,bool,string and list (of integer). Each variable that need to be serialize in that class has a public property. When i deserialize my class via XmlSerializer the public property of each variable is call. Except the variable that are list of. The List of varaibles are weel deserialisze but the property setter isn't called.
Here is the property :
Public Property Ana_Offset As List(Of Integer)
Get
Return _Ana_Offset
End Get
Set(value As List(Of Integer))
Tmp_Val = _Ana_Offset
_Ana_Offset = value
RaiseEvent VariableChanged(_Ana_Offset, Tmp_Val, "_Ana_Offset", 0)
End Set
End Property
The class is something like this
<Serializable()> Public Class SACCVar
Private _Code_Produit As String
Private _Ana_Offset As New List(Of Integer)
Public Event VariableChanged(ByVal Val As Object, ByVal Old_Val As Object, desc As String, index As Integer)
End Class
The strange fact i just realise while posting is that i got no Set "event" but the get event is fired wit no data return, but for other variable the get isn't fired..?
Thanks for help

SubmitChanges does not update foreign key to the database

My problem occurs in a Windows Phone 8.1 Silverlight VB application. The CatDataContext defines a table Books with items Title and a foreign key _seriesID, with belongs to a table Series.
<Table()>
Public Class Series
Implements INotifyPropertyChanged, INotifyPropertyChanging
' Define ID: private field, public property, and database column.
Private _seriesID As Integer
<Column(IsPrimaryKey:=True, IsDbGenerated:=True, DbType:="INT NOT NULL Identity", CanBeNull:=False,
AutoSync:=AutoSync.OnInsert)>
Public Property SeriesID() As Integer
Get
Return _seriesID
End Get
Set(ByVal value As Integer)
If _seriesID <> value Then
NotifyPropertyChanging("SeriesID")
_seriesID = value
NotifyPropertyChanged("SeriesID")
End If
End Set
End Property
' Define name: private field, public property, and database column.
Private _Name As String
<Column()>
Public Property Name() As String
Get
Return _Name
End Get
Set(ByVal value As String)
If _Name <> value Then
NotifyPropertyChanging("Name")
_Name = value
NotifyPropertyChanged("Name")
End If
End Set
End Property
#Region "INotifyPropertyChanged Members"
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
' Used to notify that a property changed
Private Sub NotifyPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
#End Region
#Region "INotifyPropertyChanging Members"
Public Event PropertyChanging As PropertyChangingEventHandler Implements INotifyPropertyChanging.PropertyChanging
' Used to notify that a property is about to change
Private Sub NotifyPropertyChanging(ByVal propertyName As String)
RaiseEvent PropertyChanging(Me, New PropertyChangingEventArgs(propertyName))
End Sub
#End Region
End Class
<Table()>
Public Class Book
Implements INotifyPropertyChanged, INotifyPropertyChanging
' Define ID: private field, public property, and database column.
Private _bookID As Integer
<Column(IsPrimaryKey:=True, IsDbGenerated:=True, DbType:="INT NOT NULL Identity", CanBeNull:=False,
AutoSync:=AutoSync.OnInsert)>
Public Property BookID() As Integer
Get
Return _bookID
End Get
Set(ByVal value As Integer)
If _bookID <> value Then
NotifyPropertyChanging("BookID")
_bookID = value
NotifyPropertyChanged("BookID")
End If
End Set
End Property
' Define title: private field, public property, and database column.
Private _title As String
<Column()>
Public Property Title() As String
Get
Return _title
End Get
Set(ByVal value As String)
If _title <> value Then
NotifyPropertyChanging("Title")
_title = value
NotifyPropertyChanged("Title")
End If
End Set
End Property
' Internal column for the associated series ID value.
<Column()>
Friend _seriesID As Integer
Private _series As EntityRef(Of Series)
<Association(Storage:="_series", ThisKey:="_seriesID", OtherKey:="SeriesID")>
Public Property BookSeries() As Series
Get
Return _series.Entity
End Get
Set(ByVal value As Series)
NotifyPropertyChanging("BookSeries")
_series.Entity = value
If value IsNot Nothing Then
_seriesID = value.SeriesID
End If
NotifyPropertyChanged("BookSeries")
End Set
End Property
#Region "INotifyPropertyChanged Members"
Public Event PropertyChanged As PropertyChangedEventHandler Implements NotifyPropertyChanged.PropertyChanged
' Used to notify that a property changed
Private Sub NotifyPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
#End Region
#Region "INotifyPropertyChanging Members"
Public Event PropertyChanging As PropertyChangingEventHandler Implements INotifyPropertyChanging.PropertyChanging
' Used to notify that a property is about to change
Private Sub NotifyPropertyChanging(ByVal propertyName As String)
RaiseEvent PropertyChanging(Me, New PropertyChangingEventArgs(propertyName))
End Sub
#End Region
Updating of the field Title alone, or the fields Title and _seriesID works fine. However when I only change the _seriesID then no update of the underlying database is done. In this case .GetModifiedMembers shows no modifications.
A reference to a demo project showing this problem is given here: demo project.
Thanks for your attention.
_seriesID (in Book) is just a member variable. You can set it from outside Book because it is Friend, but further nothing happens.
Title, on the other hand, is a property that fires NotifyPropertyChanged. That means that the context is notified that the Book object has been modified if you change Title.
So if you change _seriesID and Title, the Book object is marked as modified and saved, along with the changed value of _seriesID. But if you change _seriesID alone, the object remains 'unchanged'.
I think this is generated code (LINQ-to-SQL? I don't really recognize it) and you shouldn't modify it manually. If you want _seriesID to be changed, you have to set BookSeries.

How to do a collection property inside another collection in user control

I have a user control with a property "Rules" that is a generic list.
Every "rule" is associated to a combobox control and i have to create a property to host data for the combobox. I used another generic list to accomplish this.
In design works well, i can add items normally in property grid, but when i run the program the values are not maintained.
Rules property:
Private _regras As New List(Of ParametrosColunasGrid)
<Category("Ecletica")> _
<Browsable(True)> _
<System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Content)>
Public Property Regras() As List(Of ParametrosColunasGrid)
Get
Return _regras
End Get
Set(value As List(Of ParametrosColunasGrid))
_regras = value
End Set
End Property
Public Class ParametrosColunasGrid
'...
Private _itens_Combo As New List(Of ItemComboBox)
<DesignerSerializationVisibility(DesignerSerializationVisibility.Content)>
Public Property ItensCombo As List(Of ItemComboBox)
Get
Return _itens_Combo
End Get
Set(value As List(Of ItemComboBox))
_itens_Combo = value
End Set
End Property
'...
End Class
ItemComboBox Class:
<Serializable()>
Public Class ItemComboBox
Public Property Value As String
Public Property Key As String
Public Overrides Function ToString() As String
Return _Value
End Function
End Class

Datagridview bound to object - Making the new rows appear blank

I have a Datagridview that changes its content according to a selection the user makes in a listBox.
The DGV consits of 2 comboboxes (Country, Product) and 1 textbox (Quantity).
I've created a class combined of 3 integers.
This class is used as a type of list, which is the datasource for the DGV.
There is also another list containing the prior list, so I have a list of datasources.
The DGV's datasource is a BindingSource that changes whenever the SelectedIndex of the listBox is fired.
My problem occurs whenever a new row is added to the DGV:
I use the BindingSource.AddNew which calls the constructor of the class, but it must assign values to each item in the class. That way, whenever I click any cell in the DGV I don't get a blank row.
Moreover, when the BS changes and then returned, another row is added.
What I want to get is a blank row - empty comboboxes and textbox.
Thanks for your help!
The class:
Public Class PoList
Private _CountryID As Integer
Private _ProductID As Integer
Private _Quantity As Integer
Public Sub New(ByVal CountryID As Integer, ByVal ProductID As Integer, ByVal Quantity As Integer)
_CountryID = CountryID
_ProductID = ProductID
_Quantity = Quantity
End Sub
Private Sub New()
_CountryID = 1
_ProductID = 2
_Quantity = Nothing
End Sub
Public Property CountryID() As Integer
Get
Return _CountryID
End Get
Set(ByVal value As Integer)
_CountryID = value
End Set
End Property
Public Property ProductID() As Integer
Get
Return _ProductID
End Get
Set(ByVal value As Integer)
_ProductID = value
End Set
End Property
Public Property Quantity() As Integer
Get
Return _Quantity
End Get
Set(ByVal value As Integer)
_Quantity = value
End Set
End Property
Public Shared Function CreateNewPoList() As PoList
Return New PoList
End Function
End Class
Private Sub List_AddRow(ByVal sender As Object, ByVal e As AddingNewEventArgs) Handles AllListBindingSource.AddingNew
e.NewObject = PoList.CreateNewPoList
End Sub
Creating a new inner list:
AllList.Add(New List(Of PoList))
AllListBindingSource.AddNew()
AllListBindingSource.DataSource = AllList(TableCounter)
AddPoDetails.DataSource = AllListBindingSource
SelectedIndexChanged event:
AllListBindingSource.DataSource = AllList(AddPoList.SelectedIndex)
AddPoDetails.DataSource = Nothing
AddPoDetails.DataSource = AllListBindingSource
Right, lets see if I can help you.
As I interpret it you have a list filled with lists. These lists don't know their own identity and is based on the current index in the list.
First of I wouldn't use Bindingsource.AddNew I would add the new object straight to the list instead.
AllList(TableCounter).Add(New Polist())
This way you know exactly how many objects has been created, by using events you aren't quite sure are you.
To refresh the list do this:
AllListBindingSource.ResetBindings(true)
Which will update your DGV with the new line.
Now you need to restructure your class since when you create a new Polist you set a value to nothing. This will crash your table. What you need to do is this:
Private _Quantity As String
Public Property Quantity() As String
Get
Return _Quantity
End Get
Set(ByVal value As String)
_Quantity = value
End Set
End Property
Using a string is the only way for you to get a blank textbox, I would recommend you to have 0 as default if you are using Quantity as an integer (which you should). Your constructor needs to be changed to this:
Private Sub New()
_CountryID = 0
_ProductID = 0
_Quantity = ""
End Sub
In your combobox columns you have to add a blank item in the top (I'm guessing your adding them manually), should be possible by having a blank row in the top of the items.

Generic Collections

In VB6, there used to be a Collection data type that would allow retrieval of an item in the collection by either its key or its ordinal. However, it wasn't strongly typed.
Now, with VB.Net, I am looking for a suitable replacement that is strongly type and could be used with a generic collection.
This is a simple example of what I want to do. The only problem is that the underlying collection class, BindingList, does not support efficient retrieval of an item by an alpha key, so I have to loop through the elements to get the item I am looking for. For large collections, this is not efficient.
I have looked though the various Collection type classes and have found no suitable replacement.
This is what I want to do, except for the looping that is done with the Item property.
Rather than just saying "Use Hash tables" or something like that, if you could, please include the detailed out as I have done for the short example below.
Public Class Car
Public Sub New(ByVal keyName As String, ByVal property1 As String)
_KeyName = keyName
_Property1 = property1
End Sub
Dim _KeyName As String
Public Property KeyName() As String
Get
Return _KeyName
End Get
Set(ByVal value As String)
_KeyName = value
End Set
End Property
Public _Property1 As String
Public Property Property1() As String
Get
Return _Property1
End Get
Set(ByVal value As String)
_Property1 = value
End Set
End Property
End Class
Public Class Cars
Inherits System.ComponentModel.BindingList(Of Car)
Public Overloads ReadOnly Property Item(ByVal key As String) As Car
Get
For Each CurrentCar As Car In Me.Items
If CurrentCar.KeyName = key Then
Return CurrentCar
End If
Next
Return Nothing
End Get
End Property
End Class
I believe you're looking for Dictionary<TKey, TValue>. In fact, if you do want your own collection class that's strongly typed and isn't (itself) generic, if you change your parent class to Dictionary<string, Car>, you should be all set. This all does, of course, assume that you add the cars to the collection with an explicit string key. If you want the lookup to be based on the value of a property in the collection, you'd do better either using or inheriting from List<Car> and using LINQ to query the list. You could then have...
Public Overloads ReadOnly Property Item(ByVal key As String) As Car
Get
Return (from c in Me where c.KeyName = key select c).SingleOrDefault()
End Get
End Property
Do you really need both access by key AND index?
If you do not, then use a Dictionary(Of String, Car), and use
- MyCol.Items("XXX") to retrieve an item by key (or the shorthand MyCol("XXX"))
- MyCol.ContainsKey("XXX") to test if a key exists in the collection
- For Each Entry as KeyValuePair(Of String, Car) in MyCol if you want to enumerate all objects AND their key
- For Each Entry as Car in MyCol.Values if you want to enumerate the entries without consideration for the key
If you need both access by index and key, I'm afraid your best bet is to use a List(of Car) and a Dictionary(of Car) rolled into one custom collection class, because I believe they went away from that kind of collection which is not really all that useful for most problems.
This is what I am thinking is my best solution. I welcome comments for a better way!
Imports Microsoft.VisualBasic
Public Class Car
Implements Xs(Of Car).IKeyName
Private _KeyName As String
Public Sub New(ByVal keyName As String, ByVal property1 As String)
_KeyName = keyName
_Property1 = property1
End Sub
Public Property KeyName() As String Implements Xs(Of Car).IKeyName.KeyName
Get
Return _KeyName
End Get
Set(ByVal value As String)
_KeyName = value
End Set
End Property
Public _Property1 As String
Public Property Property1() As String
Get
Return _Property1
End Get
Set(ByVal value As String)
_Property1 = value
End Set
End Property
End Class
Public Class Cars
Inherits System.ComponentModel.BindingList(Of Car)
Public Overloads ReadOnly Property Item(ByVal key As String) As Car
Get
For Each CurrentCar As Car In Me.Items
If CurrentCar.KeyName = key Then
Return CurrentCar
End If
Next
Return Nothing
End Get
End Property
End Class
Public Class X
Private _KeyName As String
Public Property KeyName() As String
Get
Return _Keyname
End Get
Set(ByVal value As String)
_Keyname = value
End Set
End Property
End Class
Public Class Xs(Of X)
Inherits Hashtable
Interface IKeyName
Property KeyName() As String
End Interface
Public Shadows Sub Add(ByVal item As IKeyName)
MyBase.Add(item.KeyName, item)
End Sub
Public Shadows ReadOnly Property Item(ByVal key As String) As x
Get
If Me.ContainsKey(key) Then
Return MyBase.Item(key)
Else
'If I mispell a key, I don't want to end up creating a new mispelled version, I want an error
Throw New Exception("Element with key " & key & " is not found")
End If
End Get
End Property
End Class
Public Class Cars2
Inherits Xs(Of Car)
End Class
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim MyCar As New Car("key1", "prop1")
'First approach
Dim MyCars As New Cars
MyCars.Add(MyCar)
Dim RetrievedCar As Car = MyCars.Item("key1") 'Inefficient retrieval by key (uses looping)
'Second approach
Dim Cars2 As New Cars2
Cars2.Add(MyCar)
Dim RetrievedCar2 As Car = Cars2.Item("key1") 'Can now efficiently retreive an item by its key
End Sub
The OrderedDictionary in the System.Collections.Specialized namespace can be accessed by index and by key, if you ever need that. But looking at your solution, it looks like a standard Dictionary, but less efficient because it forces a string type for keys.
Is there any reason you can't use the Dictionary .NET provides you, or another collection type that's already in .NET like OrderedDictionary?