views:

64

answers:

3

I am trying to use an IsDirty flag to control CanExecute and Navigational controls during an object edit.

The problem is that in order for this to work I think I have to use onPropertyChanged for my IsDirty method so that my controls get change notification.(I want some controls to be disabled when my object IsDirty) Unfortunately I get a nasty stackoverflow because it spirals into a horrible loop of IsDirty...hehe..

has anyone been able to get something similar to this to work? All I am doing is setting IsDirty to true in my OnPropertyChanged method. Then in my canExecute methods I am seeing if it is set to true, but then on my controls I need to Databind to it...which is causing all the issue.

does anyone know how to implement something like this?

this is my solution

:: In ViewModelBase

Private _isdirty As Boolean = False
        Protected Property IsDirty As Boolean
            Get
                Return _isdirty
            End Get
            Set(ByVal value As Boolean)
                If _isdirty = Not value Then
                    _isdirty = value
                    If _isdirty = True Then
                        DisableNavigation()
                    Else
                        EnableNavigation()
                    End If
                End If
            End Set
        End Property

Private _haschanges As Boolean
        Public Property HasChanges As Boolean
            Get
                Return _haschanges
            End Get
            Set(ByVal value As Boolean)
                If value = Not _haschanges Then
                    _haschanges = value
                    OnPropertyChanged("HasChanges")
                End If
            End Set
        End Property



Protected Sub EnableNavigation()
            'Keep from firing multiple onPropertyChanged events
            If HasChanges = True Then
                HasChanges = False
            End If

            GetEvent(Of DisableNavigationEvent).Publish(False)

        End Sub

        Protected Sub DisableNavigation()
            'Keep from firing multiple onPropertyChanged events
            If HasChanges = False Then
                HasChanges = True
            End If
            GetEvent(Of DisableNavigationEvent).Publish(True)

        End Sub

::In EditViewModelBase that Derives from ViewModelBase.

Protected Overrides Sub OnPropertyChanged(ByVal strPropertyName As String)
            MyBase.OnPropertyChanged(strPropertyName)

            If SetsIsDirty(strPropertyName) Then
                If isLoading = False Then

                    IsDirty = True
                Else
                    IsDirty = False

                End If
            End If



        End Sub
        ''' <summary>
        ''' Helps prevent stackoverflows by filtering what gets checked for isDirty
        ''' </summary>
        ''' <param name="str"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Protected Function SetsIsDirty(ByVal str As String) As Boolean

            If str = "CurrentVisualState" Then Return False
            If str = "TabsEnabled" Then Return False
            If str = "IsLoading" Then Return False
            If str = "EnableOfficeSelection" Then Return False

            Return True

        End Function

:: In my viewModel

Public ReadOnly Property SaveCommand() As ICommand
            Get
                If _cmdSave Is Nothing Then
                    _cmdSave = New RelayCommand(Of DoctorOffice)(AddressOf SaveExecute, Function() CanSaveExecute())
                End If
                Return _cmdSave
            End Get
        End Property

Private Function CanSaveExecute() As Boolean
            'if the object is dirty you want to be able to save it.
            Return IsDirty

        End Function

        Private Sub SaveExecute(ByVal param As DoctorOffice)
            BeginWait()
            GetService(Of Services.IDoctorOfficesService).Update(SelectedDoctorOffice, False)
            EndWait()

        End Sub
+1  A: 

The simplest way to avoid the stack overflow is to a a guard clause to your IsDirty property setter:

public bool IsDirty
{
    get { return _isDirty; }
    set
    {
        if (_isDirty == value)
            return;
        _isDirty = value;
        NotifyPropertyChanged("IsDirty");
    }
}

Unfortunately just doing this you'll still have an issue if you try to set IsDirty = false because it will get reset back to true by your PropertyChanged method. To avoid this you should be checking the property name in that method and skipping the setting of IsDirty if the changed property name is "IsDirty".

John Bowen
I had actually tried dong this, except the guard statement in the setter...ill give that a try too. I have several other 'state' booleans that i have setup a method that is checked and then sets the is dirty...ill post some code once i get it migrated to home from work.
ecathell
A: 

You don't need to notify that IsDirty has changed. Just make it a plain property or field and it should work fine (and no infinite loop).

This is assuming you are using the RelayCommand that everyone seems to use (for good reason) from Josh Smith's article in MSDN Magazine.

Phil Sandler
if you dont raise notifyproperty changed your standard controls wont know that they need to refresh.
ecathell
Ah, missed the part about disabling controls--thought it was just about CanExecute.
Phil Sandler
A: 

just have your CanExecute predicate for you ICommand to include the IsDirty property

e.g.

public class MyViewModel
{
  public CanSave { get { return IsDirty;}}
  public void Save(object parameter)
  {
    //Do stuff here
  }

  public ICommand SaveCommand = new RelayCommand(() => this.Save,() => this.CanSave);
}

or if CanSave is only referencing IsDirty you could set your ICommand to be this:

public ICommand SaveCommand = new RelayCommand(() => this.Save,() => this.IsDirty);

As long as RelayCommand uses CommandManager.RequerySuggested for the CanExecuteChanged event the CanSave predicate will be requeried anytime any binding value changes in the ViewModel.

And this is a big point because without CommandManager.RequerySuggested WPF won't know to update the UI. This can become sort of expensive because everytime any value is changed in the viewmodel all of your RelayCommands get requeried. But it's probably negligible as long as your CanExecute predicate is a simple calculation, i.e. if you are calling a database or web service in your CanExecute predicate expect some serious performance problems :)

Jose
im going to mark yours correct because it was closest to what I needed to do and I didnt really go into enough depth in the beginning...
ecathell