Rebinding of Child Collection

Rebinding of Child Collection

Old forum URL: forums.lhotka.net/forums/t/3525.aspx


culprit posted on Wednesday, September 12, 2007

Dear List:

Another poser. I am implementing an invoice form with child details. Everything works great until I save and rebind then the child seems to lose connection because it doesnt fire validation checking and no longer fires the PropertyChanged event on the parent Invoice object. I have a parent object, CurrentInvoice, which is assigned to InvoiceBindingSource and InvoiceDetails property assigned to InvoiceDetailsBindingSource. I am following the code in the new 3.0 projecttracker but am having difficulty after clicking an Apply button. Here is the code in the applicable areas:

Public Sub New(ByVal Invoice As Invoice)

' This call is required by the Windows Form Designer.

InitializeComponent()

' Add any initialization after the InitializeComponent() call.

CurrentInvoice = Invoice

'_patientID = CurrentInvoice.PatientID

BindUI()

ApplyAuthorizationRules()

Me.btnSave.Enabled = False

Me.btnApply.Enabled = False

End Sub

Public Sub ApplyAuthorizationRules() Implements RSSoftware.ScriptAssist.ObjectEdit.ApplyAuthorizationRules

' have the controls enable/disable/etc

Me.ReadWriteAuthorization1.ResetControlAuthorization()

Dim canEdit As Boolean = ScriptAssist.Invoice.CanEditObject

btnApply.Enabled = canEdit

Me.btnSave.Enabled = canEdit

Me.GridView1.OptionsBehavior.Editable = canEdit

' enable/disable appropriate buttons

' enable/disable role column in grid

End Sub

Public Sub BindUI() Implements RSSoftware.ScriptAssist.ObjectEdit.BindUI

CurrentInvoice.BeginEdit()

Me.InvoiceBindingSource.DataSource = CurrentInvoice

End Sub

Public Function RebindUI(ByVal Save As Boolean, ByVal Rebind As Boolean) As Boolean Implements RSSoftware.ScriptAssist.ObjectEdit.RebindUI

' disable events

Me.InvoiceBindingSource.RaiseListChangedEvents = False

Me.InvoiceDetailsBindingSource.RaiseListChangedEvents = False

Try

' unbind the UI

ScriptAssist.Utilities.UnbindBindingSource(Me.InvoiceDetailsBindingSource, Save, False)

ScriptAssist.Utilities.UnbindBindingSource(Me.InvoiceBindingSource, Save, True)

Me.InvoiceDetailsBindingSource.DataSource = Me.InvoiceBindingSource

' save or cancel changes

If Save Then

CurrentInvoice.ApplyEdit()

Try

Dim temp As ScriptAssist.Invoice = CurrentInvoice.Clone()

CurrentInvoice = temp.Save()

Catch ex As Csla.DataPortalException When TypeOf ex.BusinessException Is SqlClient.SqlException

If CType(ex.BusinessException, SqlClient.SqlException).Class = 16 Then

'Most likely due to concurrency error - recommend closing drug and reopening

MessageBox.Show("This record has been changed in another window either on this machine or " _

& Microsoft.VisualBasic.ControlChars.CrLf & "on another client machine while this record was open. Click OK, make a note" _

& Microsoft.VisualBasic.ControlChars.CrLf & "of any changes you have made here you wish saved for this record, close this" _

& Microsoft.VisualBasic.ControlChars.CrLf & "window, the reopen this record in a new window and make the desired changes " _

& Microsoft.VisualBasic.ControlChars.CrLf & "finally saving the record.", "CONCURRENCY ERROR", MessageBoxButtons.OK, MessageBoxIcon.Stop)

Else

MessageBox.Show(ex.BusinessException.ToString, _

"Error saving", MessageBoxButtons.OK, _

MessageBoxIcon.Exclamation)

End If

Catch ex As Csla.DataPortalException

MessageBox.Show(ex.BusinessException.ToString, _

"Error saving", MessageBoxButtons.OK, _

MessageBoxIcon.Exclamation)

Catch ex As Csla.Validation.ValidationException

Dim sMsg As New System.Text.StringBuilder("One or more fields have failed validation. Refer to any errors")

sMsg.AppendLine(" on this form and the following list: ")

sMsg.AppendLine()

For Each BrokenRule As Csla.Validation.BrokenRule In CurrentInvoice.BrokenRulesCollection

sMsg.AppendLine(" " & BrokenRule.Description)

Next

MessageBox.Show(sMsg.ToString, "VALIDATION ERROR", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)

Catch ex As Exception

MessageBox.Show(ex.ToString, _

"Error saving", MessageBoxButtons.OK, _

MessageBoxIcon.Exclamation)

End Try

Else

CurrentInvoice.CancelEdit()

End If

Finally

' rebind UI if requested

If Rebind Then

BindUI()

End If

' restore events

Me.InvoiceBindingSource.RaiseListChangedEvents = True

Me.InvoiceDetailsBindingSource.RaiseListChangedEvents = True

If Rebind Then

' refresh the UI if rebinding

Me.InvoiceBindingSource.ResetBindings(False)

Me.InvoiceDetailsBindingSource.ResetBindings(False)

End If

End Try

If CurrentInvoice.IsDirty = False And CurrentInvoice.IsValid = True Then

Return True

Else

Return False

End If

End Function

Your help and guidance is greatly appreciated.

Rob

RockfordLhotka replied on Wednesday, September 12, 2007

CSLA automatically rehooks the PropertyChanged event so your list gets the event and raises a ListChanged event. It can do this because BusinessListBase manages the ownership of those child objects - it controls the references.

BusinessBase does not manage the reference from your root object to its children. You declare that field in your code, and so you are responsible for its management. This includes handling the ListChanged event so you know to raise the root's PropertyChanged event.

ProjectTracker doesn't do that, because the root object has no properties that derive from the state of its children. At some point I should probably add some data like that, so I can illustrate the concept.

However, you clearly have code to hook the event right? All you need to do is call that code when the root gets deserialized - which means overriding the OnDeserialized method that is declared by BusinessBase and rehooking the ListChanged event at that point.

culprit replied on Wednesday, September 12, 2007

Sweet, thanks Rocky.

Here is that overridden sub:

Protected Overrides Sub OnDeserialized(ByVal context As System.Runtime.Serialization.StreamingContext)

MyBase.OnDeserialized(context)

AddHandler _invoiceDetails.ListChanged, AddressOf _invoiceDetails_ListChanged

End Sub

xal replied on Wednesday, September 12, 2007

Looking at that long code listing, there's one thing that called my attention:

ScriptAssist.Utilities.UnbindBindingSource(Me.InvoiceDetailsBindingSource, Save, False)

ScriptAssist.Utilities.UnbindBindingSource(Me.InvoiceBindingSource, Save, True)

Me.InvoiceDetailsBindingSource.DataSource = Me.InvoiceBindingSource


What do those methods do?
In general you want to unbind just the root binding source, since the rest of them are bound to that one.
The binding source & bound controls don't play nice if you pass "Nothing" as the datasource to a binding source, but instead of doing whatever your UnbindBindingSource is doing, try calling the appropriate EndEdit (s) and then do:
InvoiceBindingSource.DataSource = GetType(Invoice)

That effectively clears the current datasource from the "root" binding source and nothing crashes.
Then you only have to rewire the root binding source when you're done saving with one simple line:
InvoiceBindingSource.DataSource = CurrentInvoice.

I hope that helps.

Andrés

Copyright (c) Marimer LLC