Reputation: 174427
I have a view that contains a list of items, a TextBox
and a Save button. The TextBox
is bound to a property of the currently selected item of the list. The DataSource
of the list is bound to an ObservableCollection<T>
in the ViewModel.
Now, when the user selects another item in the list and hasn't saved his changes to the TextBox
, he should be asked whether or not he wants to discard the changes he made. The selected item in the list should only be changed if he answers this with yes.
The problem I have is this:
I need to implement the check for changes in the ViewModel, but I don't know where, as the ViewModel doesn't get notified, when the selected item is changing.
I came up with the following method, but it doesn't seem to be clean:
There is an event SelectedItemsChanging
on the list. I could use the EventToCommand
behavior and pass the CancelEventArgs
as a parameter to the command. In the command, I check of the item was changed and if so, I could use the messenger to send a message the View listens for. The View will then show the confirmation dialog to the user and return the result back to ViewModel somehow. The ViewModel in turn sets the Cancel
property of the event args to true
if the user doesn't want to discard his changes.
This doesn't look clean to me, because it rips this simple functionality apart and smears it over three files, making it very hard to understand.
Are there any best practices for a scenario like this?
Upvotes: 1
Views: 517
Reputation: 701
In MVVM for Windows 8 you can override PageModel.OnNavigating method. http://w8mvvm.codeplex.com/wikipage?title=HandlePageNavigation&referringTitle=Documentation
http://visualstudiogallery.msdn.microsoft.com/b287f569-7bf9-40d2-80f3-02c9945f1f33
Upvotes: 0
Reputation: 174427
After some more research I came across the "Interaction Request" "pattern" as used by PRISM. This is basically the same solution I already lined out in my answer, just a little bit more sophisticated. That's what I am using now.
It works like this:
IInteractionRequest<TInteractionData>
. This interface only contains an event Raised
.In the View a behavior is bound to this property.
<i:Interaction.Behaviors>
<Interaction:NotificationMessageBoxBehavior
SourceObject="{Binding NotificationRequest}" />
</i:Interaction.Behaviors>
The bound behavior (in the example NotificationMessageBoxBehavior
) is what controls how the interaction request is handled. For example, in a Windows application this simply calls MessageBox.Show
, but there might be an alternative implementation that works in Silverlight applications.
The behaviors are all derived from a common base class that derives from Behavior<FrameworkElement>
and has a dependency property SourceObject
of type IInteractionRequest<TInteractionData>
. That's how the behavior and the interaction request are brought together: The behavior subscribes to the Raised
event of the SourceObject
and executes the interaction request when the event is raised.
The concrete implementation of the interaction data that is used in the request can contain a callback that is called after the request has been completed. This callback can have a parameter. Like this, the result of the interaction request can flow back to the ViewModel.
Upvotes: 0
Reputation: 7468
I would put Dirty checking on the model class. Example below:
Public m_dirtyFields As New Dictionary(Of String, String)
Private Sub AddDirtyField(ByVal ColName As String, ByVal OrigValue As String)
If Not m_dirtyFields.ContainsKey(ColName) Then
m_dirtyFields.Add(ColName, OrigValue)
OnPropertyChanged("IsDirty")
End If
End Sub
Private Sub RemoveDirtyField(ByVal ColName As String)
If m_dirtyFields.ContainsKey(ColName) Then
m_dirtyFields.Remove(ColName)
End If
OnPropertyChanged("IsDirty")
End Sub
Private Sub OnAddress1Changing(ByVal value As String)
If Not m_dirtyFields.ContainsKey("Address1") Then
AddDirtyField("Address1", Address1)
Else
If m_dirtyFields("Address1") = value Then RemoveDirtyField("Address1")
End If
End Sub
Public ReadOnly Property IsDirty
Get
If _Initialized = False Then
m_dirtyFields.Clear()
_Initialized = True
End If
If m_dirtyFields.Count > 0 Then
Return True
Else : Return False
End If
End Get
End Property
The above example checks property values add them to a dictionary based on similarity of original value and returns Dirty based on items in the dictionary.
In your ViewModel, you can just check MyObject.IsDirty and if it is changed, pop up a message box asking the user to save (or not).
Additionally, you can have the Listbox disabled (so the user cant change records) while a record is dirty, but having a property on the ViewModel which advertizes the Dirty property of the SelectedItem.
Upvotes: 1