Warning Dialog Before Property Change

Warning Dialog Before Property Change

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


josh.kodroff posted on Tuesday, August 14, 2007

I have a property that when changed needs to display the user with a warning message to confirm or cancel the change.  In order to present the warning message and allow the user to cancel the change, I removed databinding from the field and took care of getting and setting the BO property manually .

The problem is that by removing databinding, I also removed the ErrorProvider control's hookup to the validation rules.

Any ideas on how to get both requirements:
1.  A warning message before changing a property that can cancel an edit to a property (the properties that need warnings are bound to comboboxes and checkboxes).
2.  An error provider that reads the broken rules associated with the property.

triplea replied on Tuesday, August 14, 2007

I think it would be better to move all the logic into a customer control (e.g. ComboboxEx and CheckboxEx each extending the base controls). You could then display the visual warning on the appropriate event (avoiding all this removing of bindings etc which always leads to troubles for me). Also, regarding (2), you can always ask your object (which should always be of type MyBusinessBase) for the asociated  broken rules by exposing an appropriate public method.
I have done something similar (e.g. limiting number of characters types in a textbox, enabling/disabling based on security etc) and it shouldn't be too much trouble.

ajj3085 replied on Tuesday, August 14, 2007

This is probably the best approach.  Its the UIs responsiblity to warn the user of any adverse consequences of changing a property value, not the business layers.  Its also probably best not to remove databinding, but instead cancel the change even before.  how you do this depends on your control.

SonOfPirate replied on Tuesday, August 14, 2007

Not sure if this needs to be very complicated.  We've come up with what I believe to be a relatively simple and flexible solution to this exact problem: we added a PropertyChanging event with the PropertyChangingEventArgs derived from CancelEventArgs.  Using the INotifyPropertyChanging interface, UI forms, etc. can register handlers for the event then poll the property being changed, display a confirmation dialog and set the EventArgs.Cancel property accordingly.  Within the property, we simply check if e.Cancel == false before updating the property's variable with the new value.  Has worked without a problem for us thus far.

HTH

 

Patrick.Roeper replied on Tuesday, August 14, 2007

So if you were firing an event from within the business object, and the UI is handling the event, is this one of the situations where serialization fails for a system using remoting? I vaguely remember reading a blog that rocky wrote a little while back that said there was a problem with serialization and events being handled from non-serializable objects (like a form).

I know there was an easy solution like marking the event with [field: NonSerializable] in C#... I dont remember the VB implementation, I just remember it being the majority of the blog post..

triplea replied on Wednesday, August 15, 2007

SonOfPirate:

Using the INotifyPropertyChanging interface, UI forms, etc. can register handlers for the event then poll the property being changed, display a confirmation dialog and set the EventArgs.Cancel property accordingly. 

I guess that works fine as well but isn't that a bit sneaky since you shift UI logic in your BO? I occasionally use some of my BOs to run batch updates (e.g. load customer info from a spreadsheet and call my CustomerEdit object, modify and call Save()) in which case i wouldn't want notifications to come up.
I still think its not too complicated to create a custom control :-)

McManus replied on Wednesday, August 15, 2007

triplea:
SonOfPirate:

Using the INotifyPropertyChanging interface, UI forms, etc. can register handlers for the event then poll the property being changed, display a confirmation dialog and set the EventArgs.Cancel property accordingly. 

I guess that works fine as well but isn't that a bit sneaky since you shift UI logic in your BO? I occasionally use some of my BOs to run batch updates (e.g. load customer info from a spreadsheet and call my CustomerEdit object, modify and call Save()) in which case i wouldn't want notifications to come up.
I still think its not too complicated to create a custom control :-)

Triplea,

We use exactly the same method as SonOfPirate. Ín my opinion you don't shift UI logic into your BO. Showing a confirmation dialog is done in the UI (more precisely in the event handler for the PropertyChangingEvent). The BO only makes it possible to have this functionality in the UI by raising the cancelable event just before the property is changed.

Cheers,
Herman

SonOfPirate replied on Wednesday, August 15, 2007

Yep, McManus has it right.  All we are doing is making it possible for the UI to capture what is happening.  Nothing in the BO says that anything has to happen or that it has to be in the UI - anything can handle the event and do anything with it that it wants.

