"Replacing" an object in a List(Of Object)

  • Thread starter Thread starter BobRoyAce
  • Start date Start date
B

BobRoyAce

Let's say that I have two classes: Franchise and FranchiseOwner.
Suppose that the Franchise class has a property, called
FranchiseOwners, which is a List(Of FranchiseOwner).

Now, suppose that I have a form, with a module-level object variable
of type Franchise, called m_oFranchise. Let's say that it has 3
Franchise Owners in it's FranchiseOwners property (i.e.
FranchiseOwners.Count = 3). The user selects one of the Franchise
Owners in a grid and then clicks on a button to EDIT them. This brings
up another form where the user can edit the Franchise Owner's
information and then click to save the changes. This edit form has a
readonly property that contains the edited FranchiseOwner.

If I was adding a new FranchiseOwner, I would simply add the
FranchiseOwner entered on the form to the FranchiseOwners list
(via .Add method) However, in this scenario, I am actually allowing
the user to update a FranchiseOwner already in the list.

My question relates to how I go about replacing the current
FranchiseOwner in the FranchiseOwners list with the one that was
entered on the edit form. How would I do that?
 
My question relates to how I go about replacing the current
FranchiseOwner in the FranchiseOwners list with the one that was
entered on the edit form. How would I do that?
I am not sure that I follow you completely, but normally you do not have to
do anything - you edit the object and that's it. As long as you are passing
a reference to the object to be edited (which is the normal way to do it) to
the form, just do your edits and close the form. The list will have the
updated object automatically, since the one on the edit form and the one in
the list are one and the same.

Lars
 
Lars said:
I am not sure that I follow you completely, but normally you do not have
to do anything - you edit the object and that's it. As long as you are
passing a reference to the object to be edited (which is the normal way to
do it) to the form, just do your edits and close the form. The list will
have the updated object automatically, since the one on the edit form and
the one in the list are one and the same.

Or, if your architecture requires a check to see if they are editing a
current record instead of adding a new one, you could loop through all the
objects in the collection until you find the one that matches the one they
want to edit and then alter it

For Each objFranchiseOwner as FranchiseOwner in FranchiseOwners
If objFranchiseOwner.Id = TheirFranchiseOwnerObject.Id Then
objFranchiseOwner = TheirFranchiseOwnerObject
Exit For
End If
Next

But if you know they are going to edit an object in the collection then use
Lars' method. Pull out a reference to the correct object in the collection
and allow the user to make changes directly against it.
 
The problem is that the user could CANCEL their changes. So, for
example, consider the following scenario:
User selects Franchise Owner on main form and clicks on button to
edit.
User makes changes to Franchise Owner on edit form, with the
underlying FranchiseOwner object being updated to reflect those
changes.
User tries to "save" changes, but the FranchiseOwner is not valid,
so can't.
User changes their mind, deciding to not make changes after all.

If I pass the FranchiseOwner object ByRef, the new values will be in
the originally selected FranchiseOwner's properties (the one from the
main form).

So, I was thinking that I would pass a copy of the FranchiseOwner, let
the user change things as they wish, and then only update the
FranchiseOwner on the main form if their changes are valid and they
don't decide to throw them out.

Any ideas?
 
BobRoyAce said:
The problem is that the user could CANCEL their changes. So, for
example, consider the following scenario:
User selects Franchise Owner on main form and clicks on button to
edit.
User makes changes to Franchise Owner on edit form, with the
underlying FranchiseOwner object being updated to reflect those
changes.
User tries to "save" changes, but the FranchiseOwner is not valid,
so can't.
User changes their mind, deciding to not make changes after all.

If I pass the FranchiseOwner object ByRef, the new values will be in
the originally selected FranchiseOwner's properties (the one from the
main form).

So, I was thinking that I would pass a copy of the FranchiseOwner, let
the user change things as they wish, and then only update the
FranchiseOwner on the main form if their changes are valid and they
don't decide to throw them out.

Use the solution I posted then. Let them create a new object and validate
it, and if it passes your checks then update the corresponding object in the
collection
 
Use the solution I posted then. Let them create a new object and validate
it, and if it passes your checks then update the corresponding object in the
collection

OK, yea, I see now how your solution works now. Thanks...
 
You could also implement IEditable on your business class. This enables you
to do a StartEdit when they start changing it, and a CancelEdit to roll it
back.

RobinS.
--------------------------
 
You could also implement IEditable on your business class. This enables you
to do a StartEdit when they start changing it, and a CancelEdit to roll it
back.

I've never heard of IEditable, but it sounds very interesting. Could
you point me to how I would implement something like that in VB?
 
BobRoyAce said:
I've never heard of IEditable, but it sounds very interesting. Could
you point me to how I would implement something like that in VB?

I will post an example this weekend, assuming you're still interested. I
have an example in VB; I changed computers and have to track it down...

RobinS.
 
I will post an example this weekend, assuming you're still interested. I
have an example in VB; I changed computers and have to track it down...

