4

So I'm having this problem that hopefully you can help with.

I'm writing a WPF application using MVVM Light as a framework. In this situation, I have a list of items, and the SelectedItem is bound to a details view where the user can edit the item. There is a Save button in this case for explicit saving of data.

My problem is that when the user edits the data, the changes immediately shows up in the list. If the user cancels, it resets the selected item, but it's still changed. How do I prevent changes from propogating?

I tried to implement a cloning implementation, but as soon as I did that, MVVM Light's messaging system ends up getting into a loop, resulting in a StackOverflowException due to the fact that I keep cloning the object. As well, the clone implementation is ugly.

Any idea on how I can do this properly?

EDIT:

Basic XAML for list view:

    <DataGrid DataContext="{Binding SubJobTypes}"
              ItemsSource="{Binding}"
              SelectedItem="{Binding ElementName=Root, Path=DataContext.SelectedSubJobType, Mode=TwoWay}">
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding Name}"/>
        </DataGrid.Columns>
    </DataGrid>

Basic XAML for edit view:

   <StackPanel>
        <StackPanel>
            <StackPanel Orientation="Horizontal" DataContext="{Binding Path=CurrentSubJobType}">
                <TextBlock Text="Name"/>
                <TextBox Text="{Binding Path=Name, Mode=TwoWay}" Width="150"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <Button Content="{Binding Path=SubmitCommandText, FallbackValue=Submit}" >
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="Click">
                            <GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding Path=SaveSubJobTypeCommand}"/>
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                </Button>
                <Button Content="Cancel" >
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="Click">
                            <GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding Path=CancelCommand}"/>
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                </Button>
                <Button Content="Delete">
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="Click">
                            <GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding Path=DeleteCommand}"/>
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                </Button>
            </StackPanel>
        </StackPanel>
    </StackPanel>

ViewModels are standard, won't bother posting

Camron B
  • 1,650
  • 2
  • 14
  • 30

3 Answers3

4

Instead of disabling the binding mechanism you should have two view models, one for the list items and one for the detail view. Once the an item is selected the list view model sends a property changed changed message. The detail view model then loads the items data, or clones the model and intializes itself with the data. Now the detail view model can alter its local instance of the model.

Once finished the data is saved to the database and the detail view model sends a message that the item has changed. The list view model now receives the message and can use the model to change its item view model, or you can reload the item model from the database and then update the item view model.

This way you do not have to mess around with manually writing the values to the model and if you only clone the model you should not run into any problems with the messaging.

AxelEckenberger
  • 16,628
  • 3
  • 48
  • 70
  • This is pretty much what I have. I have two ViewModels already. However, the messaging framework never creates a copy, so when I modify the item on the details view, it is the same object as in the list view. As for cloning the object, I understand that would work, but I was hoping for a better way, as most examples don't clone the objects. By "better", I mean a solution where I don't have to maintain a list of mappings in every class I need to do this for. – Camron B Jun 23 '11 at 19:08
  • @CamronBute: You do not have to maintain mappings, both view models (the item view model and the detail view model) are based upon the same model. The message passes either the complete model or only the ID, depending whether you want to load the data from the database or use the data cached on the client. When using a Serialization/Deserialization mechansim you can create a clone of an object quite easily (e.g. JavaScriptSerializer). The detail has a LoadMetho that takes the model clones it and assigns it to a private property. This is a copy that now can be edited. – AxelEckenberger Jun 23 '11 at 19:33
1

It would appear that you are (Two-way) binding to the same object in the ViewModel in both the Master and Detail views.

Possible alternatives are to:

  • create a copy of the object
  • create additional parameters in your ViewModel, such as "EditName", and bind them to the edit view instead of the name. They would initially be set to the same value as your Name property. On Save, you would set this.Name = this.EditName. On cancel, you would set this.EditName = this.Name.
Wonko the Sane
  • 10,623
  • 8
  • 67
  • 92
  • 1. One way binding does not fit the problem. 2. Yep, that's the way to go, but only if you have two different view models and if the model is the object you mean. 3. Not nice design as it complicates things. Separate your concerns ... and thanks for all the fish ;-) – AxelEckenberger Jun 23 '11 at 18:27
  • Agreed - removed the one-way binding comment. I also agree that the additional paramaters is not an ideal design (but it is a possibility). I would certainly go with the copy of the object (in a separate ViewModel) - they are separate Views (even if the "Edit" view is shown at the same time within the "List" view). – Wonko the Sane Jun 23 '11 at 18:48
0

I think you might want to disable "TwoWay" databinding and only write data back to the model when the user presses the Submit button.

Scrappydog
  • 2,864
  • 1
  • 21
  • 23
  • -1 as this is not conforming ton the idea of mvvm to let the binding handle the updating of the model. Aproach is flawed and leads to too much manual and coupled code. Better use model clone to initialize two different view models. – AxelEckenberger Jun 23 '11 at 18:22
  • MVVM is a pattern not a LAW. A simple solution to a one time challenge that violates the pattern isn't "wrong"... The model clone approach is a valid approach, but it might be adding unneeded complexity in a simple scenario... (and the original question doesn't imply a large sophisticated application) – Scrappydog Jun 23 '11 at 18:35
  • 1
    Granted it is not a law (and the to quote Laurent Buginion "the MVVM police will not come"). But it has an idea behind it that produces testible and loosely coupled code. The same idea also reduces the code (except boilerplate code handled by snippets) that also make a project more maintainable. And ... it keeps you sane! – AxelEckenberger Jun 23 '11 at 20:16