CSLA4: Windows .Net

CSLA4: Windows .Net

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


gajit posted on Sunday, February 19, 2012

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

 

 

 

JonnyBee replied on Sunday, February 19, 2012

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)

gajit replied on Sunday, February 19, 2012

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

JonnyBee replied on Sunday, February 19, 2012

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/

 

gajit replied on Sunday, February 19, 2012

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 

JonnyBee replied on Monday, February 20, 2012

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> .

 

gajit replied on Monday, February 20, 2012

I wish it was tjhat simple.

The form comprises 2 textboxes and some buttons...

 

G.

RockfordLhotka replied on Monday, February 20, 2012

gajit

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.

gajit replied on Tuesday, February 21, 2012

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