I would appreciate that...thanks. I don't seem to be able to get Lars
solution to work. I created a simpler test, coded as follows:

Dim oList As New MerchantList
Dim oMerchant As Merchant

' Add five Merchants to the listBoxControl1
For i As Integer = 1 To 5
oMerchant = New Merchant
oMerchant.pkMerchantID = i
oMerchant.MerchantLegalBusinessName = "Merchant #" & i.ToString
oList.Merchants.Add(oMerchant)
Next

' Report of BEFORE results...
Dim sTemp As String = "BEFORE: " & vbCrLf & "------" & vbCrLf
For Each oMerchant In oList.Merchants
sTemp += oMerchant.pkMerchantID.ToString & ": " &
oMerchant.MerchantLegalBusinessName & vbCrLf
Next

' Create a NEW Merchant
Dim oNewMerchant As New Merchant
oNewMerchant.pkMerchantID = 3
oNewMerchant.MerchantLegalBusinessName = "Merchant Three (NEW)"

' Report NEW Merchant...
sTemp += vbCrLf & "NEW: " & oNewMerchant.pkMerchantID.ToString &
": " & oNewMerchant.MerchantLegalBusinessName & vbCrLf

' Find NEW Merchant in list and replace one with matching
pkMerchantID
For Each oMerchant In oList.Merchants
If (oNewMerchant.pkMerchantID = oMerchant.pkMerchantID) Then
oMerchant = oNewMerchant
Exit For
End If
Next

'Report AFTER results...
sTemp += vbCrLf & "AFTER:" & vbCrLf & "-----" & vbCrLf
For Each oMerchant In oList.Merchants
sTemp += oMerchant.pkMerchantID.ToString & ": " &
oMerchant.MerchantLegalBusinessName & vbCrLf
Next

Even this simple test did not work as can be seen by the results
reported below:

BEFORE:
------
1: Merchant #1
2: Merchant #2
3: Merchant #3
4: Merchant #4
5: Merchant #5

NEW: 3: Merchant Three (NEW)

AFTER:
-----
1: Merchant #1
2: Merchant #2
3: Merchant #3
4: Merchant #4
5: Merchant #5
 
' Find NEW Merchant in list and replace one with matching
pkMerchantID
For Each oMerchant In oList.Merchants
If (oNewMerchant.pkMerchantID = oMerchant.pkMerchantID) Then
oMerchant = oNewMerchant
Exit For
End If
Next

Lars meant that you could modify the properties of the existing object
in the list.

Each time through the loop, the variable oMerchant is set to a
reference to an item in oList. There are then two references to the
object, one help by oMerchant and the other held by the list. When
you change oMerchant, you are changing only oMerchant, not the
reference held by the list.

If you were to change the properties of the object, i.e.:
oMerchant.MerchantLegalBusinessName = "Merchant Three (NEW)"
then you would be changing the property of the existing object and you
would see the change when you enumerated the list.

You could also remove the existing object and add the new one. If
oList is an ArrayList you could do something like:

For Each oMerchant In oList.Merchants
If (oNewMerchant.pkMerchantID = oMerchant.pkMerchantID) Then
oList.Remove(oMerchant)
oList.Add(oNewMerchant)
Exit For
End If
Next
 
RobinS said:
I will post an example this weekend, assuming you're still interested. I
have an example in VB; I changed computers and have to track it down...

RobinS.

This class shows how to implement iEditable. You also need to implement
iCloneable. I use my own method (CloneMe) that calls the Clone method; this
is a pattern I use because not all of my classes are shallow, and sometimes
I have to use a different method to do my cloning.

If you are looking to bind a list of business objects to a DataGridView, I
know how to do that. When you do that, it uses the iEditable methods
automatically.

When the user edits any of the data, you call BeginEdit.
If the user rolls it back, you call CancelEdit.
When the user hits Save, you call EndEdit.
If you bind through a binding source, IIRC it calls these methods properly.

-----------------------------
Imports System.ComponentModel

Public Class Product
Implements IEditableObject, ICloneable

Private _editing As Boolean
Private Shared _loading As Boolean

#Region " Constructors"

'you must have a public constructor for the dgv to add a record
Public Sub New()
If Not _loading Then
Me.SetDefaults()
End If
End Sub

Private Sub SetDefaults()
ProductID = 0
ProductName = String.Empty
End Sub

#End Region

#Region " Public Properties"

Private _lastProduct As Product
''' <summary>
''' this is the info for the Product before the user started editing it
''' when they start editing, it clones the Product instance
''' </summary>
Public Property lastProduct() As Product
Get
Return _lastProduct
End Get
Set(ByVal value As Product)
_lastProduct = value
End Set
End Property

