OK, I've managed to successfully write a quick EditPerson form for Windows Forms using CSLA4.2.2 - so I'm now making progress.
I'm using the EncapInvoke model.
My first test was using a MockDb and I am able to list, edit and add new records in my person "table".
So, my next step was to create the DataAccess layer for a SQL Server Express implementation - as this is my core development background.
Everything works - until I try to Save a changed, or new record. I get an exception;
System.InvalidOperationException = {"Object is still being edited and can not be saved"} StackTrace = " at Csla.BusinessBase`1.Save() in C:\Visual Studio Projects\csla\40\Source\Csla\BusinessBase.cs:line 126 at Intro.EditPerson.SaveRecord(Boolean rebind)"
Any ideas? The code in the client is exactly the same regardless of the Dal I'm using, so I'm thinking the issue lies within CSLA rather than my code... but I'm sure someone would have heard of it by now?
This is my "SaveRecord" code:
Private Sub SaveRecord(ByVal rebind As Boolean)
Using busy As New StatusBusy("Saving...")
' stop the flow of events
Me.PersonEditBindingSource.RaiseListChangedEvents = False
' do the save
Dim temp As PersonEdit = mPERSONEDIT.Clone
temp.ApplyEdit()
Try
mPERSONEDIT = temp.Save '<------------- error's at this point
PersonEdit.BeginEdit()
If rebind Then
' rebind the UI
With Me.PersonEditBindingSource
.DataSource = Nothing
.RaiseListChangedEvents = True ' this is the key to updating children
.DataSource = mPERSONEDIT
End With
ApplyAuthorizationRules()
End If
Catch ex As Csla.DataPortalException
MessageBox.Show(ex.BusinessException.ToString, _
"Error saving", MessageBoxButtons.OK, _
MessageBoxIcon.Exclamation)
Catch ex As Exception
MessageBox.Show(ex.ToString, _
"Error saving", MessageBoxButtons.OK, _
MessageBoxIcon.Exclamation)
Finally
Me.PersonEditBindingSource.RaiseListChangedEvents = True
End Try
End Using
End Sub
Feedback would be appreciated.
Thanks,
Graham
You must unbind the object completely from the UI. This is described in Rocky's books.
And you do not have to make a clone in your code - this is done automatically by CSLA DataPortal.
These extensions is translated from CslaContrib to vb.net:
Public NotInheritable Class BindingSourceExtensions
Private Sub New()
End Sub
''' <summary>
''' Unbind the binding source and the Data object. Use this Method to safely disconnect the data object from a BindingSource before saving data.
''' </summary>
''' <param name="source">The source.</param>
''' <param name="cancel">if set to <c>true</c> then call CancelEdit else call EndEdit.</param>
''' <param name="isRoot">if set to <c>true</c> this BindingSource contains the Root object. Set to <c>false</c> for nested BindingSources</param>
<System.Runtime.CompilerServices.Extension> _
Public Shared Sub Unbind(source As BindingSource, cancel As Boolean, isRoot As Boolean)
Dim current As IEditableObject = Nothing
' position may be -1 when bindigsource is already unbound
' and accessing source.Current will then throw Exception.
If (source.DataSource IsNot Nothing) AndAlso (source.Position > -1) Then
current = TryCast(source.Current, IEditableObject)
End If
' set Raise list changed to True
source.RaiseListChangedEvents = False
' tell currency manager to suspend binding
source.SuspendBinding()
If isRoot Then
source.DataSource = Nothing
End If
If current Is Nothing Then
Return
End If
If cancel Then
current.CancelEdit()
Else
current.EndEdit()
End If
End Sub
''' <summary>
''' Rebind the binding source and business object.
''' </summary>
''' <param name="source">The source.</param>
''' <param name="data">The data object.</param>
<System.Runtime.CompilerServices.Extension> _
Public Shared Sub Rebind(source As BindingSource, data As Object)
source.Rebind(data, False)
End Sub
''' <summary>
''' Rebind the binding source and business object.
''' </summary>
''' <param name="source">The source.</param>
''' <param name="data">The data object.</param>
''' <param name="metadataChanged">if set to <c>true</c> then metadata (type) was changed.</param>
<System.Runtime.CompilerServices.Extension> _
Public Shared Sub Rebind(source As BindingSource, data As Object, metadataChanged As Boolean)
source.RaiseListChangedEvents = True
source.SuspendBinding()
If data IsNot Nothing Then
source.DataSource = data
End If
' set Raise list changed to True
source.RaiseListChangedEvents = True
' tell currency manager to resume binding
source.ResumeBinding()
' Notify UI controls that the dataobject/list was reset - but metadata was not changed
source.ResetBindings(metadataChanged)
End Sub
End Class
So you save code should be something like this:
Using busy As New StatusBusy("Saving...")
' unbind from bindingsource
Me.PersonEditBindingSource.Unbind(False, True)
Try
mPERSONEDIT = mPERSONEDIT .Save
.......
and make sure to rebind your bo before you leave the method:
Me.PersonEditBindingSource.Rebind(mPERSONEDIT)
Just when I thought I was getting to grips...... that's confused me completely.
My code was working code for CSLA2.1 - and now it's breaking code? You'll excuse me if I missed whatever reference was made to unbinding an object - but there was no WindowsForms book - and as this code previously worked, you'll understand why I'm not clear that it no longer works.
There is no Unbind method for the .Net BindingSource - how is that implemented in my .Net code?
I don't understand why it would be worrking code for one Dal and not another...
My form currently inherits from what was previously WinPart in the CSLA implemetation. Are you saying there is a new helper control that I should be inheriting from?
Graham
Unbind/Rebind is implemented in the BindingSourceExtensions in my previous post.
Binding/Unbinding is described in the Using CSLA 3.0 ebook - also available for VB.
I also have a blog post on Bind/Rebind here:
http://jonnybekkum.wordpress.com/2009/10/20/forms-databinding-the-magic-sequence-of-binduiunbindui/
Thanks Jonny.
I downloaded the WfUI from SVN as discussed in a previous post and it shows clearly the implementation - thanks.
I do have an issue with Closing a Winpart...
In say my PersonEdit (Winpart) form, I close The form,
But when the Mainform tries to close the Winpart, it blows up on:
Private Sub CloseWinPart(ByVal sender As Object, ByVal e As EventArgs)
Dim part As WinPart = DirectCast(sender, WinPart)
RemoveHandler part.CloseWinPart, AddressOf CloseWinPart
part.Visible = False
SplitContainer1.Panel2.Controls.Remove(part) '<----- Blows up here
part.Dispose()
"Cannot bind to the property or column Id on the DataSource.
Parameter name: dataMember"
I can alleviate that issue by Rebinding after all events - such as Save and Close - which I would normally just close the form - that's what I used to do in CSLA2.x.
Is this normal or is there something I need to do before Closing the form, without rebinding?
Thanks,
Graham
Most likely caused by the Items collection being active in databinding on a ComboBox.
I recommend to unbind combobox items after <bo> i unbound and rebind items before rebind of <bo> .
I wish it was tjhat simple.
The form comprises 2 textboxes and some buttons...
G.
I can alleviate that issue by Rebinding after all events - such as Save and Close - which I would normally just close the form - that's what I used to do in CSLA2.x.
I think the challenges you are facing are due to changes made in 3.0 (2006 or so) designed to highlight bugs in Windows Forms UI code around binding and unbinding.
I made those changes because without them, most UI code was wrong, and nobody knew it was wrong other than many people's programs would "randomly" fail.
I fully appreciate your frustration. A whole lot of people have hit these same challenging issues over the past several years since 3.0 came out.
The core rule is simple: you must unbind your object graph from the UI before manipulating it in code.
Windows Forms data binding assumes total ownership of the object graph as long as it is bound. I fought that, and fought that, but ultimately just couldn't win. So all we can do is live with the reality.
Sadly, unbinding isn't trivial (unless you have just a single object). This is because you must understand and accommodate the way Windows Forms data binding handles currency. Every binding source has currency, and whatever object is current will have an elevated edit level. It is up to you to lower that edit level after unbinding.
To make that more "fun", there are differences between parent and child binding source objects.
All this is detailed in the Using CSLA .NET 3.0 ebook in some detail.
The Expert 2008 Business Objects book has some content that discusses some helper types we added to the framework around the year 2008 to help ease this unbinding process.
I do plan to update the WfUI project to use those helpers when I get time, but I haven't done WinForms work for a few years, so I need to relearn how all that stuff works myself...
Just at the moment I'm in the middle of a bunch of travel for work, the 4.3 release, and some potentially very serious family health issues. If the health issues take a bad turn (which sadly seems likely) I may go dark for some time to deal with everything involved.
In other words, don't hold your breath for the WfUI changes - the existing code may have to suffice.
Thanks Rocky - I appreciate you taking the time to explain.
I have the 2008 book so I'll have a quick scan over that. I think my UI code probably originated from the 2005 book, so it could probably do with freshening up anyway.
It is frustrating, but I'm sure I'll get there - I've made significant progress in the last week with your help and Jonny's, so I'm confident I'll be "off to the races" very soon.
I wish you well with the family health issues - I don't think anyway would expect you to do anything else.
Thanks again,
Graham
Copyright (c) Marimer LLC