As for the question about batch updates, you are correct that firing multiple events is undesirable.  I did fail to mention that we make use of the RaisePropertyChangedEvents property in our OnPropertyChanging method to filter when and when not to raise the event.  Our thinking is that if you don't want to raise post-change events then you probably don't want to raise pre-change events.  But, if that logic breaksdown, you can always add a RaisePropertyChangingEvents property and distinguish between the two.  For batch updates, I would think you'd want them both off so this would suffice.

HTH

 

triplea replied on Wednesday, August 15, 2007

OK I think you guys have convinced me that the approach is fine. I guess I stuck similar logic in my user controls since I have CSLA specific controls (e.g. CslaTextbox, CslaCombobox etc) that expect to be databound with an object of type MyBusinessBase and do lots of UI specific work within there. So to keep things consistent in my projects I would refrain from using the events but otherwise I like the approach and might consider it in the future :-)

josh.kodroff replied on Monday, August 27, 2007

There's a few gotchas in SonOfPirate's solution, which I suspect he left out deliberately because he knew I was a Penn State fan.  (j/k)  Actually, the solution's not too hard, but a little intimidating if you're inexperienced with custom delegates as I am.

Here's the code for the complete solution:

First,. define your interface and args and delegate stuff:

Public Interface INotifyPropertyChanging
    Event PropertyChanging As PropertyChangingEventHandler
End Interface

Public Class PropertyChangingEventArgs
    Inherits System.ComponentModel.CancelEventArgs
    Private mPropertyName As String
    Private mProposedValue As Object

    Public Sub New(ByVal propertyName As String, ByVal proposedValue As Object)
        mPropertyName = propertyName
        mProposedValue = proposedValue
    End Sub

    Public ReadOnly Property PropertyName() As String
        Get
            Return mPropertyName
        End Get
    End Property

    Public ReadOnly Property ProposedValue() As Object
        Get
            Return mProposedValue
        End Get
    End Property
End Class

Public Delegate Sub PropertyChangingEventHandler(ByVal sender As Object, ByVal e As PropertyChangingEventArgs)

Then you need to put this stuff in the class that's going to raise the event:

Implements INotifyPropertyChanging
<NotUndoable()> <NonSerialized()> Private mNonSerializableHandlers As PropertyChangingEventHandler
    Private mSerializableHandlers As PropertyChangingEventHandler
    Public Custom Event PropertyChanging As PropertyChangingEventHandler Implements INotifyPropertyChanging.PropertyChanging
        AddHandler(ByVal value As PropertyChangingEventHandler)
            If value.Method.IsPublic AndAlso _
                (value.Method.DeclaringType.IsSerializable OrElse value.Method.IsStatic) Then
                mSerializableHandlers = DirectCast(System.Delegate.Combine(mSerializableHandlers, value), PropertyChangingEventHandler)
            Else
                mNonSerializableHandlers = DirectCast(System.Delegate.Combine(mNonSerializableHandlers, value), PropertyChangingEventHandler)
            End If
        End AddHandler

        RemoveHandler(ByVal value As PropertyChangingEventHandler)
            If value.Method.IsPublic AndAlso _
                (value.Method.DeclaringType.IsSerializable OrElse value.Method.IsStatic) Then
                mSerializableHandlers = DirectCast(System.Delegate.Remove(mSerializableHandlers, value), PropertyChangingEventHandler)
            Else
                mNonSerializableHandlers = DirectCast(System.Delegate.Remove(mNonSerializableHandlers, value), PropertyChangingEventHandler)
            End If

        End RemoveHandler

        RaiseEvent(ByVal sender As Object, ByVal e As PropertyChangingEventArgs)
            Dim nonSerializableHandlers As PropertyChangingEventHandler = mNonSerializableHandlers
            If nonSerializableHandlers IsNot Nothing Then
                nonSerializableHandlers.Invoke(sender, e)
            End If

            Dim serializableHandlers As PropertyChangingEventHandler = mSerializableHandlers
            If serializableHandlers IsNot Nothing Then
                serializableHandlers.Invoke(sender, e)
            End If
        End RaiseEvent
    End Event

Finally, to raise the event on the property whose change you need to warn the user on:

Public Property DangerousProperty() As Integer
        Get
            Return _dangerousProperty
        End Get
        Set(ByVal value As Integer)
            If _
dangerousProperty <> value Then
                Dim e As New PropertyChangingEventArgs("
DangerousProperty", value)
                RaiseEvent PropertyChanging(Me, e)
                If e.Cancel = False Then
                    _
_dangerousProperty = value
                    ' other effects of property change  
                    PropertyHasChanged()
               End If
            End If
        End Set
    End Property

Copyright (c) Marimer LLC