Private _originalProduct As Product
''' <summary>
''' when you create a new instance, save a copy of the original.
''' This would be used (for examle) on the Grid screen. If they modify
one,
''' and then delete it, you need the original primary key in order to do
the actual delete.
''' </summary>
Public Property originalProduct() As Product
Get
Return _originalProduct
End Get
Set(ByVal value As Product)
_originalProduct = value
End Set
End Property


Private _ProductID As Integer
''' <summary>
''' Product ID
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property ProductID() As Integer
Get
Return _ProductID
End Get
Private Set(ByVal value As Integer)
_ProductID = value
End Set
End Property

Private _ProductName As String
''' <summary>
''' Name of the product.
''' </summary>
''' <value>ProductName</value>
''' <returns>ProductName</returns>
''' <remarks></remarks>
Public Property ProductName() As String
Get
Return _ProductName
End Get
Set(ByVal value As String)
If _ProductName <> value Then
Dim propertyName As String = "ProductName"
ValidationInstance.ValidateClear(propertyName)
ValidationInstance.ValidateRequired(propertyName, value)
ValidationInstance.ValidateLength(propertyName, value, 24)
Me.DataStateChanged(EntityStateEnum.Modified)
_ProductName = value
End If
End Set
End Property

#End Region

#Region " Public Methods"

'methods Create, Save, Delete removed for brevity

#End Region

#Region "iEditable"

Public Sub BeginEdit() Implements
System.ComponentModel.IEditableObject.BeginEdit
If Not _editing Then
_loading = True 'set this so it won't initialize it because
you're going to clone it anyway.
lastProduct = Me.CloneMe()
End If
_editing = True

End Sub

'roll back the changes by setting the current property values back to
the original
'also need to roll back the Views information
Public Sub CancelEdit() Implements
System.ComponentModel.IEditableObject.CancelEdit
If _editing Then
'You can't do this because [me] is read-only. So you have to set
each and every stinking property.
'this = (Plan)(this.LastPlan.Clone());
ProductID = lastProduct.ProductID
ProductName = lastProduct.ProductName
End If
_editing = False
End Sub

''' <summary>
''' accept the current values by setting the editing flag back to false
''' this happens when they go to a different row or close the screen
''' </summary>
''' <remarks></remarks>
Public Sub EndEdit() Implements
System.ComponentModel.IEditableObject.EndEdit
_editing = False
End Sub

#End Region

#Region "iCloneable"

Public Function CloneMe() As Product
Dim prod As Product = New Product()
prod = DirectCast(Me.Clone, Product)
Return prod
End Function


Public Function Clone() As Object Implements System.ICloneable.Clone
Return Me.MemberwiseClone()
End Function

#End Region

End Class
 
Public Property ProductName() As String
Get
Return _ProductName
End Get
Set(ByVal value As String)
If _ProductName <> value Then
Dim propertyName As String = "ProductName"
ValidationInstance.ValidateClear(propertyName)
ValidationInstance.ValidateRequired(propertyName, value)
ValidationInstance.ValidateLength(propertyName, value, 24)
Me.DataStateChanged(EntityStateEnum.Modified)
_ProductName = value
End If
End Set
End Property

Thanks a lot Robin. I appreciate the example.

By the way, what's ValidationInstance (e.g.
ValidationInstance.ValidateClear)? Also, what's DataStateChanged (e.g.
Me.DataStateChanged(EntityStateEnum.Modified)? Lastly, what could you
show me values of EntityStateEnum?
 
BobRoyAce said:
Thanks a lot Robin. I appreciate the example.

By the way, what's ValidationInstance (e.g.
ValidationInstance.ValidateClear)? Also, what's DataStateChanged (e.g.
Me.DataStateChanged(EntityStateEnum.Modified)? Lastly, what could you
show me values of EntityStateEnum?

Oh, sorry! Those are part of my base class used for all of my business
objects. ValidationClear clears any errors on the instance. ValidateRequired
and ValidateLength are two methods that make sure the required fields are
filled in, and none are too long. This is used in conjunction with the
ErrorProvider on the form to flag which fields have errors.

The Me.DataStateChanged is a property in my base class that sets whether the
instance has been modified, or it's being added or deleted. The values are
Unchanged, Added, Deleted, and Modified. There DataStateChanged method is
called in the property set method. This is akin to the data table rowstate
property.

I am using a List(Of Product) to show my list in a grid, and tracking the
rows that are changed via the DataStateChanged, then updating all of them
when the user hits Save. I have the same list bound to a show-one-at-a-time
form, and by checking the data state of the current instance, I can tell if
it has been modified or not before letting the user navigate to a new
record.

At one point, I tried implementing INotifyPropertyChanged, but I couldn't
figure out how to capture the event.

The validation and DataState stuff came out of Deborah Kurata's "Doing
Objects in VB2005" book, which shows you how to set up your business
classes, a DataAccess layer, and the UI layer, and interact between all of
them. That was the basis for this code. It's a great book, and I learned a
lot from it.

I expanded on her project and decided to do a grid and implement iEditable
to see how it worked.

Let me know if you have any more q's.

RobinS.
GoldMail, Inc.
 
Back
Top